1
# Copyright (C) 2006-2011 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 (
51
from bzrlib.remote import (
57
RemoteRepositoryFormat,
59
from bzrlib.repofmt import groupcompress_repo, knitpack_repo
60
from bzrlib.revision import NULL_REVISION
61
from bzrlib.smart import medium, request
62
from bzrlib.smart.client import _SmartClient
63
from bzrlib.smart.repository import (
64
SmartServerRepositoryGetParentMap,
65
SmartServerRepositoryGetStream_1_19,
67
from bzrlib.symbol_versioning import deprecated_in
68
from bzrlib.tests import (
71
from bzrlib.tests.scenarios import load_tests_apply_scenarios
72
from bzrlib.transport.memory import MemoryTransport
73
from bzrlib.transport.remote import (
80
load_tests = load_tests_apply_scenarios
83
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
87
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
89
{'transport_server': test_server.SmartTCPServer_for_testing})]
93
super(BasicRemoteObjectTests, self).setUp()
94
self.transport = self.get_transport()
95
# make a branch that can be opened over the smart transport
96
self.local_wt = BzrDir.create_standalone_workingtree('.')
97
self.addCleanup(self.transport.disconnect)
99
def test_create_remote_bzrdir(self):
100
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
101
self.assertIsInstance(b, BzrDir)
103
def test_open_remote_branch(self):
104
# open a standalone branch in the working directory
105
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
106
branch = b.open_branch()
107
self.assertIsInstance(branch, Branch)
109
def test_remote_repository(self):
110
b = BzrDir.open_from_transport(self.transport)
111
repo = b.open_repository()
112
revid = u'\xc823123123'.encode('utf8')
113
self.assertFalse(repo.has_revision(revid))
114
self.local_wt.commit(message='test commit', rev_id=revid)
115
self.assertTrue(repo.has_revision(revid))
117
def test_remote_branch_revision_history(self):
118
b = BzrDir.open_from_transport(self.transport).open_branch()
120
self.applyDeprecated(deprecated_in((2, 5, 0)), b.revision_history))
121
r1 = self.local_wt.commit('1st commit')
122
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
123
self.assertEqual([r1, r2],
124
self.applyDeprecated(deprecated_in((2, 5, 0)), b.revision_history))
126
def test_find_correct_format(self):
127
"""Should open a RemoteBzrDir over a RemoteTransport"""
128
fmt = BzrDirFormat.find_format(self.transport)
129
self.assertTrue(bzrdir.RemoteBzrProber
130
in controldir.ControlDirFormat._server_probers)
131
self.assertIsInstance(fmt, RemoteBzrDirFormat)
133
def test_open_detected_smart_format(self):
134
fmt = BzrDirFormat.find_format(self.transport)
135
d = fmt.open(self.transport)
136
self.assertIsInstance(d, BzrDir)
138
def test_remote_branch_repr(self):
139
b = BzrDir.open_from_transport(self.transport).open_branch()
140
self.assertStartsWith(str(b), 'RemoteBranch(')
142
def test_remote_bzrdir_repr(self):
143
b = BzrDir.open_from_transport(self.transport)
144
self.assertStartsWith(str(b), 'RemoteBzrDir(')
146
def test_remote_branch_format_supports_stacking(self):
148
self.make_branch('unstackable', format='pack-0.92')
149
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
150
self.assertFalse(b._format.supports_stacking())
151
self.make_branch('stackable', format='1.9')
152
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
153
self.assertTrue(b._format.supports_stacking())
155
def test_remote_repo_format_supports_external_references(self):
157
bd = self.make_bzrdir('unstackable', format='pack-0.92')
158
r = bd.create_repository()
159
self.assertFalse(r._format.supports_external_lookups)
160
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
161
self.assertFalse(r._format.supports_external_lookups)
162
bd = self.make_bzrdir('stackable', format='1.9')
163
r = bd.create_repository()
164
self.assertTrue(r._format.supports_external_lookups)
165
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
166
self.assertTrue(r._format.supports_external_lookups)
168
def test_remote_branch_set_append_revisions_only(self):
169
# Make a format 1.9 branch, which supports append_revisions_only
170
branch = self.make_branch('branch', format='1.9')
171
config = branch.get_config()
172
branch.set_append_revisions_only(True)
174
'True', config.get_user_option('append_revisions_only'))
175
branch.set_append_revisions_only(False)
177
'False', config.get_user_option('append_revisions_only'))
179
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
180
branch = self.make_branch('branch', format='knit')
181
config = branch.get_config()
183
errors.UpgradeRequired, branch.set_append_revisions_only, True)
186
class FakeProtocol(object):
187
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
189
def __init__(self, body, fake_client):
191
self._body_buffer = None
192
self._fake_client = fake_client
194
def read_body_bytes(self, count=-1):
195
if self._body_buffer is None:
196
self._body_buffer = StringIO(self.body)
197
bytes = self._body_buffer.read(count)
198
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
199
self._fake_client.expecting_body = False
202
def cancel_read_body(self):
203
self._fake_client.expecting_body = False
205
def read_streamed_body(self):
209
class FakeClient(_SmartClient):
210
"""Lookalike for _SmartClient allowing testing."""
212
def __init__(self, fake_medium_base='fake base'):
213
"""Create a FakeClient."""
216
self.expecting_body = False
217
# if non-None, this is the list of expected calls, with only the
218
# method name and arguments included. the body might be hard to
219
# compute so is not included. If a call is None, that call can
221
self._expected_calls = None
222
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
224
def add_expected_call(self, call_name, call_args, response_type,
225
response_args, response_body=None):
226
if self._expected_calls is None:
227
self._expected_calls = []
228
self._expected_calls.append((call_name, call_args))
229
self.responses.append((response_type, response_args, response_body))
231
def add_success_response(self, *args):
232
self.responses.append(('success', args, None))
234
def add_success_response_with_body(self, body, *args):
235
self.responses.append(('success', args, body))
236
if self._expected_calls is not None:
237
self._expected_calls.append(None)
239
def add_error_response(self, *args):
240
self.responses.append(('error', args))
242
def add_unknown_method_response(self, verb):
243
self.responses.append(('unknown', verb))
245
def finished_test(self):
246
if self._expected_calls:
247
raise AssertionError("%r finished but was still expecting %r"
248
% (self, self._expected_calls[0]))
250
def _get_next_response(self):
252
response_tuple = self.responses.pop(0)
253
except IndexError, e:
254
raise AssertionError("%r didn't expect any more calls"
256
if response_tuple[0] == 'unknown':
257
raise errors.UnknownSmartMethod(response_tuple[1])
258
elif response_tuple[0] == 'error':
259
raise errors.ErrorFromSmartServer(response_tuple[1])
260
return response_tuple
262
def _check_call(self, method, args):
263
if self._expected_calls is None:
264
# the test should be updated to say what it expects
267
next_call = self._expected_calls.pop(0)
269
raise AssertionError("%r didn't expect any more calls "
271
% (self, method, args,))
272
if next_call is None:
274
if method != next_call[0] or args != next_call[1]:
275
raise AssertionError("%r expected %r%r "
277
% (self, next_call[0], next_call[1], method, args,))
279
def call(self, method, *args):
280
self._check_call(method, args)
281
self._calls.append(('call', method, args))
282
return self._get_next_response()[1]
284
def call_expecting_body(self, method, *args):
285
self._check_call(method, args)
286
self._calls.append(('call_expecting_body', method, args))
287
result = self._get_next_response()
288
self.expecting_body = True
289
return result[1], FakeProtocol(result[2], self)
291
def call_with_body_bytes(self, method, args, body):
292
self._check_call(method, args)
293
self._calls.append(('call_with_body_bytes', method, args, body))
294
result = self._get_next_response()
295
return result[1], FakeProtocol(result[2], self)
297
def call_with_body_bytes_expecting_body(self, method, args, body):
298
self._check_call(method, args)
299
self._calls.append(('call_with_body_bytes_expecting_body', method,
301
result = self._get_next_response()
302
self.expecting_body = True
303
return result[1], FakeProtocol(result[2], self)
305
def call_with_body_stream(self, args, stream):
306
# Explicitly consume the stream before checking for an error, because
307
# that's what happens a real medium.
308
stream = list(stream)
309
self._check_call(args[0], args[1:])
310
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
311
result = self._get_next_response()
312
# The second value returned from call_with_body_stream is supposed to
313
# be a response_handler object, but so far no tests depend on that.
314
response_handler = None
315
return result[1], response_handler
318
class FakeMedium(medium.SmartClientMedium):
320
def __init__(self, client_calls, base):
321
medium.SmartClientMedium.__init__(self, base)
322
self._client_calls = client_calls
324
def disconnect(self):
325
self._client_calls.append(('disconnect medium',))
328
class TestVfsHas(tests.TestCase):
330
def test_unicode_path(self):
331
client = FakeClient('/')
332
client.add_success_response('yes',)
333
transport = RemoteTransport('bzr://localhost/', _client=client)
334
filename = u'/hell\u00d8'.encode('utf8')
335
result = transport.has(filename)
337
[('call', 'has', (filename,))],
339
self.assertTrue(result)
342
class TestRemote(tests.TestCaseWithMemoryTransport):
344
def get_branch_format(self):
345
reference_bzrdir_format = bzrdir.format_registry.get('default')()
346
return reference_bzrdir_format.get_branch_format()
348
def get_repo_format(self):
349
reference_bzrdir_format = bzrdir.format_registry.get('default')()
350
return reference_bzrdir_format.repository_format
352
def assertFinished(self, fake_client):
353
"""Assert that all of a FakeClient's expected calls have occurred."""
354
fake_client.finished_test()
357
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
358
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
360
def assertRemotePath(self, expected, client_base, transport_base):
361
"""Assert that the result of
362
SmartClientMedium.remote_path_from_transport is the expected value for
363
a given client_base and transport_base.
365
client_medium = medium.SmartClientMedium(client_base)
366
t = transport.get_transport(transport_base)
367
result = client_medium.remote_path_from_transport(t)
368
self.assertEqual(expected, result)
370
def test_remote_path_from_transport(self):
371
"""SmartClientMedium.remote_path_from_transport calculates a URL for
372
the given transport relative to the root of the client base URL.
374
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
375
self.assertRemotePath(
376
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
378
def assertRemotePathHTTP(self, expected, transport_base, relpath):
379
"""Assert that the result of
380
HttpTransportBase.remote_path_from_transport is the expected value for
381
a given transport_base and relpath of that transport. (Note that
382
HttpTransportBase is a subclass of SmartClientMedium)
384
base_transport = transport.get_transport(transport_base)
385
client_medium = base_transport.get_smart_medium()
386
cloned_transport = base_transport.clone(relpath)
387
result = client_medium.remote_path_from_transport(cloned_transport)
388
self.assertEqual(expected, result)
390
def test_remote_path_from_transport_http(self):
391
"""Remote paths for HTTP transports are calculated differently to other
392
transports. They are just relative to the client base, not the root
393
directory of the host.
395
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
396
self.assertRemotePathHTTP(
397
'../xyz/', scheme + '//host/path', '../xyz/')
398
self.assertRemotePathHTTP(
399
'xyz/', scheme + '//host/path', 'xyz/')
402
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
403
"""Tests for the behaviour of client_medium.remote_is_at_least."""
405
def test_initially_unlimited(self):
406
"""A fresh medium assumes that the remote side supports all
409
client_medium = medium.SmartClientMedium('dummy base')
410
self.assertFalse(client_medium._is_remote_before((99, 99)))
412
def test__remember_remote_is_before(self):
413
"""Calling _remember_remote_is_before ratchets down the known remote
416
client_medium = medium.SmartClientMedium('dummy base')
417
# Mark the remote side as being less than 1.6. The remote side may
419
client_medium._remember_remote_is_before((1, 6))
420
self.assertTrue(client_medium._is_remote_before((1, 6)))
421
self.assertFalse(client_medium._is_remote_before((1, 5)))
422
# Calling _remember_remote_is_before again with a lower value works.
423
client_medium._remember_remote_is_before((1, 5))
424
self.assertTrue(client_medium._is_remote_before((1, 5)))
425
# If you call _remember_remote_is_before with a higher value it logs a
426
# warning, and continues to remember the lower value.
427
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
428
client_medium._remember_remote_is_before((1, 9))
429
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
430
self.assertTrue(client_medium._is_remote_before((1, 5)))
433
class TestBzrDirCloningMetaDir(TestRemote):
435
def test_backwards_compat(self):
436
self.setup_smart_server_with_call_log()
437
a_dir = self.make_bzrdir('.')
438
self.reset_smart_call_log()
439
verb = 'BzrDir.cloning_metadir'
440
self.disable_verb(verb)
441
format = a_dir.cloning_metadir()
442
call_count = len([call for call in self.hpss_calls if
443
call.call.method == verb])
444
self.assertEqual(1, call_count)
446
def test_branch_reference(self):
447
transport = self.get_transport('quack')
448
referenced = self.make_branch('referenced')
449
expected = referenced.bzrdir.cloning_metadir()
450
client = FakeClient(transport.base)
451
client.add_expected_call(
452
'BzrDir.cloning_metadir', ('quack/', 'False'),
453
'error', ('BranchReference',)),
454
client.add_expected_call(
455
'BzrDir.open_branchV3', ('quack/',),
456
'success', ('ref', self.get_url('referenced'))),
457
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
459
result = a_bzrdir.cloning_metadir()
460
# We should have got a control dir matching the referenced branch.
461
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
462
self.assertEqual(expected._repository_format, result._repository_format)
463
self.assertEqual(expected._branch_format, result._branch_format)
464
self.assertFinished(client)
466
def test_current_server(self):
467
transport = self.get_transport('.')
468
transport = transport.clone('quack')
469
self.make_bzrdir('quack')
470
client = FakeClient(transport.base)
471
reference_bzrdir_format = bzrdir.format_registry.get('default')()
472
control_name = reference_bzrdir_format.network_name()
473
client.add_expected_call(
474
'BzrDir.cloning_metadir', ('quack/', 'False'),
475
'success', (control_name, '', ('branch', ''))),
476
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
478
result = a_bzrdir.cloning_metadir()
479
# We should have got a reference control dir with default branch and
480
# repository formats.
481
# This pokes a little, just to be sure.
482
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
483
self.assertEqual(None, result._repository_format)
484
self.assertEqual(None, result._branch_format)
485
self.assertFinished(client)
488
class TestBzrDirOpen(TestRemote):
490
def make_fake_client_and_transport(self, path='quack'):
491
transport = MemoryTransport()
492
transport.mkdir(path)
493
transport = transport.clone(path)
494
client = FakeClient(transport.base)
495
return client, transport
497
def test_absent(self):
498
client, transport = self.make_fake_client_and_transport()
499
client.add_expected_call(
500
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
501
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
502
RemoteBzrDirFormat(), _client=client, _force_probe=True)
503
self.assertFinished(client)
505
def test_present_without_workingtree(self):
506
client, transport = self.make_fake_client_and_transport()
507
client.add_expected_call(
508
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
509
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
510
_client=client, _force_probe=True)
511
self.assertIsInstance(bd, RemoteBzrDir)
512
self.assertFalse(bd.has_workingtree())
513
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
514
self.assertFinished(client)
516
def test_present_with_workingtree(self):
517
client, transport = self.make_fake_client_and_transport()
518
client.add_expected_call(
519
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
520
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
521
_client=client, _force_probe=True)
522
self.assertIsInstance(bd, RemoteBzrDir)
523
self.assertTrue(bd.has_workingtree())
524
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
525
self.assertFinished(client)
527
def test_backwards_compat(self):
528
client, transport = self.make_fake_client_and_transport()
529
client.add_expected_call(
530
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
531
client.add_expected_call(
532
'BzrDir.open', ('quack/',), 'success', ('yes',))
533
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
534
_client=client, _force_probe=True)
535
self.assertIsInstance(bd, RemoteBzrDir)
536
self.assertFinished(client)
538
def test_backwards_compat_hpss_v2(self):
539
client, transport = self.make_fake_client_and_transport()
540
# Monkey-patch fake client to simulate real-world behaviour with v2
541
# server: upon first RPC call detect the protocol version, and because
542
# the version is 2 also do _remember_remote_is_before((1, 6)) before
543
# continuing with the RPC.
544
orig_check_call = client._check_call
545
def check_call(method, args):
546
client._medium._protocol_version = 2
547
client._medium._remember_remote_is_before((1, 6))
548
client._check_call = orig_check_call
549
client._check_call(method, args)
550
client._check_call = check_call
551
client.add_expected_call(
552
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
553
client.add_expected_call(
554
'BzrDir.open', ('quack/',), 'success', ('yes',))
555
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
556
_client=client, _force_probe=True)
557
self.assertIsInstance(bd, RemoteBzrDir)
558
self.assertFinished(client)
561
class TestBzrDirOpenBranch(TestRemote):
563
def test_backwards_compat(self):
564
self.setup_smart_server_with_call_log()
565
self.make_branch('.')
566
a_dir = BzrDir.open(self.get_url('.'))
567
self.reset_smart_call_log()
568
verb = 'BzrDir.open_branchV3'
569
self.disable_verb(verb)
570
format = a_dir.open_branch()
571
call_count = len([call for call in self.hpss_calls if
572
call.call.method == verb])
573
self.assertEqual(1, call_count)
575
def test_branch_present(self):
576
reference_format = self.get_repo_format()
577
network_name = reference_format.network_name()
578
branch_network_name = self.get_branch_format().network_name()
579
transport = MemoryTransport()
580
transport.mkdir('quack')
581
transport = transport.clone('quack')
582
client = FakeClient(transport.base)
583
client.add_expected_call(
584
'BzrDir.open_branchV3', ('quack/',),
585
'success', ('branch', branch_network_name))
586
client.add_expected_call(
587
'BzrDir.find_repositoryV3', ('quack/',),
588
'success', ('ok', '', 'no', 'no', 'no', network_name))
589
client.add_expected_call(
590
'Branch.get_stacked_on_url', ('quack/',),
591
'error', ('NotStacked',))
592
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
594
result = bzrdir.open_branch()
595
self.assertIsInstance(result, RemoteBranch)
596
self.assertEqual(bzrdir, result.bzrdir)
597
self.assertFinished(client)
599
def test_branch_missing(self):
600
transport = MemoryTransport()
601
transport.mkdir('quack')
602
transport = transport.clone('quack')
603
client = FakeClient(transport.base)
604
client.add_error_response('nobranch')
605
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
607
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
609
[('call', 'BzrDir.open_branchV3', ('quack/',))],
612
def test__get_tree_branch(self):
613
# _get_tree_branch is a form of open_branch, but it should only ask for
614
# branch opening, not any other network requests.
616
def open_branch(name=None):
617
calls.append("Called")
619
transport = MemoryTransport()
620
# no requests on the network - catches other api calls being made.
621
client = FakeClient(transport.base)
622
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
624
# patch the open_branch call to record that it was called.
625
bzrdir.open_branch = open_branch
626
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
627
self.assertEqual(["Called"], calls)
628
self.assertEqual([], client._calls)
630
def test_url_quoting_of_path(self):
631
# Relpaths on the wire should not be URL-escaped. So "~" should be
632
# transmitted as "~", not "%7E".
633
transport = RemoteTCPTransport('bzr://localhost/~hello/')
634
client = FakeClient(transport.base)
635
reference_format = self.get_repo_format()
636
network_name = reference_format.network_name()
637
branch_network_name = self.get_branch_format().network_name()
638
client.add_expected_call(
639
'BzrDir.open_branchV3', ('~hello/',),
640
'success', ('branch', branch_network_name))
641
client.add_expected_call(
642
'BzrDir.find_repositoryV3', ('~hello/',),
643
'success', ('ok', '', 'no', 'no', 'no', network_name))
644
client.add_expected_call(
645
'Branch.get_stacked_on_url', ('~hello/',),
646
'error', ('NotStacked',))
647
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
649
result = bzrdir.open_branch()
650
self.assertFinished(client)
652
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
653
reference_format = self.get_repo_format()
654
network_name = reference_format.network_name()
655
transport = MemoryTransport()
656
transport.mkdir('quack')
657
transport = transport.clone('quack')
659
rich_response = 'yes'
663
subtree_response = 'yes'
665
subtree_response = 'no'
666
client = FakeClient(transport.base)
667
client.add_success_response(
668
'ok', '', rich_response, subtree_response, external_lookup,
670
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
672
result = bzrdir.open_repository()
674
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
676
self.assertIsInstance(result, RemoteRepository)
677
self.assertEqual(bzrdir, result.bzrdir)
678
self.assertEqual(rich_root, result._format.rich_root_data)
679
self.assertEqual(subtrees, result._format.supports_tree_reference)
681
def test_open_repository_sets_format_attributes(self):
682
self.check_open_repository(True, True)
683
self.check_open_repository(False, True)
684
self.check_open_repository(True, False)
685
self.check_open_repository(False, False)
686
self.check_open_repository(False, False, 'yes')
688
def test_old_server(self):
689
"""RemoteBzrDirFormat should fail to probe if the server version is too
692
self.assertRaises(errors.NotBranchError,
693
RemoteBzrProber.probe_transport, OldServerTransport())
696
class TestBzrDirCreateBranch(TestRemote):
698
def test_backwards_compat(self):
699
self.setup_smart_server_with_call_log()
700
repo = self.make_repository('.')
701
self.reset_smart_call_log()
702
self.disable_verb('BzrDir.create_branch')
703
branch = repo.bzrdir.create_branch()
704
create_branch_call_count = len([call for call in self.hpss_calls if
705
call.call.method == 'BzrDir.create_branch'])
706
self.assertEqual(1, create_branch_call_count)
708
def test_current_server(self):
709
transport = self.get_transport('.')
710
transport = transport.clone('quack')
711
self.make_repository('quack')
712
client = FakeClient(transport.base)
713
reference_bzrdir_format = bzrdir.format_registry.get('default')()
714
reference_format = reference_bzrdir_format.get_branch_format()
715
network_name = reference_format.network_name()
716
reference_repo_fmt = reference_bzrdir_format.repository_format
717
reference_repo_name = reference_repo_fmt.network_name()
718
client.add_expected_call(
719
'BzrDir.create_branch', ('quack/', network_name),
720
'success', ('ok', network_name, '', 'no', 'no', 'yes',
721
reference_repo_name))
722
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
724
branch = a_bzrdir.create_branch()
725
# We should have got a remote branch
726
self.assertIsInstance(branch, remote.RemoteBranch)
727
# its format should have the settings from the response
728
format = branch._format
729
self.assertEqual(network_name, format.network_name())
731
def test_already_open_repo_and_reused_medium(self):
732
"""Bug 726584: create_branch(..., repository=repo) should work
733
regardless of what the smart medium's base URL is.
735
self.transport_server = test_server.SmartTCPServer_for_testing
736
transport = self.get_transport('.')
737
repo = self.make_repository('quack')
738
# Client's medium rooted a transport root (not at the bzrdir)
739
client = FakeClient(transport.base)
740
transport = transport.clone('quack')
741
reference_bzrdir_format = bzrdir.format_registry.get('default')()
742
reference_format = reference_bzrdir_format.get_branch_format()
743
network_name = reference_format.network_name()
744
reference_repo_fmt = reference_bzrdir_format.repository_format
745
reference_repo_name = reference_repo_fmt.network_name()
746
client.add_expected_call(
747
'BzrDir.create_branch', ('extra/quack/', network_name),
748
'success', ('ok', network_name, '', 'no', 'no', 'yes',
749
reference_repo_name))
750
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
752
branch = a_bzrdir.create_branch(repository=repo)
753
# We should have got a remote branch
754
self.assertIsInstance(branch, remote.RemoteBranch)
755
# its format should have the settings from the response
756
format = branch._format
757
self.assertEqual(network_name, format.network_name())
760
class TestBzrDirCreateRepository(TestRemote):
762
def test_backwards_compat(self):
763
self.setup_smart_server_with_call_log()
764
bzrdir = self.make_bzrdir('.')
765
self.reset_smart_call_log()
766
self.disable_verb('BzrDir.create_repository')
767
repo = bzrdir.create_repository()
768
create_repo_call_count = len([call for call in self.hpss_calls if
769
call.call.method == 'BzrDir.create_repository'])
770
self.assertEqual(1, create_repo_call_count)
772
def test_current_server(self):
773
transport = self.get_transport('.')
774
transport = transport.clone('quack')
775
self.make_bzrdir('quack')
776
client = FakeClient(transport.base)
777
reference_bzrdir_format = bzrdir.format_registry.get('default')()
778
reference_format = reference_bzrdir_format.repository_format
779
network_name = reference_format.network_name()
780
client.add_expected_call(
781
'BzrDir.create_repository', ('quack/',
782
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
784
'success', ('ok', 'yes', 'yes', 'yes', network_name))
785
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
787
repo = a_bzrdir.create_repository()
788
# We should have got a remote repository
789
self.assertIsInstance(repo, remote.RemoteRepository)
790
# its format should have the settings from the response
791
format = repo._format
792
self.assertTrue(format.rich_root_data)
793
self.assertTrue(format.supports_tree_reference)
794
self.assertTrue(format.supports_external_lookups)
795
self.assertEqual(network_name, format.network_name())
798
class TestBzrDirOpenRepository(TestRemote):
800
def test_backwards_compat_1_2_3(self):
801
# fallback all the way to the first version.
802
reference_format = self.get_repo_format()
803
network_name = reference_format.network_name()
804
server_url = 'bzr://example.com/'
805
self.permit_url(server_url)
806
client = FakeClient(server_url)
807
client.add_unknown_method_response('BzrDir.find_repositoryV3')
808
client.add_unknown_method_response('BzrDir.find_repositoryV2')
809
client.add_success_response('ok', '', 'no', 'no')
810
# A real repository instance will be created to determine the network
812
client.add_success_response_with_body(
813
"Bazaar-NG meta directory, format 1\n", 'ok')
814
client.add_success_response_with_body(
815
reference_format.get_format_string(), 'ok')
816
# PackRepository wants to do a stat
817
client.add_success_response('stat', '0', '65535')
818
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
820
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
822
repo = bzrdir.open_repository()
824
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
825
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
826
('call', 'BzrDir.find_repository', ('quack/',)),
827
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
828
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
829
('call', 'stat', ('/quack/.bzr/repository',)),
832
self.assertEqual(network_name, repo._format.network_name())
834
def test_backwards_compat_2(self):
835
# fallback to find_repositoryV2
836
reference_format = self.get_repo_format()
837
network_name = reference_format.network_name()
838
server_url = 'bzr://example.com/'
839
self.permit_url(server_url)
840
client = FakeClient(server_url)
841
client.add_unknown_method_response('BzrDir.find_repositoryV3')
842
client.add_success_response('ok', '', 'no', 'no', 'no')
843
# A real repository instance will be created to determine the network
845
client.add_success_response_with_body(
846
"Bazaar-NG meta directory, format 1\n", 'ok')
847
client.add_success_response_with_body(
848
reference_format.get_format_string(), 'ok')
849
# PackRepository wants to do a stat
850
client.add_success_response('stat', '0', '65535')
851
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
853
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
855
repo = bzrdir.open_repository()
857
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
858
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
859
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
860
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
861
('call', 'stat', ('/quack/.bzr/repository',)),
864
self.assertEqual(network_name, repo._format.network_name())
866
def test_current_server(self):
867
reference_format = self.get_repo_format()
868
network_name = reference_format.network_name()
869
transport = MemoryTransport()
870
transport.mkdir('quack')
871
transport = transport.clone('quack')
872
client = FakeClient(transport.base)
873
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
874
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
876
repo = bzrdir.open_repository()
878
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
880
self.assertEqual(network_name, repo._format.network_name())
883
class TestBzrDirFormatInitializeEx(TestRemote):
885
def test_success(self):
886
"""Simple test for typical successful call."""
887
fmt = RemoteBzrDirFormat()
888
default_format_name = BzrDirFormat.get_default_format().network_name()
889
transport = self.get_transport()
890
client = FakeClient(transport.base)
891
client.add_expected_call(
892
'BzrDirFormat.initialize_ex_1.16',
893
(default_format_name, 'path', 'False', 'False', 'False', '',
894
'', '', '', 'False'),
896
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
897
'bzrdir fmt', 'False', '', '', 'repo lock token'))
898
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
899
# it's currently hard to test that without supplying a real remote
900
# transport connected to a real server.
901
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
902
transport, False, False, False, None, None, None, None, False)
903
self.assertFinished(client)
905
def test_error(self):
906
"""Error responses are translated, e.g. 'PermissionDenied' raises the
907
corresponding error from the client.
909
fmt = RemoteBzrDirFormat()
910
default_format_name = BzrDirFormat.get_default_format().network_name()
911
transport = self.get_transport()
912
client = FakeClient(transport.base)
913
client.add_expected_call(
914
'BzrDirFormat.initialize_ex_1.16',
915
(default_format_name, 'path', 'False', 'False', 'False', '',
916
'', '', '', 'False'),
918
('PermissionDenied', 'path', 'extra info'))
919
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
920
# it's currently hard to test that without supplying a real remote
921
# transport connected to a real server.
922
err = self.assertRaises(errors.PermissionDenied,
923
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
924
False, False, False, None, None, None, None, False)
925
self.assertEqual('path', err.path)
926
self.assertEqual(': extra info', err.extra)
927
self.assertFinished(client)
929
def test_error_from_real_server(self):
930
"""Integration test for error translation."""
931
transport = self.make_smart_server('foo')
932
transport = transport.clone('no-such-path')
933
fmt = RemoteBzrDirFormat()
934
err = self.assertRaises(errors.NoSuchFile,
935
fmt.initialize_on_transport_ex, transport, create_prefix=False)
938
class OldSmartClient(object):
939
"""A fake smart client for test_old_version that just returns a version one
940
response to the 'hello' (query version) command.
943
def get_request(self):
944
input_file = StringIO('ok\x011\n')
945
output_file = StringIO()
946
client_medium = medium.SmartSimplePipesClientMedium(
947
input_file, output_file)
948
return medium.SmartClientStreamMediumRequest(client_medium)
950
def protocol_version(self):
954
class OldServerTransport(object):
955
"""A fake transport for test_old_server that reports it's smart server
956
protocol version as version one.
962
def get_smart_client(self):
963
return OldSmartClient()
966
class RemoteBzrDirTestCase(TestRemote):
968
def make_remote_bzrdir(self, transport, client):
969
"""Make a RemotebzrDir using 'client' as the _client."""
970
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
974
class RemoteBranchTestCase(RemoteBzrDirTestCase):
976
def lock_remote_branch(self, branch):
977
"""Trick a RemoteBranch into thinking it is locked."""
978
branch._lock_mode = 'w'
979
branch._lock_count = 2
980
branch._lock_token = 'branch token'
981
branch._repo_lock_token = 'repo token'
982
branch.repository._lock_mode = 'w'
983
branch.repository._lock_count = 2
984
branch.repository._lock_token = 'repo token'
986
def make_remote_branch(self, transport, client):
987
"""Make a RemoteBranch using 'client' as its _SmartClient.
989
A RemoteBzrDir and RemoteRepository will also be created to fill out
990
the RemoteBranch, albeit with stub values for some of their attributes.
992
# we do not want bzrdir to make any remote calls, so use False as its
993
# _client. If it tries to make a remote call, this will fail
995
bzrdir = self.make_remote_bzrdir(transport, False)
996
repo = RemoteRepository(bzrdir, None, _client=client)
997
branch_format = self.get_branch_format()
998
format = RemoteBranchFormat(network_name=branch_format.network_name())
999
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1002
class TestBranchGetParent(RemoteBranchTestCase):
1004
def test_no_parent(self):
1005
# in an empty branch we decode the response properly
1006
transport = MemoryTransport()
1007
client = FakeClient(transport.base)
1008
client.add_expected_call(
1009
'Branch.get_stacked_on_url', ('quack/',),
1010
'error', ('NotStacked',))
1011
client.add_expected_call(
1012
'Branch.get_parent', ('quack/',),
1014
transport.mkdir('quack')
1015
transport = transport.clone('quack')
1016
branch = self.make_remote_branch(transport, client)
1017
result = branch.get_parent()
1018
self.assertFinished(client)
1019
self.assertEqual(None, result)
1021
def test_parent_relative(self):
1022
transport = MemoryTransport()
1023
client = FakeClient(transport.base)
1024
client.add_expected_call(
1025
'Branch.get_stacked_on_url', ('kwaak/',),
1026
'error', ('NotStacked',))
1027
client.add_expected_call(
1028
'Branch.get_parent', ('kwaak/',),
1029
'success', ('../foo/',))
1030
transport.mkdir('kwaak')
1031
transport = transport.clone('kwaak')
1032
branch = self.make_remote_branch(transport, client)
1033
result = branch.get_parent()
1034
self.assertEqual(transport.clone('../foo').base, result)
1036
def test_parent_absolute(self):
1037
transport = MemoryTransport()
1038
client = FakeClient(transport.base)
1039
client.add_expected_call(
1040
'Branch.get_stacked_on_url', ('kwaak/',),
1041
'error', ('NotStacked',))
1042
client.add_expected_call(
1043
'Branch.get_parent', ('kwaak/',),
1044
'success', ('http://foo/',))
1045
transport.mkdir('kwaak')
1046
transport = transport.clone('kwaak')
1047
branch = self.make_remote_branch(transport, client)
1048
result = branch.get_parent()
1049
self.assertEqual('http://foo/', result)
1050
self.assertFinished(client)
1053
class TestBranchSetParentLocation(RemoteBranchTestCase):
1055
def test_no_parent(self):
1056
# We call the verb when setting parent to None
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.set_parent_location', ('quack/', 'b', 'r', ''),
1065
transport.mkdir('quack')
1066
transport = transport.clone('quack')
1067
branch = self.make_remote_branch(transport, client)
1068
branch._lock_token = 'b'
1069
branch._repo_lock_token = 'r'
1070
branch._set_parent_location(None)
1071
self.assertFinished(client)
1073
def test_parent(self):
1074
transport = MemoryTransport()
1075
client = FakeClient(transport.base)
1076
client.add_expected_call(
1077
'Branch.get_stacked_on_url', ('kwaak/',),
1078
'error', ('NotStacked',))
1079
client.add_expected_call(
1080
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1082
transport.mkdir('kwaak')
1083
transport = transport.clone('kwaak')
1084
branch = self.make_remote_branch(transport, client)
1085
branch._lock_token = 'b'
1086
branch._repo_lock_token = 'r'
1087
branch._set_parent_location('foo')
1088
self.assertFinished(client)
1090
def test_backwards_compat(self):
1091
self.setup_smart_server_with_call_log()
1092
branch = self.make_branch('.')
1093
self.reset_smart_call_log()
1094
verb = 'Branch.set_parent_location'
1095
self.disable_verb(verb)
1096
branch.set_parent('http://foo/')
1097
self.assertLength(12, self.hpss_calls)
1100
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1102
def test_backwards_compat(self):
1103
self.setup_smart_server_with_call_log()
1104
branch = self.make_branch('.')
1105
self.reset_smart_call_log()
1106
verb = 'Branch.get_tags_bytes'
1107
self.disable_verb(verb)
1108
branch.tags.get_tag_dict()
1109
call_count = len([call for call in self.hpss_calls if
1110
call.call.method == verb])
1111
self.assertEqual(1, call_count)
1113
def test_trivial(self):
1114
transport = MemoryTransport()
1115
client = FakeClient(transport.base)
1116
client.add_expected_call(
1117
'Branch.get_stacked_on_url', ('quack/',),
1118
'error', ('NotStacked',))
1119
client.add_expected_call(
1120
'Branch.get_tags_bytes', ('quack/',),
1122
transport.mkdir('quack')
1123
transport = transport.clone('quack')
1124
branch = self.make_remote_branch(transport, client)
1125
result = branch.tags.get_tag_dict()
1126
self.assertFinished(client)
1127
self.assertEqual({}, result)
1130
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1132
def test_trivial(self):
1133
transport = MemoryTransport()
1134
client = FakeClient(transport.base)
1135
client.add_expected_call(
1136
'Branch.get_stacked_on_url', ('quack/',),
1137
'error', ('NotStacked',))
1138
client.add_expected_call(
1139
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1141
transport.mkdir('quack')
1142
transport = transport.clone('quack')
1143
branch = self.make_remote_branch(transport, client)
1144
self.lock_remote_branch(branch)
1145
branch._set_tags_bytes('tags bytes')
1146
self.assertFinished(client)
1147
self.assertEqual('tags bytes', client._calls[-1][-1])
1149
def test_backwards_compatible(self):
1150
transport = MemoryTransport()
1151
client = FakeClient(transport.base)
1152
client.add_expected_call(
1153
'Branch.get_stacked_on_url', ('quack/',),
1154
'error', ('NotStacked',))
1155
client.add_expected_call(
1156
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1157
'unknown', ('Branch.set_tags_bytes',))
1158
transport.mkdir('quack')
1159
transport = transport.clone('quack')
1160
branch = self.make_remote_branch(transport, client)
1161
self.lock_remote_branch(branch)
1162
class StubRealBranch(object):
1165
def _set_tags_bytes(self, bytes):
1166
self.calls.append(('set_tags_bytes', bytes))
1167
real_branch = StubRealBranch()
1168
branch._real_branch = real_branch
1169
branch._set_tags_bytes('tags bytes')
1170
# Call a second time, to exercise the 'remote version already inferred'
1172
branch._set_tags_bytes('tags bytes')
1173
self.assertFinished(client)
1175
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1178
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1180
def test_uses_last_revision_info_and_tags_by_default(self):
1181
transport = MemoryTransport()
1182
client = FakeClient(transport.base)
1183
client.add_expected_call(
1184
'Branch.get_stacked_on_url', ('quack/',),
1185
'error', ('NotStacked',))
1186
client.add_expected_call(
1187
'Branch.last_revision_info', ('quack/',),
1188
'success', ('ok', '1', 'rev-tip'))
1189
client.add_expected_call(
1190
'Branch.get_config_file', ('quack/',),
1191
'success', ('ok',), '')
1192
transport.mkdir('quack')
1193
transport = transport.clone('quack')
1194
branch = self.make_remote_branch(transport, client)
1195
result = branch.heads_to_fetch()
1196
self.assertFinished(client)
1197
self.assertEqual((set(['rev-tip']), set()), result)
1199
def test_uses_last_revision_info_and_tags_when_set(self):
1200
transport = MemoryTransport()
1201
client = FakeClient(transport.base)
1202
client.add_expected_call(
1203
'Branch.get_stacked_on_url', ('quack/',),
1204
'error', ('NotStacked',))
1205
client.add_expected_call(
1206
'Branch.last_revision_info', ('quack/',),
1207
'success', ('ok', '1', 'rev-tip'))
1208
client.add_expected_call(
1209
'Branch.get_config_file', ('quack/',),
1210
'success', ('ok',), 'branch.fetch_tags = True')
1211
# XXX: this will break if the default format's serialization of tags
1212
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1213
client.add_expected_call(
1214
'Branch.get_tags_bytes', ('quack/',),
1215
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1216
transport.mkdir('quack')
1217
transport = transport.clone('quack')
1218
branch = self.make_remote_branch(transport, client)
1219
result = branch.heads_to_fetch()
1220
self.assertFinished(client)
1222
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1224
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1225
transport = MemoryTransport()
1226
client = FakeClient(transport.base)
1227
client.add_expected_call(
1228
'Branch.get_stacked_on_url', ('quack/',),
1229
'error', ('NotStacked',))
1230
client.add_expected_call(
1231
'Branch.heads_to_fetch', ('quack/',),
1232
'success', (['tip'], ['tagged-1', 'tagged-2']))
1233
transport.mkdir('quack')
1234
transport = transport.clone('quack')
1235
branch = self.make_remote_branch(transport, client)
1236
branch._format._use_default_local_heads_to_fetch = lambda: False
1237
result = branch.heads_to_fetch()
1238
self.assertFinished(client)
1239
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1241
def make_branch_with_tags(self):
1242
self.setup_smart_server_with_call_log()
1243
# Make a branch with a single revision.
1244
builder = self.make_branch_builder('foo')
1245
builder.start_series()
1246
builder.build_snapshot('tip', None, [
1247
('add', ('', 'root-id', 'directory', ''))])
1248
builder.finish_series()
1249
branch = builder.get_branch()
1250
# Add two tags to that branch
1251
branch.tags.set_tag('tag-1', 'rev-1')
1252
branch.tags.set_tag('tag-2', 'rev-2')
1255
def test_backwards_compatible(self):
1256
branch = self.make_branch_with_tags()
1257
c = branch.get_config()
1258
c.set_user_option('branch.fetch_tags', 'True')
1259
self.addCleanup(branch.lock_read().unlock)
1260
# Disable the heads_to_fetch verb
1261
verb = 'Branch.heads_to_fetch'
1262
self.disable_verb(verb)
1263
self.reset_smart_call_log()
1264
result = branch.heads_to_fetch()
1265
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1267
['Branch.last_revision_info', 'Branch.get_config_file',
1268
'Branch.get_tags_bytes'],
1269
[call.call.method for call in self.hpss_calls])
1271
def test_backwards_compatible_no_tags(self):
1272
branch = self.make_branch_with_tags()
1273
c = branch.get_config()
1274
c.set_user_option('branch.fetch_tags', 'False')
1275
self.addCleanup(branch.lock_read().unlock)
1276
# Disable the heads_to_fetch verb
1277
verb = 'Branch.heads_to_fetch'
1278
self.disable_verb(verb)
1279
self.reset_smart_call_log()
1280
result = branch.heads_to_fetch()
1281
self.assertEqual((set(['tip']), set()), result)
1283
['Branch.last_revision_info', 'Branch.get_config_file'],
1284
[call.call.method for call in self.hpss_calls])
1287
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1289
def test_empty_branch(self):
1290
# in an empty branch we decode the response properly
1291
transport = MemoryTransport()
1292
client = FakeClient(transport.base)
1293
client.add_expected_call(
1294
'Branch.get_stacked_on_url', ('quack/',),
1295
'error', ('NotStacked',))
1296
client.add_expected_call(
1297
'Branch.last_revision_info', ('quack/',),
1298
'success', ('ok', '0', 'null:'))
1299
transport.mkdir('quack')
1300
transport = transport.clone('quack')
1301
branch = self.make_remote_branch(transport, client)
1302
result = branch.last_revision_info()
1303
self.assertFinished(client)
1304
self.assertEqual((0, NULL_REVISION), result)
1306
def test_non_empty_branch(self):
1307
# in a non-empty branch we also decode the response properly
1308
revid = u'\xc8'.encode('utf8')
1309
transport = MemoryTransport()
1310
client = FakeClient(transport.base)
1311
client.add_expected_call(
1312
'Branch.get_stacked_on_url', ('kwaak/',),
1313
'error', ('NotStacked',))
1314
client.add_expected_call(
1315
'Branch.last_revision_info', ('kwaak/',),
1316
'success', ('ok', '2', revid))
1317
transport.mkdir('kwaak')
1318
transport = transport.clone('kwaak')
1319
branch = self.make_remote_branch(transport, client)
1320
result = branch.last_revision_info()
1321
self.assertEqual((2, revid), result)
1324
class TestBranch_get_stacked_on_url(TestRemote):
1325
"""Test Branch._get_stacked_on_url rpc"""
1327
def test_get_stacked_on_invalid_url(self):
1328
# test that asking for a stacked on url the server can't access works.
1329
# This isn't perfect, but then as we're in the same process there
1330
# really isn't anything we can do to be 100% sure that the server
1331
# doesn't just open in - this test probably needs to be rewritten using
1332
# a spawn()ed server.
1333
stacked_branch = self.make_branch('stacked', format='1.9')
1334
memory_branch = self.make_branch('base', format='1.9')
1335
vfs_url = self.get_vfs_only_url('base')
1336
stacked_branch.set_stacked_on_url(vfs_url)
1337
transport = stacked_branch.bzrdir.root_transport
1338
client = FakeClient(transport.base)
1339
client.add_expected_call(
1340
'Branch.get_stacked_on_url', ('stacked/',),
1341
'success', ('ok', vfs_url))
1342
# XXX: Multiple calls are bad, this second call documents what is
1344
client.add_expected_call(
1345
'Branch.get_stacked_on_url', ('stacked/',),
1346
'success', ('ok', vfs_url))
1347
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1349
repo_fmt = remote.RemoteRepositoryFormat()
1350
repo_fmt._custom_format = stacked_branch.repository._format
1351
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1353
result = branch.get_stacked_on_url()
1354
self.assertEqual(vfs_url, result)
1356
def test_backwards_compatible(self):
1357
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1358
base_branch = self.make_branch('base', format='1.6')
1359
stacked_branch = self.make_branch('stacked', format='1.6')
1360
stacked_branch.set_stacked_on_url('../base')
1361
client = FakeClient(self.get_url())
1362
branch_network_name = self.get_branch_format().network_name()
1363
client.add_expected_call(
1364
'BzrDir.open_branchV3', ('stacked/',),
1365
'success', ('branch', branch_network_name))
1366
client.add_expected_call(
1367
'BzrDir.find_repositoryV3', ('stacked/',),
1368
'success', ('ok', '', 'no', 'no', 'yes',
1369
stacked_branch.repository._format.network_name()))
1370
# called twice, once from constructor and then again by us
1371
client.add_expected_call(
1372
'Branch.get_stacked_on_url', ('stacked/',),
1373
'unknown', ('Branch.get_stacked_on_url',))
1374
client.add_expected_call(
1375
'Branch.get_stacked_on_url', ('stacked/',),
1376
'unknown', ('Branch.get_stacked_on_url',))
1377
# this will also do vfs access, but that goes direct to the transport
1378
# and isn't seen by the FakeClient.
1379
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1380
RemoteBzrDirFormat(), _client=client)
1381
branch = bzrdir.open_branch()
1382
result = branch.get_stacked_on_url()
1383
self.assertEqual('../base', result)
1384
self.assertFinished(client)
1385
# it's in the fallback list both for the RemoteRepository and its vfs
1387
self.assertEqual(1, len(branch.repository._fallback_repositories))
1389
len(branch.repository._real_repository._fallback_repositories))
1391
def test_get_stacked_on_real_branch(self):
1392
base_branch = self.make_branch('base')
1393
stacked_branch = self.make_branch('stacked')
1394
stacked_branch.set_stacked_on_url('../base')
1395
reference_format = self.get_repo_format()
1396
network_name = reference_format.network_name()
1397
client = FakeClient(self.get_url())
1398
branch_network_name = self.get_branch_format().network_name()
1399
client.add_expected_call(
1400
'BzrDir.open_branchV3', ('stacked/',),
1401
'success', ('branch', branch_network_name))
1402
client.add_expected_call(
1403
'BzrDir.find_repositoryV3', ('stacked/',),
1404
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1405
# called twice, once from constructor and then again by us
1406
client.add_expected_call(
1407
'Branch.get_stacked_on_url', ('stacked/',),
1408
'success', ('ok', '../base'))
1409
client.add_expected_call(
1410
'Branch.get_stacked_on_url', ('stacked/',),
1411
'success', ('ok', '../base'))
1412
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1413
RemoteBzrDirFormat(), _client=client)
1414
branch = bzrdir.open_branch()
1415
result = branch.get_stacked_on_url()
1416
self.assertEqual('../base', result)
1417
self.assertFinished(client)
1418
# it's in the fallback list both for the RemoteRepository.
1419
self.assertEqual(1, len(branch.repository._fallback_repositories))
1420
# And we haven't had to construct a real repository.
1421
self.assertEqual(None, branch.repository._real_repository)
1424
class TestBranchSetLastRevision(RemoteBranchTestCase):
1426
def test_set_empty(self):
1427
# _set_last_revision_info('null:') is translated to calling
1428
# Branch.set_last_revision(path, '') on the wire.
1429
transport = MemoryTransport()
1430
transport.mkdir('branch')
1431
transport = transport.clone('branch')
1433
client = FakeClient(transport.base)
1434
client.add_expected_call(
1435
'Branch.get_stacked_on_url', ('branch/',),
1436
'error', ('NotStacked',))
1437
client.add_expected_call(
1438
'Branch.lock_write', ('branch/', '', ''),
1439
'success', ('ok', 'branch token', 'repo token'))
1440
client.add_expected_call(
1441
'Branch.last_revision_info',
1443
'success', ('ok', '0', 'null:'))
1444
client.add_expected_call(
1445
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1447
client.add_expected_call(
1448
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1450
branch = self.make_remote_branch(transport, client)
1451
# This is a hack to work around the problem that RemoteBranch currently
1452
# unnecessarily invokes _ensure_real upon a call to lock_write.
1453
branch._ensure_real = lambda: None
1455
result = branch._set_last_revision(NULL_REVISION)
1457
self.assertEqual(None, result)
1458
self.assertFinished(client)
1460
def test_set_nonempty(self):
1461
# set_last_revision_info(N, rev-idN) is translated to calling
1462
# Branch.set_last_revision(path, rev-idN) on the wire.
1463
transport = MemoryTransport()
1464
transport.mkdir('branch')
1465
transport = transport.clone('branch')
1467
client = FakeClient(transport.base)
1468
client.add_expected_call(
1469
'Branch.get_stacked_on_url', ('branch/',),
1470
'error', ('NotStacked',))
1471
client.add_expected_call(
1472
'Branch.lock_write', ('branch/', '', ''),
1473
'success', ('ok', 'branch token', 'repo token'))
1474
client.add_expected_call(
1475
'Branch.last_revision_info',
1477
'success', ('ok', '0', 'null:'))
1479
encoded_body = bz2.compress('\n'.join(lines))
1480
client.add_success_response_with_body(encoded_body, 'ok')
1481
client.add_expected_call(
1482
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1484
client.add_expected_call(
1485
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1487
branch = self.make_remote_branch(transport, client)
1488
# This is a hack to work around the problem that RemoteBranch currently
1489
# unnecessarily invokes _ensure_real upon a call to lock_write.
1490
branch._ensure_real = lambda: None
1491
# Lock the branch, reset the record of remote calls.
1493
result = branch._set_last_revision('rev-id2')
1495
self.assertEqual(None, result)
1496
self.assertFinished(client)
1498
def test_no_such_revision(self):
1499
transport = MemoryTransport()
1500
transport.mkdir('branch')
1501
transport = transport.clone('branch')
1502
# A response of 'NoSuchRevision' is translated into an exception.
1503
client = FakeClient(transport.base)
1504
client.add_expected_call(
1505
'Branch.get_stacked_on_url', ('branch/',),
1506
'error', ('NotStacked',))
1507
client.add_expected_call(
1508
'Branch.lock_write', ('branch/', '', ''),
1509
'success', ('ok', 'branch token', 'repo token'))
1510
client.add_expected_call(
1511
'Branch.last_revision_info',
1513
'success', ('ok', '0', 'null:'))
1514
# get_graph calls to construct the revision history, for the set_rh
1517
encoded_body = bz2.compress('\n'.join(lines))
1518
client.add_success_response_with_body(encoded_body, 'ok')
1519
client.add_expected_call(
1520
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1521
'error', ('NoSuchRevision', 'rev-id'))
1522
client.add_expected_call(
1523
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1526
branch = self.make_remote_branch(transport, client)
1529
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1531
self.assertFinished(client)
1533
def test_tip_change_rejected(self):
1534
"""TipChangeRejected responses cause a TipChangeRejected exception to
1537
transport = MemoryTransport()
1538
transport.mkdir('branch')
1539
transport = transport.clone('branch')
1540
client = FakeClient(transport.base)
1541
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1542
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1543
client.add_expected_call(
1544
'Branch.get_stacked_on_url', ('branch/',),
1545
'error', ('NotStacked',))
1546
client.add_expected_call(
1547
'Branch.lock_write', ('branch/', '', ''),
1548
'success', ('ok', 'branch token', 'repo token'))
1549
client.add_expected_call(
1550
'Branch.last_revision_info',
1552
'success', ('ok', '0', 'null:'))
1554
encoded_body = bz2.compress('\n'.join(lines))
1555
client.add_success_response_with_body(encoded_body, 'ok')
1556
client.add_expected_call(
1557
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1558
'error', ('TipChangeRejected', rejection_msg_utf8))
1559
client.add_expected_call(
1560
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1562
branch = self.make_remote_branch(transport, client)
1563
branch._ensure_real = lambda: None
1565
# The 'TipChangeRejected' error response triggered by calling
1566
# set_last_revision_info causes a TipChangeRejected exception.
1567
err = self.assertRaises(
1568
errors.TipChangeRejected,
1569
branch._set_last_revision, 'rev-id')
1570
# The UTF-8 message from the response has been decoded into a unicode
1572
self.assertIsInstance(err.msg, unicode)
1573
self.assertEqual(rejection_msg_unicode, err.msg)
1575
self.assertFinished(client)
1578
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1580
def test_set_last_revision_info(self):
1581
# set_last_revision_info(num, 'rev-id') is translated to calling
1582
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1583
transport = MemoryTransport()
1584
transport.mkdir('branch')
1585
transport = transport.clone('branch')
1586
client = FakeClient(transport.base)
1587
# get_stacked_on_url
1588
client.add_error_response('NotStacked')
1590
client.add_success_response('ok', 'branch token', 'repo token')
1591
# query the current revision
1592
client.add_success_response('ok', '0', 'null:')
1594
client.add_success_response('ok')
1596
client.add_success_response('ok')
1598
branch = self.make_remote_branch(transport, client)
1599
# Lock the branch, reset the record of remote calls.
1602
result = branch.set_last_revision_info(1234, 'a-revision-id')
1604
[('call', 'Branch.last_revision_info', ('branch/',)),
1605
('call', 'Branch.set_last_revision_info',
1606
('branch/', 'branch token', 'repo token',
1607
'1234', 'a-revision-id'))],
1609
self.assertEqual(None, result)
1611
def test_no_such_revision(self):
1612
# A response of 'NoSuchRevision' is translated into an exception.
1613
transport = MemoryTransport()
1614
transport.mkdir('branch')
1615
transport = transport.clone('branch')
1616
client = FakeClient(transport.base)
1617
# get_stacked_on_url
1618
client.add_error_response('NotStacked')
1620
client.add_success_response('ok', 'branch token', 'repo token')
1622
client.add_error_response('NoSuchRevision', 'revid')
1624
client.add_success_response('ok')
1626
branch = self.make_remote_branch(transport, client)
1627
# Lock the branch, reset the record of remote calls.
1632
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1635
def test_backwards_compatibility(self):
1636
"""If the server does not support the Branch.set_last_revision_info
1637
verb (which is new in 1.4), then the client falls back to VFS methods.
1639
# This test is a little messy. Unlike most tests in this file, it
1640
# doesn't purely test what a Remote* object sends over the wire, and
1641
# how it reacts to responses from the wire. It instead relies partly
1642
# on asserting that the RemoteBranch will call
1643
# self._real_branch.set_last_revision_info(...).
1645
# First, set up our RemoteBranch with a FakeClient that raises
1646
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1647
transport = MemoryTransport()
1648
transport.mkdir('branch')
1649
transport = transport.clone('branch')
1650
client = FakeClient(transport.base)
1651
client.add_expected_call(
1652
'Branch.get_stacked_on_url', ('branch/',),
1653
'error', ('NotStacked',))
1654
client.add_expected_call(
1655
'Branch.last_revision_info',
1657
'success', ('ok', '0', 'null:'))
1658
client.add_expected_call(
1659
'Branch.set_last_revision_info',
1660
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1661
'unknown', 'Branch.set_last_revision_info')
1663
branch = self.make_remote_branch(transport, client)
1664
class StubRealBranch(object):
1667
def set_last_revision_info(self, revno, revision_id):
1669
('set_last_revision_info', revno, revision_id))
1670
def _clear_cached_state(self):
1672
real_branch = StubRealBranch()
1673
branch._real_branch = real_branch
1674
self.lock_remote_branch(branch)
1676
# Call set_last_revision_info, and verify it behaved as expected.
1677
result = branch.set_last_revision_info(1234, 'a-revision-id')
1679
[('set_last_revision_info', 1234, 'a-revision-id')],
1681
self.assertFinished(client)
1683
def test_unexpected_error(self):
1684
# If the server sends an error the client doesn't understand, it gets
1685
# turned into an UnknownErrorFromSmartServer, which is presented as a
1686
# non-internal error to the user.
1687
transport = MemoryTransport()
1688
transport.mkdir('branch')
1689
transport = transport.clone('branch')
1690
client = FakeClient(transport.base)
1691
# get_stacked_on_url
1692
client.add_error_response('NotStacked')
1694
client.add_success_response('ok', 'branch token', 'repo token')
1696
client.add_error_response('UnexpectedError')
1698
client.add_success_response('ok')
1700
branch = self.make_remote_branch(transport, client)
1701
# Lock the branch, reset the record of remote calls.
1705
err = self.assertRaises(
1706
errors.UnknownErrorFromSmartServer,
1707
branch.set_last_revision_info, 123, 'revid')
1708
self.assertEqual(('UnexpectedError',), err.error_tuple)
1711
def test_tip_change_rejected(self):
1712
"""TipChangeRejected responses cause a TipChangeRejected exception to
1715
transport = MemoryTransport()
1716
transport.mkdir('branch')
1717
transport = transport.clone('branch')
1718
client = FakeClient(transport.base)
1719
# get_stacked_on_url
1720
client.add_error_response('NotStacked')
1722
client.add_success_response('ok', 'branch token', 'repo token')
1724
client.add_error_response('TipChangeRejected', 'rejection message')
1726
client.add_success_response('ok')
1728
branch = self.make_remote_branch(transport, client)
1729
# Lock the branch, reset the record of remote calls.
1731
self.addCleanup(branch.unlock)
1734
# The 'TipChangeRejected' error response triggered by calling
1735
# set_last_revision_info causes a TipChangeRejected exception.
1736
err = self.assertRaises(
1737
errors.TipChangeRejected,
1738
branch.set_last_revision_info, 123, 'revid')
1739
self.assertEqual('rejection message', err.msg)
1742
class TestBranchGetSetConfig(RemoteBranchTestCase):
1744
def test_get_branch_conf(self):
1745
# in an empty branch we decode the response properly
1746
client = FakeClient()
1747
client.add_expected_call(
1748
'Branch.get_stacked_on_url', ('memory:///',),
1749
'error', ('NotStacked',),)
1750
client.add_success_response_with_body('# config file body', 'ok')
1751
transport = MemoryTransport()
1752
branch = self.make_remote_branch(transport, client)
1753
config = branch.get_config()
1754
config.has_explicit_nickname()
1756
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1757
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1760
def test_get_multi_line_branch_conf(self):
1761
# Make sure that multiple-line branch.conf files are supported
1763
# https://bugs.launchpad.net/bzr/+bug/354075
1764
client = FakeClient()
1765
client.add_expected_call(
1766
'Branch.get_stacked_on_url', ('memory:///',),
1767
'error', ('NotStacked',),)
1768
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1769
transport = MemoryTransport()
1770
branch = self.make_remote_branch(transport, client)
1771
config = branch.get_config()
1772
self.assertEqual(u'2', config.get_user_option('b'))
1774
def test_set_option(self):
1775
client = FakeClient()
1776
client.add_expected_call(
1777
'Branch.get_stacked_on_url', ('memory:///',),
1778
'error', ('NotStacked',),)
1779
client.add_expected_call(
1780
'Branch.lock_write', ('memory:///', '', ''),
1781
'success', ('ok', 'branch token', 'repo token'))
1782
client.add_expected_call(
1783
'Branch.set_config_option', ('memory:///', 'branch token',
1784
'repo token', 'foo', 'bar', ''),
1786
client.add_expected_call(
1787
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1789
transport = MemoryTransport()
1790
branch = self.make_remote_branch(transport, client)
1792
config = branch._get_config()
1793
config.set_option('foo', 'bar')
1795
self.assertFinished(client)
1797
def test_set_option_with_dict(self):
1798
client = FakeClient()
1799
client.add_expected_call(
1800
'Branch.get_stacked_on_url', ('memory:///',),
1801
'error', ('NotStacked',),)
1802
client.add_expected_call(
1803
'Branch.lock_write', ('memory:///', '', ''),
1804
'success', ('ok', 'branch token', 'repo token'))
1805
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1806
client.add_expected_call(
1807
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1808
'repo token', encoded_dict_value, 'foo', ''),
1810
client.add_expected_call(
1811
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1813
transport = MemoryTransport()
1814
branch = self.make_remote_branch(transport, client)
1816
config = branch._get_config()
1818
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1821
self.assertFinished(client)
1823
def test_backwards_compat_set_option(self):
1824
self.setup_smart_server_with_call_log()
1825
branch = self.make_branch('.')
1826
verb = 'Branch.set_config_option'
1827
self.disable_verb(verb)
1829
self.addCleanup(branch.unlock)
1830
self.reset_smart_call_log()
1831
branch._get_config().set_option('value', 'name')
1832
self.assertLength(10, self.hpss_calls)
1833
self.assertEqual('value', branch._get_config().get_option('name'))
1835
def test_backwards_compat_set_option_with_dict(self):
1836
self.setup_smart_server_with_call_log()
1837
branch = self.make_branch('.')
1838
verb = 'Branch.set_config_option_dict'
1839
self.disable_verb(verb)
1841
self.addCleanup(branch.unlock)
1842
self.reset_smart_call_log()
1843
config = branch._get_config()
1844
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
1845
config.set_option(value_dict, 'name')
1846
self.assertLength(10, self.hpss_calls)
1847
self.assertEqual(value_dict, branch._get_config().get_option('name'))
1850
class TestBranchLockWrite(RemoteBranchTestCase):
1852
def test_lock_write_unlockable(self):
1853
transport = MemoryTransport()
1854
client = FakeClient(transport.base)
1855
client.add_expected_call(
1856
'Branch.get_stacked_on_url', ('quack/',),
1857
'error', ('NotStacked',),)
1858
client.add_expected_call(
1859
'Branch.lock_write', ('quack/', '', ''),
1860
'error', ('UnlockableTransport',))
1861
transport.mkdir('quack')
1862
transport = transport.clone('quack')
1863
branch = self.make_remote_branch(transport, client)
1864
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1865
self.assertFinished(client)
1868
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1870
def test__get_config(self):
1871
client = FakeClient()
1872
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1873
transport = MemoryTransport()
1874
bzrdir = self.make_remote_bzrdir(transport, client)
1875
config = bzrdir.get_config()
1876
self.assertEqual('/', config.get_default_stack_on())
1878
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1881
def test_set_option_uses_vfs(self):
1882
self.setup_smart_server_with_call_log()
1883
bzrdir = self.make_bzrdir('.')
1884
self.reset_smart_call_log()
1885
config = bzrdir.get_config()
1886
config.set_default_stack_on('/')
1887
self.assertLength(3, self.hpss_calls)
1889
def test_backwards_compat_get_option(self):
1890
self.setup_smart_server_with_call_log()
1891
bzrdir = self.make_bzrdir('.')
1892
verb = 'BzrDir.get_config_file'
1893
self.disable_verb(verb)
1894
self.reset_smart_call_log()
1895
self.assertEqual(None,
1896
bzrdir._get_config().get_option('default_stack_on'))
1897
self.assertLength(3, self.hpss_calls)
1900
class TestTransportIsReadonly(tests.TestCase):
1902
def test_true(self):
1903
client = FakeClient()
1904
client.add_success_response('yes')
1905
transport = RemoteTransport('bzr://example.com/', medium=False,
1907
self.assertEqual(True, transport.is_readonly())
1909
[('call', 'Transport.is_readonly', ())],
1912
def test_false(self):
1913
client = FakeClient()
1914
client.add_success_response('no')
1915
transport = RemoteTransport('bzr://example.com/', medium=False,
1917
self.assertEqual(False, transport.is_readonly())
1919
[('call', 'Transport.is_readonly', ())],
1922
def test_error_from_old_server(self):
1923
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1925
Clients should treat it as a "no" response, because is_readonly is only
1926
advisory anyway (a transport could be read-write, but then the
1927
underlying filesystem could be readonly anyway).
1929
client = FakeClient()
1930
client.add_unknown_method_response('Transport.is_readonly')
1931
transport = RemoteTransport('bzr://example.com/', medium=False,
1933
self.assertEqual(False, transport.is_readonly())
1935
[('call', 'Transport.is_readonly', ())],
1939
class TestTransportMkdir(tests.TestCase):
1941
def test_permissiondenied(self):
1942
client = FakeClient()
1943
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1944
transport = RemoteTransport('bzr://example.com/', medium=False,
1946
exc = self.assertRaises(
1947
errors.PermissionDenied, transport.mkdir, 'client path')
1948
expected_error = errors.PermissionDenied('/client path', 'extra')
1949
self.assertEqual(expected_error, exc)
1952
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1954
def test_defaults_to_none(self):
1955
t = RemoteSSHTransport('bzr+ssh://example.com')
1956
self.assertIs(None, t._get_credentials()[0])
1958
def test_uses_authentication_config(self):
1959
conf = config.AuthenticationConfig()
1960
conf._get_config().update(
1961
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1964
t = RemoteSSHTransport('bzr+ssh://example.com')
1965
self.assertEqual('bar', t._get_credentials()[0])
1968
class TestRemoteRepository(TestRemote):
1969
"""Base for testing RemoteRepository protocol usage.
1971
These tests contain frozen requests and responses. We want any changes to
1972
what is sent or expected to be require a thoughtful update to these tests
1973
because they might break compatibility with different-versioned servers.
1976
def setup_fake_client_and_repository(self, transport_path):
1977
"""Create the fake client and repository for testing with.
1979
There's no real server here; we just have canned responses sent
1982
:param transport_path: Path below the root of the MemoryTransport
1983
where the repository will be created.
1985
transport = MemoryTransport()
1986
transport.mkdir(transport_path)
1987
client = FakeClient(transport.base)
1988
transport = transport.clone(transport_path)
1989
# we do not want bzrdir to make any remote calls
1990
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1992
repo = RemoteRepository(bzrdir, None, _client=client)
1996
def remoted_description(format):
1997
return 'Remote: ' + format.get_format_description()
2000
class TestBranchFormat(tests.TestCase):
2002
def test_get_format_description(self):
2003
remote_format = RemoteBranchFormat()
2004
real_format = branch.format_registry.get_default()
2005
remote_format._network_name = real_format.network_name()
2006
self.assertEqual(remoted_description(real_format),
2007
remote_format.get_format_description())
2010
class TestRepositoryFormat(TestRemoteRepository):
2012
def test_fast_delta(self):
2013
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2014
true_format = RemoteRepositoryFormat()
2015
true_format._network_name = true_name
2016
self.assertEqual(True, true_format.fast_deltas)
2017
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2018
false_format = RemoteRepositoryFormat()
2019
false_format._network_name = false_name
2020
self.assertEqual(False, false_format.fast_deltas)
2022
def test_get_format_description(self):
2023
remote_repo_format = RemoteRepositoryFormat()
2024
real_format = repository.format_registry.get_default()
2025
remote_repo_format._network_name = real_format.network_name()
2026
self.assertEqual(remoted_description(real_format),
2027
remote_repo_format.get_format_description())
2030
class TestRepositoryGatherStats(TestRemoteRepository):
2032
def test_revid_none(self):
2033
# ('ok',), body with revisions and size
2034
transport_path = 'quack'
2035
repo, client = self.setup_fake_client_and_repository(transport_path)
2036
client.add_success_response_with_body(
2037
'revisions: 2\nsize: 18\n', 'ok')
2038
result = repo.gather_stats(None)
2040
[('call_expecting_body', 'Repository.gather_stats',
2041
('quack/','','no'))],
2043
self.assertEqual({'revisions': 2, 'size': 18}, result)
2045
def test_revid_no_committers(self):
2046
# ('ok',), body without committers
2047
body = ('firstrev: 123456.300 3600\n'
2048
'latestrev: 654231.400 0\n'
2051
transport_path = 'quick'
2052
revid = u'\xc8'.encode('utf8')
2053
repo, client = self.setup_fake_client_and_repository(transport_path)
2054
client.add_success_response_with_body(body, 'ok')
2055
result = repo.gather_stats(revid)
2057
[('call_expecting_body', 'Repository.gather_stats',
2058
('quick/', revid, 'no'))],
2060
self.assertEqual({'revisions': 2, 'size': 18,
2061
'firstrev': (123456.300, 3600),
2062
'latestrev': (654231.400, 0),},
2065
def test_revid_with_committers(self):
2066
# ('ok',), body with committers
2067
body = ('committers: 128\n'
2068
'firstrev: 123456.300 3600\n'
2069
'latestrev: 654231.400 0\n'
2072
transport_path = 'buick'
2073
revid = u'\xc8'.encode('utf8')
2074
repo, client = self.setup_fake_client_and_repository(transport_path)
2075
client.add_success_response_with_body(body, 'ok')
2076
result = repo.gather_stats(revid, True)
2078
[('call_expecting_body', 'Repository.gather_stats',
2079
('buick/', revid, 'yes'))],
2081
self.assertEqual({'revisions': 2, 'size': 18,
2083
'firstrev': (123456.300, 3600),
2084
'latestrev': (654231.400, 0),},
2088
class TestRepositoryGetGraph(TestRemoteRepository):
2090
def test_get_graph(self):
2091
# get_graph returns a graph with a custom parents provider.
2092
transport_path = 'quack'
2093
repo, client = self.setup_fake_client_and_repository(transport_path)
2094
graph = repo.get_graph()
2095
self.assertNotEqual(graph._parents_provider, repo)
2098
class TestRepositoryGetParentMap(TestRemoteRepository):
2100
def test_get_parent_map_caching(self):
2101
# get_parent_map returns from cache until unlock()
2102
# setup a reponse with two revisions
2103
r1 = u'\u0e33'.encode('utf8')
2104
r2 = u'\u0dab'.encode('utf8')
2105
lines = [' '.join([r2, r1]), r1]
2106
encoded_body = bz2.compress('\n'.join(lines))
2108
transport_path = 'quack'
2109
repo, client = self.setup_fake_client_and_repository(transport_path)
2110
client.add_success_response_with_body(encoded_body, 'ok')
2111
client.add_success_response_with_body(encoded_body, 'ok')
2113
graph = repo.get_graph()
2114
parents = graph.get_parent_map([r2])
2115
self.assertEqual({r2: (r1,)}, parents)
2116
# locking and unlocking deeper should not reset
2119
parents = graph.get_parent_map([r1])
2120
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2122
[('call_with_body_bytes_expecting_body',
2123
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2127
# now we call again, and it should use the second response.
2129
graph = repo.get_graph()
2130
parents = graph.get_parent_map([r1])
2131
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2133
[('call_with_body_bytes_expecting_body',
2134
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2136
('call_with_body_bytes_expecting_body',
2137
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2143
def test_get_parent_map_reconnects_if_unknown_method(self):
2144
transport_path = 'quack'
2145
rev_id = 'revision-id'
2146
repo, client = self.setup_fake_client_and_repository(transport_path)
2147
client.add_unknown_method_response('Repository.get_parent_map')
2148
client.add_success_response_with_body(rev_id, 'ok')
2149
self.assertFalse(client._medium._is_remote_before((1, 2)))
2150
parents = repo.get_parent_map([rev_id])
2152
[('call_with_body_bytes_expecting_body',
2153
'Repository.get_parent_map',
2154
('quack/', 'include-missing:', rev_id), '\n\n0'),
2155
('disconnect medium',),
2156
('call_expecting_body', 'Repository.get_revision_graph',
2159
# The medium is now marked as being connected to an older server
2160
self.assertTrue(client._medium._is_remote_before((1, 2)))
2161
self.assertEqual({rev_id: ('null:',)}, parents)
2163
def test_get_parent_map_fallback_parentless_node(self):
2164
"""get_parent_map falls back to get_revision_graph on old servers. The
2165
results from get_revision_graph are tweaked to match the get_parent_map
2168
Specifically, a {key: ()} result from get_revision_graph means "no
2169
parents" for that key, which in get_parent_map results should be
2170
represented as {key: ('null:',)}.
2172
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2174
rev_id = 'revision-id'
2175
transport_path = 'quack'
2176
repo, client = self.setup_fake_client_and_repository(transport_path)
2177
client.add_success_response_with_body(rev_id, 'ok')
2178
client._medium._remember_remote_is_before((1, 2))
2179
parents = repo.get_parent_map([rev_id])
2181
[('call_expecting_body', 'Repository.get_revision_graph',
2184
self.assertEqual({rev_id: ('null:',)}, parents)
2186
def test_get_parent_map_unexpected_response(self):
2187
repo, client = self.setup_fake_client_and_repository('path')
2188
client.add_success_response('something unexpected!')
2190
errors.UnexpectedSmartServerResponse,
2191
repo.get_parent_map, ['a-revision-id'])
2193
def test_get_parent_map_negative_caches_missing_keys(self):
2194
self.setup_smart_server_with_call_log()
2195
repo = self.make_repository('foo')
2196
self.assertIsInstance(repo, RemoteRepository)
2198
self.addCleanup(repo.unlock)
2199
self.reset_smart_call_log()
2200
graph = repo.get_graph()
2201
self.assertEqual({},
2202
graph.get_parent_map(['some-missing', 'other-missing']))
2203
self.assertLength(1, self.hpss_calls)
2204
# No call if we repeat this
2205
self.reset_smart_call_log()
2206
graph = repo.get_graph()
2207
self.assertEqual({},
2208
graph.get_parent_map(['some-missing', 'other-missing']))
2209
self.assertLength(0, self.hpss_calls)
2210
# Asking for more unknown keys makes a request.
2211
self.reset_smart_call_log()
2212
graph = repo.get_graph()
2213
self.assertEqual({},
2214
graph.get_parent_map(['some-missing', 'other-missing',
2216
self.assertLength(1, self.hpss_calls)
2218
def disableExtraResults(self):
2219
self.overrideAttr(SmartServerRepositoryGetParentMap,
2220
'no_extra_results', True)
2222
def test_null_cached_missing_and_stop_key(self):
2223
self.setup_smart_server_with_call_log()
2224
# Make a branch with a single revision.
2225
builder = self.make_branch_builder('foo')
2226
builder.start_series()
2227
builder.build_snapshot('first', None, [
2228
('add', ('', 'root-id', 'directory', ''))])
2229
builder.finish_series()
2230
branch = builder.get_branch()
2231
repo = branch.repository
2232
self.assertIsInstance(repo, RemoteRepository)
2233
# Stop the server from sending extra results.
2234
self.disableExtraResults()
2236
self.addCleanup(repo.unlock)
2237
self.reset_smart_call_log()
2238
graph = repo.get_graph()
2239
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2240
# 'first' it will be a candidate for the stop_keys of subsequent
2241
# requests, and because 'null:' was queried but not returned it will be
2242
# cached as missing.
2243
self.assertEqual({'first': ('null:',)},
2244
graph.get_parent_map(['first', 'null:']))
2245
# Now query for another key. This request will pass along a recipe of
2246
# start and stop keys describing the already cached results, and this
2247
# recipe's revision count must be correct (or else it will trigger an
2248
# error from the server).
2249
self.assertEqual({}, graph.get_parent_map(['another-key']))
2250
# This assertion guards against disableExtraResults silently failing to
2251
# work, thus invalidating the test.
2252
self.assertLength(2, self.hpss_calls)
2254
def test_get_parent_map_gets_ghosts_from_result(self):
2255
# asking for a revision should negatively cache close ghosts in its
2257
self.setup_smart_server_with_call_log()
2258
tree = self.make_branch_and_memory_tree('foo')
2261
builder = treebuilder.TreeBuilder()
2262
builder.start_tree(tree)
2264
builder.finish_tree()
2265
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2266
rev_id = tree.commit('')
2270
self.addCleanup(tree.unlock)
2271
repo = tree.branch.repository
2272
self.assertIsInstance(repo, RemoteRepository)
2274
repo.get_parent_map([rev_id])
2275
self.reset_smart_call_log()
2276
# Now asking for rev_id's ghost parent should not make calls
2277
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2278
self.assertLength(0, self.hpss_calls)
2280
def test_exposes_get_cached_parent_map(self):
2281
"""RemoteRepository exposes get_cached_parent_map from
2284
r1 = u'\u0e33'.encode('utf8')
2285
r2 = u'\u0dab'.encode('utf8')
2286
lines = [' '.join([r2, r1]), r1]
2287
encoded_body = bz2.compress('\n'.join(lines))
2289
transport_path = 'quack'
2290
repo, client = self.setup_fake_client_and_repository(transport_path)
2291
client.add_success_response_with_body(encoded_body, 'ok')
2293
# get_cached_parent_map should *not* trigger an RPC
2294
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2295
self.assertEqual([], client._calls)
2296
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2297
self.assertEqual({r1: (NULL_REVISION,)},
2298
repo.get_cached_parent_map([r1]))
2300
[('call_with_body_bytes_expecting_body',
2301
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2307
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2309
def test_allows_new_revisions(self):
2310
"""get_parent_map's results can be updated by commit."""
2311
smart_server = test_server.SmartTCPServer_for_testing()
2312
self.start_server(smart_server)
2313
self.make_branch('branch')
2314
branch = Branch.open(smart_server.get_url() + '/branch')
2315
tree = branch.create_checkout('tree', lightweight=True)
2317
self.addCleanup(tree.unlock)
2318
graph = tree.branch.repository.get_graph()
2319
# This provides an opportunity for the missing rev-id to be cached.
2320
self.assertEqual({}, graph.get_parent_map(['rev1']))
2321
tree.commit('message', rev_id='rev1')
2322
graph = tree.branch.repository.get_graph()
2323
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2326
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2328
def test_null_revision(self):
2329
# a null revision has the predictable result {}, we should have no wire
2330
# traffic when calling it with this argument
2331
transport_path = 'empty'
2332
repo, client = self.setup_fake_client_and_repository(transport_path)
2333
client.add_success_response('notused')
2334
# actual RemoteRepository.get_revision_graph is gone, but there's an
2335
# equivalent private method for testing
2336
result = repo._get_revision_graph(NULL_REVISION)
2337
self.assertEqual([], client._calls)
2338
self.assertEqual({}, result)
2340
def test_none_revision(self):
2341
# with none we want the entire graph
2342
r1 = u'\u0e33'.encode('utf8')
2343
r2 = u'\u0dab'.encode('utf8')
2344
lines = [' '.join([r2, r1]), r1]
2345
encoded_body = '\n'.join(lines)
2347
transport_path = 'sinhala'
2348
repo, client = self.setup_fake_client_and_repository(transport_path)
2349
client.add_success_response_with_body(encoded_body, 'ok')
2350
# actual RemoteRepository.get_revision_graph is gone, but there's an
2351
# equivalent private method for testing
2352
result = repo._get_revision_graph(None)
2354
[('call_expecting_body', 'Repository.get_revision_graph',
2357
self.assertEqual({r1: (), r2: (r1, )}, result)
2359
def test_specific_revision(self):
2360
# with a specific revision we want the graph for that
2361
# with none we want the entire graph
2362
r11 = u'\u0e33'.encode('utf8')
2363
r12 = u'\xc9'.encode('utf8')
2364
r2 = u'\u0dab'.encode('utf8')
2365
lines = [' '.join([r2, r11, r12]), r11, r12]
2366
encoded_body = '\n'.join(lines)
2368
transport_path = 'sinhala'
2369
repo, client = self.setup_fake_client_and_repository(transport_path)
2370
client.add_success_response_with_body(encoded_body, 'ok')
2371
result = repo._get_revision_graph(r2)
2373
[('call_expecting_body', 'Repository.get_revision_graph',
2376
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2378
def test_no_such_revision(self):
2380
transport_path = 'sinhala'
2381
repo, client = self.setup_fake_client_and_repository(transport_path)
2382
client.add_error_response('nosuchrevision', revid)
2383
# also check that the right revision is reported in the error
2384
self.assertRaises(errors.NoSuchRevision,
2385
repo._get_revision_graph, revid)
2387
[('call_expecting_body', 'Repository.get_revision_graph',
2388
('sinhala/', revid))],
2391
def test_unexpected_error(self):
2393
transport_path = 'sinhala'
2394
repo, client = self.setup_fake_client_and_repository(transport_path)
2395
client.add_error_response('AnUnexpectedError')
2396
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2397
repo._get_revision_graph, revid)
2398
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2401
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2404
repo, client = self.setup_fake_client_and_repository('quack')
2405
client.add_expected_call(
2406
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2407
'success', ('ok', 'rev-five'))
2408
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2409
self.assertEqual((True, 'rev-five'), result)
2410
self.assertFinished(client)
2412
def test_history_incomplete(self):
2413
repo, client = self.setup_fake_client_and_repository('quack')
2414
client.add_expected_call(
2415
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2416
'success', ('history-incomplete', 10, 'rev-ten'))
2417
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2418
self.assertEqual((False, (10, 'rev-ten')), result)
2419
self.assertFinished(client)
2421
def test_history_incomplete_with_fallback(self):
2422
"""A 'history-incomplete' response causes the fallback repository to be
2423
queried too, if one is set.
2425
# Make a repo with a fallback repo, both using a FakeClient.
2426
format = remote.response_tuple_to_repo_format(
2427
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2428
repo, client = self.setup_fake_client_and_repository('quack')
2429
repo._format = format
2430
fallback_repo, ignored = self.setup_fake_client_and_repository(
2432
fallback_repo._client = client
2433
fallback_repo._format = format
2434
repo.add_fallback_repository(fallback_repo)
2435
# First the client should ask the primary repo
2436
client.add_expected_call(
2437
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2438
'success', ('history-incomplete', 2, 'rev-two'))
2439
# Then it should ask the fallback, using revno/revid from the
2440
# history-incomplete response as the known revno/revid.
2441
client.add_expected_call(
2442
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2443
'success', ('ok', 'rev-one'))
2444
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2445
self.assertEqual((True, 'rev-one'), result)
2446
self.assertFinished(client)
2448
def test_nosuchrevision(self):
2449
# 'nosuchrevision' is returned when the known-revid is not found in the
2450
# remote repo. The client translates that response to NoSuchRevision.
2451
repo, client = self.setup_fake_client_and_repository('quack')
2452
client.add_expected_call(
2453
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2454
'error', ('nosuchrevision', 'rev-foo'))
2456
errors.NoSuchRevision,
2457
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2458
self.assertFinished(client)
2460
def test_branch_fallback_locking(self):
2461
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2462
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2463
will be invoked, which will fail if the repo is unlocked.
2465
self.setup_smart_server_with_call_log()
2466
tree = self.make_branch_and_memory_tree('.')
2469
rev1 = tree.commit('First')
2470
rev2 = tree.commit('Second')
2472
branch = tree.branch
2473
self.assertFalse(branch.is_locked())
2474
self.reset_smart_call_log()
2475
verb = 'Repository.get_rev_id_for_revno'
2476
self.disable_verb(verb)
2477
self.assertEqual(rev1, branch.get_rev_id(1))
2478
self.assertLength(1, [call for call in self.hpss_calls if
2479
call.call.method == verb])
2482
class TestRepositoryIsShared(TestRemoteRepository):
2484
def test_is_shared(self):
2485
# ('yes', ) for Repository.is_shared -> 'True'.
2486
transport_path = 'quack'
2487
repo, client = self.setup_fake_client_and_repository(transport_path)
2488
client.add_success_response('yes')
2489
result = repo.is_shared()
2491
[('call', 'Repository.is_shared', ('quack/',))],
2493
self.assertEqual(True, result)
2495
def test_is_not_shared(self):
2496
# ('no', ) for Repository.is_shared -> 'False'.
2497
transport_path = 'qwack'
2498
repo, client = self.setup_fake_client_and_repository(transport_path)
2499
client.add_success_response('no')
2500
result = repo.is_shared()
2502
[('call', 'Repository.is_shared', ('qwack/',))],
2504
self.assertEqual(False, result)
2507
class TestRepositoryLockWrite(TestRemoteRepository):
2509
def test_lock_write(self):
2510
transport_path = 'quack'
2511
repo, client = self.setup_fake_client_and_repository(transport_path)
2512
client.add_success_response('ok', 'a token')
2513
token = repo.lock_write().repository_token
2515
[('call', 'Repository.lock_write', ('quack/', ''))],
2517
self.assertEqual('a token', token)
2519
def test_lock_write_already_locked(self):
2520
transport_path = 'quack'
2521
repo, client = self.setup_fake_client_and_repository(transport_path)
2522
client.add_error_response('LockContention')
2523
self.assertRaises(errors.LockContention, repo.lock_write)
2525
[('call', 'Repository.lock_write', ('quack/', ''))],
2528
def test_lock_write_unlockable(self):
2529
transport_path = 'quack'
2530
repo, client = self.setup_fake_client_and_repository(transport_path)
2531
client.add_error_response('UnlockableTransport')
2532
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2534
[('call', 'Repository.lock_write', ('quack/', ''))],
2538
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2540
def test_backwards_compat(self):
2541
self.setup_smart_server_with_call_log()
2542
repo = self.make_repository('.')
2543
self.reset_smart_call_log()
2544
verb = 'Repository.set_make_working_trees'
2545
self.disable_verb(verb)
2546
repo.set_make_working_trees(True)
2547
call_count = len([call for call in self.hpss_calls if
2548
call.call.method == verb])
2549
self.assertEqual(1, call_count)
2551
def test_current(self):
2552
transport_path = 'quack'
2553
repo, client = self.setup_fake_client_and_repository(transport_path)
2554
client.add_expected_call(
2555
'Repository.set_make_working_trees', ('quack/', 'True'),
2557
client.add_expected_call(
2558
'Repository.set_make_working_trees', ('quack/', 'False'),
2560
repo.set_make_working_trees(True)
2561
repo.set_make_working_trees(False)
2564
class TestRepositoryUnlock(TestRemoteRepository):
2566
def test_unlock(self):
2567
transport_path = 'quack'
2568
repo, client = self.setup_fake_client_and_repository(transport_path)
2569
client.add_success_response('ok', 'a token')
2570
client.add_success_response('ok')
2574
[('call', 'Repository.lock_write', ('quack/', '')),
2575
('call', 'Repository.unlock', ('quack/', 'a token'))],
2578
def test_unlock_wrong_token(self):
2579
# If somehow the token is wrong, unlock will raise TokenMismatch.
2580
transport_path = 'quack'
2581
repo, client = self.setup_fake_client_and_repository(transport_path)
2582
client.add_success_response('ok', 'a token')
2583
client.add_error_response('TokenMismatch')
2585
self.assertRaises(errors.TokenMismatch, repo.unlock)
2588
class TestRepositoryHasRevision(TestRemoteRepository):
2590
def test_none(self):
2591
# repo.has_revision(None) should not cause any traffic.
2592
transport_path = 'quack'
2593
repo, client = self.setup_fake_client_and_repository(transport_path)
2595
# The null revision is always there, so has_revision(None) == True.
2596
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2598
# The remote repo shouldn't be accessed.
2599
self.assertEqual([], client._calls)
2602
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2603
"""Base class for Repository.insert_stream and .insert_stream_1.19
2607
def checkInsertEmptyStream(self, repo, client):
2608
"""Insert an empty stream, checking the result.
2610
This checks that there are no resume_tokens or missing_keys, and that
2611
the client is finished.
2613
sink = repo._get_sink()
2614
fmt = repository.format_registry.get_default()
2615
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2616
self.assertEqual([], resume_tokens)
2617
self.assertEqual(set(), missing_keys)
2618
self.assertFinished(client)
2621
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2622
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2625
This test case is very similar to TestRepositoryInsertStream_1_19.
2629
TestRemoteRepository.setUp(self)
2630
self.disable_verb('Repository.insert_stream_1.19')
2632
def test_unlocked_repo(self):
2633
transport_path = 'quack'
2634
repo, client = self.setup_fake_client_and_repository(transport_path)
2635
client.add_expected_call(
2636
'Repository.insert_stream_1.19', ('quack/', ''),
2637
'unknown', ('Repository.insert_stream_1.19',))
2638
client.add_expected_call(
2639
'Repository.insert_stream', ('quack/', ''),
2641
client.add_expected_call(
2642
'Repository.insert_stream', ('quack/', ''),
2644
self.checkInsertEmptyStream(repo, client)
2646
def test_locked_repo_with_no_lock_token(self):
2647
transport_path = 'quack'
2648
repo, client = self.setup_fake_client_and_repository(transport_path)
2649
client.add_expected_call(
2650
'Repository.lock_write', ('quack/', ''),
2651
'success', ('ok', ''))
2652
client.add_expected_call(
2653
'Repository.insert_stream_1.19', ('quack/', ''),
2654
'unknown', ('Repository.insert_stream_1.19',))
2655
client.add_expected_call(
2656
'Repository.insert_stream', ('quack/', ''),
2658
client.add_expected_call(
2659
'Repository.insert_stream', ('quack/', ''),
2662
self.checkInsertEmptyStream(repo, client)
2664
def test_locked_repo_with_lock_token(self):
2665
transport_path = 'quack'
2666
repo, client = self.setup_fake_client_and_repository(transport_path)
2667
client.add_expected_call(
2668
'Repository.lock_write', ('quack/', ''),
2669
'success', ('ok', 'a token'))
2670
client.add_expected_call(
2671
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2672
'unknown', ('Repository.insert_stream_1.19',))
2673
client.add_expected_call(
2674
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2676
client.add_expected_call(
2677
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2680
self.checkInsertEmptyStream(repo, client)
2682
def test_stream_with_inventory_deltas(self):
2683
"""'inventory-deltas' substreams cannot be sent to the
2684
Repository.insert_stream verb, because not all servers that implement
2685
that verb will accept them. So when one is encountered the RemoteSink
2686
immediately stops using that verb and falls back to VFS insert_stream.
2688
transport_path = 'quack'
2689
repo, client = self.setup_fake_client_and_repository(transport_path)
2690
client.add_expected_call(
2691
'Repository.insert_stream_1.19', ('quack/', ''),
2692
'unknown', ('Repository.insert_stream_1.19',))
2693
client.add_expected_call(
2694
'Repository.insert_stream', ('quack/', ''),
2696
client.add_expected_call(
2697
'Repository.insert_stream', ('quack/', ''),
2699
# Create a fake real repository for insert_stream to fall back on, so
2700
# that we can directly see the records the RemoteSink passes to the
2705
def insert_stream(self, stream, src_format, resume_tokens):
2706
for substream_kind, substream in stream:
2707
self.records.append(
2708
(substream_kind, [record.key for record in substream]))
2709
return ['fake tokens'], ['fake missing keys']
2710
fake_real_sink = FakeRealSink()
2711
class FakeRealRepository:
2712
def _get_sink(self):
2713
return fake_real_sink
2714
def is_in_write_group(self):
2716
def refresh_data(self):
2718
repo._real_repository = FakeRealRepository()
2719
sink = repo._get_sink()
2720
fmt = repository.format_registry.get_default()
2721
stream = self.make_stream_with_inv_deltas(fmt)
2722
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2723
# Every record from the first inventory delta should have been sent to
2725
expected_records = [
2726
('inventory-deltas', [('rev2',), ('rev3',)]),
2727
('texts', [('some-rev', 'some-file')])]
2728
self.assertEqual(expected_records, fake_real_sink.records)
2729
# The return values from the real sink's insert_stream are propagated
2730
# back to the original caller.
2731
self.assertEqual(['fake tokens'], resume_tokens)
2732
self.assertEqual(['fake missing keys'], missing_keys)
2733
self.assertFinished(client)
2735
def make_stream_with_inv_deltas(self, fmt):
2736
"""Make a simple stream with an inventory delta followed by more
2737
records and more substreams to test that all records and substreams
2738
from that point on are used.
2740
This sends, in order:
2741
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2743
* texts substream: (some-rev, some-file)
2745
# Define a stream using generators so that it isn't rewindable.
2746
inv = inventory.Inventory(revision_id='rev1')
2747
inv.root.revision = 'rev1'
2748
def stream_with_inv_delta():
2749
yield ('inventories', inventories_substream())
2750
yield ('inventory-deltas', inventory_delta_substream())
2752
versionedfile.FulltextContentFactory(
2753
('some-rev', 'some-file'), (), None, 'content')])
2754
def inventories_substream():
2755
# An empty inventory fulltext. This will be streamed normally.
2756
text = fmt._serializer.write_inventory_to_string(inv)
2757
yield versionedfile.FulltextContentFactory(
2758
('rev1',), (), None, text)
2759
def inventory_delta_substream():
2760
# An inventory delta. This can't be streamed via this verb, so it
2761
# will trigger a fallback to VFS insert_stream.
2762
entry = inv.make_entry(
2763
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2764
entry.revision = 'ghost'
2765
delta = [(None, 'newdir', 'newdir-id', entry)]
2766
serializer = inventory_delta.InventoryDeltaSerializer(
2767
versioned_root=True, tree_references=False)
2768
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2769
yield versionedfile.ChunkedContentFactory(
2770
('rev2',), (('rev1',)), None, lines)
2772
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2773
yield versionedfile.ChunkedContentFactory(
2774
('rev3',), (('rev1',)), None, lines)
2775
return stream_with_inv_delta()
2778
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2780
def test_unlocked_repo(self):
2781
transport_path = 'quack'
2782
repo, client = self.setup_fake_client_and_repository(transport_path)
2783
client.add_expected_call(
2784
'Repository.insert_stream_1.19', ('quack/', ''),
2786
client.add_expected_call(
2787
'Repository.insert_stream_1.19', ('quack/', ''),
2789
self.checkInsertEmptyStream(repo, client)
2791
def test_locked_repo_with_no_lock_token(self):
2792
transport_path = 'quack'
2793
repo, client = self.setup_fake_client_and_repository(transport_path)
2794
client.add_expected_call(
2795
'Repository.lock_write', ('quack/', ''),
2796
'success', ('ok', ''))
2797
client.add_expected_call(
2798
'Repository.insert_stream_1.19', ('quack/', ''),
2800
client.add_expected_call(
2801
'Repository.insert_stream_1.19', ('quack/', ''),
2804
self.checkInsertEmptyStream(repo, client)
2806
def test_locked_repo_with_lock_token(self):
2807
transport_path = 'quack'
2808
repo, client = self.setup_fake_client_and_repository(transport_path)
2809
client.add_expected_call(
2810
'Repository.lock_write', ('quack/', ''),
2811
'success', ('ok', 'a token'))
2812
client.add_expected_call(
2813
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2815
client.add_expected_call(
2816
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2819
self.checkInsertEmptyStream(repo, client)
2822
class TestRepositoryTarball(TestRemoteRepository):
2824
# This is a canned tarball reponse we can validate against
2826
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2827
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2828
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2829
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2830
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2831
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2832
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2833
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2834
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2835
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2836
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2837
'nWQ7QH/F3JFOFCQ0aSPfA='
2840
def test_repository_tarball(self):
2841
# Test that Repository.tarball generates the right operations
2842
transport_path = 'repo'
2843
expected_calls = [('call_expecting_body', 'Repository.tarball',
2844
('repo/', 'bz2',),),
2846
repo, client = self.setup_fake_client_and_repository(transport_path)
2847
client.add_success_response_with_body(self.tarball_content, 'ok')
2848
# Now actually ask for the tarball
2849
tarball_file = repo._get_tarball('bz2')
2851
self.assertEqual(expected_calls, client._calls)
2852
self.assertEqual(self.tarball_content, tarball_file.read())
2854
tarball_file.close()
2857
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2858
"""RemoteRepository.copy_content_into optimizations"""
2860
def test_copy_content_remote_to_local(self):
2861
self.transport_server = test_server.SmartTCPServer_for_testing
2862
src_repo = self.make_repository('repo1')
2863
src_repo = repository.Repository.open(self.get_url('repo1'))
2864
# At the moment the tarball-based copy_content_into can't write back
2865
# into a smart server. It would be good if it could upload the
2866
# tarball; once that works we'd have to create repositories of
2867
# different formats. -- mbp 20070410
2868
dest_url = self.get_vfs_only_url('repo2')
2869
dest_bzrdir = BzrDir.create(dest_url)
2870
dest_repo = dest_bzrdir.create_repository()
2871
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2872
self.assertTrue(isinstance(src_repo, RemoteRepository))
2873
src_repo.copy_content_into(dest_repo)
2876
class _StubRealPackRepository(object):
2878
def __init__(self, calls):
2880
self._pack_collection = _StubPackCollection(calls)
2882
def is_in_write_group(self):
2885
def refresh_data(self):
2886
self.calls.append(('pack collection reload_pack_names',))
2889
class _StubPackCollection(object):
2891
def __init__(self, calls):
2895
self.calls.append(('pack collection autopack',))
2898
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2899
"""Tests for RemoteRepository.autopack implementation."""
2902
"""When the server returns 'ok' and there's no _real_repository, then
2903
nothing else happens: the autopack method is done.
2905
transport_path = 'quack'
2906
repo, client = self.setup_fake_client_and_repository(transport_path)
2907
client.add_expected_call(
2908
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2910
self.assertFinished(client)
2912
def test_ok_with_real_repo(self):
2913
"""When the server returns 'ok' and there is a _real_repository, then
2914
the _real_repository's reload_pack_name's method will be called.
2916
transport_path = 'quack'
2917
repo, client = self.setup_fake_client_and_repository(transport_path)
2918
client.add_expected_call(
2919
'PackRepository.autopack', ('quack/',),
2921
repo._real_repository = _StubRealPackRepository(client._calls)
2924
[('call', 'PackRepository.autopack', ('quack/',)),
2925
('pack collection reload_pack_names',)],
2928
def test_backwards_compatibility(self):
2929
"""If the server does not recognise the PackRepository.autopack verb,
2930
fallback to the real_repository's implementation.
2932
transport_path = 'quack'
2933
repo, client = self.setup_fake_client_and_repository(transport_path)
2934
client.add_unknown_method_response('PackRepository.autopack')
2935
def stub_ensure_real():
2936
client._calls.append(('_ensure_real',))
2937
repo._real_repository = _StubRealPackRepository(client._calls)
2938
repo._ensure_real = stub_ensure_real
2941
[('call', 'PackRepository.autopack', ('quack/',)),
2943
('pack collection autopack',)],
2946
def test_oom_error_reporting(self):
2947
"""An out-of-memory condition on the server is reported clearly"""
2948
transport_path = 'quack'
2949
repo, client = self.setup_fake_client_and_repository(transport_path)
2950
client.add_expected_call(
2951
'PackRepository.autopack', ('quack/',),
2952
'error', ('MemoryError',))
2953
err = self.assertRaises(errors.BzrError, repo.autopack)
2954
self.assertContainsRe(str(err), "^remote server out of mem")
2957
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2958
"""Base class for unit tests for bzrlib.remote._translate_error."""
2960
def translateTuple(self, error_tuple, **context):
2961
"""Call _translate_error with an ErrorFromSmartServer built from the
2964
:param error_tuple: A tuple of a smart server response, as would be
2965
passed to an ErrorFromSmartServer.
2966
:kwargs context: context items to call _translate_error with.
2968
:returns: The error raised by _translate_error.
2970
# Raise the ErrorFromSmartServer before passing it as an argument,
2971
# because _translate_error may need to re-raise it with a bare 'raise'
2973
server_error = errors.ErrorFromSmartServer(error_tuple)
2974
translated_error = self.translateErrorFromSmartServer(
2975
server_error, **context)
2976
return translated_error
2978
def translateErrorFromSmartServer(self, error_object, **context):
2979
"""Like translateTuple, but takes an already constructed
2980
ErrorFromSmartServer rather than a tuple.
2984
except errors.ErrorFromSmartServer, server_error:
2985
translated_error = self.assertRaises(
2986
errors.BzrError, remote._translate_error, server_error,
2988
return translated_error
2991
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2992
"""Unit tests for bzrlib.remote._translate_error.
2994
Given an ErrorFromSmartServer (which has an error tuple from a smart
2995
server) and some context, _translate_error raises more specific errors from
2998
This test case covers the cases where _translate_error succeeds in
2999
translating an ErrorFromSmartServer to something better. See
3000
TestErrorTranslationRobustness for other cases.
3003
def test_NoSuchRevision(self):
3004
branch = self.make_branch('')
3006
translated_error = self.translateTuple(
3007
('NoSuchRevision', revid), branch=branch)
3008
expected_error = errors.NoSuchRevision(branch, revid)
3009
self.assertEqual(expected_error, translated_error)
3011
def test_nosuchrevision(self):
3012
repository = self.make_repository('')
3014
translated_error = self.translateTuple(
3015
('nosuchrevision', revid), repository=repository)
3016
expected_error = errors.NoSuchRevision(repository, revid)
3017
self.assertEqual(expected_error, translated_error)
3019
def test_nobranch(self):
3020
bzrdir = self.make_bzrdir('')
3021
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3022
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3023
self.assertEqual(expected_error, translated_error)
3025
def test_nobranch_one_arg(self):
3026
bzrdir = self.make_bzrdir('')
3027
translated_error = self.translateTuple(
3028
('nobranch', 'extra detail'), bzrdir=bzrdir)
3029
expected_error = errors.NotBranchError(
3030
path=bzrdir.root_transport.base,
3031
detail='extra detail')
3032
self.assertEqual(expected_error, translated_error)
3034
def test_norepository(self):
3035
bzrdir = self.make_bzrdir('')
3036
translated_error = self.translateTuple(('norepository',),
3038
expected_error = errors.NoRepositoryPresent(bzrdir)
3039
self.assertEqual(expected_error, translated_error)
3041
def test_LockContention(self):
3042
translated_error = self.translateTuple(('LockContention',))
3043
expected_error = errors.LockContention('(remote lock)')
3044
self.assertEqual(expected_error, translated_error)
3046
def test_UnlockableTransport(self):
3047
bzrdir = self.make_bzrdir('')
3048
translated_error = self.translateTuple(
3049
('UnlockableTransport',), bzrdir=bzrdir)
3050
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3051
self.assertEqual(expected_error, translated_error)
3053
def test_LockFailed(self):
3054
lock = 'str() of a server lock'
3055
why = 'str() of why'
3056
translated_error = self.translateTuple(('LockFailed', lock, why))
3057
expected_error = errors.LockFailed(lock, why)
3058
self.assertEqual(expected_error, translated_error)
3060
def test_TokenMismatch(self):
3061
token = 'a lock token'
3062
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3063
expected_error = errors.TokenMismatch(token, '(remote token)')
3064
self.assertEqual(expected_error, translated_error)
3066
def test_Diverged(self):
3067
branch = self.make_branch('a')
3068
other_branch = self.make_branch('b')
3069
translated_error = self.translateTuple(
3070
('Diverged',), branch=branch, other_branch=other_branch)
3071
expected_error = errors.DivergedBranches(branch, other_branch)
3072
self.assertEqual(expected_error, translated_error)
3074
def test_NotStacked(self):
3075
branch = self.make_branch('')
3076
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3077
expected_error = errors.NotStacked(branch)
3078
self.assertEqual(expected_error, translated_error)
3080
def test_ReadError_no_args(self):
3082
translated_error = self.translateTuple(('ReadError',), path=path)
3083
expected_error = errors.ReadError(path)
3084
self.assertEqual(expected_error, translated_error)
3086
def test_ReadError(self):
3088
translated_error = self.translateTuple(('ReadError', path))
3089
expected_error = errors.ReadError(path)
3090
self.assertEqual(expected_error, translated_error)
3092
def test_IncompatibleRepositories(self):
3093
translated_error = self.translateTuple(('IncompatibleRepositories',
3094
"repo1", "repo2", "details here"))
3095
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3097
self.assertEqual(expected_error, translated_error)
3099
def test_PermissionDenied_no_args(self):
3101
translated_error = self.translateTuple(('PermissionDenied',),
3103
expected_error = errors.PermissionDenied(path)
3104
self.assertEqual(expected_error, translated_error)
3106
def test_PermissionDenied_one_arg(self):
3108
translated_error = self.translateTuple(('PermissionDenied', path))
3109
expected_error = errors.PermissionDenied(path)
3110
self.assertEqual(expected_error, translated_error)
3112
def test_PermissionDenied_one_arg_and_context(self):
3113
"""Given a choice between a path from the local context and a path on
3114
the wire, _translate_error prefers the path from the local context.
3116
local_path = 'local path'
3117
remote_path = 'remote path'
3118
translated_error = self.translateTuple(
3119
('PermissionDenied', remote_path), path=local_path)
3120
expected_error = errors.PermissionDenied(local_path)
3121
self.assertEqual(expected_error, translated_error)
3123
def test_PermissionDenied_two_args(self):
3125
extra = 'a string with extra info'
3126
translated_error = self.translateTuple(
3127
('PermissionDenied', path, extra))
3128
expected_error = errors.PermissionDenied(path, extra)
3129
self.assertEqual(expected_error, translated_error)
3131
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3133
def test_NoSuchFile_context_path(self):
3134
local_path = "local path"
3135
translated_error = self.translateTuple(('ReadError', "remote path"),
3137
expected_error = errors.ReadError(local_path)
3138
self.assertEqual(expected_error, translated_error)
3140
def test_NoSuchFile_without_context(self):
3141
remote_path = "remote path"
3142
translated_error = self.translateTuple(('ReadError', remote_path))
3143
expected_error = errors.ReadError(remote_path)
3144
self.assertEqual(expected_error, translated_error)
3146
def test_ReadOnlyError(self):
3147
translated_error = self.translateTuple(('ReadOnlyError',))
3148
expected_error = errors.TransportNotPossible("readonly transport")
3149
self.assertEqual(expected_error, translated_error)
3151
def test_MemoryError(self):
3152
translated_error = self.translateTuple(('MemoryError',))
3153
self.assertStartsWith(str(translated_error),
3154
"remote server out of memory")
3156
def test_generic_IndexError_no_classname(self):
3157
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3158
translated_error = self.translateErrorFromSmartServer(err)
3159
expected_error = errors.UnknownErrorFromSmartServer(err)
3160
self.assertEqual(expected_error, translated_error)
3162
# GZ 2011-03-02: TODO test generic non-ascii error string
3164
def test_generic_KeyError(self):
3165
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3166
translated_error = self.translateErrorFromSmartServer(err)
3167
expected_error = errors.UnknownErrorFromSmartServer(err)
3168
self.assertEqual(expected_error, translated_error)
3171
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3172
"""Unit tests for bzrlib.remote._translate_error's robustness.
3174
TestErrorTranslationSuccess is for cases where _translate_error can
3175
translate successfully. This class about how _translate_err behaves when
3176
it fails to translate: it re-raises the original error.
3179
def test_unrecognised_server_error(self):
3180
"""If the error code from the server is not recognised, the original
3181
ErrorFromSmartServer is propagated unmodified.
3183
error_tuple = ('An unknown error tuple',)
3184
server_error = errors.ErrorFromSmartServer(error_tuple)
3185
translated_error = self.translateErrorFromSmartServer(server_error)
3186
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3187
self.assertEqual(expected_error, translated_error)
3189
def test_context_missing_a_key(self):
3190
"""In case of a bug in the client, or perhaps an unexpected response
3191
from a server, _translate_error returns the original error tuple from
3192
the server and mutters a warning.
3194
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3195
# in the context dict. So let's give it an empty context dict instead
3196
# to exercise its error recovery.
3198
error_tuple = ('NoSuchRevision', 'revid')
3199
server_error = errors.ErrorFromSmartServer(error_tuple)
3200
translated_error = self.translateErrorFromSmartServer(server_error)
3201
self.assertEqual(server_error, translated_error)
3202
# In addition to re-raising ErrorFromSmartServer, some debug info has
3203
# been muttered to the log file for developer to look at.
3204
self.assertContainsRe(
3206
"Missing key 'branch' in context")
3208
def test_path_missing(self):
3209
"""Some translations (PermissionDenied, ReadError) can determine the
3210
'path' variable from either the wire or the local context. If neither
3211
has it, then an error is raised.
3213
error_tuple = ('ReadError',)
3214
server_error = errors.ErrorFromSmartServer(error_tuple)
3215
translated_error = self.translateErrorFromSmartServer(server_error)
3216
self.assertEqual(server_error, translated_error)
3217
# In addition to re-raising ErrorFromSmartServer, some debug info has
3218
# been muttered to the log file for developer to look at.
3219
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3222
class TestStacking(tests.TestCaseWithTransport):
3223
"""Tests for operations on stacked remote repositories.
3225
The underlying format type must support stacking.
3228
def test_access_stacked_remote(self):
3229
# based on <http://launchpad.net/bugs/261315>
3230
# make a branch stacked on another repository containing an empty
3231
# revision, then open it over hpss - we should be able to see that
3233
base_transport = self.get_transport()
3234
base_builder = self.make_branch_builder('base', format='1.9')
3235
base_builder.start_series()
3236
base_revid = base_builder.build_snapshot('rev-id', None,
3237
[('add', ('', None, 'directory', None))],
3239
base_builder.finish_series()
3240
stacked_branch = self.make_branch('stacked', format='1.9')
3241
stacked_branch.set_stacked_on_url('../base')
3242
# start a server looking at this
3243
smart_server = test_server.SmartTCPServer_for_testing()
3244
self.start_server(smart_server)
3245
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3246
# can get its branch and repository
3247
remote_branch = remote_bzrdir.open_branch()
3248
remote_repo = remote_branch.repository
3249
remote_repo.lock_read()
3251
# it should have an appropriate fallback repository, which should also
3252
# be a RemoteRepository
3253
self.assertLength(1, remote_repo._fallback_repositories)
3254
self.assertIsInstance(remote_repo._fallback_repositories[0],
3256
# and it has the revision committed to the underlying repository;
3257
# these have varying implementations so we try several of them
3258
self.assertTrue(remote_repo.has_revisions([base_revid]))
3259
self.assertTrue(remote_repo.has_revision(base_revid))
3260
self.assertEqual(remote_repo.get_revision(base_revid).message,
3263
remote_repo.unlock()
3265
def prepare_stacked_remote_branch(self):
3266
"""Get stacked_upon and stacked branches with content in each."""
3267
self.setup_smart_server_with_call_log()
3268
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3269
tree1.commit('rev1', rev_id='rev1')
3270
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3271
).open_workingtree()
3272
local_tree = tree2.branch.create_checkout('local')
3273
local_tree.commit('local changes make me feel good.')
3274
branch2 = Branch.open(self.get_url('tree2'))
3276
self.addCleanup(branch2.unlock)
3277
return tree1.branch, branch2
3279
def test_stacked_get_parent_map(self):
3280
# the public implementation of get_parent_map obeys stacking
3281
_, branch = self.prepare_stacked_remote_branch()
3282
repo = branch.repository
3283
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3285
def test_unstacked_get_parent_map(self):
3286
# _unstacked_provider.get_parent_map ignores stacking
3287
_, branch = self.prepare_stacked_remote_branch()
3288
provider = branch.repository._unstacked_provider
3289
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3291
def fetch_stream_to_rev_order(self, stream):
3293
for kind, substream in stream:
3294
if not kind == 'revisions':
3297
for content in substream:
3298
result.append(content.key[-1])
3301
def get_ordered_revs(self, format, order, branch_factory=None):
3302
"""Get a list of the revisions in a stream to format format.
3304
:param format: The format of the target.
3305
:param order: the order that target should have requested.
3306
:param branch_factory: A callable to create a trunk and stacked branch
3307
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3308
:result: The revision ids in the stream, in the order seen,
3309
the topological order of revisions in the source.
3311
unordered_format = bzrdir.format_registry.get(format)()
3312
target_repository_format = unordered_format.repository_format
3314
self.assertEqual(order, target_repository_format._fetch_order)
3315
if branch_factory is None:
3316
branch_factory = self.prepare_stacked_remote_branch
3317
_, stacked = branch_factory()
3318
source = stacked.repository._get_source(target_repository_format)
3319
tip = stacked.last_revision()
3320
stacked.repository._ensure_real()
3321
graph = stacked.repository.get_graph()
3322
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3323
if r != NULL_REVISION]
3325
search = _mod_graph.PendingAncestryResult([tip], stacked.repository)
3326
self.reset_smart_call_log()
3327
stream = source.get_stream(search)
3328
# We trust that if a revision is in the stream the rest of the new
3329
# content for it is too, as per our main fetch tests; here we are
3330
# checking that the revisions are actually included at all, and their
3332
return self.fetch_stream_to_rev_order(stream), revs
3334
def test_stacked_get_stream_unordered(self):
3335
# Repository._get_source.get_stream() from a stacked repository with
3336
# unordered yields the full data from both stacked and stacked upon
3338
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3339
self.assertEqual(set(expected_revs), set(rev_ord))
3340
# Getting unordered results should have made a streaming data request
3341
# from the server, then one from the backing branch.
3342
self.assertLength(2, self.hpss_calls)
3344
def test_stacked_on_stacked_get_stream_unordered(self):
3345
# Repository._get_source.get_stream() from a stacked repository which
3346
# is itself stacked yields the full data from all three sources.
3347
def make_stacked_stacked():
3348
_, stacked = self.prepare_stacked_remote_branch()
3349
tree = stacked.bzrdir.sprout('tree3', stacked=True
3350
).open_workingtree()
3351
local_tree = tree.branch.create_checkout('local-tree3')
3352
local_tree.commit('more local changes are better')
3353
branch = Branch.open(self.get_url('tree3'))
3355
self.addCleanup(branch.unlock)
3357
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3358
branch_factory=make_stacked_stacked)
3359
self.assertEqual(set(expected_revs), set(rev_ord))
3360
# Getting unordered results should have made a streaming data request
3361
# from the server, and one from each backing repo
3362
self.assertLength(3, self.hpss_calls)
3364
def test_stacked_get_stream_topological(self):
3365
# Repository._get_source.get_stream() from a stacked repository with
3366
# topological sorting yields the full data from both stacked and
3367
# stacked upon sources in topological order.
3368
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3369
self.assertEqual(expected_revs, rev_ord)
3370
# Getting topological sort requires VFS calls still - one of which is
3371
# pushing up from the bound branch.
3372
self.assertLength(14, self.hpss_calls)
3374
def test_stacked_get_stream_groupcompress(self):
3375
# Repository._get_source.get_stream() from a stacked repository with
3376
# groupcompress sorting yields the full data from both stacked and
3377
# stacked upon sources in groupcompress order.
3378
raise tests.TestSkipped('No groupcompress ordered format available')
3379
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3380
self.assertEqual(expected_revs, reversed(rev_ord))
3381
# Getting unordered results should have made a streaming data request
3382
# from the backing branch, and one from the stacked on branch.
3383
self.assertLength(2, self.hpss_calls)
3385
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3386
# When pulling some fixed amount of content that is more than the
3387
# source has (because some is coming from a fallback branch, no error
3388
# should be received. This was reported as bug 360791.
3389
# Need three branches: a trunk, a stacked branch, and a preexisting
3390
# branch pulling content from stacked and trunk.
3391
self.setup_smart_server_with_call_log()
3392
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3393
r1 = trunk.commit('start')
3394
stacked_branch = trunk.branch.create_clone_on_transport(
3395
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3396
local = self.make_branch('local', format='1.9-rich-root')
3397
local.repository.fetch(stacked_branch.repository,
3398
stacked_branch.last_revision())
3401
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3404
super(TestRemoteBranchEffort, self).setUp()
3405
# Create a smart server that publishes whatever the backing VFS server
3407
self.smart_server = test_server.SmartTCPServer_for_testing()
3408
self.start_server(self.smart_server, self.get_server())
3409
# Log all HPSS calls into self.hpss_calls.
3410
_SmartClient.hooks.install_named_hook(
3411
'call', self.capture_hpss_call, None)
3412
self.hpss_calls = []
3414
def capture_hpss_call(self, params):
3415
self.hpss_calls.append(params.method)
3417
def test_copy_content_into_avoids_revision_history(self):
3418
local = self.make_branch('local')
3419
builder = self.make_branch_builder('remote')
3420
builder.build_commit(message="Commit.")
3421
remote_branch_url = self.smart_server.get_url() + 'remote'
3422
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3423
local.repository.fetch(remote_branch.repository)
3424
self.hpss_calls = []
3425
remote_branch.copy_content_into(local)
3426
self.assertFalse('Branch.revision_history' in self.hpss_calls)
3428
def test_fetch_everything_needs_just_one_call(self):
3429
local = self.make_branch('local')
3430
builder = self.make_branch_builder('remote')
3431
builder.build_commit(message="Commit.")
3432
remote_branch_url = self.smart_server.get_url() + 'remote'
3433
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3434
self.hpss_calls = []
3435
local.repository.fetch(
3436
remote_branch.repository,
3437
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
3438
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
3440
def override_verb(self, verb_name, verb):
3441
request_handlers = request.request_handlers
3442
orig_verb = request_handlers.get(verb_name)
3443
request_handlers.register(verb_name, verb, override_existing=True)
3444
self.addCleanup(request_handlers.register, verb_name, orig_verb,
3445
override_existing=True)
3447
def test_fetch_everything_backwards_compat(self):
3448
"""Can fetch with EverythingResult even with pre 2.4 servers.
3450
Pre-2.4 do not support 'everything' searches with the
3451
Repository.get_stream_1.19 verb.
3454
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
3455
"""A version of the Repository.get_stream_1.19 verb patched to
3456
reject 'everything' searches the way 2.3 and earlier do.
3458
def recreate_search(self, repository, search_bytes,
3459
discard_excess=False):
3460
verb_log.append(search_bytes.split('\n', 1)[0])
3461
if search_bytes == 'everything':
3463
request.FailedSmartServerResponse(('BadSearch',)))
3464
return super(OldGetStreamVerb,
3465
self).recreate_search(repository, search_bytes,
3466
discard_excess=discard_excess)
3467
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
3468
local = self.make_branch('local')
3469
builder = self.make_branch_builder('remote')
3470
builder.build_commit(message="Commit.")
3471
remote_branch_url = self.smart_server.get_url() + 'remote'
3472
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3473
self.hpss_calls = []
3474
local.repository.fetch(
3475
remote_branch.repository,
3476
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
3477
# make sure the overridden verb was used
3478
self.assertLength(1, verb_log)
3479
# more than one HPSS call is needed, but because it's a VFS callback
3480
# its hard to predict exactly how many.
3481
self.assertTrue(len(self.hpss_calls) > 1)
3484
class TestUpdateBoundBranchWithModifiedBoundLocation(
3485
tests.TestCaseWithTransport):
3486
"""Ensure correct handling of bound_location modifications.
3488
This is tested against a smart server as http://pad.lv/786980 was about a
3489
ReadOnlyError (write attempt during a read-only transaction) which can only
3490
happen in this context.
3494
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
3495
self.transport_server = test_server.SmartTCPServer_for_testing
3497
def make_master_and_checkout(self, master_name, checkout_name):
3498
# Create the master branch and its associated checkout
3499
self.master = self.make_branch_and_tree(master_name)
3500
self.checkout = self.master.branch.create_checkout(checkout_name)
3501
# Modify the master branch so there is something to update
3502
self.master.commit('add stuff')
3503
self.last_revid = self.master.commit('even more stuff')
3504
self.bound_location = self.checkout.branch.get_bound_location()
3506
def assertUpdateSucceeds(self, new_location):
3507
self.checkout.branch.set_bound_location(new_location)
3508
self.checkout.update()
3509
self.assertEquals(self.last_revid, self.checkout.last_revision())
3511
def test_without_final_slash(self):
3512
self.make_master_and_checkout('master', 'checkout')
3513
# For unclear reasons some users have a bound_location without a final
3514
# '/', simulate that by forcing such a value
3515
self.assertEndsWith(self.bound_location, '/')
3516
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
3518
def test_plus_sign(self):
3519
self.make_master_and_checkout('+master', 'checkout')
3520
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
3522
def test_tilda(self):
3523
# Embed ~ in the middle of the path just to avoid any $HOME
3525
self.make_master_and_checkout('mas~ter', 'checkout')
3526
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))