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.tests import (
70
from bzrlib.tests.scenarios import load_tests_apply_scenarios
71
from bzrlib.transport.memory import MemoryTransport
72
from bzrlib.transport.remote import (
79
load_tests = load_tests_apply_scenarios
82
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
86
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
88
{'transport_server': test_server.SmartTCPServer_for_testing})]
92
super(BasicRemoteObjectTests, self).setUp()
93
self.transport = self.get_transport()
94
# make a branch that can be opened over the smart transport
95
self.local_wt = BzrDir.create_standalone_workingtree('.')
96
self.addCleanup(self.transport.disconnect)
98
def test_create_remote_bzrdir(self):
99
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
100
self.assertIsInstance(b, BzrDir)
102
def test_open_remote_branch(self):
103
# open a standalone branch in the working directory
104
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
105
branch = b.open_branch()
106
self.assertIsInstance(branch, Branch)
108
def test_remote_repository(self):
109
b = BzrDir.open_from_transport(self.transport)
110
repo = b.open_repository()
111
revid = u'\xc823123123'.encode('utf8')
112
self.assertFalse(repo.has_revision(revid))
113
self.local_wt.commit(message='test commit', rev_id=revid)
114
self.assertTrue(repo.has_revision(revid))
116
def test_remote_branch_revision_history(self):
117
b = BzrDir.open_from_transport(self.transport).open_branch()
118
self.assertEqual([], b.revision_history())
119
r1 = self.local_wt.commit('1st commit')
120
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
121
self.assertEqual([r1, r2], b.revision_history())
123
def test_find_correct_format(self):
124
"""Should open a RemoteBzrDir over a RemoteTransport"""
125
fmt = BzrDirFormat.find_format(self.transport)
126
self.assertTrue(bzrdir.RemoteBzrProber
127
in controldir.ControlDirFormat._server_probers)
128
self.assertIsInstance(fmt, RemoteBzrDirFormat)
130
def test_open_detected_smart_format(self):
131
fmt = BzrDirFormat.find_format(self.transport)
132
d = fmt.open(self.transport)
133
self.assertIsInstance(d, BzrDir)
135
def test_remote_branch_repr(self):
136
b = BzrDir.open_from_transport(self.transport).open_branch()
137
self.assertStartsWith(str(b), 'RemoteBranch(')
139
def test_remote_bzrdir_repr(self):
140
b = BzrDir.open_from_transport(self.transport)
141
self.assertStartsWith(str(b), 'RemoteBzrDir(')
143
def test_remote_branch_format_supports_stacking(self):
145
self.make_branch('unstackable', format='pack-0.92')
146
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
147
self.assertFalse(b._format.supports_stacking())
148
self.make_branch('stackable', format='1.9')
149
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
150
self.assertTrue(b._format.supports_stacking())
152
def test_remote_repo_format_supports_external_references(self):
154
bd = self.make_bzrdir('unstackable', format='pack-0.92')
155
r = bd.create_repository()
156
self.assertFalse(r._format.supports_external_lookups)
157
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
158
self.assertFalse(r._format.supports_external_lookups)
159
bd = self.make_bzrdir('stackable', format='1.9')
160
r = bd.create_repository()
161
self.assertTrue(r._format.supports_external_lookups)
162
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
163
self.assertTrue(r._format.supports_external_lookups)
165
def test_remote_branch_set_append_revisions_only(self):
166
# Make a format 1.9 branch, which supports append_revisions_only
167
branch = self.make_branch('branch', format='1.9')
168
config = branch.get_config()
169
branch.set_append_revisions_only(True)
171
'True', config.get_user_option('append_revisions_only'))
172
branch.set_append_revisions_only(False)
174
'False', config.get_user_option('append_revisions_only'))
176
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
177
branch = self.make_branch('branch', format='knit')
178
config = branch.get_config()
180
errors.UpgradeRequired, branch.set_append_revisions_only, True)
183
class FakeProtocol(object):
184
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
186
def __init__(self, body, fake_client):
188
self._body_buffer = None
189
self._fake_client = fake_client
191
def read_body_bytes(self, count=-1):
192
if self._body_buffer is None:
193
self._body_buffer = StringIO(self.body)
194
bytes = self._body_buffer.read(count)
195
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
196
self._fake_client.expecting_body = False
199
def cancel_read_body(self):
200
self._fake_client.expecting_body = False
202
def read_streamed_body(self):
206
class FakeClient(_SmartClient):
207
"""Lookalike for _SmartClient allowing testing."""
209
def __init__(self, fake_medium_base='fake base'):
210
"""Create a FakeClient."""
213
self.expecting_body = False
214
# if non-None, this is the list of expected calls, with only the
215
# method name and arguments included. the body might be hard to
216
# compute so is not included. If a call is None, that call can
218
self._expected_calls = None
219
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
221
def add_expected_call(self, call_name, call_args, response_type,
222
response_args, response_body=None):
223
if self._expected_calls is None:
224
self._expected_calls = []
225
self._expected_calls.append((call_name, call_args))
226
self.responses.append((response_type, response_args, response_body))
228
def add_success_response(self, *args):
229
self.responses.append(('success', args, None))
231
def add_success_response_with_body(self, body, *args):
232
self.responses.append(('success', args, body))
233
if self._expected_calls is not None:
234
self._expected_calls.append(None)
236
def add_error_response(self, *args):
237
self.responses.append(('error', args))
239
def add_unknown_method_response(self, verb):
240
self.responses.append(('unknown', verb))
242
def finished_test(self):
243
if self._expected_calls:
244
raise AssertionError("%r finished but was still expecting %r"
245
% (self, self._expected_calls[0]))
247
def _get_next_response(self):
249
response_tuple = self.responses.pop(0)
250
except IndexError, e:
251
raise AssertionError("%r didn't expect any more calls"
253
if response_tuple[0] == 'unknown':
254
raise errors.UnknownSmartMethod(response_tuple[1])
255
elif response_tuple[0] == 'error':
256
raise errors.ErrorFromSmartServer(response_tuple[1])
257
return response_tuple
259
def _check_call(self, method, args):
260
if self._expected_calls is None:
261
# the test should be updated to say what it expects
264
next_call = self._expected_calls.pop(0)
266
raise AssertionError("%r didn't expect any more calls "
268
% (self, method, args,))
269
if next_call is None:
271
if method != next_call[0] or args != next_call[1]:
272
raise AssertionError("%r expected %r%r "
274
% (self, next_call[0], next_call[1], method, args,))
276
def call(self, method, *args):
277
self._check_call(method, args)
278
self._calls.append(('call', method, args))
279
return self._get_next_response()[1]
281
def call_expecting_body(self, method, *args):
282
self._check_call(method, args)
283
self._calls.append(('call_expecting_body', method, args))
284
result = self._get_next_response()
285
self.expecting_body = True
286
return result[1], FakeProtocol(result[2], self)
288
def call_with_body_bytes(self, method, args, body):
289
self._check_call(method, args)
290
self._calls.append(('call_with_body_bytes', method, args, body))
291
result = self._get_next_response()
292
return result[1], FakeProtocol(result[2], self)
294
def call_with_body_bytes_expecting_body(self, method, args, body):
295
self._check_call(method, args)
296
self._calls.append(('call_with_body_bytes_expecting_body', method,
298
result = self._get_next_response()
299
self.expecting_body = True
300
return result[1], FakeProtocol(result[2], self)
302
def call_with_body_stream(self, args, stream):
303
# Explicitly consume the stream before checking for an error, because
304
# that's what happens a real medium.
305
stream = list(stream)
306
self._check_call(args[0], args[1:])
307
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
308
result = self._get_next_response()
309
# The second value returned from call_with_body_stream is supposed to
310
# be a response_handler object, but so far no tests depend on that.
311
response_handler = None
312
return result[1], response_handler
315
class FakeMedium(medium.SmartClientMedium):
317
def __init__(self, client_calls, base):
318
medium.SmartClientMedium.__init__(self, base)
319
self._client_calls = client_calls
321
def disconnect(self):
322
self._client_calls.append(('disconnect medium',))
325
class TestVfsHas(tests.TestCase):
327
def test_unicode_path(self):
328
client = FakeClient('/')
329
client.add_success_response('yes',)
330
transport = RemoteTransport('bzr://localhost/', _client=client)
331
filename = u'/hell\u00d8'.encode('utf8')
332
result = transport.has(filename)
334
[('call', 'has', (filename,))],
336
self.assertTrue(result)
339
class TestRemote(tests.TestCaseWithMemoryTransport):
341
def get_branch_format(self):
342
reference_bzrdir_format = bzrdir.format_registry.get('default')()
343
return reference_bzrdir_format.get_branch_format()
345
def get_repo_format(self):
346
reference_bzrdir_format = bzrdir.format_registry.get('default')()
347
return reference_bzrdir_format.repository_format
349
def assertFinished(self, fake_client):
350
"""Assert that all of a FakeClient's expected calls have occurred."""
351
fake_client.finished_test()
354
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
355
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
357
def assertRemotePath(self, expected, client_base, transport_base):
358
"""Assert that the result of
359
SmartClientMedium.remote_path_from_transport is the expected value for
360
a given client_base and transport_base.
362
client_medium = medium.SmartClientMedium(client_base)
363
t = transport.get_transport(transport_base)
364
result = client_medium.remote_path_from_transport(t)
365
self.assertEqual(expected, result)
367
def test_remote_path_from_transport(self):
368
"""SmartClientMedium.remote_path_from_transport calculates a URL for
369
the given transport relative to the root of the client base URL.
371
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
372
self.assertRemotePath(
373
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
375
def assertRemotePathHTTP(self, expected, transport_base, relpath):
376
"""Assert that the result of
377
HttpTransportBase.remote_path_from_transport is the expected value for
378
a given transport_base and relpath of that transport. (Note that
379
HttpTransportBase is a subclass of SmartClientMedium)
381
base_transport = transport.get_transport(transport_base)
382
client_medium = base_transport.get_smart_medium()
383
cloned_transport = base_transport.clone(relpath)
384
result = client_medium.remote_path_from_transport(cloned_transport)
385
self.assertEqual(expected, result)
387
def test_remote_path_from_transport_http(self):
388
"""Remote paths for HTTP transports are calculated differently to other
389
transports. They are just relative to the client base, not the root
390
directory of the host.
392
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
393
self.assertRemotePathHTTP(
394
'../xyz/', scheme + '//host/path', '../xyz/')
395
self.assertRemotePathHTTP(
396
'xyz/', scheme + '//host/path', 'xyz/')
399
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
400
"""Tests for the behaviour of client_medium.remote_is_at_least."""
402
def test_initially_unlimited(self):
403
"""A fresh medium assumes that the remote side supports all
406
client_medium = medium.SmartClientMedium('dummy base')
407
self.assertFalse(client_medium._is_remote_before((99, 99)))
409
def test__remember_remote_is_before(self):
410
"""Calling _remember_remote_is_before ratchets down the known remote
413
client_medium = medium.SmartClientMedium('dummy base')
414
# Mark the remote side as being less than 1.6. The remote side may
416
client_medium._remember_remote_is_before((1, 6))
417
self.assertTrue(client_medium._is_remote_before((1, 6)))
418
self.assertFalse(client_medium._is_remote_before((1, 5)))
419
# Calling _remember_remote_is_before again with a lower value works.
420
client_medium._remember_remote_is_before((1, 5))
421
self.assertTrue(client_medium._is_remote_before((1, 5)))
422
# If you call _remember_remote_is_before with a higher value it logs a
423
# warning, and continues to remember the lower value.
424
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
425
client_medium._remember_remote_is_before((1, 9))
426
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
427
self.assertTrue(client_medium._is_remote_before((1, 5)))
430
class TestBzrDirCloningMetaDir(TestRemote):
432
def test_backwards_compat(self):
433
self.setup_smart_server_with_call_log()
434
a_dir = self.make_bzrdir('.')
435
self.reset_smart_call_log()
436
verb = 'BzrDir.cloning_metadir'
437
self.disable_verb(verb)
438
format = a_dir.cloning_metadir()
439
call_count = len([call for call in self.hpss_calls if
440
call.call.method == verb])
441
self.assertEqual(1, call_count)
443
def test_branch_reference(self):
444
transport = self.get_transport('quack')
445
referenced = self.make_branch('referenced')
446
expected = referenced.bzrdir.cloning_metadir()
447
client = FakeClient(transport.base)
448
client.add_expected_call(
449
'BzrDir.cloning_metadir', ('quack/', 'False'),
450
'error', ('BranchReference',)),
451
client.add_expected_call(
452
'BzrDir.open_branchV3', ('quack/',),
453
'success', ('ref', self.get_url('referenced'))),
454
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
456
result = a_bzrdir.cloning_metadir()
457
# We should have got a control dir matching the referenced branch.
458
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
459
self.assertEqual(expected._repository_format, result._repository_format)
460
self.assertEqual(expected._branch_format, result._branch_format)
461
self.assertFinished(client)
463
def test_current_server(self):
464
transport = self.get_transport('.')
465
transport = transport.clone('quack')
466
self.make_bzrdir('quack')
467
client = FakeClient(transport.base)
468
reference_bzrdir_format = bzrdir.format_registry.get('default')()
469
control_name = reference_bzrdir_format.network_name()
470
client.add_expected_call(
471
'BzrDir.cloning_metadir', ('quack/', 'False'),
472
'success', (control_name, '', ('branch', ''))),
473
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
475
result = a_bzrdir.cloning_metadir()
476
# We should have got a reference control dir with default branch and
477
# repository formats.
478
# This pokes a little, just to be sure.
479
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
480
self.assertEqual(None, result._repository_format)
481
self.assertEqual(None, result._branch_format)
482
self.assertFinished(client)
485
class TestBzrDirOpen(TestRemote):
487
def make_fake_client_and_transport(self, path='quack'):
488
transport = MemoryTransport()
489
transport.mkdir(path)
490
transport = transport.clone(path)
491
client = FakeClient(transport.base)
492
return client, transport
494
def test_absent(self):
495
client, transport = self.make_fake_client_and_transport()
496
client.add_expected_call(
497
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
498
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
499
RemoteBzrDirFormat(), _client=client, _force_probe=True)
500
self.assertFinished(client)
502
def test_present_without_workingtree(self):
503
client, transport = self.make_fake_client_and_transport()
504
client.add_expected_call(
505
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
506
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
507
_client=client, _force_probe=True)
508
self.assertIsInstance(bd, RemoteBzrDir)
509
self.assertFalse(bd.has_workingtree())
510
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
511
self.assertFinished(client)
513
def test_present_with_workingtree(self):
514
client, transport = self.make_fake_client_and_transport()
515
client.add_expected_call(
516
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
517
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
518
_client=client, _force_probe=True)
519
self.assertIsInstance(bd, RemoteBzrDir)
520
self.assertTrue(bd.has_workingtree())
521
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
522
self.assertFinished(client)
524
def test_backwards_compat(self):
525
client, transport = self.make_fake_client_and_transport()
526
client.add_expected_call(
527
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
528
client.add_expected_call(
529
'BzrDir.open', ('quack/',), 'success', ('yes',))
530
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
531
_client=client, _force_probe=True)
532
self.assertIsInstance(bd, RemoteBzrDir)
533
self.assertFinished(client)
535
def test_backwards_compat_hpss_v2(self):
536
client, transport = self.make_fake_client_and_transport()
537
# Monkey-patch fake client to simulate real-world behaviour with v2
538
# server: upon first RPC call detect the protocol version, and because
539
# the version is 2 also do _remember_remote_is_before((1, 6)) before
540
# continuing with the RPC.
541
orig_check_call = client._check_call
542
def check_call(method, args):
543
client._medium._protocol_version = 2
544
client._medium._remember_remote_is_before((1, 6))
545
client._check_call = orig_check_call
546
client._check_call(method, args)
547
client._check_call = check_call
548
client.add_expected_call(
549
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
550
client.add_expected_call(
551
'BzrDir.open', ('quack/',), 'success', ('yes',))
552
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
553
_client=client, _force_probe=True)
554
self.assertIsInstance(bd, RemoteBzrDir)
555
self.assertFinished(client)
558
class TestBzrDirOpenBranch(TestRemote):
560
def test_backwards_compat(self):
561
self.setup_smart_server_with_call_log()
562
self.make_branch('.')
563
a_dir = BzrDir.open(self.get_url('.'))
564
self.reset_smart_call_log()
565
verb = 'BzrDir.open_branchV3'
566
self.disable_verb(verb)
567
format = a_dir.open_branch()
568
call_count = len([call for call in self.hpss_calls if
569
call.call.method == verb])
570
self.assertEqual(1, call_count)
572
def test_branch_present(self):
573
reference_format = self.get_repo_format()
574
network_name = reference_format.network_name()
575
branch_network_name = self.get_branch_format().network_name()
576
transport = MemoryTransport()
577
transport.mkdir('quack')
578
transport = transport.clone('quack')
579
client = FakeClient(transport.base)
580
client.add_expected_call(
581
'BzrDir.open_branchV3', ('quack/',),
582
'success', ('branch', branch_network_name))
583
client.add_expected_call(
584
'BzrDir.find_repositoryV3', ('quack/',),
585
'success', ('ok', '', 'no', 'no', 'no', network_name))
586
client.add_expected_call(
587
'Branch.get_stacked_on_url', ('quack/',),
588
'error', ('NotStacked',))
589
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
591
result = bzrdir.open_branch()
592
self.assertIsInstance(result, RemoteBranch)
593
self.assertEqual(bzrdir, result.bzrdir)
594
self.assertFinished(client)
596
def test_branch_missing(self):
597
transport = MemoryTransport()
598
transport.mkdir('quack')
599
transport = transport.clone('quack')
600
client = FakeClient(transport.base)
601
client.add_error_response('nobranch')
602
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
604
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
606
[('call', 'BzrDir.open_branchV3', ('quack/',))],
609
def test__get_tree_branch(self):
610
# _get_tree_branch is a form of open_branch, but it should only ask for
611
# branch opening, not any other network requests.
613
def open_branch(name=None):
614
calls.append("Called")
616
transport = MemoryTransport()
617
# no requests on the network - catches other api calls being made.
618
client = FakeClient(transport.base)
619
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
621
# patch the open_branch call to record that it was called.
622
bzrdir.open_branch = open_branch
623
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
624
self.assertEqual(["Called"], calls)
625
self.assertEqual([], client._calls)
627
def test_url_quoting_of_path(self):
628
# Relpaths on the wire should not be URL-escaped. So "~" should be
629
# transmitted as "~", not "%7E".
630
transport = RemoteTCPTransport('bzr://localhost/~hello/')
631
client = FakeClient(transport.base)
632
reference_format = self.get_repo_format()
633
network_name = reference_format.network_name()
634
branch_network_name = self.get_branch_format().network_name()
635
client.add_expected_call(
636
'BzrDir.open_branchV3', ('~hello/',),
637
'success', ('branch', branch_network_name))
638
client.add_expected_call(
639
'BzrDir.find_repositoryV3', ('~hello/',),
640
'success', ('ok', '', 'no', 'no', 'no', network_name))
641
client.add_expected_call(
642
'Branch.get_stacked_on_url', ('~hello/',),
643
'error', ('NotStacked',))
644
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
646
result = bzrdir.open_branch()
647
self.assertFinished(client)
649
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
650
reference_format = self.get_repo_format()
651
network_name = reference_format.network_name()
652
transport = MemoryTransport()
653
transport.mkdir('quack')
654
transport = transport.clone('quack')
656
rich_response = 'yes'
660
subtree_response = 'yes'
662
subtree_response = 'no'
663
client = FakeClient(transport.base)
664
client.add_success_response(
665
'ok', '', rich_response, subtree_response, external_lookup,
667
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
669
result = bzrdir.open_repository()
671
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
673
self.assertIsInstance(result, RemoteRepository)
674
self.assertEqual(bzrdir, result.bzrdir)
675
self.assertEqual(rich_root, result._format.rich_root_data)
676
self.assertEqual(subtrees, result._format.supports_tree_reference)
678
def test_open_repository_sets_format_attributes(self):
679
self.check_open_repository(True, True)
680
self.check_open_repository(False, True)
681
self.check_open_repository(True, False)
682
self.check_open_repository(False, False)
683
self.check_open_repository(False, False, 'yes')
685
def test_old_server(self):
686
"""RemoteBzrDirFormat should fail to probe if the server version is too
689
self.assertRaises(errors.NotBranchError,
690
RemoteBzrProber.probe_transport, OldServerTransport())
693
class TestBzrDirCreateBranch(TestRemote):
695
def test_backwards_compat(self):
696
self.setup_smart_server_with_call_log()
697
repo = self.make_repository('.')
698
self.reset_smart_call_log()
699
self.disable_verb('BzrDir.create_branch')
700
branch = repo.bzrdir.create_branch()
701
create_branch_call_count = len([call for call in self.hpss_calls if
702
call.call.method == 'BzrDir.create_branch'])
703
self.assertEqual(1, create_branch_call_count)
705
def test_current_server(self):
706
transport = self.get_transport('.')
707
transport = transport.clone('quack')
708
self.make_repository('quack')
709
client = FakeClient(transport.base)
710
reference_bzrdir_format = bzrdir.format_registry.get('default')()
711
reference_format = reference_bzrdir_format.get_branch_format()
712
network_name = reference_format.network_name()
713
reference_repo_fmt = reference_bzrdir_format.repository_format
714
reference_repo_name = reference_repo_fmt.network_name()
715
client.add_expected_call(
716
'BzrDir.create_branch', ('quack/', network_name),
717
'success', ('ok', network_name, '', 'no', 'no', 'yes',
718
reference_repo_name))
719
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
721
branch = a_bzrdir.create_branch()
722
# We should have got a remote branch
723
self.assertIsInstance(branch, remote.RemoteBranch)
724
# its format should have the settings from the response
725
format = branch._format
726
self.assertEqual(network_name, format.network_name())
728
def test_already_open_repo_and_reused_medium(self):
729
"""Bug 726584: create_branch(..., repository=repo) should work
730
regardless of what the smart medium's base URL is.
732
self.transport_server = test_server.SmartTCPServer_for_testing
733
transport = self.get_transport('.')
734
repo = self.make_repository('quack')
735
# Client's medium rooted a transport root (not at the bzrdir)
736
client = FakeClient(transport.base)
737
transport = transport.clone('quack')
738
reference_bzrdir_format = bzrdir.format_registry.get('default')()
739
reference_format = reference_bzrdir_format.get_branch_format()
740
network_name = reference_format.network_name()
741
reference_repo_fmt = reference_bzrdir_format.repository_format
742
reference_repo_name = reference_repo_fmt.network_name()
743
client.add_expected_call(
744
'BzrDir.create_branch', ('extra/quack/', network_name),
745
'success', ('ok', network_name, '', 'no', 'no', 'yes',
746
reference_repo_name))
747
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
749
branch = a_bzrdir.create_branch(repository=repo)
750
# We should have got a remote branch
751
self.assertIsInstance(branch, remote.RemoteBranch)
752
# its format should have the settings from the response
753
format = branch._format
754
self.assertEqual(network_name, format.network_name())
757
class TestBzrDirCreateRepository(TestRemote):
759
def test_backwards_compat(self):
760
self.setup_smart_server_with_call_log()
761
bzrdir = self.make_bzrdir('.')
762
self.reset_smart_call_log()
763
self.disable_verb('BzrDir.create_repository')
764
repo = bzrdir.create_repository()
765
create_repo_call_count = len([call for call in self.hpss_calls if
766
call.call.method == 'BzrDir.create_repository'])
767
self.assertEqual(1, create_repo_call_count)
769
def test_current_server(self):
770
transport = self.get_transport('.')
771
transport = transport.clone('quack')
772
self.make_bzrdir('quack')
773
client = FakeClient(transport.base)
774
reference_bzrdir_format = bzrdir.format_registry.get('default')()
775
reference_format = reference_bzrdir_format.repository_format
776
network_name = reference_format.network_name()
777
client.add_expected_call(
778
'BzrDir.create_repository', ('quack/',
779
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
781
'success', ('ok', 'yes', 'yes', 'yes', network_name))
782
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
784
repo = a_bzrdir.create_repository()
785
# We should have got a remote repository
786
self.assertIsInstance(repo, remote.RemoteRepository)
787
# its format should have the settings from the response
788
format = repo._format
789
self.assertTrue(format.rich_root_data)
790
self.assertTrue(format.supports_tree_reference)
791
self.assertTrue(format.supports_external_lookups)
792
self.assertEqual(network_name, format.network_name())
795
class TestBzrDirOpenRepository(TestRemote):
797
def test_backwards_compat_1_2_3(self):
798
# fallback all the way to the first version.
799
reference_format = self.get_repo_format()
800
network_name = reference_format.network_name()
801
server_url = 'bzr://example.com/'
802
self.permit_url(server_url)
803
client = FakeClient(server_url)
804
client.add_unknown_method_response('BzrDir.find_repositoryV3')
805
client.add_unknown_method_response('BzrDir.find_repositoryV2')
806
client.add_success_response('ok', '', 'no', 'no')
807
# A real repository instance will be created to determine the network
809
client.add_success_response_with_body(
810
"Bazaar-NG meta directory, format 1\n", 'ok')
811
client.add_success_response_with_body(
812
reference_format.get_format_string(), 'ok')
813
# PackRepository wants to do a stat
814
client.add_success_response('stat', '0', '65535')
815
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
817
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
819
repo = bzrdir.open_repository()
821
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
822
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
823
('call', 'BzrDir.find_repository', ('quack/',)),
824
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
825
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
826
('call', 'stat', ('/quack/.bzr/repository',)),
829
self.assertEqual(network_name, repo._format.network_name())
831
def test_backwards_compat_2(self):
832
# fallback to find_repositoryV2
833
reference_format = self.get_repo_format()
834
network_name = reference_format.network_name()
835
server_url = 'bzr://example.com/'
836
self.permit_url(server_url)
837
client = FakeClient(server_url)
838
client.add_unknown_method_response('BzrDir.find_repositoryV3')
839
client.add_success_response('ok', '', 'no', 'no', 'no')
840
# A real repository instance will be created to determine the network
842
client.add_success_response_with_body(
843
"Bazaar-NG meta directory, format 1\n", 'ok')
844
client.add_success_response_with_body(
845
reference_format.get_format_string(), 'ok')
846
# PackRepository wants to do a stat
847
client.add_success_response('stat', '0', '65535')
848
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
850
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
852
repo = bzrdir.open_repository()
854
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
855
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
856
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
857
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
858
('call', 'stat', ('/quack/.bzr/repository',)),
861
self.assertEqual(network_name, repo._format.network_name())
863
def test_current_server(self):
864
reference_format = self.get_repo_format()
865
network_name = reference_format.network_name()
866
transport = MemoryTransport()
867
transport.mkdir('quack')
868
transport = transport.clone('quack')
869
client = FakeClient(transport.base)
870
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
871
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
873
repo = bzrdir.open_repository()
875
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
877
self.assertEqual(network_name, repo._format.network_name())
880
class TestBzrDirFormatInitializeEx(TestRemote):
882
def test_success(self):
883
"""Simple test for typical successful call."""
884
fmt = RemoteBzrDirFormat()
885
default_format_name = BzrDirFormat.get_default_format().network_name()
886
transport = self.get_transport()
887
client = FakeClient(transport.base)
888
client.add_expected_call(
889
'BzrDirFormat.initialize_ex_1.16',
890
(default_format_name, 'path', 'False', 'False', 'False', '',
891
'', '', '', 'False'),
893
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
894
'bzrdir fmt', 'False', '', '', 'repo lock token'))
895
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
896
# it's currently hard to test that without supplying a real remote
897
# transport connected to a real server.
898
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
899
transport, False, False, False, None, None, None, None, False)
900
self.assertFinished(client)
902
def test_error(self):
903
"""Error responses are translated, e.g. 'PermissionDenied' raises the
904
corresponding error from the client.
906
fmt = RemoteBzrDirFormat()
907
default_format_name = BzrDirFormat.get_default_format().network_name()
908
transport = self.get_transport()
909
client = FakeClient(transport.base)
910
client.add_expected_call(
911
'BzrDirFormat.initialize_ex_1.16',
912
(default_format_name, 'path', 'False', 'False', 'False', '',
913
'', '', '', 'False'),
915
('PermissionDenied', 'path', 'extra info'))
916
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
917
# it's currently hard to test that without supplying a real remote
918
# transport connected to a real server.
919
err = self.assertRaises(errors.PermissionDenied,
920
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
921
False, False, False, None, None, None, None, False)
922
self.assertEqual('path', err.path)
923
self.assertEqual(': extra info', err.extra)
924
self.assertFinished(client)
926
def test_error_from_real_server(self):
927
"""Integration test for error translation."""
928
transport = self.make_smart_server('foo')
929
transport = transport.clone('no-such-path')
930
fmt = RemoteBzrDirFormat()
931
err = self.assertRaises(errors.NoSuchFile,
932
fmt.initialize_on_transport_ex, transport, create_prefix=False)
935
class OldSmartClient(object):
936
"""A fake smart client for test_old_version that just returns a version one
937
response to the 'hello' (query version) command.
940
def get_request(self):
941
input_file = StringIO('ok\x011\n')
942
output_file = StringIO()
943
client_medium = medium.SmartSimplePipesClientMedium(
944
input_file, output_file)
945
return medium.SmartClientStreamMediumRequest(client_medium)
947
def protocol_version(self):
951
class OldServerTransport(object):
952
"""A fake transport for test_old_server that reports it's smart server
953
protocol version as version one.
959
def get_smart_client(self):
960
return OldSmartClient()
963
class RemoteBzrDirTestCase(TestRemote):
965
def make_remote_bzrdir(self, transport, client):
966
"""Make a RemotebzrDir using 'client' as the _client."""
967
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
971
class RemoteBranchTestCase(RemoteBzrDirTestCase):
973
def lock_remote_branch(self, branch):
974
"""Trick a RemoteBranch into thinking it is locked."""
975
branch._lock_mode = 'w'
976
branch._lock_count = 2
977
branch._lock_token = 'branch token'
978
branch._repo_lock_token = 'repo token'
979
branch.repository._lock_mode = 'w'
980
branch.repository._lock_count = 2
981
branch.repository._lock_token = 'repo token'
983
def make_remote_branch(self, transport, client):
984
"""Make a RemoteBranch using 'client' as its _SmartClient.
986
A RemoteBzrDir and RemoteRepository will also be created to fill out
987
the RemoteBranch, albeit with stub values for some of their attributes.
989
# we do not want bzrdir to make any remote calls, so use False as its
990
# _client. If it tries to make a remote call, this will fail
992
bzrdir = self.make_remote_bzrdir(transport, False)
993
repo = RemoteRepository(bzrdir, None, _client=client)
994
branch_format = self.get_branch_format()
995
format = RemoteBranchFormat(network_name=branch_format.network_name())
996
return RemoteBranch(bzrdir, repo, _client=client, format=format)
999
class TestBranchGetParent(RemoteBranchTestCase):
1001
def test_no_parent(self):
1002
# in an empty branch we decode the response properly
1003
transport = MemoryTransport()
1004
client = FakeClient(transport.base)
1005
client.add_expected_call(
1006
'Branch.get_stacked_on_url', ('quack/',),
1007
'error', ('NotStacked',))
1008
client.add_expected_call(
1009
'Branch.get_parent', ('quack/',),
1011
transport.mkdir('quack')
1012
transport = transport.clone('quack')
1013
branch = self.make_remote_branch(transport, client)
1014
result = branch.get_parent()
1015
self.assertFinished(client)
1016
self.assertEqual(None, result)
1018
def test_parent_relative(self):
1019
transport = MemoryTransport()
1020
client = FakeClient(transport.base)
1021
client.add_expected_call(
1022
'Branch.get_stacked_on_url', ('kwaak/',),
1023
'error', ('NotStacked',))
1024
client.add_expected_call(
1025
'Branch.get_parent', ('kwaak/',),
1026
'success', ('../foo/',))
1027
transport.mkdir('kwaak')
1028
transport = transport.clone('kwaak')
1029
branch = self.make_remote_branch(transport, client)
1030
result = branch.get_parent()
1031
self.assertEqual(transport.clone('../foo').base, result)
1033
def test_parent_absolute(self):
1034
transport = MemoryTransport()
1035
client = FakeClient(transport.base)
1036
client.add_expected_call(
1037
'Branch.get_stacked_on_url', ('kwaak/',),
1038
'error', ('NotStacked',))
1039
client.add_expected_call(
1040
'Branch.get_parent', ('kwaak/',),
1041
'success', ('http://foo/',))
1042
transport.mkdir('kwaak')
1043
transport = transport.clone('kwaak')
1044
branch = self.make_remote_branch(transport, client)
1045
result = branch.get_parent()
1046
self.assertEqual('http://foo/', result)
1047
self.assertFinished(client)
1050
class TestBranchSetParentLocation(RemoteBranchTestCase):
1052
def test_no_parent(self):
1053
# We call the verb when setting parent to None
1054
transport = MemoryTransport()
1055
client = FakeClient(transport.base)
1056
client.add_expected_call(
1057
'Branch.get_stacked_on_url', ('quack/',),
1058
'error', ('NotStacked',))
1059
client.add_expected_call(
1060
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1062
transport.mkdir('quack')
1063
transport = transport.clone('quack')
1064
branch = self.make_remote_branch(transport, client)
1065
branch._lock_token = 'b'
1066
branch._repo_lock_token = 'r'
1067
branch._set_parent_location(None)
1068
self.assertFinished(client)
1070
def test_parent(self):
1071
transport = MemoryTransport()
1072
client = FakeClient(transport.base)
1073
client.add_expected_call(
1074
'Branch.get_stacked_on_url', ('kwaak/',),
1075
'error', ('NotStacked',))
1076
client.add_expected_call(
1077
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1079
transport.mkdir('kwaak')
1080
transport = transport.clone('kwaak')
1081
branch = self.make_remote_branch(transport, client)
1082
branch._lock_token = 'b'
1083
branch._repo_lock_token = 'r'
1084
branch._set_parent_location('foo')
1085
self.assertFinished(client)
1087
def test_backwards_compat(self):
1088
self.setup_smart_server_with_call_log()
1089
branch = self.make_branch('.')
1090
self.reset_smart_call_log()
1091
verb = 'Branch.set_parent_location'
1092
self.disable_verb(verb)
1093
branch.set_parent('http://foo/')
1094
self.assertLength(12, self.hpss_calls)
1097
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1099
def test_backwards_compat(self):
1100
self.setup_smart_server_with_call_log()
1101
branch = self.make_branch('.')
1102
self.reset_smart_call_log()
1103
verb = 'Branch.get_tags_bytes'
1104
self.disable_verb(verb)
1105
branch.tags.get_tag_dict()
1106
call_count = len([call for call in self.hpss_calls if
1107
call.call.method == verb])
1108
self.assertEqual(1, call_count)
1110
def test_trivial(self):
1111
transport = MemoryTransport()
1112
client = FakeClient(transport.base)
1113
client.add_expected_call(
1114
'Branch.get_stacked_on_url', ('quack/',),
1115
'error', ('NotStacked',))
1116
client.add_expected_call(
1117
'Branch.get_tags_bytes', ('quack/',),
1119
transport.mkdir('quack')
1120
transport = transport.clone('quack')
1121
branch = self.make_remote_branch(transport, client)
1122
result = branch.tags.get_tag_dict()
1123
self.assertFinished(client)
1124
self.assertEqual({}, result)
1127
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1129
def test_trivial(self):
1130
transport = MemoryTransport()
1131
client = FakeClient(transport.base)
1132
client.add_expected_call(
1133
'Branch.get_stacked_on_url', ('quack/',),
1134
'error', ('NotStacked',))
1135
client.add_expected_call(
1136
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1138
transport.mkdir('quack')
1139
transport = transport.clone('quack')
1140
branch = self.make_remote_branch(transport, client)
1141
self.lock_remote_branch(branch)
1142
branch._set_tags_bytes('tags bytes')
1143
self.assertFinished(client)
1144
self.assertEqual('tags bytes', client._calls[-1][-1])
1146
def test_backwards_compatible(self):
1147
transport = MemoryTransport()
1148
client = FakeClient(transport.base)
1149
client.add_expected_call(
1150
'Branch.get_stacked_on_url', ('quack/',),
1151
'error', ('NotStacked',))
1152
client.add_expected_call(
1153
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1154
'unknown', ('Branch.set_tags_bytes',))
1155
transport.mkdir('quack')
1156
transport = transport.clone('quack')
1157
branch = self.make_remote_branch(transport, client)
1158
self.lock_remote_branch(branch)
1159
class StubRealBranch(object):
1162
def _set_tags_bytes(self, bytes):
1163
self.calls.append(('set_tags_bytes', bytes))
1164
real_branch = StubRealBranch()
1165
branch._real_branch = real_branch
1166
branch._set_tags_bytes('tags bytes')
1167
# Call a second time, to exercise the 'remote version already inferred'
1169
branch._set_tags_bytes('tags bytes')
1170
self.assertFinished(client)
1172
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1175
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1177
def test_uses_last_revision_info_and_tags_by_default(self):
1178
transport = MemoryTransport()
1179
client = FakeClient(transport.base)
1180
client.add_expected_call(
1181
'Branch.get_stacked_on_url', ('quack/',),
1182
'error', ('NotStacked',))
1183
client.add_expected_call(
1184
'Branch.last_revision_info', ('quack/',),
1185
'success', ('ok', '1', 'rev-tip'))
1186
client.add_expected_call(
1187
'Branch.get_config_file', ('quack/',),
1188
'success', ('ok',), '')
1189
transport.mkdir('quack')
1190
transport = transport.clone('quack')
1191
branch = self.make_remote_branch(transport, client)
1192
result = branch.heads_to_fetch()
1193
self.assertFinished(client)
1194
self.assertEqual((set(['rev-tip']), set()), result)
1196
def test_uses_last_revision_info_and_tags_when_set(self):
1197
transport = MemoryTransport()
1198
client = FakeClient(transport.base)
1199
client.add_expected_call(
1200
'Branch.get_stacked_on_url', ('quack/',),
1201
'error', ('NotStacked',))
1202
client.add_expected_call(
1203
'Branch.last_revision_info', ('quack/',),
1204
'success', ('ok', '1', 'rev-tip'))
1205
client.add_expected_call(
1206
'Branch.get_config_file', ('quack/',),
1207
'success', ('ok',), 'branch.fetch_tags = True')
1208
# XXX: this will break if the default format's serialization of tags
1209
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1210
client.add_expected_call(
1211
'Branch.get_tags_bytes', ('quack/',),
1212
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1213
transport.mkdir('quack')
1214
transport = transport.clone('quack')
1215
branch = self.make_remote_branch(transport, client)
1216
result = branch.heads_to_fetch()
1217
self.assertFinished(client)
1219
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1221
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1222
transport = MemoryTransport()
1223
client = FakeClient(transport.base)
1224
client.add_expected_call(
1225
'Branch.get_stacked_on_url', ('quack/',),
1226
'error', ('NotStacked',))
1227
client.add_expected_call(
1228
'Branch.heads_to_fetch', ('quack/',),
1229
'success', (['tip'], ['tagged-1', 'tagged-2']))
1230
transport.mkdir('quack')
1231
transport = transport.clone('quack')
1232
branch = self.make_remote_branch(transport, client)
1233
branch._format._use_default_local_heads_to_fetch = lambda: False
1234
result = branch.heads_to_fetch()
1235
self.assertFinished(client)
1236
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1238
def make_branch_with_tags(self):
1239
self.setup_smart_server_with_call_log()
1240
# Make a branch with a single revision.
1241
builder = self.make_branch_builder('foo')
1242
builder.start_series()
1243
builder.build_snapshot('tip', None, [
1244
('add', ('', 'root-id', 'directory', ''))])
1245
builder.finish_series()
1246
branch = builder.get_branch()
1247
# Add two tags to that branch
1248
branch.tags.set_tag('tag-1', 'rev-1')
1249
branch.tags.set_tag('tag-2', 'rev-2')
1252
def test_backwards_compatible(self):
1253
branch = self.make_branch_with_tags()
1254
c = branch.get_config()
1255
c.set_user_option('branch.fetch_tags', 'True')
1256
self.addCleanup(branch.lock_read().unlock)
1257
# Disable the heads_to_fetch verb
1258
verb = 'Branch.heads_to_fetch'
1259
self.disable_verb(verb)
1260
self.reset_smart_call_log()
1261
result = branch.heads_to_fetch()
1262
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1264
['Branch.last_revision_info', 'Branch.get_config_file',
1265
'Branch.get_tags_bytes'],
1266
[call.call.method for call in self.hpss_calls])
1268
def test_backwards_compatible_no_tags(self):
1269
branch = self.make_branch_with_tags()
1270
c = branch.get_config()
1271
c.set_user_option('branch.fetch_tags', 'False')
1272
self.addCleanup(branch.lock_read().unlock)
1273
# Disable the heads_to_fetch verb
1274
verb = 'Branch.heads_to_fetch'
1275
self.disable_verb(verb)
1276
self.reset_smart_call_log()
1277
result = branch.heads_to_fetch()
1278
self.assertEqual((set(['tip']), set()), result)
1280
['Branch.last_revision_info', 'Branch.get_config_file'],
1281
[call.call.method for call in self.hpss_calls])
1284
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1286
def test_empty_branch(self):
1287
# in an empty branch we decode the response properly
1288
transport = MemoryTransport()
1289
client = FakeClient(transport.base)
1290
client.add_expected_call(
1291
'Branch.get_stacked_on_url', ('quack/',),
1292
'error', ('NotStacked',))
1293
client.add_expected_call(
1294
'Branch.last_revision_info', ('quack/',),
1295
'success', ('ok', '0', 'null:'))
1296
transport.mkdir('quack')
1297
transport = transport.clone('quack')
1298
branch = self.make_remote_branch(transport, client)
1299
result = branch.last_revision_info()
1300
self.assertFinished(client)
1301
self.assertEqual((0, NULL_REVISION), result)
1303
def test_non_empty_branch(self):
1304
# in a non-empty branch we also decode the response properly
1305
revid = u'\xc8'.encode('utf8')
1306
transport = MemoryTransport()
1307
client = FakeClient(transport.base)
1308
client.add_expected_call(
1309
'Branch.get_stacked_on_url', ('kwaak/',),
1310
'error', ('NotStacked',))
1311
client.add_expected_call(
1312
'Branch.last_revision_info', ('kwaak/',),
1313
'success', ('ok', '2', revid))
1314
transport.mkdir('kwaak')
1315
transport = transport.clone('kwaak')
1316
branch = self.make_remote_branch(transport, client)
1317
result = branch.last_revision_info()
1318
self.assertEqual((2, revid), result)
1321
class TestBranch_get_stacked_on_url(TestRemote):
1322
"""Test Branch._get_stacked_on_url rpc"""
1324
def test_get_stacked_on_invalid_url(self):
1325
# test that asking for a stacked on url the server can't access works.
1326
# This isn't perfect, but then as we're in the same process there
1327
# really isn't anything we can do to be 100% sure that the server
1328
# doesn't just open in - this test probably needs to be rewritten using
1329
# a spawn()ed server.
1330
stacked_branch = self.make_branch('stacked', format='1.9')
1331
memory_branch = self.make_branch('base', format='1.9')
1332
vfs_url = self.get_vfs_only_url('base')
1333
stacked_branch.set_stacked_on_url(vfs_url)
1334
transport = stacked_branch.bzrdir.root_transport
1335
client = FakeClient(transport.base)
1336
client.add_expected_call(
1337
'Branch.get_stacked_on_url', ('stacked/',),
1338
'success', ('ok', vfs_url))
1339
# XXX: Multiple calls are bad, this second call documents what is
1341
client.add_expected_call(
1342
'Branch.get_stacked_on_url', ('stacked/',),
1343
'success', ('ok', vfs_url))
1344
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1346
repo_fmt = remote.RemoteRepositoryFormat()
1347
repo_fmt._custom_format = stacked_branch.repository._format
1348
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1350
result = branch.get_stacked_on_url()
1351
self.assertEqual(vfs_url, result)
1353
def test_backwards_compatible(self):
1354
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1355
base_branch = self.make_branch('base', format='1.6')
1356
stacked_branch = self.make_branch('stacked', format='1.6')
1357
stacked_branch.set_stacked_on_url('../base')
1358
client = FakeClient(self.get_url())
1359
branch_network_name = self.get_branch_format().network_name()
1360
client.add_expected_call(
1361
'BzrDir.open_branchV3', ('stacked/',),
1362
'success', ('branch', branch_network_name))
1363
client.add_expected_call(
1364
'BzrDir.find_repositoryV3', ('stacked/',),
1365
'success', ('ok', '', 'no', 'no', 'yes',
1366
stacked_branch.repository._format.network_name()))
1367
# called twice, once from constructor and then again by us
1368
client.add_expected_call(
1369
'Branch.get_stacked_on_url', ('stacked/',),
1370
'unknown', ('Branch.get_stacked_on_url',))
1371
client.add_expected_call(
1372
'Branch.get_stacked_on_url', ('stacked/',),
1373
'unknown', ('Branch.get_stacked_on_url',))
1374
# this will also do vfs access, but that goes direct to the transport
1375
# and isn't seen by the FakeClient.
1376
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1377
RemoteBzrDirFormat(), _client=client)
1378
branch = bzrdir.open_branch()
1379
result = branch.get_stacked_on_url()
1380
self.assertEqual('../base', result)
1381
self.assertFinished(client)
1382
# it's in the fallback list both for the RemoteRepository and its vfs
1384
self.assertEqual(1, len(branch.repository._fallback_repositories))
1386
len(branch.repository._real_repository._fallback_repositories))
1388
def test_get_stacked_on_real_branch(self):
1389
base_branch = self.make_branch('base')
1390
stacked_branch = self.make_branch('stacked')
1391
stacked_branch.set_stacked_on_url('../base')
1392
reference_format = self.get_repo_format()
1393
network_name = reference_format.network_name()
1394
client = FakeClient(self.get_url())
1395
branch_network_name = self.get_branch_format().network_name()
1396
client.add_expected_call(
1397
'BzrDir.open_branchV3', ('stacked/',),
1398
'success', ('branch', branch_network_name))
1399
client.add_expected_call(
1400
'BzrDir.find_repositoryV3', ('stacked/',),
1401
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1402
# called twice, once from constructor and then again by us
1403
client.add_expected_call(
1404
'Branch.get_stacked_on_url', ('stacked/',),
1405
'success', ('ok', '../base'))
1406
client.add_expected_call(
1407
'Branch.get_stacked_on_url', ('stacked/',),
1408
'success', ('ok', '../base'))
1409
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1410
RemoteBzrDirFormat(), _client=client)
1411
branch = bzrdir.open_branch()
1412
result = branch.get_stacked_on_url()
1413
self.assertEqual('../base', result)
1414
self.assertFinished(client)
1415
# it's in the fallback list both for the RemoteRepository.
1416
self.assertEqual(1, len(branch.repository._fallback_repositories))
1417
# And we haven't had to construct a real repository.
1418
self.assertEqual(None, branch.repository._real_repository)
1421
class TestBranchSetLastRevision(RemoteBranchTestCase):
1423
def test_set_empty(self):
1424
# _set_last_revision_info('null:') is translated to calling
1425
# Branch.set_last_revision(path, '') on the wire.
1426
transport = MemoryTransport()
1427
transport.mkdir('branch')
1428
transport = transport.clone('branch')
1430
client = FakeClient(transport.base)
1431
client.add_expected_call(
1432
'Branch.get_stacked_on_url', ('branch/',),
1433
'error', ('NotStacked',))
1434
client.add_expected_call(
1435
'Branch.lock_write', ('branch/', '', ''),
1436
'success', ('ok', 'branch token', 'repo token'))
1437
client.add_expected_call(
1438
'Branch.last_revision_info',
1440
'success', ('ok', '0', 'null:'))
1441
client.add_expected_call(
1442
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1444
client.add_expected_call(
1445
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1447
branch = self.make_remote_branch(transport, client)
1448
# This is a hack to work around the problem that RemoteBranch currently
1449
# unnecessarily invokes _ensure_real upon a call to lock_write.
1450
branch._ensure_real = lambda: None
1452
result = branch._set_last_revision(NULL_REVISION)
1454
self.assertEqual(None, result)
1455
self.assertFinished(client)
1457
def test_set_nonempty(self):
1458
# set_last_revision_info(N, rev-idN) is translated to calling
1459
# Branch.set_last_revision(path, rev-idN) on the wire.
1460
transport = MemoryTransport()
1461
transport.mkdir('branch')
1462
transport = transport.clone('branch')
1464
client = FakeClient(transport.base)
1465
client.add_expected_call(
1466
'Branch.get_stacked_on_url', ('branch/',),
1467
'error', ('NotStacked',))
1468
client.add_expected_call(
1469
'Branch.lock_write', ('branch/', '', ''),
1470
'success', ('ok', 'branch token', 'repo token'))
1471
client.add_expected_call(
1472
'Branch.last_revision_info',
1474
'success', ('ok', '0', 'null:'))
1476
encoded_body = bz2.compress('\n'.join(lines))
1477
client.add_success_response_with_body(encoded_body, 'ok')
1478
client.add_expected_call(
1479
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1481
client.add_expected_call(
1482
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1484
branch = self.make_remote_branch(transport, client)
1485
# This is a hack to work around the problem that RemoteBranch currently
1486
# unnecessarily invokes _ensure_real upon a call to lock_write.
1487
branch._ensure_real = lambda: None
1488
# Lock the branch, reset the record of remote calls.
1490
result = branch._set_last_revision('rev-id2')
1492
self.assertEqual(None, result)
1493
self.assertFinished(client)
1495
def test_no_such_revision(self):
1496
transport = MemoryTransport()
1497
transport.mkdir('branch')
1498
transport = transport.clone('branch')
1499
# A response of 'NoSuchRevision' is translated into an exception.
1500
client = FakeClient(transport.base)
1501
client.add_expected_call(
1502
'Branch.get_stacked_on_url', ('branch/',),
1503
'error', ('NotStacked',))
1504
client.add_expected_call(
1505
'Branch.lock_write', ('branch/', '', ''),
1506
'success', ('ok', 'branch token', 'repo token'))
1507
client.add_expected_call(
1508
'Branch.last_revision_info',
1510
'success', ('ok', '0', 'null:'))
1511
# get_graph calls to construct the revision history, for the set_rh
1514
encoded_body = bz2.compress('\n'.join(lines))
1515
client.add_success_response_with_body(encoded_body, 'ok')
1516
client.add_expected_call(
1517
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1518
'error', ('NoSuchRevision', 'rev-id'))
1519
client.add_expected_call(
1520
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1523
branch = self.make_remote_branch(transport, client)
1526
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1528
self.assertFinished(client)
1530
def test_tip_change_rejected(self):
1531
"""TipChangeRejected responses cause a TipChangeRejected exception to
1534
transport = MemoryTransport()
1535
transport.mkdir('branch')
1536
transport = transport.clone('branch')
1537
client = FakeClient(transport.base)
1538
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1539
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1540
client.add_expected_call(
1541
'Branch.get_stacked_on_url', ('branch/',),
1542
'error', ('NotStacked',))
1543
client.add_expected_call(
1544
'Branch.lock_write', ('branch/', '', ''),
1545
'success', ('ok', 'branch token', 'repo token'))
1546
client.add_expected_call(
1547
'Branch.last_revision_info',
1549
'success', ('ok', '0', 'null:'))
1551
encoded_body = bz2.compress('\n'.join(lines))
1552
client.add_success_response_with_body(encoded_body, 'ok')
1553
client.add_expected_call(
1554
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1555
'error', ('TipChangeRejected', rejection_msg_utf8))
1556
client.add_expected_call(
1557
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1559
branch = self.make_remote_branch(transport, client)
1560
branch._ensure_real = lambda: None
1562
# The 'TipChangeRejected' error response triggered by calling
1563
# set_last_revision_info causes a TipChangeRejected exception.
1564
err = self.assertRaises(
1565
errors.TipChangeRejected,
1566
branch._set_last_revision, 'rev-id')
1567
# The UTF-8 message from the response has been decoded into a unicode
1569
self.assertIsInstance(err.msg, unicode)
1570
self.assertEqual(rejection_msg_unicode, err.msg)
1572
self.assertFinished(client)
1575
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1577
def test_set_last_revision_info(self):
1578
# set_last_revision_info(num, 'rev-id') is translated to calling
1579
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1580
transport = MemoryTransport()
1581
transport.mkdir('branch')
1582
transport = transport.clone('branch')
1583
client = FakeClient(transport.base)
1584
# get_stacked_on_url
1585
client.add_error_response('NotStacked')
1587
client.add_success_response('ok', 'branch token', 'repo token')
1588
# query the current revision
1589
client.add_success_response('ok', '0', 'null:')
1591
client.add_success_response('ok')
1593
client.add_success_response('ok')
1595
branch = self.make_remote_branch(transport, client)
1596
# Lock the branch, reset the record of remote calls.
1599
result = branch.set_last_revision_info(1234, 'a-revision-id')
1601
[('call', 'Branch.last_revision_info', ('branch/',)),
1602
('call', 'Branch.set_last_revision_info',
1603
('branch/', 'branch token', 'repo token',
1604
'1234', 'a-revision-id'))],
1606
self.assertEqual(None, result)
1608
def test_no_such_revision(self):
1609
# A response of 'NoSuchRevision' is translated into an exception.
1610
transport = MemoryTransport()
1611
transport.mkdir('branch')
1612
transport = transport.clone('branch')
1613
client = FakeClient(transport.base)
1614
# get_stacked_on_url
1615
client.add_error_response('NotStacked')
1617
client.add_success_response('ok', 'branch token', 'repo token')
1619
client.add_error_response('NoSuchRevision', 'revid')
1621
client.add_success_response('ok')
1623
branch = self.make_remote_branch(transport, client)
1624
# Lock the branch, reset the record of remote calls.
1629
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1632
def test_backwards_compatibility(self):
1633
"""If the server does not support the Branch.set_last_revision_info
1634
verb (which is new in 1.4), then the client falls back to VFS methods.
1636
# This test is a little messy. Unlike most tests in this file, it
1637
# doesn't purely test what a Remote* object sends over the wire, and
1638
# how it reacts to responses from the wire. It instead relies partly
1639
# on asserting that the RemoteBranch will call
1640
# self._real_branch.set_last_revision_info(...).
1642
# First, set up our RemoteBranch with a FakeClient that raises
1643
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1644
transport = MemoryTransport()
1645
transport.mkdir('branch')
1646
transport = transport.clone('branch')
1647
client = FakeClient(transport.base)
1648
client.add_expected_call(
1649
'Branch.get_stacked_on_url', ('branch/',),
1650
'error', ('NotStacked',))
1651
client.add_expected_call(
1652
'Branch.last_revision_info',
1654
'success', ('ok', '0', 'null:'))
1655
client.add_expected_call(
1656
'Branch.set_last_revision_info',
1657
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1658
'unknown', 'Branch.set_last_revision_info')
1660
branch = self.make_remote_branch(transport, client)
1661
class StubRealBranch(object):
1664
def set_last_revision_info(self, revno, revision_id):
1666
('set_last_revision_info', revno, revision_id))
1667
def _clear_cached_state(self):
1669
real_branch = StubRealBranch()
1670
branch._real_branch = real_branch
1671
self.lock_remote_branch(branch)
1673
# Call set_last_revision_info, and verify it behaved as expected.
1674
result = branch.set_last_revision_info(1234, 'a-revision-id')
1676
[('set_last_revision_info', 1234, 'a-revision-id')],
1678
self.assertFinished(client)
1680
def test_unexpected_error(self):
1681
# If the server sends an error the client doesn't understand, it gets
1682
# turned into an UnknownErrorFromSmartServer, which is presented as a
1683
# non-internal error to the user.
1684
transport = MemoryTransport()
1685
transport.mkdir('branch')
1686
transport = transport.clone('branch')
1687
client = FakeClient(transport.base)
1688
# get_stacked_on_url
1689
client.add_error_response('NotStacked')
1691
client.add_success_response('ok', 'branch token', 'repo token')
1693
client.add_error_response('UnexpectedError')
1695
client.add_success_response('ok')
1697
branch = self.make_remote_branch(transport, client)
1698
# Lock the branch, reset the record of remote calls.
1702
err = self.assertRaises(
1703
errors.UnknownErrorFromSmartServer,
1704
branch.set_last_revision_info, 123, 'revid')
1705
self.assertEqual(('UnexpectedError',), err.error_tuple)
1708
def test_tip_change_rejected(self):
1709
"""TipChangeRejected responses cause a TipChangeRejected exception to
1712
transport = MemoryTransport()
1713
transport.mkdir('branch')
1714
transport = transport.clone('branch')
1715
client = FakeClient(transport.base)
1716
# get_stacked_on_url
1717
client.add_error_response('NotStacked')
1719
client.add_success_response('ok', 'branch token', 'repo token')
1721
client.add_error_response('TipChangeRejected', 'rejection message')
1723
client.add_success_response('ok')
1725
branch = self.make_remote_branch(transport, client)
1726
# Lock the branch, reset the record of remote calls.
1728
self.addCleanup(branch.unlock)
1731
# The 'TipChangeRejected' error response triggered by calling
1732
# set_last_revision_info causes a TipChangeRejected exception.
1733
err = self.assertRaises(
1734
errors.TipChangeRejected,
1735
branch.set_last_revision_info, 123, 'revid')
1736
self.assertEqual('rejection message', err.msg)
1739
class TestBranchGetSetConfig(RemoteBranchTestCase):
1741
def test_get_branch_conf(self):
1742
# in an empty branch we decode the response properly
1743
client = FakeClient()
1744
client.add_expected_call(
1745
'Branch.get_stacked_on_url', ('memory:///',),
1746
'error', ('NotStacked',),)
1747
client.add_success_response_with_body('# config file body', 'ok')
1748
transport = MemoryTransport()
1749
branch = self.make_remote_branch(transport, client)
1750
config = branch.get_config()
1751
config.has_explicit_nickname()
1753
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1754
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1757
def test_get_multi_line_branch_conf(self):
1758
# Make sure that multiple-line branch.conf files are supported
1760
# https://bugs.launchpad.net/bzr/+bug/354075
1761
client = FakeClient()
1762
client.add_expected_call(
1763
'Branch.get_stacked_on_url', ('memory:///',),
1764
'error', ('NotStacked',),)
1765
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1766
transport = MemoryTransport()
1767
branch = self.make_remote_branch(transport, client)
1768
config = branch.get_config()
1769
self.assertEqual(u'2', config.get_user_option('b'))
1771
def test_set_option(self):
1772
client = FakeClient()
1773
client.add_expected_call(
1774
'Branch.get_stacked_on_url', ('memory:///',),
1775
'error', ('NotStacked',),)
1776
client.add_expected_call(
1777
'Branch.lock_write', ('memory:///', '', ''),
1778
'success', ('ok', 'branch token', 'repo token'))
1779
client.add_expected_call(
1780
'Branch.set_config_option', ('memory:///', 'branch token',
1781
'repo token', 'foo', 'bar', ''),
1783
client.add_expected_call(
1784
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1786
transport = MemoryTransport()
1787
branch = self.make_remote_branch(transport, client)
1789
config = branch._get_config()
1790
config.set_option('foo', 'bar')
1792
self.assertFinished(client)
1794
def test_set_option_with_dict(self):
1795
client = FakeClient()
1796
client.add_expected_call(
1797
'Branch.get_stacked_on_url', ('memory:///',),
1798
'error', ('NotStacked',),)
1799
client.add_expected_call(
1800
'Branch.lock_write', ('memory:///', '', ''),
1801
'success', ('ok', 'branch token', 'repo token'))
1802
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1803
client.add_expected_call(
1804
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1805
'repo token', encoded_dict_value, 'foo', ''),
1807
client.add_expected_call(
1808
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1810
transport = MemoryTransport()
1811
branch = self.make_remote_branch(transport, client)
1813
config = branch._get_config()
1815
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1818
self.assertFinished(client)
1820
def test_backwards_compat_set_option(self):
1821
self.setup_smart_server_with_call_log()
1822
branch = self.make_branch('.')
1823
verb = 'Branch.set_config_option'
1824
self.disable_verb(verb)
1826
self.addCleanup(branch.unlock)
1827
self.reset_smart_call_log()
1828
branch._get_config().set_option('value', 'name')
1829
self.assertLength(10, self.hpss_calls)
1830
self.assertEqual('value', branch._get_config().get_option('name'))
1832
def test_backwards_compat_set_option_with_dict(self):
1833
self.setup_smart_server_with_call_log()
1834
branch = self.make_branch('.')
1835
verb = 'Branch.set_config_option_dict'
1836
self.disable_verb(verb)
1838
self.addCleanup(branch.unlock)
1839
self.reset_smart_call_log()
1840
config = branch._get_config()
1841
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
1842
config.set_option(value_dict, 'name')
1843
self.assertLength(10, self.hpss_calls)
1844
self.assertEqual(value_dict, branch._get_config().get_option('name'))
1847
class TestBranchLockWrite(RemoteBranchTestCase):
1849
def test_lock_write_unlockable(self):
1850
transport = MemoryTransport()
1851
client = FakeClient(transport.base)
1852
client.add_expected_call(
1853
'Branch.get_stacked_on_url', ('quack/',),
1854
'error', ('NotStacked',),)
1855
client.add_expected_call(
1856
'Branch.lock_write', ('quack/', '', ''),
1857
'error', ('UnlockableTransport',))
1858
transport.mkdir('quack')
1859
transport = transport.clone('quack')
1860
branch = self.make_remote_branch(transport, client)
1861
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1862
self.assertFinished(client)
1865
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1867
def test__get_config(self):
1868
client = FakeClient()
1869
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1870
transport = MemoryTransport()
1871
bzrdir = self.make_remote_bzrdir(transport, client)
1872
config = bzrdir.get_config()
1873
self.assertEqual('/', config.get_default_stack_on())
1875
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1878
def test_set_option_uses_vfs(self):
1879
self.setup_smart_server_with_call_log()
1880
bzrdir = self.make_bzrdir('.')
1881
self.reset_smart_call_log()
1882
config = bzrdir.get_config()
1883
config.set_default_stack_on('/')
1884
self.assertLength(3, self.hpss_calls)
1886
def test_backwards_compat_get_option(self):
1887
self.setup_smart_server_with_call_log()
1888
bzrdir = self.make_bzrdir('.')
1889
verb = 'BzrDir.get_config_file'
1890
self.disable_verb(verb)
1891
self.reset_smart_call_log()
1892
self.assertEqual(None,
1893
bzrdir._get_config().get_option('default_stack_on'))
1894
self.assertLength(3, self.hpss_calls)
1897
class TestTransportIsReadonly(tests.TestCase):
1899
def test_true(self):
1900
client = FakeClient()
1901
client.add_success_response('yes')
1902
transport = RemoteTransport('bzr://example.com/', medium=False,
1904
self.assertEqual(True, transport.is_readonly())
1906
[('call', 'Transport.is_readonly', ())],
1909
def test_false(self):
1910
client = FakeClient()
1911
client.add_success_response('no')
1912
transport = RemoteTransport('bzr://example.com/', medium=False,
1914
self.assertEqual(False, transport.is_readonly())
1916
[('call', 'Transport.is_readonly', ())],
1919
def test_error_from_old_server(self):
1920
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1922
Clients should treat it as a "no" response, because is_readonly is only
1923
advisory anyway (a transport could be read-write, but then the
1924
underlying filesystem could be readonly anyway).
1926
client = FakeClient()
1927
client.add_unknown_method_response('Transport.is_readonly')
1928
transport = RemoteTransport('bzr://example.com/', medium=False,
1930
self.assertEqual(False, transport.is_readonly())
1932
[('call', 'Transport.is_readonly', ())],
1936
class TestTransportMkdir(tests.TestCase):
1938
def test_permissiondenied(self):
1939
client = FakeClient()
1940
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1941
transport = RemoteTransport('bzr://example.com/', medium=False,
1943
exc = self.assertRaises(
1944
errors.PermissionDenied, transport.mkdir, 'client path')
1945
expected_error = errors.PermissionDenied('/client path', 'extra')
1946
self.assertEqual(expected_error, exc)
1949
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1951
def test_defaults_to_none(self):
1952
t = RemoteSSHTransport('bzr+ssh://example.com')
1953
self.assertIs(None, t._get_credentials()[0])
1955
def test_uses_authentication_config(self):
1956
conf = config.AuthenticationConfig()
1957
conf._get_config().update(
1958
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1961
t = RemoteSSHTransport('bzr+ssh://example.com')
1962
self.assertEqual('bar', t._get_credentials()[0])
1965
class TestRemoteRepository(TestRemote):
1966
"""Base for testing RemoteRepository protocol usage.
1968
These tests contain frozen requests and responses. We want any changes to
1969
what is sent or expected to be require a thoughtful update to these tests
1970
because they might break compatibility with different-versioned servers.
1973
def setup_fake_client_and_repository(self, transport_path):
1974
"""Create the fake client and repository for testing with.
1976
There's no real server here; we just have canned responses sent
1979
:param transport_path: Path below the root of the MemoryTransport
1980
where the repository will be created.
1982
transport = MemoryTransport()
1983
transport.mkdir(transport_path)
1984
client = FakeClient(transport.base)
1985
transport = transport.clone(transport_path)
1986
# we do not want bzrdir to make any remote calls
1987
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1989
repo = RemoteRepository(bzrdir, None, _client=client)
1993
def remoted_description(format):
1994
return 'Remote: ' + format.get_format_description()
1997
class TestBranchFormat(tests.TestCase):
1999
def test_get_format_description(self):
2000
remote_format = RemoteBranchFormat()
2001
real_format = branch.format_registry.get_default()
2002
remote_format._network_name = real_format.network_name()
2003
self.assertEqual(remoted_description(real_format),
2004
remote_format.get_format_description())
2007
class TestRepositoryFormat(TestRemoteRepository):
2009
def test_fast_delta(self):
2010
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2011
true_format = RemoteRepositoryFormat()
2012
true_format._network_name = true_name
2013
self.assertEqual(True, true_format.fast_deltas)
2014
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2015
false_format = RemoteRepositoryFormat()
2016
false_format._network_name = false_name
2017
self.assertEqual(False, false_format.fast_deltas)
2019
def test_get_format_description(self):
2020
remote_repo_format = RemoteRepositoryFormat()
2021
real_format = repository.format_registry.get_default()
2022
remote_repo_format._network_name = real_format.network_name()
2023
self.assertEqual(remoted_description(real_format),
2024
remote_repo_format.get_format_description())
2027
class TestRepositoryGatherStats(TestRemoteRepository):
2029
def test_revid_none(self):
2030
# ('ok',), body with revisions and size
2031
transport_path = 'quack'
2032
repo, client = self.setup_fake_client_and_repository(transport_path)
2033
client.add_success_response_with_body(
2034
'revisions: 2\nsize: 18\n', 'ok')
2035
result = repo.gather_stats(None)
2037
[('call_expecting_body', 'Repository.gather_stats',
2038
('quack/','','no'))],
2040
self.assertEqual({'revisions': 2, 'size': 18}, result)
2042
def test_revid_no_committers(self):
2043
# ('ok',), body without committers
2044
body = ('firstrev: 123456.300 3600\n'
2045
'latestrev: 654231.400 0\n'
2048
transport_path = 'quick'
2049
revid = u'\xc8'.encode('utf8')
2050
repo, client = self.setup_fake_client_and_repository(transport_path)
2051
client.add_success_response_with_body(body, 'ok')
2052
result = repo.gather_stats(revid)
2054
[('call_expecting_body', 'Repository.gather_stats',
2055
('quick/', revid, 'no'))],
2057
self.assertEqual({'revisions': 2, 'size': 18,
2058
'firstrev': (123456.300, 3600),
2059
'latestrev': (654231.400, 0),},
2062
def test_revid_with_committers(self):
2063
# ('ok',), body with committers
2064
body = ('committers: 128\n'
2065
'firstrev: 123456.300 3600\n'
2066
'latestrev: 654231.400 0\n'
2069
transport_path = 'buick'
2070
revid = u'\xc8'.encode('utf8')
2071
repo, client = self.setup_fake_client_and_repository(transport_path)
2072
client.add_success_response_with_body(body, 'ok')
2073
result = repo.gather_stats(revid, True)
2075
[('call_expecting_body', 'Repository.gather_stats',
2076
('buick/', revid, 'yes'))],
2078
self.assertEqual({'revisions': 2, 'size': 18,
2080
'firstrev': (123456.300, 3600),
2081
'latestrev': (654231.400, 0),},
2085
class TestRepositoryGetGraph(TestRemoteRepository):
2087
def test_get_graph(self):
2088
# get_graph returns a graph with a custom parents provider.
2089
transport_path = 'quack'
2090
repo, client = self.setup_fake_client_and_repository(transport_path)
2091
graph = repo.get_graph()
2092
self.assertNotEqual(graph._parents_provider, repo)
2095
class TestRepositoryGetParentMap(TestRemoteRepository):
2097
def test_get_parent_map_caching(self):
2098
# get_parent_map returns from cache until unlock()
2099
# setup a reponse with two revisions
2100
r1 = u'\u0e33'.encode('utf8')
2101
r2 = u'\u0dab'.encode('utf8')
2102
lines = [' '.join([r2, r1]), r1]
2103
encoded_body = bz2.compress('\n'.join(lines))
2105
transport_path = 'quack'
2106
repo, client = self.setup_fake_client_and_repository(transport_path)
2107
client.add_success_response_with_body(encoded_body, 'ok')
2108
client.add_success_response_with_body(encoded_body, 'ok')
2110
graph = repo.get_graph()
2111
parents = graph.get_parent_map([r2])
2112
self.assertEqual({r2: (r1,)}, parents)
2113
# locking and unlocking deeper should not reset
2116
parents = graph.get_parent_map([r1])
2117
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2119
[('call_with_body_bytes_expecting_body',
2120
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2124
# now we call again, and it should use the second response.
2126
graph = repo.get_graph()
2127
parents = graph.get_parent_map([r1])
2128
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2130
[('call_with_body_bytes_expecting_body',
2131
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2133
('call_with_body_bytes_expecting_body',
2134
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2140
def test_get_parent_map_reconnects_if_unknown_method(self):
2141
transport_path = 'quack'
2142
rev_id = 'revision-id'
2143
repo, client = self.setup_fake_client_and_repository(transport_path)
2144
client.add_unknown_method_response('Repository.get_parent_map')
2145
client.add_success_response_with_body(rev_id, 'ok')
2146
self.assertFalse(client._medium._is_remote_before((1, 2)))
2147
parents = repo.get_parent_map([rev_id])
2149
[('call_with_body_bytes_expecting_body',
2150
'Repository.get_parent_map',
2151
('quack/', 'include-missing:', rev_id), '\n\n0'),
2152
('disconnect medium',),
2153
('call_expecting_body', 'Repository.get_revision_graph',
2156
# The medium is now marked as being connected to an older server
2157
self.assertTrue(client._medium._is_remote_before((1, 2)))
2158
self.assertEqual({rev_id: ('null:',)}, parents)
2160
def test_get_parent_map_fallback_parentless_node(self):
2161
"""get_parent_map falls back to get_revision_graph on old servers. The
2162
results from get_revision_graph are tweaked to match the get_parent_map
2165
Specifically, a {key: ()} result from get_revision_graph means "no
2166
parents" for that key, which in get_parent_map results should be
2167
represented as {key: ('null:',)}.
2169
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2171
rev_id = 'revision-id'
2172
transport_path = 'quack'
2173
repo, client = self.setup_fake_client_and_repository(transport_path)
2174
client.add_success_response_with_body(rev_id, 'ok')
2175
client._medium._remember_remote_is_before((1, 2))
2176
parents = repo.get_parent_map([rev_id])
2178
[('call_expecting_body', 'Repository.get_revision_graph',
2181
self.assertEqual({rev_id: ('null:',)}, parents)
2183
def test_get_parent_map_unexpected_response(self):
2184
repo, client = self.setup_fake_client_and_repository('path')
2185
client.add_success_response('something unexpected!')
2187
errors.UnexpectedSmartServerResponse,
2188
repo.get_parent_map, ['a-revision-id'])
2190
def test_get_parent_map_negative_caches_missing_keys(self):
2191
self.setup_smart_server_with_call_log()
2192
repo = self.make_repository('foo')
2193
self.assertIsInstance(repo, RemoteRepository)
2195
self.addCleanup(repo.unlock)
2196
self.reset_smart_call_log()
2197
graph = repo.get_graph()
2198
self.assertEqual({},
2199
graph.get_parent_map(['some-missing', 'other-missing']))
2200
self.assertLength(1, self.hpss_calls)
2201
# No call if we repeat this
2202
self.reset_smart_call_log()
2203
graph = repo.get_graph()
2204
self.assertEqual({},
2205
graph.get_parent_map(['some-missing', 'other-missing']))
2206
self.assertLength(0, self.hpss_calls)
2207
# Asking for more unknown keys makes a request.
2208
self.reset_smart_call_log()
2209
graph = repo.get_graph()
2210
self.assertEqual({},
2211
graph.get_parent_map(['some-missing', 'other-missing',
2213
self.assertLength(1, self.hpss_calls)
2215
def disableExtraResults(self):
2216
self.overrideAttr(SmartServerRepositoryGetParentMap,
2217
'no_extra_results', True)
2219
def test_null_cached_missing_and_stop_key(self):
2220
self.setup_smart_server_with_call_log()
2221
# Make a branch with a single revision.
2222
builder = self.make_branch_builder('foo')
2223
builder.start_series()
2224
builder.build_snapshot('first', None, [
2225
('add', ('', 'root-id', 'directory', ''))])
2226
builder.finish_series()
2227
branch = builder.get_branch()
2228
repo = branch.repository
2229
self.assertIsInstance(repo, RemoteRepository)
2230
# Stop the server from sending extra results.
2231
self.disableExtraResults()
2233
self.addCleanup(repo.unlock)
2234
self.reset_smart_call_log()
2235
graph = repo.get_graph()
2236
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2237
# 'first' it will be a candidate for the stop_keys of subsequent
2238
# requests, and because 'null:' was queried but not returned it will be
2239
# cached as missing.
2240
self.assertEqual({'first': ('null:',)},
2241
graph.get_parent_map(['first', 'null:']))
2242
# Now query for another key. This request will pass along a recipe of
2243
# start and stop keys describing the already cached results, and this
2244
# recipe's revision count must be correct (or else it will trigger an
2245
# error from the server).
2246
self.assertEqual({}, graph.get_parent_map(['another-key']))
2247
# This assertion guards against disableExtraResults silently failing to
2248
# work, thus invalidating the test.
2249
self.assertLength(2, self.hpss_calls)
2251
def test_get_parent_map_gets_ghosts_from_result(self):
2252
# asking for a revision should negatively cache close ghosts in its
2254
self.setup_smart_server_with_call_log()
2255
tree = self.make_branch_and_memory_tree('foo')
2258
builder = treebuilder.TreeBuilder()
2259
builder.start_tree(tree)
2261
builder.finish_tree()
2262
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2263
rev_id = tree.commit('')
2267
self.addCleanup(tree.unlock)
2268
repo = tree.branch.repository
2269
self.assertIsInstance(repo, RemoteRepository)
2271
repo.get_parent_map([rev_id])
2272
self.reset_smart_call_log()
2273
# Now asking for rev_id's ghost parent should not make calls
2274
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2275
self.assertLength(0, self.hpss_calls)
2277
def test_exposes_get_cached_parent_map(self):
2278
"""RemoteRepository exposes get_cached_parent_map from
2281
r1 = u'\u0e33'.encode('utf8')
2282
r2 = u'\u0dab'.encode('utf8')
2283
lines = [' '.join([r2, r1]), r1]
2284
encoded_body = bz2.compress('\n'.join(lines))
2286
transport_path = 'quack'
2287
repo, client = self.setup_fake_client_and_repository(transport_path)
2288
client.add_success_response_with_body(encoded_body, 'ok')
2290
# get_cached_parent_map should *not* trigger an RPC
2291
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2292
self.assertEqual([], client._calls)
2293
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2294
self.assertEqual({r1: (NULL_REVISION,)},
2295
repo.get_cached_parent_map([r1]))
2297
[('call_with_body_bytes_expecting_body',
2298
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2304
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2306
def test_allows_new_revisions(self):
2307
"""get_parent_map's results can be updated by commit."""
2308
smart_server = test_server.SmartTCPServer_for_testing()
2309
self.start_server(smart_server)
2310
self.make_branch('branch')
2311
branch = Branch.open(smart_server.get_url() + '/branch')
2312
tree = branch.create_checkout('tree', lightweight=True)
2314
self.addCleanup(tree.unlock)
2315
graph = tree.branch.repository.get_graph()
2316
# This provides an opportunity for the missing rev-id to be cached.
2317
self.assertEqual({}, graph.get_parent_map(['rev1']))
2318
tree.commit('message', rev_id='rev1')
2319
graph = tree.branch.repository.get_graph()
2320
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2323
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2325
def test_null_revision(self):
2326
# a null revision has the predictable result {}, we should have no wire
2327
# traffic when calling it with this argument
2328
transport_path = 'empty'
2329
repo, client = self.setup_fake_client_and_repository(transport_path)
2330
client.add_success_response('notused')
2331
# actual RemoteRepository.get_revision_graph is gone, but there's an
2332
# equivalent private method for testing
2333
result = repo._get_revision_graph(NULL_REVISION)
2334
self.assertEqual([], client._calls)
2335
self.assertEqual({}, result)
2337
def test_none_revision(self):
2338
# with none we want the entire graph
2339
r1 = u'\u0e33'.encode('utf8')
2340
r2 = u'\u0dab'.encode('utf8')
2341
lines = [' '.join([r2, r1]), r1]
2342
encoded_body = '\n'.join(lines)
2344
transport_path = 'sinhala'
2345
repo, client = self.setup_fake_client_and_repository(transport_path)
2346
client.add_success_response_with_body(encoded_body, 'ok')
2347
# actual RemoteRepository.get_revision_graph is gone, but there's an
2348
# equivalent private method for testing
2349
result = repo._get_revision_graph(None)
2351
[('call_expecting_body', 'Repository.get_revision_graph',
2354
self.assertEqual({r1: (), r2: (r1, )}, result)
2356
def test_specific_revision(self):
2357
# with a specific revision we want the graph for that
2358
# with none we want the entire graph
2359
r11 = u'\u0e33'.encode('utf8')
2360
r12 = u'\xc9'.encode('utf8')
2361
r2 = u'\u0dab'.encode('utf8')
2362
lines = [' '.join([r2, r11, r12]), r11, r12]
2363
encoded_body = '\n'.join(lines)
2365
transport_path = 'sinhala'
2366
repo, client = self.setup_fake_client_and_repository(transport_path)
2367
client.add_success_response_with_body(encoded_body, 'ok')
2368
result = repo._get_revision_graph(r2)
2370
[('call_expecting_body', 'Repository.get_revision_graph',
2373
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2375
def test_no_such_revision(self):
2377
transport_path = 'sinhala'
2378
repo, client = self.setup_fake_client_and_repository(transport_path)
2379
client.add_error_response('nosuchrevision', revid)
2380
# also check that the right revision is reported in the error
2381
self.assertRaises(errors.NoSuchRevision,
2382
repo._get_revision_graph, revid)
2384
[('call_expecting_body', 'Repository.get_revision_graph',
2385
('sinhala/', revid))],
2388
def test_unexpected_error(self):
2390
transport_path = 'sinhala'
2391
repo, client = self.setup_fake_client_and_repository(transport_path)
2392
client.add_error_response('AnUnexpectedError')
2393
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2394
repo._get_revision_graph, revid)
2395
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2398
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2401
repo, client = self.setup_fake_client_and_repository('quack')
2402
client.add_expected_call(
2403
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2404
'success', ('ok', 'rev-five'))
2405
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2406
self.assertEqual((True, 'rev-five'), result)
2407
self.assertFinished(client)
2409
def test_history_incomplete(self):
2410
repo, client = self.setup_fake_client_and_repository('quack')
2411
client.add_expected_call(
2412
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2413
'success', ('history-incomplete', 10, 'rev-ten'))
2414
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2415
self.assertEqual((False, (10, 'rev-ten')), result)
2416
self.assertFinished(client)
2418
def test_history_incomplete_with_fallback(self):
2419
"""A 'history-incomplete' response causes the fallback repository to be
2420
queried too, if one is set.
2422
# Make a repo with a fallback repo, both using a FakeClient.
2423
format = remote.response_tuple_to_repo_format(
2424
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2425
repo, client = self.setup_fake_client_and_repository('quack')
2426
repo._format = format
2427
fallback_repo, ignored = self.setup_fake_client_and_repository(
2429
fallback_repo._client = client
2430
fallback_repo._format = format
2431
repo.add_fallback_repository(fallback_repo)
2432
# First the client should ask the primary repo
2433
client.add_expected_call(
2434
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2435
'success', ('history-incomplete', 2, 'rev-two'))
2436
# Then it should ask the fallback, using revno/revid from the
2437
# history-incomplete response as the known revno/revid.
2438
client.add_expected_call(
2439
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2440
'success', ('ok', 'rev-one'))
2441
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2442
self.assertEqual((True, 'rev-one'), result)
2443
self.assertFinished(client)
2445
def test_nosuchrevision(self):
2446
# 'nosuchrevision' is returned when the known-revid is not found in the
2447
# remote repo. The client translates that response to NoSuchRevision.
2448
repo, client = self.setup_fake_client_and_repository('quack')
2449
client.add_expected_call(
2450
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2451
'error', ('nosuchrevision', 'rev-foo'))
2453
errors.NoSuchRevision,
2454
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2455
self.assertFinished(client)
2457
def test_branch_fallback_locking(self):
2458
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2459
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2460
will be invoked, which will fail if the repo is unlocked.
2462
self.setup_smart_server_with_call_log()
2463
tree = self.make_branch_and_memory_tree('.')
2466
rev1 = tree.commit('First')
2467
rev2 = tree.commit('Second')
2469
branch = tree.branch
2470
self.assertFalse(branch.is_locked())
2471
self.reset_smart_call_log()
2472
verb = 'Repository.get_rev_id_for_revno'
2473
self.disable_verb(verb)
2474
self.assertEqual(rev1, branch.get_rev_id(1))
2475
self.assertLength(1, [call for call in self.hpss_calls if
2476
call.call.method == verb])
2479
class TestRepositoryIsShared(TestRemoteRepository):
2481
def test_is_shared(self):
2482
# ('yes', ) for Repository.is_shared -> 'True'.
2483
transport_path = 'quack'
2484
repo, client = self.setup_fake_client_and_repository(transport_path)
2485
client.add_success_response('yes')
2486
result = repo.is_shared()
2488
[('call', 'Repository.is_shared', ('quack/',))],
2490
self.assertEqual(True, result)
2492
def test_is_not_shared(self):
2493
# ('no', ) for Repository.is_shared -> 'False'.
2494
transport_path = 'qwack'
2495
repo, client = self.setup_fake_client_and_repository(transport_path)
2496
client.add_success_response('no')
2497
result = repo.is_shared()
2499
[('call', 'Repository.is_shared', ('qwack/',))],
2501
self.assertEqual(False, result)
2504
class TestRepositoryLockWrite(TestRemoteRepository):
2506
def test_lock_write(self):
2507
transport_path = 'quack'
2508
repo, client = self.setup_fake_client_and_repository(transport_path)
2509
client.add_success_response('ok', 'a token')
2510
token = repo.lock_write().repository_token
2512
[('call', 'Repository.lock_write', ('quack/', ''))],
2514
self.assertEqual('a token', token)
2516
def test_lock_write_already_locked(self):
2517
transport_path = 'quack'
2518
repo, client = self.setup_fake_client_and_repository(transport_path)
2519
client.add_error_response('LockContention')
2520
self.assertRaises(errors.LockContention, repo.lock_write)
2522
[('call', 'Repository.lock_write', ('quack/', ''))],
2525
def test_lock_write_unlockable(self):
2526
transport_path = 'quack'
2527
repo, client = self.setup_fake_client_and_repository(transport_path)
2528
client.add_error_response('UnlockableTransport')
2529
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2531
[('call', 'Repository.lock_write', ('quack/', ''))],
2535
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2537
def test_backwards_compat(self):
2538
self.setup_smart_server_with_call_log()
2539
repo = self.make_repository('.')
2540
self.reset_smart_call_log()
2541
verb = 'Repository.set_make_working_trees'
2542
self.disable_verb(verb)
2543
repo.set_make_working_trees(True)
2544
call_count = len([call for call in self.hpss_calls if
2545
call.call.method == verb])
2546
self.assertEqual(1, call_count)
2548
def test_current(self):
2549
transport_path = 'quack'
2550
repo, client = self.setup_fake_client_and_repository(transport_path)
2551
client.add_expected_call(
2552
'Repository.set_make_working_trees', ('quack/', 'True'),
2554
client.add_expected_call(
2555
'Repository.set_make_working_trees', ('quack/', 'False'),
2557
repo.set_make_working_trees(True)
2558
repo.set_make_working_trees(False)
2561
class TestRepositoryUnlock(TestRemoteRepository):
2563
def test_unlock(self):
2564
transport_path = 'quack'
2565
repo, client = self.setup_fake_client_and_repository(transport_path)
2566
client.add_success_response('ok', 'a token')
2567
client.add_success_response('ok')
2571
[('call', 'Repository.lock_write', ('quack/', '')),
2572
('call', 'Repository.unlock', ('quack/', 'a token'))],
2575
def test_unlock_wrong_token(self):
2576
# If somehow the token is wrong, unlock will raise TokenMismatch.
2577
transport_path = 'quack'
2578
repo, client = self.setup_fake_client_and_repository(transport_path)
2579
client.add_success_response('ok', 'a token')
2580
client.add_error_response('TokenMismatch')
2582
self.assertRaises(errors.TokenMismatch, repo.unlock)
2585
class TestRepositoryHasRevision(TestRemoteRepository):
2587
def test_none(self):
2588
# repo.has_revision(None) should not cause any traffic.
2589
transport_path = 'quack'
2590
repo, client = self.setup_fake_client_and_repository(transport_path)
2592
# The null revision is always there, so has_revision(None) == True.
2593
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2595
# The remote repo shouldn't be accessed.
2596
self.assertEqual([], client._calls)
2599
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2600
"""Base class for Repository.insert_stream and .insert_stream_1.19
2604
def checkInsertEmptyStream(self, repo, client):
2605
"""Insert an empty stream, checking the result.
2607
This checks that there are no resume_tokens or missing_keys, and that
2608
the client is finished.
2610
sink = repo._get_sink()
2611
fmt = repository.format_registry.get_default()
2612
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2613
self.assertEqual([], resume_tokens)
2614
self.assertEqual(set(), missing_keys)
2615
self.assertFinished(client)
2618
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2619
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2622
This test case is very similar to TestRepositoryInsertStream_1_19.
2626
TestRemoteRepository.setUp(self)
2627
self.disable_verb('Repository.insert_stream_1.19')
2629
def test_unlocked_repo(self):
2630
transport_path = 'quack'
2631
repo, client = self.setup_fake_client_and_repository(transport_path)
2632
client.add_expected_call(
2633
'Repository.insert_stream_1.19', ('quack/', ''),
2634
'unknown', ('Repository.insert_stream_1.19',))
2635
client.add_expected_call(
2636
'Repository.insert_stream', ('quack/', ''),
2638
client.add_expected_call(
2639
'Repository.insert_stream', ('quack/', ''),
2641
self.checkInsertEmptyStream(repo, client)
2643
def test_locked_repo_with_no_lock_token(self):
2644
transport_path = 'quack'
2645
repo, client = self.setup_fake_client_and_repository(transport_path)
2646
client.add_expected_call(
2647
'Repository.lock_write', ('quack/', ''),
2648
'success', ('ok', ''))
2649
client.add_expected_call(
2650
'Repository.insert_stream_1.19', ('quack/', ''),
2651
'unknown', ('Repository.insert_stream_1.19',))
2652
client.add_expected_call(
2653
'Repository.insert_stream', ('quack/', ''),
2655
client.add_expected_call(
2656
'Repository.insert_stream', ('quack/', ''),
2659
self.checkInsertEmptyStream(repo, client)
2661
def test_locked_repo_with_lock_token(self):
2662
transport_path = 'quack'
2663
repo, client = self.setup_fake_client_and_repository(transport_path)
2664
client.add_expected_call(
2665
'Repository.lock_write', ('quack/', ''),
2666
'success', ('ok', 'a token'))
2667
client.add_expected_call(
2668
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2669
'unknown', ('Repository.insert_stream_1.19',))
2670
client.add_expected_call(
2671
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2673
client.add_expected_call(
2674
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2677
self.checkInsertEmptyStream(repo, client)
2679
def test_stream_with_inventory_deltas(self):
2680
"""'inventory-deltas' substreams cannot be sent to the
2681
Repository.insert_stream verb, because not all servers that implement
2682
that verb will accept them. So when one is encountered the RemoteSink
2683
immediately stops using that verb and falls back to VFS insert_stream.
2685
transport_path = 'quack'
2686
repo, client = self.setup_fake_client_and_repository(transport_path)
2687
client.add_expected_call(
2688
'Repository.insert_stream_1.19', ('quack/', ''),
2689
'unknown', ('Repository.insert_stream_1.19',))
2690
client.add_expected_call(
2691
'Repository.insert_stream', ('quack/', ''),
2693
client.add_expected_call(
2694
'Repository.insert_stream', ('quack/', ''),
2696
# Create a fake real repository for insert_stream to fall back on, so
2697
# that we can directly see the records the RemoteSink passes to the
2702
def insert_stream(self, stream, src_format, resume_tokens):
2703
for substream_kind, substream in stream:
2704
self.records.append(
2705
(substream_kind, [record.key for record in substream]))
2706
return ['fake tokens'], ['fake missing keys']
2707
fake_real_sink = FakeRealSink()
2708
class FakeRealRepository:
2709
def _get_sink(self):
2710
return fake_real_sink
2711
def is_in_write_group(self):
2713
def refresh_data(self):
2715
repo._real_repository = FakeRealRepository()
2716
sink = repo._get_sink()
2717
fmt = repository.format_registry.get_default()
2718
stream = self.make_stream_with_inv_deltas(fmt)
2719
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2720
# Every record from the first inventory delta should have been sent to
2722
expected_records = [
2723
('inventory-deltas', [('rev2',), ('rev3',)]),
2724
('texts', [('some-rev', 'some-file')])]
2725
self.assertEqual(expected_records, fake_real_sink.records)
2726
# The return values from the real sink's insert_stream are propagated
2727
# back to the original caller.
2728
self.assertEqual(['fake tokens'], resume_tokens)
2729
self.assertEqual(['fake missing keys'], missing_keys)
2730
self.assertFinished(client)
2732
def make_stream_with_inv_deltas(self, fmt):
2733
"""Make a simple stream with an inventory delta followed by more
2734
records and more substreams to test that all records and substreams
2735
from that point on are used.
2737
This sends, in order:
2738
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2740
* texts substream: (some-rev, some-file)
2742
# Define a stream using generators so that it isn't rewindable.
2743
inv = inventory.Inventory(revision_id='rev1')
2744
inv.root.revision = 'rev1'
2745
def stream_with_inv_delta():
2746
yield ('inventories', inventories_substream())
2747
yield ('inventory-deltas', inventory_delta_substream())
2749
versionedfile.FulltextContentFactory(
2750
('some-rev', 'some-file'), (), None, 'content')])
2751
def inventories_substream():
2752
# An empty inventory fulltext. This will be streamed normally.
2753
text = fmt._serializer.write_inventory_to_string(inv)
2754
yield versionedfile.FulltextContentFactory(
2755
('rev1',), (), None, text)
2756
def inventory_delta_substream():
2757
# An inventory delta. This can't be streamed via this verb, so it
2758
# will trigger a fallback to VFS insert_stream.
2759
entry = inv.make_entry(
2760
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2761
entry.revision = 'ghost'
2762
delta = [(None, 'newdir', 'newdir-id', entry)]
2763
serializer = inventory_delta.InventoryDeltaSerializer(
2764
versioned_root=True, tree_references=False)
2765
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2766
yield versionedfile.ChunkedContentFactory(
2767
('rev2',), (('rev1',)), None, lines)
2769
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2770
yield versionedfile.ChunkedContentFactory(
2771
('rev3',), (('rev1',)), None, lines)
2772
return stream_with_inv_delta()
2775
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2777
def test_unlocked_repo(self):
2778
transport_path = 'quack'
2779
repo, client = self.setup_fake_client_and_repository(transport_path)
2780
client.add_expected_call(
2781
'Repository.insert_stream_1.19', ('quack/', ''),
2783
client.add_expected_call(
2784
'Repository.insert_stream_1.19', ('quack/', ''),
2786
self.checkInsertEmptyStream(repo, client)
2788
def test_locked_repo_with_no_lock_token(self):
2789
transport_path = 'quack'
2790
repo, client = self.setup_fake_client_and_repository(transport_path)
2791
client.add_expected_call(
2792
'Repository.lock_write', ('quack/', ''),
2793
'success', ('ok', ''))
2794
client.add_expected_call(
2795
'Repository.insert_stream_1.19', ('quack/', ''),
2797
client.add_expected_call(
2798
'Repository.insert_stream_1.19', ('quack/', ''),
2801
self.checkInsertEmptyStream(repo, client)
2803
def test_locked_repo_with_lock_token(self):
2804
transport_path = 'quack'
2805
repo, client = self.setup_fake_client_and_repository(transport_path)
2806
client.add_expected_call(
2807
'Repository.lock_write', ('quack/', ''),
2808
'success', ('ok', 'a token'))
2809
client.add_expected_call(
2810
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2812
client.add_expected_call(
2813
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2816
self.checkInsertEmptyStream(repo, client)
2819
class TestRepositoryTarball(TestRemoteRepository):
2821
# This is a canned tarball reponse we can validate against
2823
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2824
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2825
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2826
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2827
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2828
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2829
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2830
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2831
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2832
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2833
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2834
'nWQ7QH/F3JFOFCQ0aSPfA='
2837
def test_repository_tarball(self):
2838
# Test that Repository.tarball generates the right operations
2839
transport_path = 'repo'
2840
expected_calls = [('call_expecting_body', 'Repository.tarball',
2841
('repo/', 'bz2',),),
2843
repo, client = self.setup_fake_client_and_repository(transport_path)
2844
client.add_success_response_with_body(self.tarball_content, 'ok')
2845
# Now actually ask for the tarball
2846
tarball_file = repo._get_tarball('bz2')
2848
self.assertEqual(expected_calls, client._calls)
2849
self.assertEqual(self.tarball_content, tarball_file.read())
2851
tarball_file.close()
2854
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2855
"""RemoteRepository.copy_content_into optimizations"""
2857
def test_copy_content_remote_to_local(self):
2858
self.transport_server = test_server.SmartTCPServer_for_testing
2859
src_repo = self.make_repository('repo1')
2860
src_repo = repository.Repository.open(self.get_url('repo1'))
2861
# At the moment the tarball-based copy_content_into can't write back
2862
# into a smart server. It would be good if it could upload the
2863
# tarball; once that works we'd have to create repositories of
2864
# different formats. -- mbp 20070410
2865
dest_url = self.get_vfs_only_url('repo2')
2866
dest_bzrdir = BzrDir.create(dest_url)
2867
dest_repo = dest_bzrdir.create_repository()
2868
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2869
self.assertTrue(isinstance(src_repo, RemoteRepository))
2870
src_repo.copy_content_into(dest_repo)
2873
class _StubRealPackRepository(object):
2875
def __init__(self, calls):
2877
self._pack_collection = _StubPackCollection(calls)
2879
def is_in_write_group(self):
2882
def refresh_data(self):
2883
self.calls.append(('pack collection reload_pack_names',))
2886
class _StubPackCollection(object):
2888
def __init__(self, calls):
2892
self.calls.append(('pack collection autopack',))
2895
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2896
"""Tests for RemoteRepository.autopack implementation."""
2899
"""When the server returns 'ok' and there's no _real_repository, then
2900
nothing else happens: the autopack method is done.
2902
transport_path = 'quack'
2903
repo, client = self.setup_fake_client_and_repository(transport_path)
2904
client.add_expected_call(
2905
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2907
self.assertFinished(client)
2909
def test_ok_with_real_repo(self):
2910
"""When the server returns 'ok' and there is a _real_repository, then
2911
the _real_repository's reload_pack_name's method will be called.
2913
transport_path = 'quack'
2914
repo, client = self.setup_fake_client_and_repository(transport_path)
2915
client.add_expected_call(
2916
'PackRepository.autopack', ('quack/',),
2918
repo._real_repository = _StubRealPackRepository(client._calls)
2921
[('call', 'PackRepository.autopack', ('quack/',)),
2922
('pack collection reload_pack_names',)],
2925
def test_backwards_compatibility(self):
2926
"""If the server does not recognise the PackRepository.autopack verb,
2927
fallback to the real_repository's implementation.
2929
transport_path = 'quack'
2930
repo, client = self.setup_fake_client_and_repository(transport_path)
2931
client.add_unknown_method_response('PackRepository.autopack')
2932
def stub_ensure_real():
2933
client._calls.append(('_ensure_real',))
2934
repo._real_repository = _StubRealPackRepository(client._calls)
2935
repo._ensure_real = stub_ensure_real
2938
[('call', 'PackRepository.autopack', ('quack/',)),
2940
('pack collection autopack',)],
2943
def test_oom_error_reporting(self):
2944
"""An out-of-memory condition on the server is reported clearly"""
2945
transport_path = 'quack'
2946
repo, client = self.setup_fake_client_and_repository(transport_path)
2947
client.add_expected_call(
2948
'PackRepository.autopack', ('quack/',),
2949
'error', ('MemoryError',))
2950
err = self.assertRaises(errors.BzrError, repo.autopack)
2951
self.assertContainsRe(str(err), "^remote server out of mem")
2954
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2955
"""Base class for unit tests for bzrlib.remote._translate_error."""
2957
def translateTuple(self, error_tuple, **context):
2958
"""Call _translate_error with an ErrorFromSmartServer built from the
2961
:param error_tuple: A tuple of a smart server response, as would be
2962
passed to an ErrorFromSmartServer.
2963
:kwargs context: context items to call _translate_error with.
2965
:returns: The error raised by _translate_error.
2967
# Raise the ErrorFromSmartServer before passing it as an argument,
2968
# because _translate_error may need to re-raise it with a bare 'raise'
2970
server_error = errors.ErrorFromSmartServer(error_tuple)
2971
translated_error = self.translateErrorFromSmartServer(
2972
server_error, **context)
2973
return translated_error
2975
def translateErrorFromSmartServer(self, error_object, **context):
2976
"""Like translateTuple, but takes an already constructed
2977
ErrorFromSmartServer rather than a tuple.
2981
except errors.ErrorFromSmartServer, server_error:
2982
translated_error = self.assertRaises(
2983
errors.BzrError, remote._translate_error, server_error,
2985
return translated_error
2988
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2989
"""Unit tests for bzrlib.remote._translate_error.
2991
Given an ErrorFromSmartServer (which has an error tuple from a smart
2992
server) and some context, _translate_error raises more specific errors from
2995
This test case covers the cases where _translate_error succeeds in
2996
translating an ErrorFromSmartServer to something better. See
2997
TestErrorTranslationRobustness for other cases.
3000
def test_NoSuchRevision(self):
3001
branch = self.make_branch('')
3003
translated_error = self.translateTuple(
3004
('NoSuchRevision', revid), branch=branch)
3005
expected_error = errors.NoSuchRevision(branch, revid)
3006
self.assertEqual(expected_error, translated_error)
3008
def test_nosuchrevision(self):
3009
repository = self.make_repository('')
3011
translated_error = self.translateTuple(
3012
('nosuchrevision', revid), repository=repository)
3013
expected_error = errors.NoSuchRevision(repository, revid)
3014
self.assertEqual(expected_error, translated_error)
3016
def test_nobranch(self):
3017
bzrdir = self.make_bzrdir('')
3018
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3019
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3020
self.assertEqual(expected_error, translated_error)
3022
def test_nobranch_one_arg(self):
3023
bzrdir = self.make_bzrdir('')
3024
translated_error = self.translateTuple(
3025
('nobranch', 'extra detail'), bzrdir=bzrdir)
3026
expected_error = errors.NotBranchError(
3027
path=bzrdir.root_transport.base,
3028
detail='extra detail')
3029
self.assertEqual(expected_error, translated_error)
3031
def test_norepository(self):
3032
bzrdir = self.make_bzrdir('')
3033
translated_error = self.translateTuple(('norepository',),
3035
expected_error = errors.NoRepositoryPresent(bzrdir)
3036
self.assertEqual(expected_error, translated_error)
3038
def test_LockContention(self):
3039
translated_error = self.translateTuple(('LockContention',))
3040
expected_error = errors.LockContention('(remote lock)')
3041
self.assertEqual(expected_error, translated_error)
3043
def test_UnlockableTransport(self):
3044
bzrdir = self.make_bzrdir('')
3045
translated_error = self.translateTuple(
3046
('UnlockableTransport',), bzrdir=bzrdir)
3047
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3048
self.assertEqual(expected_error, translated_error)
3050
def test_LockFailed(self):
3051
lock = 'str() of a server lock'
3052
why = 'str() of why'
3053
translated_error = self.translateTuple(('LockFailed', lock, why))
3054
expected_error = errors.LockFailed(lock, why)
3055
self.assertEqual(expected_error, translated_error)
3057
def test_TokenMismatch(self):
3058
token = 'a lock token'
3059
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3060
expected_error = errors.TokenMismatch(token, '(remote token)')
3061
self.assertEqual(expected_error, translated_error)
3063
def test_Diverged(self):
3064
branch = self.make_branch('a')
3065
other_branch = self.make_branch('b')
3066
translated_error = self.translateTuple(
3067
('Diverged',), branch=branch, other_branch=other_branch)
3068
expected_error = errors.DivergedBranches(branch, other_branch)
3069
self.assertEqual(expected_error, translated_error)
3071
def test_NotStacked(self):
3072
branch = self.make_branch('')
3073
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3074
expected_error = errors.NotStacked(branch)
3075
self.assertEqual(expected_error, translated_error)
3077
def test_ReadError_no_args(self):
3079
translated_error = self.translateTuple(('ReadError',), path=path)
3080
expected_error = errors.ReadError(path)
3081
self.assertEqual(expected_error, translated_error)
3083
def test_ReadError(self):
3085
translated_error = self.translateTuple(('ReadError', path))
3086
expected_error = errors.ReadError(path)
3087
self.assertEqual(expected_error, translated_error)
3089
def test_IncompatibleRepositories(self):
3090
translated_error = self.translateTuple(('IncompatibleRepositories',
3091
"repo1", "repo2", "details here"))
3092
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3094
self.assertEqual(expected_error, translated_error)
3096
def test_PermissionDenied_no_args(self):
3098
translated_error = self.translateTuple(('PermissionDenied',),
3100
expected_error = errors.PermissionDenied(path)
3101
self.assertEqual(expected_error, translated_error)
3103
def test_PermissionDenied_one_arg(self):
3105
translated_error = self.translateTuple(('PermissionDenied', path))
3106
expected_error = errors.PermissionDenied(path)
3107
self.assertEqual(expected_error, translated_error)
3109
def test_PermissionDenied_one_arg_and_context(self):
3110
"""Given a choice between a path from the local context and a path on
3111
the wire, _translate_error prefers the path from the local context.
3113
local_path = 'local path'
3114
remote_path = 'remote path'
3115
translated_error = self.translateTuple(
3116
('PermissionDenied', remote_path), path=local_path)
3117
expected_error = errors.PermissionDenied(local_path)
3118
self.assertEqual(expected_error, translated_error)
3120
def test_PermissionDenied_two_args(self):
3122
extra = 'a string with extra info'
3123
translated_error = self.translateTuple(
3124
('PermissionDenied', path, extra))
3125
expected_error = errors.PermissionDenied(path, extra)
3126
self.assertEqual(expected_error, translated_error)
3128
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3130
def test_NoSuchFile_context_path(self):
3131
local_path = "local path"
3132
translated_error = self.translateTuple(('ReadError', "remote path"),
3134
expected_error = errors.ReadError(local_path)
3135
self.assertEqual(expected_error, translated_error)
3137
def test_NoSuchFile_without_context(self):
3138
remote_path = "remote path"
3139
translated_error = self.translateTuple(('ReadError', remote_path))
3140
expected_error = errors.ReadError(remote_path)
3141
self.assertEqual(expected_error, translated_error)
3143
def test_ReadOnlyError(self):
3144
translated_error = self.translateTuple(('ReadOnlyError',))
3145
expected_error = errors.TransportNotPossible("readonly transport")
3146
self.assertEqual(expected_error, translated_error)
3148
def test_MemoryError(self):
3149
translated_error = self.translateTuple(('MemoryError',))
3150
self.assertStartsWith(str(translated_error),
3151
"remote server out of memory")
3153
def test_generic_IndexError_no_classname(self):
3154
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3155
translated_error = self.translateErrorFromSmartServer(err)
3156
expected_error = errors.UnknownErrorFromSmartServer(err)
3157
self.assertEqual(expected_error, translated_error)
3159
# GZ 2011-03-02: TODO test generic non-ascii error string
3161
def test_generic_KeyError(self):
3162
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3163
translated_error = self.translateErrorFromSmartServer(err)
3164
expected_error = errors.UnknownErrorFromSmartServer(err)
3165
self.assertEqual(expected_error, translated_error)
3168
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3169
"""Unit tests for bzrlib.remote._translate_error's robustness.
3171
TestErrorTranslationSuccess is for cases where _translate_error can
3172
translate successfully. This class about how _translate_err behaves when
3173
it fails to translate: it re-raises the original error.
3176
def test_unrecognised_server_error(self):
3177
"""If the error code from the server is not recognised, the original
3178
ErrorFromSmartServer is propagated unmodified.
3180
error_tuple = ('An unknown error tuple',)
3181
server_error = errors.ErrorFromSmartServer(error_tuple)
3182
translated_error = self.translateErrorFromSmartServer(server_error)
3183
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3184
self.assertEqual(expected_error, translated_error)
3186
def test_context_missing_a_key(self):
3187
"""In case of a bug in the client, or perhaps an unexpected response
3188
from a server, _translate_error returns the original error tuple from
3189
the server and mutters a warning.
3191
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3192
# in the context dict. So let's give it an empty context dict instead
3193
# to exercise its error recovery.
3195
error_tuple = ('NoSuchRevision', 'revid')
3196
server_error = errors.ErrorFromSmartServer(error_tuple)
3197
translated_error = self.translateErrorFromSmartServer(server_error)
3198
self.assertEqual(server_error, translated_error)
3199
# In addition to re-raising ErrorFromSmartServer, some debug info has
3200
# been muttered to the log file for developer to look at.
3201
self.assertContainsRe(
3203
"Missing key 'branch' in context")
3205
def test_path_missing(self):
3206
"""Some translations (PermissionDenied, ReadError) can determine the
3207
'path' variable from either the wire or the local context. If neither
3208
has it, then an error is raised.
3210
error_tuple = ('ReadError',)
3211
server_error = errors.ErrorFromSmartServer(error_tuple)
3212
translated_error = self.translateErrorFromSmartServer(server_error)
3213
self.assertEqual(server_error, translated_error)
3214
# In addition to re-raising ErrorFromSmartServer, some debug info has
3215
# been muttered to the log file for developer to look at.
3216
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3219
class TestStacking(tests.TestCaseWithTransport):
3220
"""Tests for operations on stacked remote repositories.
3222
The underlying format type must support stacking.
3225
def test_access_stacked_remote(self):
3226
# based on <http://launchpad.net/bugs/261315>
3227
# make a branch stacked on another repository containing an empty
3228
# revision, then open it over hpss - we should be able to see that
3230
base_transport = self.get_transport()
3231
base_builder = self.make_branch_builder('base', format='1.9')
3232
base_builder.start_series()
3233
base_revid = base_builder.build_snapshot('rev-id', None,
3234
[('add', ('', None, 'directory', None))],
3236
base_builder.finish_series()
3237
stacked_branch = self.make_branch('stacked', format='1.9')
3238
stacked_branch.set_stacked_on_url('../base')
3239
# start a server looking at this
3240
smart_server = test_server.SmartTCPServer_for_testing()
3241
self.start_server(smart_server)
3242
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3243
# can get its branch and repository
3244
remote_branch = remote_bzrdir.open_branch()
3245
remote_repo = remote_branch.repository
3246
remote_repo.lock_read()
3248
# it should have an appropriate fallback repository, which should also
3249
# be a RemoteRepository
3250
self.assertLength(1, remote_repo._fallback_repositories)
3251
self.assertIsInstance(remote_repo._fallback_repositories[0],
3253
# and it has the revision committed to the underlying repository;
3254
# these have varying implementations so we try several of them
3255
self.assertTrue(remote_repo.has_revisions([base_revid]))
3256
self.assertTrue(remote_repo.has_revision(base_revid))
3257
self.assertEqual(remote_repo.get_revision(base_revid).message,
3260
remote_repo.unlock()
3262
def prepare_stacked_remote_branch(self):
3263
"""Get stacked_upon and stacked branches with content in each."""
3264
self.setup_smart_server_with_call_log()
3265
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3266
tree1.commit('rev1', rev_id='rev1')
3267
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3268
).open_workingtree()
3269
local_tree = tree2.branch.create_checkout('local')
3270
local_tree.commit('local changes make me feel good.')
3271
branch2 = Branch.open(self.get_url('tree2'))
3273
self.addCleanup(branch2.unlock)
3274
return tree1.branch, branch2
3276
def test_stacked_get_parent_map(self):
3277
# the public implementation of get_parent_map obeys stacking
3278
_, branch = self.prepare_stacked_remote_branch()
3279
repo = branch.repository
3280
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3282
def test_unstacked_get_parent_map(self):
3283
# _unstacked_provider.get_parent_map ignores stacking
3284
_, branch = self.prepare_stacked_remote_branch()
3285
provider = branch.repository._unstacked_provider
3286
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3288
def fetch_stream_to_rev_order(self, stream):
3290
for kind, substream in stream:
3291
if not kind == 'revisions':
3294
for content in substream:
3295
result.append(content.key[-1])
3298
def get_ordered_revs(self, format, order, branch_factory=None):
3299
"""Get a list of the revisions in a stream to format format.
3301
:param format: The format of the target.
3302
:param order: the order that target should have requested.
3303
:param branch_factory: A callable to create a trunk and stacked branch
3304
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3305
:result: The revision ids in the stream, in the order seen,
3306
the topological order of revisions in the source.
3308
unordered_format = bzrdir.format_registry.get(format)()
3309
target_repository_format = unordered_format.repository_format
3311
self.assertEqual(order, target_repository_format._fetch_order)
3312
if branch_factory is None:
3313
branch_factory = self.prepare_stacked_remote_branch
3314
_, stacked = branch_factory()
3315
source = stacked.repository._get_source(target_repository_format)
3316
tip = stacked.last_revision()
3317
stacked.repository._ensure_real()
3318
graph = stacked.repository.get_graph()
3319
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3320
if r != NULL_REVISION]
3322
search = _mod_graph.PendingAncestryResult([tip], stacked.repository)
3323
self.reset_smart_call_log()
3324
stream = source.get_stream(search)
3325
# We trust that if a revision is in the stream the rest of the new
3326
# content for it is too, as per our main fetch tests; here we are
3327
# checking that the revisions are actually included at all, and their
3329
return self.fetch_stream_to_rev_order(stream), revs
3331
def test_stacked_get_stream_unordered(self):
3332
# Repository._get_source.get_stream() from a stacked repository with
3333
# unordered yields the full data from both stacked and stacked upon
3335
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3336
self.assertEqual(set(expected_revs), set(rev_ord))
3337
# Getting unordered results should have made a streaming data request
3338
# from the server, then one from the backing branch.
3339
self.assertLength(2, self.hpss_calls)
3341
def test_stacked_on_stacked_get_stream_unordered(self):
3342
# Repository._get_source.get_stream() from a stacked repository which
3343
# is itself stacked yields the full data from all three sources.
3344
def make_stacked_stacked():
3345
_, stacked = self.prepare_stacked_remote_branch()
3346
tree = stacked.bzrdir.sprout('tree3', stacked=True
3347
).open_workingtree()
3348
local_tree = tree.branch.create_checkout('local-tree3')
3349
local_tree.commit('more local changes are better')
3350
branch = Branch.open(self.get_url('tree3'))
3352
self.addCleanup(branch.unlock)
3354
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3355
branch_factory=make_stacked_stacked)
3356
self.assertEqual(set(expected_revs), set(rev_ord))
3357
# Getting unordered results should have made a streaming data request
3358
# from the server, and one from each backing repo
3359
self.assertLength(3, self.hpss_calls)
3361
def test_stacked_get_stream_topological(self):
3362
# Repository._get_source.get_stream() from a stacked repository with
3363
# topological sorting yields the full data from both stacked and
3364
# stacked upon sources in topological order.
3365
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3366
self.assertEqual(expected_revs, rev_ord)
3367
# Getting topological sort requires VFS calls still - one of which is
3368
# pushing up from the bound branch.
3369
self.assertLength(14, self.hpss_calls)
3371
def test_stacked_get_stream_groupcompress(self):
3372
# Repository._get_source.get_stream() from a stacked repository with
3373
# groupcompress sorting yields the full data from both stacked and
3374
# stacked upon sources in groupcompress order.
3375
raise tests.TestSkipped('No groupcompress ordered format available')
3376
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3377
self.assertEqual(expected_revs, reversed(rev_ord))
3378
# Getting unordered results should have made a streaming data request
3379
# from the backing branch, and one from the stacked on branch.
3380
self.assertLength(2, self.hpss_calls)
3382
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3383
# When pulling some fixed amount of content that is more than the
3384
# source has (because some is coming from a fallback branch, no error
3385
# should be received. This was reported as bug 360791.
3386
# Need three branches: a trunk, a stacked branch, and a preexisting
3387
# branch pulling content from stacked and trunk.
3388
self.setup_smart_server_with_call_log()
3389
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3390
r1 = trunk.commit('start')
3391
stacked_branch = trunk.branch.create_clone_on_transport(
3392
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3393
local = self.make_branch('local', format='1.9-rich-root')
3394
local.repository.fetch(stacked_branch.repository,
3395
stacked_branch.last_revision())
3398
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3401
super(TestRemoteBranchEffort, self).setUp()
3402
# Create a smart server that publishes whatever the backing VFS server
3404
self.smart_server = test_server.SmartTCPServer_for_testing()
3405
self.start_server(self.smart_server, self.get_server())
3406
# Log all HPSS calls into self.hpss_calls.
3407
_SmartClient.hooks.install_named_hook(
3408
'call', self.capture_hpss_call, None)
3409
self.hpss_calls = []
3411
def capture_hpss_call(self, params):
3412
self.hpss_calls.append(params.method)
3414
def test_copy_content_into_avoids_revision_history(self):
3415
local = self.make_branch('local')
3416
builder = self.make_branch_builder('remote')
3417
builder.build_commit(message="Commit.")
3418
remote_branch_url = self.smart_server.get_url() + 'remote'
3419
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3420
local.repository.fetch(remote_branch.repository)
3421
self.hpss_calls = []
3422
remote_branch.copy_content_into(local)
3423
self.assertFalse('Branch.revision_history' in self.hpss_calls)
3425
def test_fetch_everything_needs_just_one_call(self):
3426
local = self.make_branch('local')
3427
builder = self.make_branch_builder('remote')
3428
builder.build_commit(message="Commit.")
3429
remote_branch_url = self.smart_server.get_url() + 'remote'
3430
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3431
self.hpss_calls = []
3432
local.repository.fetch(
3433
remote_branch.repository,
3434
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
3435
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
3437
def override_verb(self, verb_name, verb):
3438
request_handlers = request.request_handlers
3439
orig_verb = request_handlers.get(verb_name)
3440
request_handlers.register(verb_name, verb, override_existing=True)
3441
self.addCleanup(request_handlers.register, verb_name, orig_verb,
3442
override_existing=True)
3444
def test_fetch_everything_backwards_compat(self):
3445
"""Can fetch with EverythingResult even with pre 2.4 servers.
3447
Pre-2.4 do not support 'everything' searches with the
3448
Repository.get_stream_1.19 verb.
3451
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
3452
"""A version of the Repository.get_stream_1.19 verb patched to
3453
reject 'everything' searches the way 2.3 and earlier do.
3455
def recreate_search(self, repository, search_bytes,
3456
discard_excess=False):
3457
verb_log.append(search_bytes.split('\n', 1)[0])
3458
if search_bytes == 'everything':
3460
request.FailedSmartServerResponse(('BadSearch',)))
3461
return super(OldGetStreamVerb,
3462
self).recreate_search(repository, search_bytes,
3463
discard_excess=discard_excess)
3464
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
3465
local = self.make_branch('local')
3466
builder = self.make_branch_builder('remote')
3467
builder.build_commit(message="Commit.")
3468
remote_branch_url = self.smart_server.get_url() + 'remote'
3469
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3470
self.hpss_calls = []
3471
local.repository.fetch(
3472
remote_branch.repository,
3473
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
3474
# make sure the overridden verb was used
3475
self.assertLength(1, verb_log)
3476
# more than one HPSS call is needed, but because it's a VFS callback
3477
# its hard to predict exactly how many.
3478
self.assertTrue(len(self.hpss_calls) > 1)
3481
class TestUpdateBoundBranchWithModifiedBoundLocation(
3482
tests.TestCaseWithTransport):
3483
"""Ensure correct handling of bound_location modifications.
3485
This is tested against a smart server as http://pad.lv/786980 was about a
3486
ReadOnlyError (write attempt during a read-only transaction) which can only
3487
happen in this context.
3491
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
3492
self.transport_server = test_server.SmartTCPServer_for_testing
3494
def make_master_and_checkout(self, master_name, checkout_name):
3495
# Create the master branch and its associated checkout
3496
self.master = self.make_branch_and_tree(master_name)
3497
self.checkout = self.master.branch.create_checkout(checkout_name)
3498
# Modify the master branch so there is something to update
3499
self.master.commit('add stuff')
3500
self.last_revid = self.master.commit('even more stuff')
3501
self.bound_location = self.checkout.branch.get_bound_location()
3503
def assertUpdateSucceeds(self, new_location):
3504
self.checkout.branch.set_bound_location(new_location)
3505
self.checkout.update()
3506
self.assertEquals(self.last_revid, self.checkout.last_revision())
3508
def test_without_final_slash(self):
3509
self.make_master_and_checkout('master', 'checkout')
3510
# For unclear reasons some users have a bound_location without a final
3511
# '/', simulate that by forcing such a value
3512
self.assertEndsWith(self.bound_location, '/')
3513
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
3515
def test_plus_sign(self):
3516
self.make_master_and_checkout('+master', 'checkout')
3517
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
3519
def test_tilda(self):
3520
# Embed ~ in the middle of the path just to avoid any $HOME
3522
self.make_master_and_checkout('mas~ter', 'checkout')
3523
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))