1
# Copyright (C) 2006-2013, 2016 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.
41
from ..branch import Branch
49
from ..bzr.bzrdir import (
54
from ..bzr.chk_serializer import chk_bencode_serializer
55
from ..remote import (
61
RemoteRepositoryFormat,
63
from ..bzr import groupcompress_repo, knitpack_repo
64
from ..revision import (
68
from ..sixish import (
71
from ..smart import medium, request
72
from ..smart.client import _SmartClient
73
from ..smart.repository import (
74
SmartServerRepositoryGetParentMap,
75
SmartServerRepositoryGetStream_1_19,
76
_stream_to_byte_stream,
81
from .scenarios import load_tests_apply_scenarios
82
from ..transport.memory import MemoryTransport
83
from ..transport.remote import (
90
load_tests = load_tests_apply_scenarios
93
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
97
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
99
{'transport_server': test_server.SmartTCPServer_for_testing})]
103
super(BasicRemoteObjectTests, self).setUp()
104
self.transport = self.get_transport()
105
# make a branch that can be opened over the smart transport
106
self.local_wt = BzrDir.create_standalone_workingtree('.')
107
self.addCleanup(self.transport.disconnect)
109
def test_create_remote_bzrdir(self):
110
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
111
self.assertIsInstance(b, BzrDir)
113
def test_open_remote_branch(self):
114
# open a standalone branch in the working directory
115
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
116
branch = b.open_branch()
117
self.assertIsInstance(branch, Branch)
119
def test_remote_repository(self):
120
b = BzrDir.open_from_transport(self.transport)
121
repo = b.open_repository()
122
revid = u'\xc823123123'.encode('utf8')
123
self.assertFalse(repo.has_revision(revid))
124
self.local_wt.commit(message='test commit', rev_id=revid)
125
self.assertTrue(repo.has_revision(revid))
127
def test_find_correct_format(self):
128
"""Should open a RemoteBzrDir over a RemoteTransport"""
129
fmt = BzrDirFormat.find_format(self.transport)
130
self.assertTrue(bzrdir.RemoteBzrProber
131
in controldir.ControlDirFormat._server_probers)
132
self.assertIsInstance(fmt, RemoteBzrDirFormat)
134
def test_open_detected_smart_format(self):
135
fmt = BzrDirFormat.find_format(self.transport)
136
d = fmt.open(self.transport)
137
self.assertIsInstance(d, BzrDir)
139
def test_remote_branch_repr(self):
140
b = BzrDir.open_from_transport(self.transport).open_branch()
141
self.assertStartsWith(str(b), 'RemoteBranch(')
143
def test_remote_bzrdir_repr(self):
144
b = BzrDir.open_from_transport(self.transport)
145
self.assertStartsWith(str(b), 'RemoteBzrDir(')
147
def test_remote_branch_format_supports_stacking(self):
149
self.make_branch('unstackable', format='pack-0.92')
150
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
151
self.assertFalse(b._format.supports_stacking())
152
self.make_branch('stackable', format='1.9')
153
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
154
self.assertTrue(b._format.supports_stacking())
156
def test_remote_repo_format_supports_external_references(self):
158
bd = self.make_controldir('unstackable', format='pack-0.92')
159
r = bd.create_repository()
160
self.assertFalse(r._format.supports_external_lookups)
161
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
162
self.assertFalse(r._format.supports_external_lookups)
163
bd = self.make_controldir('stackable', format='1.9')
164
r = bd.create_repository()
165
self.assertTrue(r._format.supports_external_lookups)
166
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
167
self.assertTrue(r._format.supports_external_lookups)
169
def test_remote_branch_set_append_revisions_only(self):
170
# Make a format 1.9 branch, which supports append_revisions_only
171
branch = self.make_branch('branch', format='1.9')
172
branch.set_append_revisions_only(True)
173
config = branch.get_config_stack()
175
True, config.get('append_revisions_only'))
176
branch.set_append_revisions_only(False)
177
config = branch.get_config_stack()
179
False, config.get('append_revisions_only'))
181
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
182
branch = self.make_branch('branch', format='knit')
184
errors.UpgradeRequired, branch.set_append_revisions_only, True)
187
class FakeProtocol(object):
188
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
190
def __init__(self, body, fake_client):
192
self._body_buffer = None
193
self._fake_client = fake_client
195
def read_body_bytes(self, count=-1):
196
if self._body_buffer is None:
197
self._body_buffer = BytesIO(self.body)
198
bytes = self._body_buffer.read(count)
199
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
200
self._fake_client.expecting_body = False
203
def cancel_read_body(self):
204
self._fake_client.expecting_body = False
206
def read_streamed_body(self):
210
class FakeClient(_SmartClient):
211
"""Lookalike for _SmartClient allowing testing."""
213
def __init__(self, fake_medium_base='fake base'):
214
"""Create a FakeClient."""
217
self.expecting_body = False
218
# if non-None, this is the list of expected calls, with only the
219
# method name and arguments included. the body might be hard to
220
# compute so is not included. If a call is None, that call can
222
self._expected_calls = None
223
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
225
def add_expected_call(self, call_name, call_args, response_type,
226
response_args, response_body=None):
227
if self._expected_calls is None:
228
self._expected_calls = []
229
self._expected_calls.append((call_name, call_args))
230
self.responses.append((response_type, response_args, response_body))
232
def add_success_response(self, *args):
233
self.responses.append(('success', args, None))
235
def add_success_response_with_body(self, body, *args):
236
self.responses.append(('success', args, body))
237
if self._expected_calls is not None:
238
self._expected_calls.append(None)
240
def add_error_response(self, *args):
241
self.responses.append(('error', args))
243
def add_unknown_method_response(self, verb):
244
self.responses.append(('unknown', verb))
246
def finished_test(self):
247
if self._expected_calls:
248
raise AssertionError("%r finished but was still expecting %r"
249
% (self, self._expected_calls[0]))
251
def _get_next_response(self):
253
response_tuple = self.responses.pop(0)
254
except IndexError as e:
255
raise AssertionError("%r didn't expect any more calls"
257
if response_tuple[0] == 'unknown':
258
raise errors.UnknownSmartMethod(response_tuple[1])
259
elif response_tuple[0] == 'error':
260
raise errors.ErrorFromSmartServer(response_tuple[1])
261
return response_tuple
263
def _check_call(self, method, args):
264
if self._expected_calls is None:
265
# the test should be updated to say what it expects
268
next_call = self._expected_calls.pop(0)
270
raise AssertionError("%r didn't expect any more calls "
272
% (self, method, args,))
273
if next_call is None:
275
if method != next_call[0] or args != next_call[1]:
276
raise AssertionError("%r expected %r%r "
278
% (self, next_call[0], next_call[1], method, args,))
280
def call(self, method, *args):
281
self._check_call(method, args)
282
self._calls.append(('call', method, args))
283
return self._get_next_response()[1]
285
def call_expecting_body(self, method, *args):
286
self._check_call(method, args)
287
self._calls.append(('call_expecting_body', method, args))
288
result = self._get_next_response()
289
self.expecting_body = True
290
return result[1], FakeProtocol(result[2], self)
292
def call_with_body_bytes(self, method, args, body):
293
self._check_call(method, args)
294
self._calls.append(('call_with_body_bytes', method, args, body))
295
result = self._get_next_response()
296
return result[1], FakeProtocol(result[2], self)
298
def call_with_body_bytes_expecting_body(self, method, args, body):
299
self._check_call(method, args)
300
self._calls.append(('call_with_body_bytes_expecting_body', method,
302
result = self._get_next_response()
303
self.expecting_body = True
304
return result[1], FakeProtocol(result[2], self)
306
def call_with_body_stream(self, args, stream):
307
# Explicitly consume the stream before checking for an error, because
308
# that's what happens a real medium.
309
stream = list(stream)
310
self._check_call(args[0], args[1:])
311
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
312
result = self._get_next_response()
313
# The second value returned from call_with_body_stream is supposed to
314
# be a response_handler object, but so far no tests depend on that.
315
response_handler = None
316
return result[1], response_handler
319
class FakeMedium(medium.SmartClientMedium):
321
def __init__(self, client_calls, base):
322
medium.SmartClientMedium.__init__(self, base)
323
self._client_calls = client_calls
325
def disconnect(self):
326
self._client_calls.append(('disconnect medium',))
329
class TestVfsHas(tests.TestCase):
331
def test_unicode_path(self):
332
client = FakeClient('/')
333
client.add_success_response('yes',)
334
transport = RemoteTransport('bzr://localhost/', _client=client)
335
filename = u'/hell\u00d8'.encode('utf8')
336
result = transport.has(filename)
338
[('call', 'has', (filename,))],
340
self.assertTrue(result)
343
class TestRemote(tests.TestCaseWithMemoryTransport):
345
def get_branch_format(self):
346
reference_bzrdir_format = controldir.format_registry.get('default')()
347
return reference_bzrdir_format.get_branch_format()
349
def get_repo_format(self):
350
reference_bzrdir_format = controldir.format_registry.get('default')()
351
return reference_bzrdir_format.repository_format
353
def assertFinished(self, fake_client):
354
"""Assert that all of a FakeClient's expected calls have occurred."""
355
fake_client.finished_test()
358
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
359
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
361
def assertRemotePath(self, expected, client_base, transport_base):
362
"""Assert that the result of
363
SmartClientMedium.remote_path_from_transport is the expected value for
364
a given client_base and transport_base.
366
client_medium = medium.SmartClientMedium(client_base)
367
t = transport.get_transport(transport_base)
368
result = client_medium.remote_path_from_transport(t)
369
self.assertEqual(expected, result)
371
def test_remote_path_from_transport(self):
372
"""SmartClientMedium.remote_path_from_transport calculates a URL for
373
the given transport relative to the root of the client base URL.
375
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
376
self.assertRemotePath(
377
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
379
def assertRemotePathHTTP(self, expected, transport_base, relpath):
380
"""Assert that the result of
381
HttpTransportBase.remote_path_from_transport is the expected value for
382
a given transport_base and relpath of that transport. (Note that
383
HttpTransportBase is a subclass of SmartClientMedium)
385
base_transport = transport.get_transport(transport_base)
386
client_medium = base_transport.get_smart_medium()
387
cloned_transport = base_transport.clone(relpath)
388
result = client_medium.remote_path_from_transport(cloned_transport)
389
self.assertEqual(expected, result)
391
def test_remote_path_from_transport_http(self):
392
"""Remote paths for HTTP transports are calculated differently to other
393
transports. They are just relative to the client base, not the root
394
directory of the host.
396
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
397
self.assertRemotePathHTTP(
398
'../xyz/', scheme + '//host/path', '../xyz/')
399
self.assertRemotePathHTTP(
400
'xyz/', scheme + '//host/path', 'xyz/')
403
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
404
"""Tests for the behaviour of client_medium.remote_is_at_least."""
406
def test_initially_unlimited(self):
407
"""A fresh medium assumes that the remote side supports all
410
client_medium = medium.SmartClientMedium('dummy base')
411
self.assertFalse(client_medium._is_remote_before((99, 99)))
413
def test__remember_remote_is_before(self):
414
"""Calling _remember_remote_is_before ratchets down the known remote
417
client_medium = medium.SmartClientMedium('dummy base')
418
# Mark the remote side as being less than 1.6. The remote side may
420
client_medium._remember_remote_is_before((1, 6))
421
self.assertTrue(client_medium._is_remote_before((1, 6)))
422
self.assertFalse(client_medium._is_remote_before((1, 5)))
423
# Calling _remember_remote_is_before again with a lower value works.
424
client_medium._remember_remote_is_before((1, 5))
425
self.assertTrue(client_medium._is_remote_before((1, 5)))
426
# If you call _remember_remote_is_before with a higher value it logs a
427
# warning, and continues to remember the lower value.
428
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
429
client_medium._remember_remote_is_before((1, 9))
430
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
431
self.assertTrue(client_medium._is_remote_before((1, 5)))
434
class TestBzrDirCloningMetaDir(TestRemote):
436
def test_backwards_compat(self):
437
self.setup_smart_server_with_call_log()
438
a_dir = self.make_controldir('.')
439
self.reset_smart_call_log()
440
verb = 'BzrDir.cloning_metadir'
441
self.disable_verb(verb)
442
format = a_dir.cloning_metadir()
443
call_count = len([call for call in self.hpss_calls if
444
call.call.method == verb])
445
self.assertEqual(1, call_count)
447
def test_branch_reference(self):
448
transport = self.get_transport('quack')
449
referenced = self.make_branch('referenced')
450
expected = referenced.controldir.cloning_metadir()
451
client = FakeClient(transport.base)
452
client.add_expected_call(
453
'BzrDir.cloning_metadir', ('quack/', 'False'),
454
'error', ('BranchReference',)),
455
client.add_expected_call(
456
'BzrDir.open_branchV3', ('quack/',),
457
'success', ('ref', self.get_url('referenced'))),
458
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
460
result = a_controldir.cloning_metadir()
461
# We should have got a control dir matching the referenced branch.
462
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
463
self.assertEqual(expected._repository_format, result._repository_format)
464
self.assertEqual(expected._branch_format, result._branch_format)
465
self.assertFinished(client)
467
def test_current_server(self):
468
transport = self.get_transport('.')
469
transport = transport.clone('quack')
470
self.make_controldir('quack')
471
client = FakeClient(transport.base)
472
reference_bzrdir_format = controldir.format_registry.get('default')()
473
control_name = reference_bzrdir_format.network_name()
474
client.add_expected_call(
475
'BzrDir.cloning_metadir', ('quack/', 'False'),
476
'success', (control_name, '', ('branch', ''))),
477
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
479
result = a_controldir.cloning_metadir()
480
# We should have got a reference control dir with default branch and
481
# repository formats.
482
# This pokes a little, just to be sure.
483
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
484
self.assertEqual(None, result._repository_format)
485
self.assertEqual(None, result._branch_format)
486
self.assertFinished(client)
488
def test_unknown(self):
489
transport = self.get_transport('quack')
490
referenced = self.make_branch('referenced')
491
expected = referenced.controldir.cloning_metadir()
492
client = FakeClient(transport.base)
493
client.add_expected_call(
494
'BzrDir.cloning_metadir', ('quack/', 'False'),
495
'success', ('unknown', 'unknown', ('branch', ''))),
496
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
498
self.assertRaises(errors.UnknownFormatError, a_controldir.cloning_metadir)
501
class TestBzrDirCheckoutMetaDir(TestRemote):
503
def test__get_checkout_format(self):
504
transport = MemoryTransport()
505
client = FakeClient(transport.base)
506
reference_bzrdir_format = controldir.format_registry.get('default')()
507
control_name = reference_bzrdir_format.network_name()
508
client.add_expected_call(
509
'BzrDir.checkout_metadir', ('quack/', ),
510
'success', (control_name, '', ''))
511
transport.mkdir('quack')
512
transport = transport.clone('quack')
513
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
515
result = a_controldir.checkout_metadir()
516
# We should have got a reference control dir with default branch and
517
# repository formats.
518
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
519
self.assertEqual(None, result._repository_format)
520
self.assertEqual(None, result._branch_format)
521
self.assertFinished(client)
523
def test_unknown_format(self):
524
transport = MemoryTransport()
525
client = FakeClient(transport.base)
526
client.add_expected_call(
527
'BzrDir.checkout_metadir', ('quack/',),
528
'success', ('dontknow', '', ''))
529
transport.mkdir('quack')
530
transport = transport.clone('quack')
531
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
533
self.assertRaises(errors.UnknownFormatError,
534
a_controldir.checkout_metadir)
535
self.assertFinished(client)
538
class TestBzrDirGetBranches(TestRemote):
540
def test_get_branches(self):
541
transport = MemoryTransport()
542
client = FakeClient(transport.base)
543
reference_bzrdir_format = controldir.format_registry.get('default')()
544
branch_name = reference_bzrdir_format.get_branch_format().network_name()
545
client.add_success_response_with_body(
547
"foo": ("branch", branch_name),
548
"": ("branch", branch_name)}), "success")
549
client.add_success_response(
550
'ok', '', 'no', 'no', 'no',
551
reference_bzrdir_format.repository_format.network_name())
552
client.add_error_response('NotStacked')
553
client.add_success_response(
554
'ok', '', 'no', 'no', 'no',
555
reference_bzrdir_format.repository_format.network_name())
556
client.add_error_response('NotStacked')
557
transport.mkdir('quack')
558
transport = transport.clone('quack')
559
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
561
result = a_controldir.get_branches()
562
self.assertEqual({"", "foo"}, set(result.keys()))
564
[('call_expecting_body', 'BzrDir.get_branches', ('quack/',)),
565
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
566
('call', 'Branch.get_stacked_on_url', ('quack/', )),
567
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
568
('call', 'Branch.get_stacked_on_url', ('quack/', ))],
572
class TestBzrDirDestroyBranch(TestRemote):
574
def test_destroy_default(self):
575
transport = self.get_transport('quack')
576
referenced = self.make_branch('referenced')
577
client = FakeClient(transport.base)
578
client.add_expected_call(
579
'BzrDir.destroy_branch', ('quack/', ),
581
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
583
a_controldir.destroy_branch()
584
self.assertFinished(client)
587
class TestBzrDirHasWorkingTree(TestRemote):
589
def test_has_workingtree(self):
590
transport = self.get_transport('quack')
591
client = FakeClient(transport.base)
592
client.add_expected_call(
593
'BzrDir.has_workingtree', ('quack/',),
594
'success', ('yes',)),
595
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
597
self.assertTrue(a_controldir.has_workingtree())
598
self.assertFinished(client)
600
def test_no_workingtree(self):
601
transport = self.get_transport('quack')
602
client = FakeClient(transport.base)
603
client.add_expected_call(
604
'BzrDir.has_workingtree', ('quack/',),
606
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
608
self.assertFalse(a_controldir.has_workingtree())
609
self.assertFinished(client)
612
class TestBzrDirDestroyRepository(TestRemote):
614
def test_destroy_repository(self):
615
transport = self.get_transport('quack')
616
client = FakeClient(transport.base)
617
client.add_expected_call(
618
'BzrDir.destroy_repository', ('quack/',),
620
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
622
a_controldir.destroy_repository()
623
self.assertFinished(client)
626
class TestBzrDirOpen(TestRemote):
628
def make_fake_client_and_transport(self, path='quack'):
629
transport = MemoryTransport()
630
transport.mkdir(path)
631
transport = transport.clone(path)
632
client = FakeClient(transport.base)
633
return client, transport
635
def test_absent(self):
636
client, transport = self.make_fake_client_and_transport()
637
client.add_expected_call(
638
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
639
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
640
RemoteBzrDirFormat(), _client=client, _force_probe=True)
641
self.assertFinished(client)
643
def test_present_without_workingtree(self):
644
client, transport = self.make_fake_client_and_transport()
645
client.add_expected_call(
646
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
647
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
648
_client=client, _force_probe=True)
649
self.assertIsInstance(bd, RemoteBzrDir)
650
self.assertFalse(bd.has_workingtree())
651
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
652
self.assertFinished(client)
654
def test_present_with_workingtree(self):
655
client, transport = self.make_fake_client_and_transport()
656
client.add_expected_call(
657
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
658
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
659
_client=client, _force_probe=True)
660
self.assertIsInstance(bd, RemoteBzrDir)
661
self.assertTrue(bd.has_workingtree())
662
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
663
self.assertFinished(client)
665
def test_backwards_compat(self):
666
client, transport = self.make_fake_client_and_transport()
667
client.add_expected_call(
668
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
669
client.add_expected_call(
670
'BzrDir.open', ('quack/',), 'success', ('yes',))
671
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
672
_client=client, _force_probe=True)
673
self.assertIsInstance(bd, RemoteBzrDir)
674
self.assertFinished(client)
676
def test_backwards_compat_hpss_v2(self):
677
client, transport = self.make_fake_client_and_transport()
678
# Monkey-patch fake client to simulate real-world behaviour with v2
679
# server: upon first RPC call detect the protocol version, and because
680
# the version is 2 also do _remember_remote_is_before((1, 6)) before
681
# continuing with the RPC.
682
orig_check_call = client._check_call
683
def check_call(method, args):
684
client._medium._protocol_version = 2
685
client._medium._remember_remote_is_before((1, 6))
686
client._check_call = orig_check_call
687
client._check_call(method, args)
688
client._check_call = check_call
689
client.add_expected_call(
690
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
691
client.add_expected_call(
692
'BzrDir.open', ('quack/',), 'success', ('yes',))
693
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
694
_client=client, _force_probe=True)
695
self.assertIsInstance(bd, RemoteBzrDir)
696
self.assertFinished(client)
699
class TestBzrDirOpenBranch(TestRemote):
701
def test_backwards_compat(self):
702
self.setup_smart_server_with_call_log()
703
self.make_branch('.')
704
a_dir = BzrDir.open(self.get_url('.'))
705
self.reset_smart_call_log()
706
verb = 'BzrDir.open_branchV3'
707
self.disable_verb(verb)
708
format = a_dir.open_branch()
709
call_count = len([call for call in self.hpss_calls if
710
call.call.method == verb])
711
self.assertEqual(1, call_count)
713
def test_branch_present(self):
714
reference_format = self.get_repo_format()
715
network_name = reference_format.network_name()
716
branch_network_name = self.get_branch_format().network_name()
717
transport = MemoryTransport()
718
transport.mkdir('quack')
719
transport = transport.clone('quack')
720
client = FakeClient(transport.base)
721
client.add_expected_call(
722
'BzrDir.open_branchV3', ('quack/',),
723
'success', ('branch', branch_network_name))
724
client.add_expected_call(
725
'BzrDir.find_repositoryV3', ('quack/',),
726
'success', ('ok', '', 'no', 'no', 'no', network_name))
727
client.add_expected_call(
728
'Branch.get_stacked_on_url', ('quack/',),
729
'error', ('NotStacked',))
730
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
732
result = bzrdir.open_branch()
733
self.assertIsInstance(result, RemoteBranch)
734
self.assertEqual(bzrdir, result.controldir)
735
self.assertFinished(client)
737
def test_branch_missing(self):
738
transport = MemoryTransport()
739
transport.mkdir('quack')
740
transport = transport.clone('quack')
741
client = FakeClient(transport.base)
742
client.add_error_response('nobranch')
743
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
745
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
747
[('call', 'BzrDir.open_branchV3', ('quack/',))],
750
def test__get_tree_branch(self):
751
# _get_tree_branch is a form of open_branch, but it should only ask for
752
# branch opening, not any other network requests.
754
def open_branch(name=None, possible_transports=None):
755
calls.append("Called")
757
transport = MemoryTransport()
758
# no requests on the network - catches other api calls being made.
759
client = FakeClient(transport.base)
760
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
762
# patch the open_branch call to record that it was called.
763
bzrdir.open_branch = open_branch
764
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
765
self.assertEqual(["Called"], calls)
766
self.assertEqual([], client._calls)
768
def test_url_quoting_of_path(self):
769
# Relpaths on the wire should not be URL-escaped. So "~" should be
770
# transmitted as "~", not "%7E".
771
transport = RemoteTCPTransport('bzr://localhost/~hello/')
772
client = FakeClient(transport.base)
773
reference_format = self.get_repo_format()
774
network_name = reference_format.network_name()
775
branch_network_name = self.get_branch_format().network_name()
776
client.add_expected_call(
777
'BzrDir.open_branchV3', ('~hello/',),
778
'success', ('branch', branch_network_name))
779
client.add_expected_call(
780
'BzrDir.find_repositoryV3', ('~hello/',),
781
'success', ('ok', '', 'no', 'no', 'no', network_name))
782
client.add_expected_call(
783
'Branch.get_stacked_on_url', ('~hello/',),
784
'error', ('NotStacked',))
785
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
787
result = bzrdir.open_branch()
788
self.assertFinished(client)
790
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
791
reference_format = self.get_repo_format()
792
network_name = reference_format.network_name()
793
transport = MemoryTransport()
794
transport.mkdir('quack')
795
transport = transport.clone('quack')
797
rich_response = 'yes'
801
subtree_response = 'yes'
803
subtree_response = 'no'
804
client = FakeClient(transport.base)
805
client.add_success_response(
806
'ok', '', rich_response, subtree_response, external_lookup,
808
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
810
result = bzrdir.open_repository()
812
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
814
self.assertIsInstance(result, RemoteRepository)
815
self.assertEqual(bzrdir, result.controldir)
816
self.assertEqual(rich_root, result._format.rich_root_data)
817
self.assertEqual(subtrees, result._format.supports_tree_reference)
819
def test_open_repository_sets_format_attributes(self):
820
self.check_open_repository(True, True)
821
self.check_open_repository(False, True)
822
self.check_open_repository(True, False)
823
self.check_open_repository(False, False)
824
self.check_open_repository(False, False, 'yes')
826
def test_old_server(self):
827
"""RemoteBzrDirFormat should fail to probe if the server version is too
830
self.assertRaises(errors.NotBranchError,
831
RemoteBzrProber.probe_transport, OldServerTransport())
834
class TestBzrDirCreateBranch(TestRemote):
836
def test_backwards_compat(self):
837
self.setup_smart_server_with_call_log()
838
repo = self.make_repository('.')
839
self.reset_smart_call_log()
840
self.disable_verb('BzrDir.create_branch')
841
branch = repo.controldir.create_branch()
842
create_branch_call_count = len([call for call in self.hpss_calls if
843
call.call.method == 'BzrDir.create_branch'])
844
self.assertEqual(1, create_branch_call_count)
846
def test_current_server(self):
847
transport = self.get_transport('.')
848
transport = transport.clone('quack')
849
self.make_repository('quack')
850
client = FakeClient(transport.base)
851
reference_bzrdir_format = controldir.format_registry.get('default')()
852
reference_format = reference_bzrdir_format.get_branch_format()
853
network_name = reference_format.network_name()
854
reference_repo_fmt = reference_bzrdir_format.repository_format
855
reference_repo_name = reference_repo_fmt.network_name()
856
client.add_expected_call(
857
'BzrDir.create_branch', ('quack/', network_name),
858
'success', ('ok', network_name, '', 'no', 'no', 'yes',
859
reference_repo_name))
860
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
862
branch = a_controldir.create_branch()
863
# We should have got a remote branch
864
self.assertIsInstance(branch, remote.RemoteBranch)
865
# its format should have the settings from the response
866
format = branch._format
867
self.assertEqual(network_name, format.network_name())
869
def test_already_open_repo_and_reused_medium(self):
870
"""Bug 726584: create_branch(..., repository=repo) should work
871
regardless of what the smart medium's base URL is.
873
self.transport_server = test_server.SmartTCPServer_for_testing
874
transport = self.get_transport('.')
875
repo = self.make_repository('quack')
876
# Client's medium rooted a transport root (not at the bzrdir)
877
client = FakeClient(transport.base)
878
transport = transport.clone('quack')
879
reference_bzrdir_format = controldir.format_registry.get('default')()
880
reference_format = reference_bzrdir_format.get_branch_format()
881
network_name = reference_format.network_name()
882
reference_repo_fmt = reference_bzrdir_format.repository_format
883
reference_repo_name = reference_repo_fmt.network_name()
884
client.add_expected_call(
885
'BzrDir.create_branch', ('extra/quack/', network_name),
886
'success', ('ok', network_name, '', 'no', 'no', 'yes',
887
reference_repo_name))
888
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
890
branch = a_controldir.create_branch(repository=repo)
891
# We should have got a remote branch
892
self.assertIsInstance(branch, remote.RemoteBranch)
893
# its format should have the settings from the response
894
format = branch._format
895
self.assertEqual(network_name, format.network_name())
898
class TestBzrDirCreateRepository(TestRemote):
900
def test_backwards_compat(self):
901
self.setup_smart_server_with_call_log()
902
bzrdir = self.make_controldir('.')
903
self.reset_smart_call_log()
904
self.disable_verb('BzrDir.create_repository')
905
repo = bzrdir.create_repository()
906
create_repo_call_count = len([call for call in self.hpss_calls if
907
call.call.method == 'BzrDir.create_repository'])
908
self.assertEqual(1, create_repo_call_count)
910
def test_current_server(self):
911
transport = self.get_transport('.')
912
transport = transport.clone('quack')
913
self.make_controldir('quack')
914
client = FakeClient(transport.base)
915
reference_bzrdir_format = controldir.format_registry.get('default')()
916
reference_format = reference_bzrdir_format.repository_format
917
network_name = reference_format.network_name()
918
client.add_expected_call(
919
'BzrDir.create_repository', ('quack/',
920
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
922
'success', ('ok', 'yes', 'yes', 'yes', network_name))
923
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
925
repo = a_controldir.create_repository()
926
# We should have got a remote repository
927
self.assertIsInstance(repo, remote.RemoteRepository)
928
# its format should have the settings from the response
929
format = repo._format
930
self.assertTrue(format.rich_root_data)
931
self.assertTrue(format.supports_tree_reference)
932
self.assertTrue(format.supports_external_lookups)
933
self.assertEqual(network_name, format.network_name())
936
class TestBzrDirOpenRepository(TestRemote):
938
def test_backwards_compat_1_2_3(self):
939
# fallback all the way to the first version.
940
reference_format = self.get_repo_format()
941
network_name = reference_format.network_name()
942
server_url = 'bzr://example.com/'
943
self.permit_url(server_url)
944
client = FakeClient(server_url)
945
client.add_unknown_method_response('BzrDir.find_repositoryV3')
946
client.add_unknown_method_response('BzrDir.find_repositoryV2')
947
client.add_success_response('ok', '', 'no', 'no')
948
# A real repository instance will be created to determine the network
950
client.add_success_response_with_body(
951
"Bazaar-NG meta directory, format 1\n", 'ok')
952
client.add_success_response('stat', '0', '65535')
953
client.add_success_response_with_body(
954
reference_format.get_format_string(), 'ok')
955
# PackRepository wants to do a stat
956
client.add_success_response('stat', '0', '65535')
957
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
959
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
961
repo = bzrdir.open_repository()
963
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
964
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
965
('call', 'BzrDir.find_repository', ('quack/',)),
966
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
967
('call', 'stat', ('/quack/.bzr',)),
968
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
969
('call', 'stat', ('/quack/.bzr/repository',)),
972
self.assertEqual(network_name, repo._format.network_name())
974
def test_backwards_compat_2(self):
975
# fallback to find_repositoryV2
976
reference_format = self.get_repo_format()
977
network_name = reference_format.network_name()
978
server_url = 'bzr://example.com/'
979
self.permit_url(server_url)
980
client = FakeClient(server_url)
981
client.add_unknown_method_response('BzrDir.find_repositoryV3')
982
client.add_success_response('ok', '', 'no', 'no', 'no')
983
# A real repository instance will be created to determine the network
985
client.add_success_response_with_body(
986
"Bazaar-NG meta directory, format 1\n", 'ok')
987
client.add_success_response('stat', '0', '65535')
988
client.add_success_response_with_body(
989
reference_format.get_format_string(), 'ok')
990
# PackRepository wants to do a stat
991
client.add_success_response('stat', '0', '65535')
992
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
994
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
996
repo = bzrdir.open_repository()
998
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
999
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
1000
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
1001
('call', 'stat', ('/quack/.bzr',)),
1002
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
1003
('call', 'stat', ('/quack/.bzr/repository',)),
1006
self.assertEqual(network_name, repo._format.network_name())
1008
def test_current_server(self):
1009
reference_format = self.get_repo_format()
1010
network_name = reference_format.network_name()
1011
transport = MemoryTransport()
1012
transport.mkdir('quack')
1013
transport = transport.clone('quack')
1014
client = FakeClient(transport.base)
1015
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
1016
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1018
repo = bzrdir.open_repository()
1020
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
1022
self.assertEqual(network_name, repo._format.network_name())
1025
class TestBzrDirFormatInitializeEx(TestRemote):
1027
def test_success(self):
1028
"""Simple test for typical successful call."""
1029
fmt = RemoteBzrDirFormat()
1030
default_format_name = BzrDirFormat.get_default_format().network_name()
1031
transport = self.get_transport()
1032
client = FakeClient(transport.base)
1033
client.add_expected_call(
1034
'BzrDirFormat.initialize_ex_1.16',
1035
(default_format_name, 'path', 'False', 'False', 'False', '',
1036
'', '', '', 'False'),
1038
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
1039
'bzrdir fmt', 'False', '', '', 'repo lock token'))
1040
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1041
# it's currently hard to test that without supplying a real remote
1042
# transport connected to a real server.
1043
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1044
transport, False, False, False, None, None, None, None, False)
1045
self.assertFinished(client)
1047
def test_error(self):
1048
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1049
corresponding error from the client.
1051
fmt = RemoteBzrDirFormat()
1052
default_format_name = BzrDirFormat.get_default_format().network_name()
1053
transport = self.get_transport()
1054
client = FakeClient(transport.base)
1055
client.add_expected_call(
1056
'BzrDirFormat.initialize_ex_1.16',
1057
(default_format_name, 'path', 'False', 'False', 'False', '',
1058
'', '', '', 'False'),
1060
('PermissionDenied', 'path', 'extra info'))
1061
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1062
# it's currently hard to test that without supplying a real remote
1063
# transport connected to a real server.
1064
err = self.assertRaises(errors.PermissionDenied,
1065
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1066
False, False, False, None, None, None, None, False)
1067
self.assertEqual('path', err.path)
1068
self.assertEqual(': extra info', err.extra)
1069
self.assertFinished(client)
1071
def test_error_from_real_server(self):
1072
"""Integration test for error translation."""
1073
transport = self.make_smart_server('foo')
1074
transport = transport.clone('no-such-path')
1075
fmt = RemoteBzrDirFormat()
1076
err = self.assertRaises(errors.NoSuchFile,
1077
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1080
class OldSmartClient(object):
1081
"""A fake smart client for test_old_version that just returns a version one
1082
response to the 'hello' (query version) command.
1085
def get_request(self):
1086
input_file = BytesIO(b'ok\x011\n')
1087
output_file = BytesIO()
1088
client_medium = medium.SmartSimplePipesClientMedium(
1089
input_file, output_file)
1090
return medium.SmartClientStreamMediumRequest(client_medium)
1092
def protocol_version(self):
1096
class OldServerTransport(object):
1097
"""A fake transport for test_old_server that reports it's smart server
1098
protocol version as version one.
1104
def get_smart_client(self):
1105
return OldSmartClient()
1108
class RemoteBzrDirTestCase(TestRemote):
1110
def make_remote_bzrdir(self, transport, client):
1111
"""Make a RemotebzrDir using 'client' as the _client."""
1112
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1116
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1118
def lock_remote_branch(self, branch):
1119
"""Trick a RemoteBranch into thinking it is locked."""
1120
branch._lock_mode = 'w'
1121
branch._lock_count = 2
1122
branch._lock_token = 'branch token'
1123
branch._repo_lock_token = 'repo token'
1124
branch.repository._lock_mode = 'w'
1125
branch.repository._lock_count = 2
1126
branch.repository._lock_token = 'repo token'
1128
def make_remote_branch(self, transport, client):
1129
"""Make a RemoteBranch using 'client' as its _SmartClient.
1131
A RemoteBzrDir and RemoteRepository will also be created to fill out
1132
the RemoteBranch, albeit with stub values for some of their attributes.
1134
# we do not want bzrdir to make any remote calls, so use False as its
1135
# _client. If it tries to make a remote call, this will fail
1137
bzrdir = self.make_remote_bzrdir(transport, False)
1138
repo = RemoteRepository(bzrdir, None, _client=client)
1139
branch_format = self.get_branch_format()
1140
format = RemoteBranchFormat(network_name=branch_format.network_name())
1141
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1144
class TestBranchBreakLock(RemoteBranchTestCase):
1146
def test_break_lock(self):
1147
transport_path = 'quack'
1148
transport = MemoryTransport()
1149
client = FakeClient(transport.base)
1150
client.add_expected_call(
1151
'Branch.get_stacked_on_url', ('quack/',),
1152
'error', ('NotStacked',))
1153
client.add_expected_call(
1154
'Branch.break_lock', ('quack/',),
1156
transport.mkdir('quack')
1157
transport = transport.clone('quack')
1158
branch = self.make_remote_branch(transport, client)
1160
self.assertFinished(client)
1163
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1165
def test_get_physical_lock_status_yes(self):
1166
transport = MemoryTransport()
1167
client = FakeClient(transport.base)
1168
client.add_expected_call(
1169
'Branch.get_stacked_on_url', ('quack/',),
1170
'error', ('NotStacked',))
1171
client.add_expected_call(
1172
'Branch.get_physical_lock_status', ('quack/',),
1173
'success', ('yes',))
1174
transport.mkdir('quack')
1175
transport = transport.clone('quack')
1176
branch = self.make_remote_branch(transport, client)
1177
result = branch.get_physical_lock_status()
1178
self.assertFinished(client)
1179
self.assertEqual(True, result)
1181
def test_get_physical_lock_status_no(self):
1182
transport = MemoryTransport()
1183
client = FakeClient(transport.base)
1184
client.add_expected_call(
1185
'Branch.get_stacked_on_url', ('quack/',),
1186
'error', ('NotStacked',))
1187
client.add_expected_call(
1188
'Branch.get_physical_lock_status', ('quack/',),
1190
transport.mkdir('quack')
1191
transport = transport.clone('quack')
1192
branch = self.make_remote_branch(transport, client)
1193
result = branch.get_physical_lock_status()
1194
self.assertFinished(client)
1195
self.assertEqual(False, result)
1198
class TestBranchGetParent(RemoteBranchTestCase):
1200
def test_no_parent(self):
1201
# in an empty branch we decode the response properly
1202
transport = MemoryTransport()
1203
client = FakeClient(transport.base)
1204
client.add_expected_call(
1205
'Branch.get_stacked_on_url', ('quack/',),
1206
'error', ('NotStacked',))
1207
client.add_expected_call(
1208
'Branch.get_parent', ('quack/',),
1210
transport.mkdir('quack')
1211
transport = transport.clone('quack')
1212
branch = self.make_remote_branch(transport, client)
1213
result = branch.get_parent()
1214
self.assertFinished(client)
1215
self.assertEqual(None, result)
1217
def test_parent_relative(self):
1218
transport = MemoryTransport()
1219
client = FakeClient(transport.base)
1220
client.add_expected_call(
1221
'Branch.get_stacked_on_url', ('kwaak/',),
1222
'error', ('NotStacked',))
1223
client.add_expected_call(
1224
'Branch.get_parent', ('kwaak/',),
1225
'success', ('../foo/',))
1226
transport.mkdir('kwaak')
1227
transport = transport.clone('kwaak')
1228
branch = self.make_remote_branch(transport, client)
1229
result = branch.get_parent()
1230
self.assertEqual(transport.clone('../foo').base, result)
1232
def test_parent_absolute(self):
1233
transport = MemoryTransport()
1234
client = FakeClient(transport.base)
1235
client.add_expected_call(
1236
'Branch.get_stacked_on_url', ('kwaak/',),
1237
'error', ('NotStacked',))
1238
client.add_expected_call(
1239
'Branch.get_parent', ('kwaak/',),
1240
'success', ('http://foo/',))
1241
transport.mkdir('kwaak')
1242
transport = transport.clone('kwaak')
1243
branch = self.make_remote_branch(transport, client)
1244
result = branch.get_parent()
1245
self.assertEqual('http://foo/', result)
1246
self.assertFinished(client)
1249
class TestBranchSetParentLocation(RemoteBranchTestCase):
1251
def test_no_parent(self):
1252
# We call the verb when setting parent to None
1253
transport = MemoryTransport()
1254
client = FakeClient(transport.base)
1255
client.add_expected_call(
1256
'Branch.get_stacked_on_url', ('quack/',),
1257
'error', ('NotStacked',))
1258
client.add_expected_call(
1259
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1261
transport.mkdir('quack')
1262
transport = transport.clone('quack')
1263
branch = self.make_remote_branch(transport, client)
1264
branch._lock_token = 'b'
1265
branch._repo_lock_token = 'r'
1266
branch._set_parent_location(None)
1267
self.assertFinished(client)
1269
def test_parent(self):
1270
transport = MemoryTransport()
1271
client = FakeClient(transport.base)
1272
client.add_expected_call(
1273
'Branch.get_stacked_on_url', ('kwaak/',),
1274
'error', ('NotStacked',))
1275
client.add_expected_call(
1276
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1278
transport.mkdir('kwaak')
1279
transport = transport.clone('kwaak')
1280
branch = self.make_remote_branch(transport, client)
1281
branch._lock_token = 'b'
1282
branch._repo_lock_token = 'r'
1283
branch._set_parent_location('foo')
1284
self.assertFinished(client)
1286
def test_backwards_compat(self):
1287
self.setup_smart_server_with_call_log()
1288
branch = self.make_branch('.')
1289
self.reset_smart_call_log()
1290
verb = 'Branch.set_parent_location'
1291
self.disable_verb(verb)
1292
branch.set_parent('http://foo/')
1293
self.assertLength(14, self.hpss_calls)
1296
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1298
def test_backwards_compat(self):
1299
self.setup_smart_server_with_call_log()
1300
branch = self.make_branch('.')
1301
self.reset_smart_call_log()
1302
verb = 'Branch.get_tags_bytes'
1303
self.disable_verb(verb)
1304
branch.tags.get_tag_dict()
1305
call_count = len([call for call in self.hpss_calls if
1306
call.call.method == verb])
1307
self.assertEqual(1, call_count)
1309
def test_trivial(self):
1310
transport = MemoryTransport()
1311
client = FakeClient(transport.base)
1312
client.add_expected_call(
1313
'Branch.get_stacked_on_url', ('quack/',),
1314
'error', ('NotStacked',))
1315
client.add_expected_call(
1316
'Branch.get_tags_bytes', ('quack/',),
1318
transport.mkdir('quack')
1319
transport = transport.clone('quack')
1320
branch = self.make_remote_branch(transport, client)
1321
result = branch.tags.get_tag_dict()
1322
self.assertFinished(client)
1323
self.assertEqual({}, result)
1326
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1328
def test_trivial(self):
1329
transport = MemoryTransport()
1330
client = FakeClient(transport.base)
1331
client.add_expected_call(
1332
'Branch.get_stacked_on_url', ('quack/',),
1333
'error', ('NotStacked',))
1334
client.add_expected_call(
1335
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1337
transport.mkdir('quack')
1338
transport = transport.clone('quack')
1339
branch = self.make_remote_branch(transport, client)
1340
self.lock_remote_branch(branch)
1341
branch._set_tags_bytes('tags bytes')
1342
self.assertFinished(client)
1343
self.assertEqual('tags bytes', client._calls[-1][-1])
1345
def test_backwards_compatible(self):
1346
transport = MemoryTransport()
1347
client = FakeClient(transport.base)
1348
client.add_expected_call(
1349
'Branch.get_stacked_on_url', ('quack/',),
1350
'error', ('NotStacked',))
1351
client.add_expected_call(
1352
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1353
'unknown', ('Branch.set_tags_bytes',))
1354
transport.mkdir('quack')
1355
transport = transport.clone('quack')
1356
branch = self.make_remote_branch(transport, client)
1357
self.lock_remote_branch(branch)
1358
class StubRealBranch(object):
1361
def _set_tags_bytes(self, bytes):
1362
self.calls.append(('set_tags_bytes', bytes))
1363
real_branch = StubRealBranch()
1364
branch._real_branch = real_branch
1365
branch._set_tags_bytes('tags bytes')
1366
# Call a second time, to exercise the 'remote version already inferred'
1368
branch._set_tags_bytes('tags bytes')
1369
self.assertFinished(client)
1371
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1374
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1376
def test_uses_last_revision_info_and_tags_by_default(self):
1377
transport = MemoryTransport()
1378
client = FakeClient(transport.base)
1379
client.add_expected_call(
1380
'Branch.get_stacked_on_url', ('quack/',),
1381
'error', ('NotStacked',))
1382
client.add_expected_call(
1383
'Branch.last_revision_info', ('quack/',),
1384
'success', ('ok', '1', 'rev-tip'))
1385
client.add_expected_call(
1386
'Branch.get_config_file', ('quack/',),
1387
'success', ('ok',), '')
1388
transport.mkdir('quack')
1389
transport = transport.clone('quack')
1390
branch = self.make_remote_branch(transport, client)
1391
result = branch.heads_to_fetch()
1392
self.assertFinished(client)
1393
self.assertEqual(({'rev-tip'}, set()), result)
1395
def test_uses_last_revision_info_and_tags_when_set(self):
1396
transport = MemoryTransport()
1397
client = FakeClient(transport.base)
1398
client.add_expected_call(
1399
'Branch.get_stacked_on_url', ('quack/',),
1400
'error', ('NotStacked',))
1401
client.add_expected_call(
1402
'Branch.last_revision_info', ('quack/',),
1403
'success', ('ok', '1', 'rev-tip'))
1404
client.add_expected_call(
1405
'Branch.get_config_file', ('quack/',),
1406
'success', ('ok',), 'branch.fetch_tags = True')
1407
# XXX: this will break if the default format's serialization of tags
1408
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1409
client.add_expected_call(
1410
'Branch.get_tags_bytes', ('quack/',),
1411
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1412
transport.mkdir('quack')
1413
transport = transport.clone('quack')
1414
branch = self.make_remote_branch(transport, client)
1415
result = branch.heads_to_fetch()
1416
self.assertFinished(client)
1418
({'rev-tip'}, {'rev-foo', 'rev-bar'}), result)
1420
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1421
transport = MemoryTransport()
1422
client = FakeClient(transport.base)
1423
client.add_expected_call(
1424
'Branch.get_stacked_on_url', ('quack/',),
1425
'error', ('NotStacked',))
1426
client.add_expected_call(
1427
'Branch.heads_to_fetch', ('quack/',),
1428
'success', (['tip'], ['tagged-1', 'tagged-2']))
1429
transport.mkdir('quack')
1430
transport = transport.clone('quack')
1431
branch = self.make_remote_branch(transport, client)
1432
branch._format._use_default_local_heads_to_fetch = lambda: False
1433
result = branch.heads_to_fetch()
1434
self.assertFinished(client)
1435
self.assertEqual(({'tip'}, {'tagged-1', 'tagged-2'}), result)
1437
def make_branch_with_tags(self):
1438
self.setup_smart_server_with_call_log()
1439
# Make a branch with a single revision.
1440
builder = self.make_branch_builder('foo')
1441
builder.start_series()
1442
builder.build_snapshot('tip', None, [
1443
('add', ('', 'root-id', 'directory', ''))])
1444
builder.finish_series()
1445
branch = builder.get_branch()
1446
# Add two tags to that branch
1447
branch.tags.set_tag('tag-1', 'rev-1')
1448
branch.tags.set_tag('tag-2', 'rev-2')
1451
def test_backwards_compatible(self):
1452
br = self.make_branch_with_tags()
1453
br.get_config_stack().set('branch.fetch_tags', True)
1454
self.addCleanup(br.lock_read().unlock)
1455
# Disable the heads_to_fetch verb
1456
verb = 'Branch.heads_to_fetch'
1457
self.disable_verb(verb)
1458
self.reset_smart_call_log()
1459
result = br.heads_to_fetch()
1460
self.assertEqual(({'tip'}, {'rev-1', 'rev-2'}), result)
1462
['Branch.last_revision_info', 'Branch.get_tags_bytes'],
1463
[call.call.method for call in self.hpss_calls])
1465
def test_backwards_compatible_no_tags(self):
1466
br = self.make_branch_with_tags()
1467
br.get_config_stack().set('branch.fetch_tags', False)
1468
self.addCleanup(br.lock_read().unlock)
1469
# Disable the heads_to_fetch verb
1470
verb = 'Branch.heads_to_fetch'
1471
self.disable_verb(verb)
1472
self.reset_smart_call_log()
1473
result = br.heads_to_fetch()
1474
self.assertEqual(({'tip'}, set()), result)
1476
['Branch.last_revision_info'],
1477
[call.call.method for call in self.hpss_calls])
1480
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1482
def test_empty_branch(self):
1483
# in an empty branch we decode the response properly
1484
transport = MemoryTransport()
1485
client = FakeClient(transport.base)
1486
client.add_expected_call(
1487
'Branch.get_stacked_on_url', ('quack/',),
1488
'error', ('NotStacked',))
1489
client.add_expected_call(
1490
'Branch.last_revision_info', ('quack/',),
1491
'success', ('ok', '0', 'null:'))
1492
transport.mkdir('quack')
1493
transport = transport.clone('quack')
1494
branch = self.make_remote_branch(transport, client)
1495
result = branch.last_revision_info()
1496
self.assertFinished(client)
1497
self.assertEqual((0, NULL_REVISION), result)
1499
def test_non_empty_branch(self):
1500
# in a non-empty branch we also decode the response properly
1501
revid = u'\xc8'.encode('utf8')
1502
transport = MemoryTransport()
1503
client = FakeClient(transport.base)
1504
client.add_expected_call(
1505
'Branch.get_stacked_on_url', ('kwaak/',),
1506
'error', ('NotStacked',))
1507
client.add_expected_call(
1508
'Branch.last_revision_info', ('kwaak/',),
1509
'success', ('ok', '2', revid))
1510
transport.mkdir('kwaak')
1511
transport = transport.clone('kwaak')
1512
branch = self.make_remote_branch(transport, client)
1513
result = branch.last_revision_info()
1514
self.assertEqual((2, revid), result)
1517
class TestBranch_get_stacked_on_url(TestRemote):
1518
"""Test Branch._get_stacked_on_url rpc"""
1520
def test_get_stacked_on_invalid_url(self):
1521
# test that asking for a stacked on url the server can't access works.
1522
# This isn't perfect, but then as we're in the same process there
1523
# really isn't anything we can do to be 100% sure that the server
1524
# doesn't just open in - this test probably needs to be rewritten using
1525
# a spawn()ed server.
1526
stacked_branch = self.make_branch('stacked', format='1.9')
1527
memory_branch = self.make_branch('base', format='1.9')
1528
vfs_url = self.get_vfs_only_url('base')
1529
stacked_branch.set_stacked_on_url(vfs_url)
1530
transport = stacked_branch.controldir.root_transport
1531
client = FakeClient(transport.base)
1532
client.add_expected_call(
1533
'Branch.get_stacked_on_url', ('stacked/',),
1534
'success', ('ok', vfs_url))
1535
# XXX: Multiple calls are bad, this second call documents what is
1537
client.add_expected_call(
1538
'Branch.get_stacked_on_url', ('stacked/',),
1539
'success', ('ok', vfs_url))
1540
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1542
repo_fmt = remote.RemoteRepositoryFormat()
1543
repo_fmt._custom_format = stacked_branch.repository._format
1544
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1546
result = branch.get_stacked_on_url()
1547
self.assertEqual(vfs_url, result)
1549
def test_backwards_compatible(self):
1550
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1551
base_branch = self.make_branch('base', format='1.6')
1552
stacked_branch = self.make_branch('stacked', format='1.6')
1553
stacked_branch.set_stacked_on_url('../base')
1554
client = FakeClient(self.get_url())
1555
branch_network_name = self.get_branch_format().network_name()
1556
client.add_expected_call(
1557
'BzrDir.open_branchV3', ('stacked/',),
1558
'success', ('branch', branch_network_name))
1559
client.add_expected_call(
1560
'BzrDir.find_repositoryV3', ('stacked/',),
1561
'success', ('ok', '', 'no', 'no', 'yes',
1562
stacked_branch.repository._format.network_name()))
1563
# called twice, once from constructor and then again by us
1564
client.add_expected_call(
1565
'Branch.get_stacked_on_url', ('stacked/',),
1566
'unknown', ('Branch.get_stacked_on_url',))
1567
client.add_expected_call(
1568
'Branch.get_stacked_on_url', ('stacked/',),
1569
'unknown', ('Branch.get_stacked_on_url',))
1570
# this will also do vfs access, but that goes direct to the transport
1571
# and isn't seen by the FakeClient.
1572
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1573
RemoteBzrDirFormat(), _client=client)
1574
branch = bzrdir.open_branch()
1575
result = branch.get_stacked_on_url()
1576
self.assertEqual('../base', result)
1577
self.assertFinished(client)
1578
# it's in the fallback list both for the RemoteRepository and its vfs
1580
self.assertEqual(1, len(branch.repository._fallback_repositories))
1582
len(branch.repository._real_repository._fallback_repositories))
1584
def test_get_stacked_on_real_branch(self):
1585
base_branch = self.make_branch('base')
1586
stacked_branch = self.make_branch('stacked')
1587
stacked_branch.set_stacked_on_url('../base')
1588
reference_format = self.get_repo_format()
1589
network_name = reference_format.network_name()
1590
client = FakeClient(self.get_url())
1591
branch_network_name = self.get_branch_format().network_name()
1592
client.add_expected_call(
1593
'BzrDir.open_branchV3', ('stacked/',),
1594
'success', ('branch', branch_network_name))
1595
client.add_expected_call(
1596
'BzrDir.find_repositoryV3', ('stacked/',),
1597
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1598
# called twice, once from constructor and then again by us
1599
client.add_expected_call(
1600
'Branch.get_stacked_on_url', ('stacked/',),
1601
'success', ('ok', '../base'))
1602
client.add_expected_call(
1603
'Branch.get_stacked_on_url', ('stacked/',),
1604
'success', ('ok', '../base'))
1605
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1606
RemoteBzrDirFormat(), _client=client)
1607
branch = bzrdir.open_branch()
1608
result = branch.get_stacked_on_url()
1609
self.assertEqual('../base', result)
1610
self.assertFinished(client)
1611
# it's in the fallback list both for the RemoteRepository.
1612
self.assertEqual(1, len(branch.repository._fallback_repositories))
1613
# And we haven't had to construct a real repository.
1614
self.assertEqual(None, branch.repository._real_repository)
1617
class TestBranchSetLastRevision(RemoteBranchTestCase):
1619
def test_set_empty(self):
1620
# _set_last_revision_info('null:') is translated to calling
1621
# Branch.set_last_revision(path, '') on the wire.
1622
transport = MemoryTransport()
1623
transport.mkdir('branch')
1624
transport = transport.clone('branch')
1626
client = FakeClient(transport.base)
1627
client.add_expected_call(
1628
'Branch.get_stacked_on_url', ('branch/',),
1629
'error', ('NotStacked',))
1630
client.add_expected_call(
1631
'Branch.lock_write', ('branch/', '', ''),
1632
'success', ('ok', 'branch token', 'repo token'))
1633
client.add_expected_call(
1634
'Branch.last_revision_info',
1636
'success', ('ok', '0', 'null:'))
1637
client.add_expected_call(
1638
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1640
client.add_expected_call(
1641
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1643
branch = self.make_remote_branch(transport, client)
1645
result = branch._set_last_revision(NULL_REVISION)
1647
self.assertEqual(None, result)
1648
self.assertFinished(client)
1650
def test_set_nonempty(self):
1651
# set_last_revision_info(N, rev-idN) is translated to calling
1652
# Branch.set_last_revision(path, rev-idN) on the wire.
1653
transport = MemoryTransport()
1654
transport.mkdir('branch')
1655
transport = transport.clone('branch')
1657
client = FakeClient(transport.base)
1658
client.add_expected_call(
1659
'Branch.get_stacked_on_url', ('branch/',),
1660
'error', ('NotStacked',))
1661
client.add_expected_call(
1662
'Branch.lock_write', ('branch/', '', ''),
1663
'success', ('ok', 'branch token', 'repo token'))
1664
client.add_expected_call(
1665
'Branch.last_revision_info',
1667
'success', ('ok', '0', 'null:'))
1669
encoded_body = bz2.compress('\n'.join(lines))
1670
client.add_success_response_with_body(encoded_body, 'ok')
1671
client.add_expected_call(
1672
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1674
client.add_expected_call(
1675
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1677
branch = self.make_remote_branch(transport, client)
1678
# Lock the branch, reset the record of remote calls.
1680
result = branch._set_last_revision('rev-id2')
1682
self.assertEqual(None, result)
1683
self.assertFinished(client)
1685
def test_no_such_revision(self):
1686
transport = MemoryTransport()
1687
transport.mkdir('branch')
1688
transport = transport.clone('branch')
1689
# A response of 'NoSuchRevision' is translated into an exception.
1690
client = FakeClient(transport.base)
1691
client.add_expected_call(
1692
'Branch.get_stacked_on_url', ('branch/',),
1693
'error', ('NotStacked',))
1694
client.add_expected_call(
1695
'Branch.lock_write', ('branch/', '', ''),
1696
'success', ('ok', 'branch token', 'repo token'))
1697
client.add_expected_call(
1698
'Branch.last_revision_info',
1700
'success', ('ok', '0', 'null:'))
1701
# get_graph calls to construct the revision history, for the set_rh
1704
encoded_body = bz2.compress('\n'.join(lines))
1705
client.add_success_response_with_body(encoded_body, 'ok')
1706
client.add_expected_call(
1707
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1708
'error', ('NoSuchRevision', 'rev-id'))
1709
client.add_expected_call(
1710
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1713
branch = self.make_remote_branch(transport, client)
1716
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1718
self.assertFinished(client)
1720
def test_tip_change_rejected(self):
1721
"""TipChangeRejected responses cause a TipChangeRejected exception to
1724
transport = MemoryTransport()
1725
transport.mkdir('branch')
1726
transport = transport.clone('branch')
1727
client = FakeClient(transport.base)
1728
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1729
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1730
client.add_expected_call(
1731
'Branch.get_stacked_on_url', ('branch/',),
1732
'error', ('NotStacked',))
1733
client.add_expected_call(
1734
'Branch.lock_write', ('branch/', '', ''),
1735
'success', ('ok', 'branch token', 'repo token'))
1736
client.add_expected_call(
1737
'Branch.last_revision_info',
1739
'success', ('ok', '0', 'null:'))
1741
encoded_body = bz2.compress('\n'.join(lines))
1742
client.add_success_response_with_body(encoded_body, 'ok')
1743
client.add_expected_call(
1744
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1745
'error', ('TipChangeRejected', rejection_msg_utf8))
1746
client.add_expected_call(
1747
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1749
branch = self.make_remote_branch(transport, client)
1751
# The 'TipChangeRejected' error response triggered by calling
1752
# set_last_revision_info causes a TipChangeRejected exception.
1753
err = self.assertRaises(
1754
errors.TipChangeRejected,
1755
branch._set_last_revision, 'rev-id')
1756
# The UTF-8 message from the response has been decoded into a unicode
1758
self.assertIsInstance(err.msg, unicode)
1759
self.assertEqual(rejection_msg_unicode, err.msg)
1761
self.assertFinished(client)
1764
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1766
def test_set_last_revision_info(self):
1767
# set_last_revision_info(num, 'rev-id') is translated to calling
1768
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1769
transport = MemoryTransport()
1770
transport.mkdir('branch')
1771
transport = transport.clone('branch')
1772
client = FakeClient(transport.base)
1773
# get_stacked_on_url
1774
client.add_error_response('NotStacked')
1776
client.add_success_response('ok', 'branch token', 'repo token')
1777
# query the current revision
1778
client.add_success_response('ok', '0', 'null:')
1780
client.add_success_response('ok')
1782
client.add_success_response('ok')
1784
branch = self.make_remote_branch(transport, client)
1785
# Lock the branch, reset the record of remote calls.
1788
result = branch.set_last_revision_info(1234, 'a-revision-id')
1790
[('call', 'Branch.last_revision_info', ('branch/',)),
1791
('call', 'Branch.set_last_revision_info',
1792
('branch/', 'branch token', 'repo token',
1793
'1234', 'a-revision-id'))],
1795
self.assertEqual(None, result)
1797
def test_no_such_revision(self):
1798
# A response of 'NoSuchRevision' is translated into an exception.
1799
transport = MemoryTransport()
1800
transport.mkdir('branch')
1801
transport = transport.clone('branch')
1802
client = FakeClient(transport.base)
1803
# get_stacked_on_url
1804
client.add_error_response('NotStacked')
1806
client.add_success_response('ok', 'branch token', 'repo token')
1808
client.add_error_response('NoSuchRevision', 'revid')
1810
client.add_success_response('ok')
1812
branch = self.make_remote_branch(transport, client)
1813
# Lock the branch, reset the record of remote calls.
1818
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1821
def test_backwards_compatibility(self):
1822
"""If the server does not support the Branch.set_last_revision_info
1823
verb (which is new in 1.4), then the client falls back to VFS methods.
1825
# This test is a little messy. Unlike most tests in this file, it
1826
# doesn't purely test what a Remote* object sends over the wire, and
1827
# how it reacts to responses from the wire. It instead relies partly
1828
# on asserting that the RemoteBranch will call
1829
# self._real_branch.set_last_revision_info(...).
1831
# First, set up our RemoteBranch with a FakeClient that raises
1832
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1833
transport = MemoryTransport()
1834
transport.mkdir('branch')
1835
transport = transport.clone('branch')
1836
client = FakeClient(transport.base)
1837
client.add_expected_call(
1838
'Branch.get_stacked_on_url', ('branch/',),
1839
'error', ('NotStacked',))
1840
client.add_expected_call(
1841
'Branch.last_revision_info',
1843
'success', ('ok', '0', 'null:'))
1844
client.add_expected_call(
1845
'Branch.set_last_revision_info',
1846
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1847
'unknown', 'Branch.set_last_revision_info')
1849
branch = self.make_remote_branch(transport, client)
1850
class StubRealBranch(object):
1853
def set_last_revision_info(self, revno, revision_id):
1855
('set_last_revision_info', revno, revision_id))
1856
def _clear_cached_state(self):
1858
real_branch = StubRealBranch()
1859
branch._real_branch = real_branch
1860
self.lock_remote_branch(branch)
1862
# Call set_last_revision_info, and verify it behaved as expected.
1863
result = branch.set_last_revision_info(1234, 'a-revision-id')
1865
[('set_last_revision_info', 1234, 'a-revision-id')],
1867
self.assertFinished(client)
1869
def test_unexpected_error(self):
1870
# If the server sends an error the client doesn't understand, it gets
1871
# turned into an UnknownErrorFromSmartServer, which is presented as a
1872
# non-internal error to the user.
1873
transport = MemoryTransport()
1874
transport.mkdir('branch')
1875
transport = transport.clone('branch')
1876
client = FakeClient(transport.base)
1877
# get_stacked_on_url
1878
client.add_error_response('NotStacked')
1880
client.add_success_response('ok', 'branch token', 'repo token')
1882
client.add_error_response('UnexpectedError')
1884
client.add_success_response('ok')
1886
branch = self.make_remote_branch(transport, client)
1887
# Lock the branch, reset the record of remote calls.
1891
err = self.assertRaises(
1892
errors.UnknownErrorFromSmartServer,
1893
branch.set_last_revision_info, 123, 'revid')
1894
self.assertEqual(('UnexpectedError',), err.error_tuple)
1897
def test_tip_change_rejected(self):
1898
"""TipChangeRejected responses cause a TipChangeRejected exception to
1901
transport = MemoryTransport()
1902
transport.mkdir('branch')
1903
transport = transport.clone('branch')
1904
client = FakeClient(transport.base)
1905
# get_stacked_on_url
1906
client.add_error_response('NotStacked')
1908
client.add_success_response('ok', 'branch token', 'repo token')
1910
client.add_error_response('TipChangeRejected', 'rejection message')
1912
client.add_success_response('ok')
1914
branch = self.make_remote_branch(transport, client)
1915
# Lock the branch, reset the record of remote calls.
1917
self.addCleanup(branch.unlock)
1920
# The 'TipChangeRejected' error response triggered by calling
1921
# set_last_revision_info causes a TipChangeRejected exception.
1922
err = self.assertRaises(
1923
errors.TipChangeRejected,
1924
branch.set_last_revision_info, 123, 'revid')
1925
self.assertEqual('rejection message', err.msg)
1928
class TestBranchGetSetConfig(RemoteBranchTestCase):
1930
def test_get_branch_conf(self):
1931
# in an empty branch we decode the response properly
1932
client = FakeClient()
1933
client.add_expected_call(
1934
'Branch.get_stacked_on_url', ('memory:///',),
1935
'error', ('NotStacked',),)
1936
client.add_success_response_with_body('# config file body', 'ok')
1937
transport = MemoryTransport()
1938
branch = self.make_remote_branch(transport, client)
1939
config = branch.get_config()
1940
config.has_explicit_nickname()
1942
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1943
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1946
def test_get_multi_line_branch_conf(self):
1947
# Make sure that multiple-line branch.conf files are supported
1949
# https://bugs.launchpad.net/bzr/+bug/354075
1950
client = FakeClient()
1951
client.add_expected_call(
1952
'Branch.get_stacked_on_url', ('memory:///',),
1953
'error', ('NotStacked',),)
1954
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1955
transport = MemoryTransport()
1956
branch = self.make_remote_branch(transport, client)
1957
config = branch.get_config()
1958
self.assertEqual(u'2', config.get_user_option('b'))
1960
def test_set_option(self):
1961
client = FakeClient()
1962
client.add_expected_call(
1963
'Branch.get_stacked_on_url', ('memory:///',),
1964
'error', ('NotStacked',),)
1965
client.add_expected_call(
1966
'Branch.lock_write', ('memory:///', '', ''),
1967
'success', ('ok', 'branch token', 'repo token'))
1968
client.add_expected_call(
1969
'Branch.set_config_option', ('memory:///', 'branch token',
1970
'repo token', 'foo', 'bar', ''),
1972
client.add_expected_call(
1973
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1975
transport = MemoryTransport()
1976
branch = self.make_remote_branch(transport, client)
1978
config = branch._get_config()
1979
config.set_option('foo', 'bar')
1981
self.assertFinished(client)
1983
def test_set_option_with_dict(self):
1984
client = FakeClient()
1985
client.add_expected_call(
1986
'Branch.get_stacked_on_url', ('memory:///',),
1987
'error', ('NotStacked',),)
1988
client.add_expected_call(
1989
'Branch.lock_write', ('memory:///', '', ''),
1990
'success', ('ok', 'branch token', 'repo token'))
1991
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1992
client.add_expected_call(
1993
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1994
'repo token', encoded_dict_value, 'foo', ''),
1996
client.add_expected_call(
1997
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1999
transport = MemoryTransport()
2000
branch = self.make_remote_branch(transport, client)
2002
config = branch._get_config()
2004
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2007
self.assertFinished(client)
2009
def test_backwards_compat_set_option(self):
2010
self.setup_smart_server_with_call_log()
2011
branch = self.make_branch('.')
2012
verb = 'Branch.set_config_option'
2013
self.disable_verb(verb)
2015
self.addCleanup(branch.unlock)
2016
self.reset_smart_call_log()
2017
branch._get_config().set_option('value', 'name')
2018
self.assertLength(11, self.hpss_calls)
2019
self.assertEqual('value', branch._get_config().get_option('name'))
2021
def test_backwards_compat_set_option_with_dict(self):
2022
self.setup_smart_server_with_call_log()
2023
branch = self.make_branch('.')
2024
verb = 'Branch.set_config_option_dict'
2025
self.disable_verb(verb)
2027
self.addCleanup(branch.unlock)
2028
self.reset_smart_call_log()
2029
config = branch._get_config()
2030
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2031
config.set_option(value_dict, 'name')
2032
self.assertLength(11, self.hpss_calls)
2033
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2036
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2038
def test_get_branch_conf(self):
2039
# in an empty branch we decode the response properly
2040
client = FakeClient()
2041
client.add_expected_call(
2042
'Branch.get_stacked_on_url', ('memory:///',),
2043
'error', ('NotStacked',),)
2044
client.add_success_response_with_body('# config file body', 'ok')
2045
transport = MemoryTransport()
2046
branch = self.make_remote_branch(transport, client)
2047
config = branch.get_config_stack()
2049
config.get("log_format")
2051
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2052
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2055
def test_set_branch_conf(self):
2056
client = FakeClient()
2057
client.add_expected_call(
2058
'Branch.get_stacked_on_url', ('memory:///',),
2059
'error', ('NotStacked',),)
2060
client.add_expected_call(
2061
'Branch.lock_write', ('memory:///', '', ''),
2062
'success', ('ok', 'branch token', 'repo token'))
2063
client.add_expected_call(
2064
'Branch.get_config_file', ('memory:///', ),
2065
'success', ('ok', ), "# line 1\n")
2066
client.add_expected_call(
2067
'Branch.get_config_file', ('memory:///', ),
2068
'success', ('ok', ), "# line 1\n")
2069
client.add_expected_call(
2070
'Branch.put_config_file', ('memory:///', 'branch token',
2073
client.add_expected_call(
2074
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2076
transport = MemoryTransport()
2077
branch = self.make_remote_branch(transport, client)
2079
config = branch.get_config_stack()
2080
config.set('email', 'The Dude <lebowski@example.com>')
2082
self.assertFinished(client)
2084
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2085
('call', 'Branch.lock_write', ('memory:///', '', '')),
2086
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2087
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2088
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2089
('memory:///', 'branch token', 'repo token'),
2090
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2091
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2095
class TestBranchLockWrite(RemoteBranchTestCase):
2097
def test_lock_write_unlockable(self):
2098
transport = MemoryTransport()
2099
client = FakeClient(transport.base)
2100
client.add_expected_call(
2101
'Branch.get_stacked_on_url', ('quack/',),
2102
'error', ('NotStacked',),)
2103
client.add_expected_call(
2104
'Branch.lock_write', ('quack/', '', ''),
2105
'error', ('UnlockableTransport',))
2106
transport.mkdir('quack')
2107
transport = transport.clone('quack')
2108
branch = self.make_remote_branch(transport, client)
2109
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2110
self.assertFinished(client)
2113
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2115
def test_simple(self):
2116
transport = MemoryTransport()
2117
client = FakeClient(transport.base)
2118
client.add_expected_call(
2119
'Branch.get_stacked_on_url', ('quack/',),
2120
'error', ('NotStacked',),)
2121
client.add_expected_call(
2122
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2123
'success', ('ok', '0',),)
2124
client.add_expected_call(
2125
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2126
'error', ('NoSuchRevision', 'unknown',),)
2127
transport.mkdir('quack')
2128
transport = transport.clone('quack')
2129
branch = self.make_remote_branch(transport, client)
2130
self.assertEqual(0, branch.revision_id_to_revno('null:'))
2131
self.assertRaises(errors.NoSuchRevision,
2132
branch.revision_id_to_revno, 'unknown')
2133
self.assertFinished(client)
2135
def test_dotted(self):
2136
transport = MemoryTransport()
2137
client = FakeClient(transport.base)
2138
client.add_expected_call(
2139
'Branch.get_stacked_on_url', ('quack/',),
2140
'error', ('NotStacked',),)
2141
client.add_expected_call(
2142
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2143
'success', ('ok', '0',),)
2144
client.add_expected_call(
2145
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2146
'error', ('NoSuchRevision', 'unknown',),)
2147
transport.mkdir('quack')
2148
transport = transport.clone('quack')
2149
branch = self.make_remote_branch(transport, client)
2150
self.assertEqual((0, ), branch.revision_id_to_dotted_revno('null:'))
2151
self.assertRaises(errors.NoSuchRevision,
2152
branch.revision_id_to_dotted_revno, 'unknown')
2153
self.assertFinished(client)
2155
def test_dotted_no_smart_verb(self):
2156
self.setup_smart_server_with_call_log()
2157
branch = self.make_branch('.')
2158
self.disable_verb('Branch.revision_id_to_revno')
2159
self.reset_smart_call_log()
2160
self.assertEqual((0, ),
2161
branch.revision_id_to_dotted_revno('null:'))
2162
self.assertLength(8, self.hpss_calls)
2165
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2167
def test__get_config(self):
2168
client = FakeClient()
2169
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2170
transport = MemoryTransport()
2171
bzrdir = self.make_remote_bzrdir(transport, client)
2172
config = bzrdir.get_config()
2173
self.assertEqual('/', config.get_default_stack_on())
2175
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2178
def test_set_option_uses_vfs(self):
2179
self.setup_smart_server_with_call_log()
2180
bzrdir = self.make_controldir('.')
2181
self.reset_smart_call_log()
2182
config = bzrdir.get_config()
2183
config.set_default_stack_on('/')
2184
self.assertLength(4, self.hpss_calls)
2186
def test_backwards_compat_get_option(self):
2187
self.setup_smart_server_with_call_log()
2188
bzrdir = self.make_controldir('.')
2189
verb = 'BzrDir.get_config_file'
2190
self.disable_verb(verb)
2191
self.reset_smart_call_log()
2192
self.assertEqual(None,
2193
bzrdir._get_config().get_option('default_stack_on'))
2194
self.assertLength(4, self.hpss_calls)
2197
class TestTransportIsReadonly(tests.TestCase):
2199
def test_true(self):
2200
client = FakeClient()
2201
client.add_success_response('yes')
2202
transport = RemoteTransport('bzr://example.com/', medium=False,
2204
self.assertEqual(True, transport.is_readonly())
2206
[('call', 'Transport.is_readonly', ())],
2209
def test_false(self):
2210
client = FakeClient()
2211
client.add_success_response('no')
2212
transport = RemoteTransport('bzr://example.com/', medium=False,
2214
self.assertEqual(False, transport.is_readonly())
2216
[('call', 'Transport.is_readonly', ())],
2219
def test_error_from_old_server(self):
2220
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2222
Clients should treat it as a "no" response, because is_readonly is only
2223
advisory anyway (a transport could be read-write, but then the
2224
underlying filesystem could be readonly anyway).
2226
client = FakeClient()
2227
client.add_unknown_method_response('Transport.is_readonly')
2228
transport = RemoteTransport('bzr://example.com/', medium=False,
2230
self.assertEqual(False, transport.is_readonly())
2232
[('call', 'Transport.is_readonly', ())],
2236
class TestTransportMkdir(tests.TestCase):
2238
def test_permissiondenied(self):
2239
client = FakeClient()
2240
client.add_error_response('PermissionDenied', 'remote path', 'extra')
2241
transport = RemoteTransport('bzr://example.com/', medium=False,
2243
exc = self.assertRaises(
2244
errors.PermissionDenied, transport.mkdir, 'client path')
2245
expected_error = errors.PermissionDenied('/client path', 'extra')
2246
self.assertEqual(expected_error, exc)
2249
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2251
def test_defaults_to_none(self):
2252
t = RemoteSSHTransport('bzr+ssh://example.com')
2253
self.assertIs(None, t._get_credentials()[0])
2255
def test_uses_authentication_config(self):
2256
conf = config.AuthenticationConfig()
2257
conf._get_config().update(
2258
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2261
t = RemoteSSHTransport('bzr+ssh://example.com')
2262
self.assertEqual('bar', t._get_credentials()[0])
2265
class TestRemoteRepository(TestRemote):
2266
"""Base for testing RemoteRepository protocol usage.
2268
These tests contain frozen requests and responses. We want any changes to
2269
what is sent or expected to be require a thoughtful update to these tests
2270
because they might break compatibility with different-versioned servers.
2273
def setup_fake_client_and_repository(self, transport_path):
2274
"""Create the fake client and repository for testing with.
2276
There's no real server here; we just have canned responses sent
2279
:param transport_path: Path below the root of the MemoryTransport
2280
where the repository will be created.
2282
transport = MemoryTransport()
2283
transport.mkdir(transport_path)
2284
client = FakeClient(transport.base)
2285
transport = transport.clone(transport_path)
2286
# we do not want bzrdir to make any remote calls
2287
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2289
repo = RemoteRepository(bzrdir, None, _client=client)
2293
def remoted_description(format):
2294
return 'Remote: ' + format.get_format_description()
2297
class TestBranchFormat(tests.TestCase):
2299
def test_get_format_description(self):
2300
remote_format = RemoteBranchFormat()
2301
real_format = branch.format_registry.get_default()
2302
remote_format._network_name = real_format.network_name()
2303
self.assertEqual(remoted_description(real_format),
2304
remote_format.get_format_description())
2307
class TestRepositoryFormat(TestRemoteRepository):
2309
def test_fast_delta(self):
2310
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2311
true_format = RemoteRepositoryFormat()
2312
true_format._network_name = true_name
2313
self.assertEqual(True, true_format.fast_deltas)
2314
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2315
false_format = RemoteRepositoryFormat()
2316
false_format._network_name = false_name
2317
self.assertEqual(False, false_format.fast_deltas)
2319
def test_get_format_description(self):
2320
remote_repo_format = RemoteRepositoryFormat()
2321
real_format = repository.format_registry.get_default()
2322
remote_repo_format._network_name = real_format.network_name()
2323
self.assertEqual(remoted_description(real_format),
2324
remote_repo_format.get_format_description())
2327
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2329
def test_empty(self):
2330
transport_path = 'quack'
2331
repo, client = self.setup_fake_client_and_repository(transport_path)
2332
client.add_success_response_with_body('', 'ok')
2333
self.assertEqual([], repo.all_revision_ids())
2335
[('call_expecting_body', 'Repository.all_revision_ids',
2339
def test_with_some_content(self):
2340
transport_path = 'quack'
2341
repo, client = self.setup_fake_client_and_repository(transport_path)
2342
client.add_success_response_with_body(
2343
'rev1\nrev2\nanotherrev\n', 'ok')
2344
self.assertEqual(["rev1", "rev2", "anotherrev"],
2345
repo.all_revision_ids())
2347
[('call_expecting_body', 'Repository.all_revision_ids',
2352
class TestRepositoryGatherStats(TestRemoteRepository):
2354
def test_revid_none(self):
2355
# ('ok',), body with revisions and size
2356
transport_path = 'quack'
2357
repo, client = self.setup_fake_client_and_repository(transport_path)
2358
client.add_success_response_with_body(
2359
'revisions: 2\nsize: 18\n', 'ok')
2360
result = repo.gather_stats(None)
2362
[('call_expecting_body', 'Repository.gather_stats',
2363
('quack/','','no'))],
2365
self.assertEqual({'revisions': 2, 'size': 18}, result)
2367
def test_revid_no_committers(self):
2368
# ('ok',), body without committers
2369
body = ('firstrev: 123456.300 3600\n'
2370
'latestrev: 654231.400 0\n'
2373
transport_path = 'quick'
2374
revid = u'\xc8'.encode('utf8')
2375
repo, client = self.setup_fake_client_and_repository(transport_path)
2376
client.add_success_response_with_body(body, 'ok')
2377
result = repo.gather_stats(revid)
2379
[('call_expecting_body', 'Repository.gather_stats',
2380
('quick/', revid, 'no'))],
2382
self.assertEqual({'revisions': 2, 'size': 18,
2383
'firstrev': (123456.300, 3600),
2384
'latestrev': (654231.400, 0),},
2387
def test_revid_with_committers(self):
2388
# ('ok',), body with committers
2389
body = ('committers: 128\n'
2390
'firstrev: 123456.300 3600\n'
2391
'latestrev: 654231.400 0\n'
2394
transport_path = 'buick'
2395
revid = u'\xc8'.encode('utf8')
2396
repo, client = self.setup_fake_client_and_repository(transport_path)
2397
client.add_success_response_with_body(body, 'ok')
2398
result = repo.gather_stats(revid, True)
2400
[('call_expecting_body', 'Repository.gather_stats',
2401
('buick/', revid, 'yes'))],
2403
self.assertEqual({'revisions': 2, 'size': 18,
2405
'firstrev': (123456.300, 3600),
2406
'latestrev': (654231.400, 0),},
2410
class TestRepositoryBreakLock(TestRemoteRepository):
2412
def test_break_lock(self):
2413
transport_path = 'quack'
2414
repo, client = self.setup_fake_client_and_repository(transport_path)
2415
client.add_success_response('ok')
2418
[('call', 'Repository.break_lock', ('quack/',))],
2422
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2424
def test_get_serializer_format(self):
2425
transport_path = 'hill'
2426
repo, client = self.setup_fake_client_and_repository(transport_path)
2427
client.add_success_response('ok', '7')
2428
self.assertEqual('7', repo.get_serializer_format())
2430
[('call', 'VersionedFileRepository.get_serializer_format',
2435
class TestRepositoryReconcile(TestRemoteRepository):
2437
def test_reconcile(self):
2438
transport_path = 'hill'
2439
repo, client = self.setup_fake_client_and_repository(transport_path)
2440
body = ("garbage_inventories: 2\n"
2441
"inconsistent_parents: 3\n")
2442
client.add_expected_call(
2443
'Repository.lock_write', ('hill/', ''),
2444
'success', ('ok', 'a token'))
2445
client.add_success_response_with_body(body, 'ok')
2446
reconciler = repo.reconcile()
2448
[('call', 'Repository.lock_write', ('hill/', '')),
2449
('call_expecting_body', 'Repository.reconcile',
2450
('hill/', 'a token'))],
2452
self.assertEqual(2, reconciler.garbage_inventories)
2453
self.assertEqual(3, reconciler.inconsistent_parents)
2456
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2458
def test_text(self):
2459
# ('ok',), body with signature text
2460
transport_path = 'quack'
2461
repo, client = self.setup_fake_client_and_repository(transport_path)
2462
client.add_success_response_with_body(
2464
self.assertEqual("THETEXT", repo.get_signature_text("revid"))
2466
[('call_expecting_body', 'Repository.get_revision_signature_text',
2467
('quack/', 'revid'))],
2470
def test_no_signature(self):
2471
transport_path = 'quick'
2472
repo, client = self.setup_fake_client_and_repository(transport_path)
2473
client.add_error_response('nosuchrevision', 'unknown')
2474
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2477
[('call_expecting_body', 'Repository.get_revision_signature_text',
2478
('quick/', 'unknown'))],
2482
class TestRepositoryGetGraph(TestRemoteRepository):
2484
def test_get_graph(self):
2485
# get_graph returns a graph with a custom parents provider.
2486
transport_path = 'quack'
2487
repo, client = self.setup_fake_client_and_repository(transport_path)
2488
graph = repo.get_graph()
2489
self.assertNotEqual(graph._parents_provider, repo)
2492
class TestRepositoryAddSignatureText(TestRemoteRepository):
2494
def test_add_signature_text(self):
2495
transport_path = 'quack'
2496
repo, client = self.setup_fake_client_and_repository(transport_path)
2497
client.add_expected_call(
2498
'Repository.lock_write', ('quack/', ''),
2499
'success', ('ok', 'a token'))
2500
client.add_expected_call(
2501
'Repository.start_write_group', ('quack/', 'a token'),
2502
'success', ('ok', ('token1', )))
2503
client.add_expected_call(
2504
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2506
'success', ('ok', ), None)
2508
repo.start_write_group()
2510
repo.add_signature_text("rev1", "every bloody emperor"))
2512
('call_with_body_bytes_expecting_body',
2513
'Repository.add_signature_text',
2514
('quack/', 'a token', 'rev1', 'token1'),
2515
'every bloody emperor'),
2519
class TestRepositoryGetParentMap(TestRemoteRepository):
2521
def test_get_parent_map_caching(self):
2522
# get_parent_map returns from cache until unlock()
2523
# setup a reponse with two revisions
2524
r1 = u'\u0e33'.encode('utf8')
2525
r2 = u'\u0dab'.encode('utf8')
2526
lines = [' '.join([r2, r1]), r1]
2527
encoded_body = bz2.compress('\n'.join(lines))
2529
transport_path = 'quack'
2530
repo, client = self.setup_fake_client_and_repository(transport_path)
2531
client.add_success_response_with_body(encoded_body, 'ok')
2532
client.add_success_response_with_body(encoded_body, 'ok')
2534
graph = repo.get_graph()
2535
parents = graph.get_parent_map([r2])
2536
self.assertEqual({r2: (r1,)}, parents)
2537
# locking and unlocking deeper should not reset
2540
parents = graph.get_parent_map([r1])
2541
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2543
[('call_with_body_bytes_expecting_body',
2544
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2548
# now we call again, and it should use the second response.
2550
graph = repo.get_graph()
2551
parents = graph.get_parent_map([r1])
2552
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2554
[('call_with_body_bytes_expecting_body',
2555
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2557
('call_with_body_bytes_expecting_body',
2558
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2564
def test_get_parent_map_reconnects_if_unknown_method(self):
2565
transport_path = 'quack'
2566
rev_id = 'revision-id'
2567
repo, client = self.setup_fake_client_and_repository(transport_path)
2568
client.add_unknown_method_response('Repository.get_parent_map')
2569
client.add_success_response_with_body(rev_id, 'ok')
2570
self.assertFalse(client._medium._is_remote_before((1, 2)))
2571
parents = repo.get_parent_map([rev_id])
2573
[('call_with_body_bytes_expecting_body',
2574
'Repository.get_parent_map',
2575
('quack/', 'include-missing:', rev_id), '\n\n0'),
2576
('disconnect medium',),
2577
('call_expecting_body', 'Repository.get_revision_graph',
2580
# The medium is now marked as being connected to an older server
2581
self.assertTrue(client._medium._is_remote_before((1, 2)))
2582
self.assertEqual({rev_id: ('null:',)}, parents)
2584
def test_get_parent_map_fallback_parentless_node(self):
2585
"""get_parent_map falls back to get_revision_graph on old servers. The
2586
results from get_revision_graph are tweaked to match the get_parent_map
2589
Specifically, a {key: ()} result from get_revision_graph means "no
2590
parents" for that key, which in get_parent_map results should be
2591
represented as {key: ('null:',)}.
2593
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2595
rev_id = 'revision-id'
2596
transport_path = 'quack'
2597
repo, client = self.setup_fake_client_and_repository(transport_path)
2598
client.add_success_response_with_body(rev_id, 'ok')
2599
client._medium._remember_remote_is_before((1, 2))
2600
parents = repo.get_parent_map([rev_id])
2602
[('call_expecting_body', 'Repository.get_revision_graph',
2605
self.assertEqual({rev_id: ('null:',)}, parents)
2607
def test_get_parent_map_unexpected_response(self):
2608
repo, client = self.setup_fake_client_and_repository('path')
2609
client.add_success_response('something unexpected!')
2611
errors.UnexpectedSmartServerResponse,
2612
repo.get_parent_map, ['a-revision-id'])
2614
def test_get_parent_map_negative_caches_missing_keys(self):
2615
self.setup_smart_server_with_call_log()
2616
repo = self.make_repository('foo')
2617
self.assertIsInstance(repo, RemoteRepository)
2619
self.addCleanup(repo.unlock)
2620
self.reset_smart_call_log()
2621
graph = repo.get_graph()
2622
self.assertEqual({},
2623
graph.get_parent_map(['some-missing', 'other-missing']))
2624
self.assertLength(1, self.hpss_calls)
2625
# No call if we repeat this
2626
self.reset_smart_call_log()
2627
graph = repo.get_graph()
2628
self.assertEqual({},
2629
graph.get_parent_map(['some-missing', 'other-missing']))
2630
self.assertLength(0, self.hpss_calls)
2631
# Asking for more unknown keys makes a request.
2632
self.reset_smart_call_log()
2633
graph = repo.get_graph()
2634
self.assertEqual({},
2635
graph.get_parent_map(['some-missing', 'other-missing',
2637
self.assertLength(1, self.hpss_calls)
2639
def disableExtraResults(self):
2640
self.overrideAttr(SmartServerRepositoryGetParentMap,
2641
'no_extra_results', True)
2643
def test_null_cached_missing_and_stop_key(self):
2644
self.setup_smart_server_with_call_log()
2645
# Make a branch with a single revision.
2646
builder = self.make_branch_builder('foo')
2647
builder.start_series()
2648
builder.build_snapshot('first', None, [
2649
('add', ('', 'root-id', 'directory', ''))])
2650
builder.finish_series()
2651
branch = builder.get_branch()
2652
repo = branch.repository
2653
self.assertIsInstance(repo, RemoteRepository)
2654
# Stop the server from sending extra results.
2655
self.disableExtraResults()
2657
self.addCleanup(repo.unlock)
2658
self.reset_smart_call_log()
2659
graph = repo.get_graph()
2660
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2661
# 'first' it will be a candidate for the stop_keys of subsequent
2662
# requests, and because 'null:' was queried but not returned it will be
2663
# cached as missing.
2664
self.assertEqual({'first': ('null:',)},
2665
graph.get_parent_map(['first', 'null:']))
2666
# Now query for another key. This request will pass along a recipe of
2667
# start and stop keys describing the already cached results, and this
2668
# recipe's revision count must be correct (or else it will trigger an
2669
# error from the server).
2670
self.assertEqual({}, graph.get_parent_map(['another-key']))
2671
# This assertion guards against disableExtraResults silently failing to
2672
# work, thus invalidating the test.
2673
self.assertLength(2, self.hpss_calls)
2675
def test_get_parent_map_gets_ghosts_from_result(self):
2676
# asking for a revision should negatively cache close ghosts in its
2678
self.setup_smart_server_with_call_log()
2679
tree = self.make_branch_and_memory_tree('foo')
2682
builder = treebuilder.TreeBuilder()
2683
builder.start_tree(tree)
2685
builder.finish_tree()
2686
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2687
rev_id = tree.commit('')
2691
self.addCleanup(tree.unlock)
2692
repo = tree.branch.repository
2693
self.assertIsInstance(repo, RemoteRepository)
2695
repo.get_parent_map([rev_id])
2696
self.reset_smart_call_log()
2697
# Now asking for rev_id's ghost parent should not make calls
2698
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2699
self.assertLength(0, self.hpss_calls)
2701
def test_exposes_get_cached_parent_map(self):
2702
"""RemoteRepository exposes get_cached_parent_map from
2705
r1 = u'\u0e33'.encode('utf8')
2706
r2 = u'\u0dab'.encode('utf8')
2707
lines = [' '.join([r2, r1]), r1]
2708
encoded_body = bz2.compress('\n'.join(lines))
2710
transport_path = 'quack'
2711
repo, client = self.setup_fake_client_and_repository(transport_path)
2712
client.add_success_response_with_body(encoded_body, 'ok')
2714
# get_cached_parent_map should *not* trigger an RPC
2715
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2716
self.assertEqual([], client._calls)
2717
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2718
self.assertEqual({r1: (NULL_REVISION,)},
2719
repo.get_cached_parent_map([r1]))
2721
[('call_with_body_bytes_expecting_body',
2722
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2728
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2730
def test_allows_new_revisions(self):
2731
"""get_parent_map's results can be updated by commit."""
2732
smart_server = test_server.SmartTCPServer_for_testing()
2733
self.start_server(smart_server)
2734
self.make_branch('branch')
2735
branch = Branch.open(smart_server.get_url() + '/branch')
2736
tree = branch.create_checkout('tree', lightweight=True)
2738
self.addCleanup(tree.unlock)
2739
graph = tree.branch.repository.get_graph()
2740
# This provides an opportunity for the missing rev-id to be cached.
2741
self.assertEqual({}, graph.get_parent_map(['rev1']))
2742
tree.commit('message', rev_id='rev1')
2743
graph = tree.branch.repository.get_graph()
2744
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2747
class TestRepositoryGetRevisions(TestRemoteRepository):
2749
def test_hpss_missing_revision(self):
2750
transport_path = 'quack'
2751
repo, client = self.setup_fake_client_and_repository(transport_path)
2752
client.add_success_response_with_body(
2754
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2755
['somerev1', 'anotherrev2'])
2757
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2758
('quack/', ), "somerev1\nanotherrev2")],
2761
def test_hpss_get_single_revision(self):
2762
transport_path = 'quack'
2763
repo, client = self.setup_fake_client_and_repository(transport_path)
2764
somerev1 = Revision("somerev1")
2765
somerev1.committer = "Joe Committer <joe@example.com>"
2766
somerev1.timestamp = 1321828927
2767
somerev1.timezone = -60
2768
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2769
somerev1.message = "Message"
2770
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2772
# Split up body into two bits to make sure the zlib compression object
2773
# gets data fed twice.
2774
client.add_success_response_with_body(
2775
[body[:10], body[10:]], 'ok', '10')
2776
revs = repo.get_revisions(['somerev1'])
2777
self.assertEqual(revs, [somerev1])
2779
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2780
('quack/', ), "somerev1")],
2784
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2786
def test_null_revision(self):
2787
# a null revision has the predictable result {}, we should have no wire
2788
# traffic when calling it with this argument
2789
transport_path = 'empty'
2790
repo, client = self.setup_fake_client_and_repository(transport_path)
2791
client.add_success_response('notused')
2792
# actual RemoteRepository.get_revision_graph is gone, but there's an
2793
# equivalent private method for testing
2794
result = repo._get_revision_graph(NULL_REVISION)
2795
self.assertEqual([], client._calls)
2796
self.assertEqual({}, result)
2798
def test_none_revision(self):
2799
# with none we want the entire graph
2800
r1 = u'\u0e33'.encode('utf8')
2801
r2 = u'\u0dab'.encode('utf8')
2802
lines = [' '.join([r2, r1]), r1]
2803
encoded_body = '\n'.join(lines)
2805
transport_path = 'sinhala'
2806
repo, client = self.setup_fake_client_and_repository(transport_path)
2807
client.add_success_response_with_body(encoded_body, 'ok')
2808
# actual RemoteRepository.get_revision_graph is gone, but there's an
2809
# equivalent private method for testing
2810
result = repo._get_revision_graph(None)
2812
[('call_expecting_body', 'Repository.get_revision_graph',
2815
self.assertEqual({r1: (), r2: (r1, )}, result)
2817
def test_specific_revision(self):
2818
# with a specific revision we want the graph for that
2819
# with none we want the entire graph
2820
r11 = u'\u0e33'.encode('utf8')
2821
r12 = u'\xc9'.encode('utf8')
2822
r2 = u'\u0dab'.encode('utf8')
2823
lines = [' '.join([r2, r11, r12]), r11, r12]
2824
encoded_body = '\n'.join(lines)
2826
transport_path = 'sinhala'
2827
repo, client = self.setup_fake_client_and_repository(transport_path)
2828
client.add_success_response_with_body(encoded_body, 'ok')
2829
result = repo._get_revision_graph(r2)
2831
[('call_expecting_body', 'Repository.get_revision_graph',
2834
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2836
def test_no_such_revision(self):
2838
transport_path = 'sinhala'
2839
repo, client = self.setup_fake_client_and_repository(transport_path)
2840
client.add_error_response('nosuchrevision', revid)
2841
# also check that the right revision is reported in the error
2842
self.assertRaises(errors.NoSuchRevision,
2843
repo._get_revision_graph, revid)
2845
[('call_expecting_body', 'Repository.get_revision_graph',
2846
('sinhala/', revid))],
2849
def test_unexpected_error(self):
2851
transport_path = 'sinhala'
2852
repo, client = self.setup_fake_client_and_repository(transport_path)
2853
client.add_error_response('AnUnexpectedError')
2854
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2855
repo._get_revision_graph, revid)
2856
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2859
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2862
repo, client = self.setup_fake_client_and_repository('quack')
2863
client.add_expected_call(
2864
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2865
'success', ('ok', 'rev-five'))
2866
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2867
self.assertEqual((True, 'rev-five'), result)
2868
self.assertFinished(client)
2870
def test_history_incomplete(self):
2871
repo, client = self.setup_fake_client_and_repository('quack')
2872
client.add_expected_call(
2873
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2874
'success', ('history-incomplete', 10, 'rev-ten'))
2875
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2876
self.assertEqual((False, (10, 'rev-ten')), result)
2877
self.assertFinished(client)
2879
def test_history_incomplete_with_fallback(self):
2880
"""A 'history-incomplete' response causes the fallback repository to be
2881
queried too, if one is set.
2883
# Make a repo with a fallback repo, both using a FakeClient.
2884
format = remote.response_tuple_to_repo_format(
2885
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2886
repo, client = self.setup_fake_client_and_repository('quack')
2887
repo._format = format
2888
fallback_repo, ignored = self.setup_fake_client_and_repository(
2890
fallback_repo._client = client
2891
fallback_repo._format = format
2892
repo.add_fallback_repository(fallback_repo)
2893
# First the client should ask the primary repo
2894
client.add_expected_call(
2895
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2896
'success', ('history-incomplete', 2, 'rev-two'))
2897
# Then it should ask the fallback, using revno/revid from the
2898
# history-incomplete response as the known revno/revid.
2899
client.add_expected_call(
2900
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2901
'success', ('ok', 'rev-one'))
2902
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2903
self.assertEqual((True, 'rev-one'), result)
2904
self.assertFinished(client)
2906
def test_nosuchrevision(self):
2907
# 'nosuchrevision' is returned when the known-revid is not found in the
2908
# remote repo. The client translates that response to NoSuchRevision.
2909
repo, client = self.setup_fake_client_and_repository('quack')
2910
client.add_expected_call(
2911
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2912
'error', ('nosuchrevision', 'rev-foo'))
2914
errors.NoSuchRevision,
2915
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2916
self.assertFinished(client)
2918
def test_branch_fallback_locking(self):
2919
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2920
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2921
will be invoked, which will fail if the repo is unlocked.
2923
self.setup_smart_server_with_call_log()
2924
tree = self.make_branch_and_memory_tree('.')
2927
rev1 = tree.commit('First')
2928
rev2 = tree.commit('Second')
2930
branch = tree.branch
2931
self.assertFalse(branch.is_locked())
2932
self.reset_smart_call_log()
2933
verb = 'Repository.get_rev_id_for_revno'
2934
self.disable_verb(verb)
2935
self.assertEqual(rev1, branch.get_rev_id(1))
2936
self.assertLength(1, [call for call in self.hpss_calls if
2937
call.call.method == verb])
2940
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2942
def test_has_signature_for_revision_id(self):
2943
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2944
transport_path = 'quack'
2945
repo, client = self.setup_fake_client_and_repository(transport_path)
2946
client.add_success_response('yes')
2947
result = repo.has_signature_for_revision_id('A')
2949
[('call', 'Repository.has_signature_for_revision_id',
2952
self.assertEqual(True, result)
2954
def test_is_not_shared(self):
2955
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2956
transport_path = 'qwack'
2957
repo, client = self.setup_fake_client_and_repository(transport_path)
2958
client.add_success_response('no')
2959
result = repo.has_signature_for_revision_id('A')
2961
[('call', 'Repository.has_signature_for_revision_id',
2964
self.assertEqual(False, result)
2967
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2969
def test_get_physical_lock_status_yes(self):
2970
transport_path = 'qwack'
2971
repo, client = self.setup_fake_client_and_repository(transport_path)
2972
client.add_success_response('yes')
2973
result = repo.get_physical_lock_status()
2975
[('call', 'Repository.get_physical_lock_status',
2978
self.assertEqual(True, result)
2980
def test_get_physical_lock_status_no(self):
2981
transport_path = 'qwack'
2982
repo, client = self.setup_fake_client_and_repository(transport_path)
2983
client.add_success_response('no')
2984
result = repo.get_physical_lock_status()
2986
[('call', 'Repository.get_physical_lock_status',
2989
self.assertEqual(False, result)
2992
class TestRepositoryIsShared(TestRemoteRepository):
2994
def test_is_shared(self):
2995
# ('yes', ) for Repository.is_shared -> 'True'.
2996
transport_path = 'quack'
2997
repo, client = self.setup_fake_client_and_repository(transport_path)
2998
client.add_success_response('yes')
2999
result = repo.is_shared()
3001
[('call', 'Repository.is_shared', ('quack/',))],
3003
self.assertEqual(True, result)
3005
def test_is_not_shared(self):
3006
# ('no', ) for Repository.is_shared -> 'False'.
3007
transport_path = 'qwack'
3008
repo, client = self.setup_fake_client_and_repository(transport_path)
3009
client.add_success_response('no')
3010
result = repo.is_shared()
3012
[('call', 'Repository.is_shared', ('qwack/',))],
3014
self.assertEqual(False, result)
3017
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3019
def test_make_working_trees(self):
3020
# ('yes', ) for Repository.make_working_trees -> 'True'.
3021
transport_path = 'quack'
3022
repo, client = self.setup_fake_client_and_repository(transport_path)
3023
client.add_success_response('yes')
3024
result = repo.make_working_trees()
3026
[('call', 'Repository.make_working_trees', ('quack/',))],
3028
self.assertEqual(True, result)
3030
def test_no_working_trees(self):
3031
# ('no', ) for Repository.make_working_trees -> 'False'.
3032
transport_path = 'qwack'
3033
repo, client = self.setup_fake_client_and_repository(transport_path)
3034
client.add_success_response('no')
3035
result = repo.make_working_trees()
3037
[('call', 'Repository.make_working_trees', ('qwack/',))],
3039
self.assertEqual(False, result)
3042
class TestRepositoryLockWrite(TestRemoteRepository):
3044
def test_lock_write(self):
3045
transport_path = 'quack'
3046
repo, client = self.setup_fake_client_and_repository(transport_path)
3047
client.add_success_response('ok', 'a token')
3048
token = repo.lock_write().repository_token
3050
[('call', 'Repository.lock_write', ('quack/', ''))],
3052
self.assertEqual('a token', token)
3054
def test_lock_write_already_locked(self):
3055
transport_path = 'quack'
3056
repo, client = self.setup_fake_client_and_repository(transport_path)
3057
client.add_error_response('LockContention')
3058
self.assertRaises(errors.LockContention, repo.lock_write)
3060
[('call', 'Repository.lock_write', ('quack/', ''))],
3063
def test_lock_write_unlockable(self):
3064
transport_path = 'quack'
3065
repo, client = self.setup_fake_client_and_repository(transport_path)
3066
client.add_error_response('UnlockableTransport')
3067
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3069
[('call', 'Repository.lock_write', ('quack/', ''))],
3073
class TestRepositoryWriteGroups(TestRemoteRepository):
3075
def test_start_write_group(self):
3076
transport_path = 'quack'
3077
repo, client = self.setup_fake_client_and_repository(transport_path)
3078
client.add_expected_call(
3079
'Repository.lock_write', ('quack/', ''),
3080
'success', ('ok', 'a token'))
3081
client.add_expected_call(
3082
'Repository.start_write_group', ('quack/', 'a token'),
3083
'success', ('ok', ('token1', )))
3085
repo.start_write_group()
3087
def test_start_write_group_unsuspendable(self):
3088
# Some repositories do not support suspending write
3089
# groups. For those, fall back to the "real" repository.
3090
transport_path = 'quack'
3091
repo, client = self.setup_fake_client_and_repository(transport_path)
3092
def stub_ensure_real():
3093
client._calls.append(('_ensure_real',))
3094
repo._real_repository = _StubRealPackRepository(client._calls)
3095
repo._ensure_real = stub_ensure_real
3096
client.add_expected_call(
3097
'Repository.lock_write', ('quack/', ''),
3098
'success', ('ok', 'a token'))
3099
client.add_expected_call(
3100
'Repository.start_write_group', ('quack/', 'a token'),
3101
'error', ('UnsuspendableWriteGroup',))
3103
repo.start_write_group()
3104
self.assertEqual(client._calls[-2:], [
3106
('start_write_group',)])
3108
def test_commit_write_group(self):
3109
transport_path = 'quack'
3110
repo, client = self.setup_fake_client_and_repository(transport_path)
3111
client.add_expected_call(
3112
'Repository.lock_write', ('quack/', ''),
3113
'success', ('ok', 'a token'))
3114
client.add_expected_call(
3115
'Repository.start_write_group', ('quack/', 'a token'),
3116
'success', ('ok', ['token1']))
3117
client.add_expected_call(
3118
'Repository.commit_write_group', ('quack/', 'a token', ['token1']),
3121
repo.start_write_group()
3122
repo.commit_write_group()
3124
def test_abort_write_group(self):
3125
transport_path = 'quack'
3126
repo, client = self.setup_fake_client_and_repository(transport_path)
3127
client.add_expected_call(
3128
'Repository.lock_write', ('quack/', ''),
3129
'success', ('ok', 'a token'))
3130
client.add_expected_call(
3131
'Repository.start_write_group', ('quack/', 'a token'),
3132
'success', ('ok', ['token1']))
3133
client.add_expected_call(
3134
'Repository.abort_write_group', ('quack/', 'a token', ['token1']),
3137
repo.start_write_group()
3138
repo.abort_write_group(False)
3140
def test_suspend_write_group(self):
3141
transport_path = 'quack'
3142
repo, client = self.setup_fake_client_and_repository(transport_path)
3143
self.assertEqual([], repo.suspend_write_group())
3145
def test_resume_write_group(self):
3146
transport_path = 'quack'
3147
repo, client = self.setup_fake_client_and_repository(transport_path)
3148
client.add_expected_call(
3149
'Repository.lock_write', ('quack/', ''),
3150
'success', ('ok', 'a token'))
3151
client.add_expected_call(
3152
'Repository.check_write_group', ('quack/', 'a token', ['token1']),
3155
repo.resume_write_group(['token1'])
3158
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3160
def test_backwards_compat(self):
3161
self.setup_smart_server_with_call_log()
3162
repo = self.make_repository('.')
3163
self.reset_smart_call_log()
3164
verb = 'Repository.set_make_working_trees'
3165
self.disable_verb(verb)
3166
repo.set_make_working_trees(True)
3167
call_count = len([call for call in self.hpss_calls if
3168
call.call.method == verb])
3169
self.assertEqual(1, call_count)
3171
def test_current(self):
3172
transport_path = 'quack'
3173
repo, client = self.setup_fake_client_and_repository(transport_path)
3174
client.add_expected_call(
3175
'Repository.set_make_working_trees', ('quack/', 'True'),
3177
client.add_expected_call(
3178
'Repository.set_make_working_trees', ('quack/', 'False'),
3180
repo.set_make_working_trees(True)
3181
repo.set_make_working_trees(False)
3184
class TestRepositoryUnlock(TestRemoteRepository):
3186
def test_unlock(self):
3187
transport_path = 'quack'
3188
repo, client = self.setup_fake_client_and_repository(transport_path)
3189
client.add_success_response('ok', 'a token')
3190
client.add_success_response('ok')
3194
[('call', 'Repository.lock_write', ('quack/', '')),
3195
('call', 'Repository.unlock', ('quack/', 'a token'))],
3198
def test_unlock_wrong_token(self):
3199
# If somehow the token is wrong, unlock will raise TokenMismatch.
3200
transport_path = 'quack'
3201
repo, client = self.setup_fake_client_and_repository(transport_path)
3202
client.add_success_response('ok', 'a token')
3203
client.add_error_response('TokenMismatch')
3205
self.assertRaises(errors.TokenMismatch, repo.unlock)
3208
class TestRepositoryHasRevision(TestRemoteRepository):
3210
def test_none(self):
3211
# repo.has_revision(None) should not cause any traffic.
3212
transport_path = 'quack'
3213
repo, client = self.setup_fake_client_and_repository(transport_path)
3215
# The null revision is always there, so has_revision(None) == True.
3216
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3218
# The remote repo shouldn't be accessed.
3219
self.assertEqual([], client._calls)
3222
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3223
"""Test Repository.iter_file_bytes."""
3225
def test_single(self):
3226
transport_path = 'quack'
3227
repo, client = self.setup_fake_client_and_repository(transport_path)
3228
client.add_expected_call(
3229
'Repository.iter_files_bytes', ('quack/', ),
3230
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3231
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3232
"somerev", "myid")]):
3233
self.assertEqual("myid", identifier)
3234
self.assertEqual("".join(byte_stream), "mydata" * 10)
3236
def test_missing(self):
3237
transport_path = 'quack'
3238
repo, client = self.setup_fake_client_and_repository(transport_path)
3239
client.add_expected_call(
3240
'Repository.iter_files_bytes',
3242
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3243
iter(["absent\0somefile\0somerev\n"]))
3244
self.assertRaises(errors.RevisionNotPresent, list,
3245
repo.iter_files_bytes(
3246
[("somefile", "somerev", "myid")]))
3249
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3250
"""Base class for Repository.insert_stream and .insert_stream_1.19
3254
def checkInsertEmptyStream(self, repo, client):
3255
"""Insert an empty stream, checking the result.
3257
This checks that there are no resume_tokens or missing_keys, and that
3258
the client is finished.
3260
sink = repo._get_sink()
3261
fmt = repository.format_registry.get_default()
3262
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3263
self.assertEqual([], resume_tokens)
3264
self.assertEqual(set(), missing_keys)
3265
self.assertFinished(client)
3268
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3269
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3272
This test case is very similar to TestRepositoryInsertStream_1_19.
3276
super(TestRepositoryInsertStream, self).setUp()
3277
self.disable_verb('Repository.insert_stream_1.19')
3279
def test_unlocked_repo(self):
3280
transport_path = 'quack'
3281
repo, client = self.setup_fake_client_and_repository(transport_path)
3282
client.add_expected_call(
3283
'Repository.insert_stream_1.19', ('quack/', ''),
3284
'unknown', ('Repository.insert_stream_1.19',))
3285
client.add_expected_call(
3286
'Repository.insert_stream', ('quack/', ''),
3288
client.add_expected_call(
3289
'Repository.insert_stream', ('quack/', ''),
3291
self.checkInsertEmptyStream(repo, client)
3293
def test_locked_repo_with_no_lock_token(self):
3294
transport_path = 'quack'
3295
repo, client = self.setup_fake_client_and_repository(transport_path)
3296
client.add_expected_call(
3297
'Repository.lock_write', ('quack/', ''),
3298
'success', ('ok', ''))
3299
client.add_expected_call(
3300
'Repository.insert_stream_1.19', ('quack/', ''),
3301
'unknown', ('Repository.insert_stream_1.19',))
3302
client.add_expected_call(
3303
'Repository.insert_stream', ('quack/', ''),
3305
client.add_expected_call(
3306
'Repository.insert_stream', ('quack/', ''),
3309
self.checkInsertEmptyStream(repo, client)
3311
def test_locked_repo_with_lock_token(self):
3312
transport_path = 'quack'
3313
repo, client = self.setup_fake_client_and_repository(transport_path)
3314
client.add_expected_call(
3315
'Repository.lock_write', ('quack/', ''),
3316
'success', ('ok', 'a token'))
3317
client.add_expected_call(
3318
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3319
'unknown', ('Repository.insert_stream_1.19',))
3320
client.add_expected_call(
3321
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3323
client.add_expected_call(
3324
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3327
self.checkInsertEmptyStream(repo, client)
3329
def test_stream_with_inventory_deltas(self):
3330
"""'inventory-deltas' substreams cannot be sent to the
3331
Repository.insert_stream verb, because not all servers that implement
3332
that verb will accept them. So when one is encountered the RemoteSink
3333
immediately stops using that verb and falls back to VFS insert_stream.
3335
transport_path = 'quack'
3336
repo, client = self.setup_fake_client_and_repository(transport_path)
3337
client.add_expected_call(
3338
'Repository.insert_stream_1.19', ('quack/', ''),
3339
'unknown', ('Repository.insert_stream_1.19',))
3340
client.add_expected_call(
3341
'Repository.insert_stream', ('quack/', ''),
3343
client.add_expected_call(
3344
'Repository.insert_stream', ('quack/', ''),
3346
# Create a fake real repository for insert_stream to fall back on, so
3347
# that we can directly see the records the RemoteSink passes to the
3352
def insert_stream(self, stream, src_format, resume_tokens):
3353
for substream_kind, substream in stream:
3354
self.records.append(
3355
(substream_kind, [record.key for record in substream]))
3356
return ['fake tokens'], ['fake missing keys']
3357
fake_real_sink = FakeRealSink()
3358
class FakeRealRepository:
3359
def _get_sink(self):
3360
return fake_real_sink
3361
def is_in_write_group(self):
3363
def refresh_data(self):
3365
repo._real_repository = FakeRealRepository()
3366
sink = repo._get_sink()
3367
fmt = repository.format_registry.get_default()
3368
stream = self.make_stream_with_inv_deltas(fmt)
3369
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3370
# Every record from the first inventory delta should have been sent to
3372
expected_records = [
3373
('inventory-deltas', [('rev2',), ('rev3',)]),
3374
('texts', [('some-rev', 'some-file')])]
3375
self.assertEqual(expected_records, fake_real_sink.records)
3376
# The return values from the real sink's insert_stream are propagated
3377
# back to the original caller.
3378
self.assertEqual(['fake tokens'], resume_tokens)
3379
self.assertEqual(['fake missing keys'], missing_keys)
3380
self.assertFinished(client)
3382
def make_stream_with_inv_deltas(self, fmt):
3383
"""Make a simple stream with an inventory delta followed by more
3384
records and more substreams to test that all records and substreams
3385
from that point on are used.
3387
This sends, in order:
3388
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3390
* texts substream: (some-rev, some-file)
3392
# Define a stream using generators so that it isn't rewindable.
3393
inv = inventory.Inventory(revision_id='rev1')
3394
inv.root.revision = 'rev1'
3395
def stream_with_inv_delta():
3396
yield ('inventories', inventories_substream())
3397
yield ('inventory-deltas', inventory_delta_substream())
3399
versionedfile.FulltextContentFactory(
3400
('some-rev', 'some-file'), (), None, 'content')])
3401
def inventories_substream():
3402
# An empty inventory fulltext. This will be streamed normally.
3403
text = fmt._serializer.write_inventory_to_string(inv)
3404
yield versionedfile.FulltextContentFactory(
3405
('rev1',), (), None, text)
3406
def inventory_delta_substream():
3407
# An inventory delta. This can't be streamed via this verb, so it
3408
# will trigger a fallback to VFS insert_stream.
3409
entry = inv.make_entry(
3410
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3411
entry.revision = 'ghost'
3412
delta = [(None, 'newdir', 'newdir-id', entry)]
3413
serializer = inventory_delta.InventoryDeltaSerializer(
3414
versioned_root=True, tree_references=False)
3415
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3416
yield versionedfile.ChunkedContentFactory(
3417
('rev2',), (('rev1',)), None, lines)
3419
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3420
yield versionedfile.ChunkedContentFactory(
3421
('rev3',), (('rev1',)), None, lines)
3422
return stream_with_inv_delta()
3425
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3427
def test_unlocked_repo(self):
3428
transport_path = 'quack'
3429
repo, client = self.setup_fake_client_and_repository(transport_path)
3430
client.add_expected_call(
3431
'Repository.insert_stream_1.19', ('quack/', ''),
3433
client.add_expected_call(
3434
'Repository.insert_stream_1.19', ('quack/', ''),
3436
self.checkInsertEmptyStream(repo, client)
3438
def test_locked_repo_with_no_lock_token(self):
3439
transport_path = 'quack'
3440
repo, client = self.setup_fake_client_and_repository(transport_path)
3441
client.add_expected_call(
3442
'Repository.lock_write', ('quack/', ''),
3443
'success', ('ok', ''))
3444
client.add_expected_call(
3445
'Repository.insert_stream_1.19', ('quack/', ''),
3447
client.add_expected_call(
3448
'Repository.insert_stream_1.19', ('quack/', ''),
3451
self.checkInsertEmptyStream(repo, client)
3453
def test_locked_repo_with_lock_token(self):
3454
transport_path = 'quack'
3455
repo, client = self.setup_fake_client_and_repository(transport_path)
3456
client.add_expected_call(
3457
'Repository.lock_write', ('quack/', ''),
3458
'success', ('ok', 'a token'))
3459
client.add_expected_call(
3460
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3462
client.add_expected_call(
3463
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3466
self.checkInsertEmptyStream(repo, client)
3469
class TestRepositoryTarball(TestRemoteRepository):
3471
# This is a canned tarball reponse we can validate against
3473
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3474
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3475
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3476
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3477
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3478
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3479
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3480
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3481
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3482
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3483
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3484
'nWQ7QH/F3JFOFCQ0aSPfA='
3487
def test_repository_tarball(self):
3488
# Test that Repository.tarball generates the right operations
3489
transport_path = 'repo'
3490
expected_calls = [('call_expecting_body', 'Repository.tarball',
3491
('repo/', 'bz2',),),
3493
repo, client = self.setup_fake_client_and_repository(transport_path)
3494
client.add_success_response_with_body(self.tarball_content, 'ok')
3495
# Now actually ask for the tarball
3496
tarball_file = repo._get_tarball('bz2')
3498
self.assertEqual(expected_calls, client._calls)
3499
self.assertEqual(self.tarball_content, tarball_file.read())
3501
tarball_file.close()
3504
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3505
"""RemoteRepository.copy_content_into optimizations"""
3507
def test_copy_content_remote_to_local(self):
3508
self.transport_server = test_server.SmartTCPServer_for_testing
3509
src_repo = self.make_repository('repo1')
3510
src_repo = repository.Repository.open(self.get_url('repo1'))
3511
# At the moment the tarball-based copy_content_into can't write back
3512
# into a smart server. It would be good if it could upload the
3513
# tarball; once that works we'd have to create repositories of
3514
# different formats. -- mbp 20070410
3515
dest_url = self.get_vfs_only_url('repo2')
3516
dest_bzrdir = BzrDir.create(dest_url)
3517
dest_repo = dest_bzrdir.create_repository()
3518
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3519
self.assertTrue(isinstance(src_repo, RemoteRepository))
3520
src_repo.copy_content_into(dest_repo)
3523
class _StubRealPackRepository(object):
3525
def __init__(self, calls):
3527
self._pack_collection = _StubPackCollection(calls)
3529
def start_write_group(self):
3530
self.calls.append(('start_write_group',))
3532
def is_in_write_group(self):
3535
def refresh_data(self):
3536
self.calls.append(('pack collection reload_pack_names',))
3539
class _StubPackCollection(object):
3541
def __init__(self, calls):
3545
self.calls.append(('pack collection autopack',))
3548
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3549
"""Tests for RemoteRepository.autopack implementation."""
3552
"""When the server returns 'ok' and there's no _real_repository, then
3553
nothing else happens: the autopack method is done.
3555
transport_path = 'quack'
3556
repo, client = self.setup_fake_client_and_repository(transport_path)
3557
client.add_expected_call(
3558
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3560
self.assertFinished(client)
3562
def test_ok_with_real_repo(self):
3563
"""When the server returns 'ok' and there is a _real_repository, then
3564
the _real_repository's reload_pack_name's method will be called.
3566
transport_path = 'quack'
3567
repo, client = self.setup_fake_client_and_repository(transport_path)
3568
client.add_expected_call(
3569
'PackRepository.autopack', ('quack/',),
3571
repo._real_repository = _StubRealPackRepository(client._calls)
3574
[('call', 'PackRepository.autopack', ('quack/',)),
3575
('pack collection reload_pack_names',)],
3578
def test_backwards_compatibility(self):
3579
"""If the server does not recognise the PackRepository.autopack verb,
3580
fallback to the real_repository's implementation.
3582
transport_path = 'quack'
3583
repo, client = self.setup_fake_client_and_repository(transport_path)
3584
client.add_unknown_method_response('PackRepository.autopack')
3585
def stub_ensure_real():
3586
client._calls.append(('_ensure_real',))
3587
repo._real_repository = _StubRealPackRepository(client._calls)
3588
repo._ensure_real = stub_ensure_real
3591
[('call', 'PackRepository.autopack', ('quack/',)),
3593
('pack collection autopack',)],
3596
def test_oom_error_reporting(self):
3597
"""An out-of-memory condition on the server is reported clearly"""
3598
transport_path = 'quack'
3599
repo, client = self.setup_fake_client_and_repository(transport_path)
3600
client.add_expected_call(
3601
'PackRepository.autopack', ('quack/',),
3602
'error', ('MemoryError',))
3603
err = self.assertRaises(errors.BzrError, repo.autopack)
3604
self.assertContainsRe(str(err), "^remote server out of mem")
3607
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3608
"""Base class for unit tests for breezy.remote._translate_error."""
3610
def translateTuple(self, error_tuple, **context):
3611
"""Call _translate_error with an ErrorFromSmartServer built from the
3614
:param error_tuple: A tuple of a smart server response, as would be
3615
passed to an ErrorFromSmartServer.
3616
:kwargs context: context items to call _translate_error with.
3618
:returns: The error raised by _translate_error.
3620
# Raise the ErrorFromSmartServer before passing it as an argument,
3621
# because _translate_error may need to re-raise it with a bare 'raise'
3623
server_error = errors.ErrorFromSmartServer(error_tuple)
3624
translated_error = self.translateErrorFromSmartServer(
3625
server_error, **context)
3626
return translated_error
3628
def translateErrorFromSmartServer(self, error_object, **context):
3629
"""Like translateTuple, but takes an already constructed
3630
ErrorFromSmartServer rather than a tuple.
3634
except errors.ErrorFromSmartServer as server_error:
3635
translated_error = self.assertRaises(
3636
errors.BzrError, remote._translate_error, server_error,
3638
return translated_error
3641
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3642
"""Unit tests for breezy.remote._translate_error.
3644
Given an ErrorFromSmartServer (which has an error tuple from a smart
3645
server) and some context, _translate_error raises more specific errors from
3648
This test case covers the cases where _translate_error succeeds in
3649
translating an ErrorFromSmartServer to something better. See
3650
TestErrorTranslationRobustness for other cases.
3653
def test_NoSuchRevision(self):
3654
branch = self.make_branch('')
3656
translated_error = self.translateTuple(
3657
('NoSuchRevision', revid), branch=branch)
3658
expected_error = errors.NoSuchRevision(branch, revid)
3659
self.assertEqual(expected_error, translated_error)
3661
def test_nosuchrevision(self):
3662
repository = self.make_repository('')
3664
translated_error = self.translateTuple(
3665
('nosuchrevision', revid), repository=repository)
3666
expected_error = errors.NoSuchRevision(repository, revid)
3667
self.assertEqual(expected_error, translated_error)
3669
def test_nobranch(self):
3670
bzrdir = self.make_controldir('')
3671
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3672
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3673
self.assertEqual(expected_error, translated_error)
3675
def test_nobranch_one_arg(self):
3676
bzrdir = self.make_controldir('')
3677
translated_error = self.translateTuple(
3678
('nobranch', 'extra detail'), bzrdir=bzrdir)
3679
expected_error = errors.NotBranchError(
3680
path=bzrdir.root_transport.base,
3681
detail='extra detail')
3682
self.assertEqual(expected_error, translated_error)
3684
def test_norepository(self):
3685
bzrdir = self.make_controldir('')
3686
translated_error = self.translateTuple(('norepository',),
3688
expected_error = errors.NoRepositoryPresent(bzrdir)
3689
self.assertEqual(expected_error, translated_error)
3691
def test_LockContention(self):
3692
translated_error = self.translateTuple(('LockContention',))
3693
expected_error = errors.LockContention('(remote lock)')
3694
self.assertEqual(expected_error, translated_error)
3696
def test_UnlockableTransport(self):
3697
bzrdir = self.make_controldir('')
3698
translated_error = self.translateTuple(
3699
('UnlockableTransport',), bzrdir=bzrdir)
3700
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3701
self.assertEqual(expected_error, translated_error)
3703
def test_LockFailed(self):
3704
lock = 'str() of a server lock'
3705
why = 'str() of why'
3706
translated_error = self.translateTuple(('LockFailed', lock, why))
3707
expected_error = errors.LockFailed(lock, why)
3708
self.assertEqual(expected_error, translated_error)
3710
def test_TokenMismatch(self):
3711
token = 'a lock token'
3712
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3713
expected_error = errors.TokenMismatch(token, '(remote token)')
3714
self.assertEqual(expected_error, translated_error)
3716
def test_Diverged(self):
3717
branch = self.make_branch('a')
3718
other_branch = self.make_branch('b')
3719
translated_error = self.translateTuple(
3720
('Diverged',), branch=branch, other_branch=other_branch)
3721
expected_error = errors.DivergedBranches(branch, other_branch)
3722
self.assertEqual(expected_error, translated_error)
3724
def test_NotStacked(self):
3725
branch = self.make_branch('')
3726
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3727
expected_error = errors.NotStacked(branch)
3728
self.assertEqual(expected_error, translated_error)
3730
def test_ReadError_no_args(self):
3732
translated_error = self.translateTuple(('ReadError',), path=path)
3733
expected_error = errors.ReadError(path)
3734
self.assertEqual(expected_error, translated_error)
3736
def test_ReadError(self):
3738
translated_error = self.translateTuple(('ReadError', path))
3739
expected_error = errors.ReadError(path)
3740
self.assertEqual(expected_error, translated_error)
3742
def test_IncompatibleRepositories(self):
3743
translated_error = self.translateTuple(('IncompatibleRepositories',
3744
"repo1", "repo2", "details here"))
3745
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3747
self.assertEqual(expected_error, translated_error)
3749
def test_PermissionDenied_no_args(self):
3751
translated_error = self.translateTuple(('PermissionDenied',),
3753
expected_error = errors.PermissionDenied(path)
3754
self.assertEqual(expected_error, translated_error)
3756
def test_PermissionDenied_one_arg(self):
3758
translated_error = self.translateTuple(('PermissionDenied', path))
3759
expected_error = errors.PermissionDenied(path)
3760
self.assertEqual(expected_error, translated_error)
3762
def test_PermissionDenied_one_arg_and_context(self):
3763
"""Given a choice between a path from the local context and a path on
3764
the wire, _translate_error prefers the path from the local context.
3766
local_path = 'local path'
3767
remote_path = 'remote path'
3768
translated_error = self.translateTuple(
3769
('PermissionDenied', remote_path), path=local_path)
3770
expected_error = errors.PermissionDenied(local_path)
3771
self.assertEqual(expected_error, translated_error)
3773
def test_PermissionDenied_two_args(self):
3775
extra = 'a string with extra info'
3776
translated_error = self.translateTuple(
3777
('PermissionDenied', path, extra))
3778
expected_error = errors.PermissionDenied(path, extra)
3779
self.assertEqual(expected_error, translated_error)
3781
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3783
def test_NoSuchFile_context_path(self):
3784
local_path = "local path"
3785
translated_error = self.translateTuple(('ReadError', "remote path"),
3787
expected_error = errors.ReadError(local_path)
3788
self.assertEqual(expected_error, translated_error)
3790
def test_NoSuchFile_without_context(self):
3791
remote_path = "remote path"
3792
translated_error = self.translateTuple(('ReadError', remote_path))
3793
expected_error = errors.ReadError(remote_path)
3794
self.assertEqual(expected_error, translated_error)
3796
def test_ReadOnlyError(self):
3797
translated_error = self.translateTuple(('ReadOnlyError',))
3798
expected_error = errors.TransportNotPossible("readonly transport")
3799
self.assertEqual(expected_error, translated_error)
3801
def test_MemoryError(self):
3802
translated_error = self.translateTuple(('MemoryError',))
3803
self.assertStartsWith(str(translated_error),
3804
"remote server out of memory")
3806
def test_generic_IndexError_no_classname(self):
3807
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3808
translated_error = self.translateErrorFromSmartServer(err)
3809
expected_error = errors.UnknownErrorFromSmartServer(err)
3810
self.assertEqual(expected_error, translated_error)
3812
# GZ 2011-03-02: TODO test generic non-ascii error string
3814
def test_generic_KeyError(self):
3815
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3816
translated_error = self.translateErrorFromSmartServer(err)
3817
expected_error = errors.UnknownErrorFromSmartServer(err)
3818
self.assertEqual(expected_error, translated_error)
3821
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3822
"""Unit tests for breezy.remote._translate_error's robustness.
3824
TestErrorTranslationSuccess is for cases where _translate_error can
3825
translate successfully. This class about how _translate_err behaves when
3826
it fails to translate: it re-raises the original error.
3829
def test_unrecognised_server_error(self):
3830
"""If the error code from the server is not recognised, the original
3831
ErrorFromSmartServer is propagated unmodified.
3833
error_tuple = ('An unknown error tuple',)
3834
server_error = errors.ErrorFromSmartServer(error_tuple)
3835
translated_error = self.translateErrorFromSmartServer(server_error)
3836
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3837
self.assertEqual(expected_error, translated_error)
3839
def test_context_missing_a_key(self):
3840
"""In case of a bug in the client, or perhaps an unexpected response
3841
from a server, _translate_error returns the original error tuple from
3842
the server and mutters a warning.
3844
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3845
# in the context dict. So let's give it an empty context dict instead
3846
# to exercise its error recovery.
3848
error_tuple = ('NoSuchRevision', 'revid')
3849
server_error = errors.ErrorFromSmartServer(error_tuple)
3850
translated_error = self.translateErrorFromSmartServer(server_error)
3851
self.assertEqual(server_error, translated_error)
3852
# In addition to re-raising ErrorFromSmartServer, some debug info has
3853
# been muttered to the log file for developer to look at.
3854
self.assertContainsRe(
3856
"Missing key 'branch' in context")
3858
def test_path_missing(self):
3859
"""Some translations (PermissionDenied, ReadError) can determine the
3860
'path' variable from either the wire or the local context. If neither
3861
has it, then an error is raised.
3863
error_tuple = ('ReadError',)
3864
server_error = errors.ErrorFromSmartServer(error_tuple)
3865
translated_error = self.translateErrorFromSmartServer(server_error)
3866
self.assertEqual(server_error, translated_error)
3867
# In addition to re-raising ErrorFromSmartServer, some debug info has
3868
# been muttered to the log file for developer to look at.
3869
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3872
class TestStacking(tests.TestCaseWithTransport):
3873
"""Tests for operations on stacked remote repositories.
3875
The underlying format type must support stacking.
3878
def test_access_stacked_remote(self):
3879
# based on <http://launchpad.net/bugs/261315>
3880
# make a branch stacked on another repository containing an empty
3881
# revision, then open it over hpss - we should be able to see that
3883
base_transport = self.get_transport()
3884
base_builder = self.make_branch_builder('base', format='1.9')
3885
base_builder.start_series()
3886
base_revid = base_builder.build_snapshot('rev-id', None,
3887
[('add', ('', None, 'directory', None))],
3889
base_builder.finish_series()
3890
stacked_branch = self.make_branch('stacked', format='1.9')
3891
stacked_branch.set_stacked_on_url('../base')
3892
# start a server looking at this
3893
smart_server = test_server.SmartTCPServer_for_testing()
3894
self.start_server(smart_server)
3895
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3896
# can get its branch and repository
3897
remote_branch = remote_bzrdir.open_branch()
3898
remote_repo = remote_branch.repository
3899
remote_repo.lock_read()
3901
# it should have an appropriate fallback repository, which should also
3902
# be a RemoteRepository
3903
self.assertLength(1, remote_repo._fallback_repositories)
3904
self.assertIsInstance(remote_repo._fallback_repositories[0],
3906
# and it has the revision committed to the underlying repository;
3907
# these have varying implementations so we try several of them
3908
self.assertTrue(remote_repo.has_revisions([base_revid]))
3909
self.assertTrue(remote_repo.has_revision(base_revid))
3910
self.assertEqual(remote_repo.get_revision(base_revid).message,
3913
remote_repo.unlock()
3915
def prepare_stacked_remote_branch(self):
3916
"""Get stacked_upon and stacked branches with content in each."""
3917
self.setup_smart_server_with_call_log()
3918
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3919
tree1.commit('rev1', rev_id='rev1')
3920
tree2 = tree1.branch.controldir.sprout('tree2', stacked=True
3921
).open_workingtree()
3922
local_tree = tree2.branch.create_checkout('local')
3923
local_tree.commit('local changes make me feel good.')
3924
branch2 = Branch.open(self.get_url('tree2'))
3926
self.addCleanup(branch2.unlock)
3927
return tree1.branch, branch2
3929
def test_stacked_get_parent_map(self):
3930
# the public implementation of get_parent_map obeys stacking
3931
_, branch = self.prepare_stacked_remote_branch()
3932
repo = branch.repository
3933
self.assertEqual({'rev1'}, set(repo.get_parent_map(['rev1'])))
3935
def test_unstacked_get_parent_map(self):
3936
# _unstacked_provider.get_parent_map ignores stacking
3937
_, branch = self.prepare_stacked_remote_branch()
3938
provider = branch.repository._unstacked_provider
3939
self.assertEqual(set(), set(provider.get_parent_map(['rev1'])))
3941
def fetch_stream_to_rev_order(self, stream):
3943
for kind, substream in stream:
3944
if not kind == 'revisions':
3947
for content in substream:
3948
result.append(content.key[-1])
3951
def get_ordered_revs(self, format, order, branch_factory=None):
3952
"""Get a list of the revisions in a stream to format format.
3954
:param format: The format of the target.
3955
:param order: the order that target should have requested.
3956
:param branch_factory: A callable to create a trunk and stacked branch
3957
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3958
:result: The revision ids in the stream, in the order seen,
3959
the topological order of revisions in the source.
3961
unordered_format = controldir.format_registry.get(format)()
3962
target_repository_format = unordered_format.repository_format
3964
self.assertEqual(order, target_repository_format._fetch_order)
3965
if branch_factory is None:
3966
branch_factory = self.prepare_stacked_remote_branch
3967
_, stacked = branch_factory()
3968
source = stacked.repository._get_source(target_repository_format)
3969
tip = stacked.last_revision()
3970
stacked.repository._ensure_real()
3971
graph = stacked.repository.get_graph()
3972
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3973
if r != NULL_REVISION]
3975
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3976
self.reset_smart_call_log()
3977
stream = source.get_stream(search)
3978
# We trust that if a revision is in the stream the rest of the new
3979
# content for it is too, as per our main fetch tests; here we are
3980
# checking that the revisions are actually included at all, and their
3982
return self.fetch_stream_to_rev_order(stream), revs
3984
def test_stacked_get_stream_unordered(self):
3985
# Repository._get_source.get_stream() from a stacked repository with
3986
# unordered yields the full data from both stacked and stacked upon
3988
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3989
self.assertEqual(set(expected_revs), set(rev_ord))
3990
# Getting unordered results should have made a streaming data request
3991
# from the server, then one from the backing branch.
3992
self.assertLength(2, self.hpss_calls)
3994
def test_stacked_on_stacked_get_stream_unordered(self):
3995
# Repository._get_source.get_stream() from a stacked repository which
3996
# is itself stacked yields the full data from all three sources.
3997
def make_stacked_stacked():
3998
_, stacked = self.prepare_stacked_remote_branch()
3999
tree = stacked.controldir.sprout('tree3', stacked=True
4000
).open_workingtree()
4001
local_tree = tree.branch.create_checkout('local-tree3')
4002
local_tree.commit('more local changes are better')
4003
branch = Branch.open(self.get_url('tree3'))
4005
self.addCleanup(branch.unlock)
4007
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
4008
branch_factory=make_stacked_stacked)
4009
self.assertEqual(set(expected_revs), set(rev_ord))
4010
# Getting unordered results should have made a streaming data request
4011
# from the server, and one from each backing repo
4012
self.assertLength(3, self.hpss_calls)
4014
def test_stacked_get_stream_topological(self):
4015
# Repository._get_source.get_stream() from a stacked repository with
4016
# topological sorting yields the full data from both stacked and
4017
# stacked upon sources in topological order.
4018
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4019
self.assertEqual(expected_revs, rev_ord)
4020
# Getting topological sort requires VFS calls still - one of which is
4021
# pushing up from the bound branch.
4022
self.assertLength(14, self.hpss_calls)
4024
def test_stacked_get_stream_groupcompress(self):
4025
# Repository._get_source.get_stream() from a stacked repository with
4026
# groupcompress sorting yields the full data from both stacked and
4027
# stacked upon sources in groupcompress order.
4028
raise tests.TestSkipped('No groupcompress ordered format available')
4029
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4030
self.assertEqual(expected_revs, reversed(rev_ord))
4031
# Getting unordered results should have made a streaming data request
4032
# from the backing branch, and one from the stacked on branch.
4033
self.assertLength(2, self.hpss_calls)
4035
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4036
# When pulling some fixed amount of content that is more than the
4037
# source has (because some is coming from a fallback branch, no error
4038
# should be received. This was reported as bug 360791.
4039
# Need three branches: a trunk, a stacked branch, and a preexisting
4040
# branch pulling content from stacked and trunk.
4041
self.setup_smart_server_with_call_log()
4042
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4043
r1 = trunk.commit('start')
4044
stacked_branch = trunk.branch.create_clone_on_transport(
4045
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4046
local = self.make_branch('local', format='1.9-rich-root')
4047
local.repository.fetch(stacked_branch.repository,
4048
stacked_branch.last_revision())
4051
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4054
super(TestRemoteBranchEffort, self).setUp()
4055
# Create a smart server that publishes whatever the backing VFS server
4057
self.smart_server = test_server.SmartTCPServer_for_testing()
4058
self.start_server(self.smart_server, self.get_server())
4059
# Log all HPSS calls into self.hpss_calls.
4060
_SmartClient.hooks.install_named_hook(
4061
'call', self.capture_hpss_call, None)
4062
self.hpss_calls = []
4064
def capture_hpss_call(self, params):
4065
self.hpss_calls.append(params.method)
4067
def test_copy_content_into_avoids_revision_history(self):
4068
local = self.make_branch('local')
4069
builder = self.make_branch_builder('remote')
4070
builder.build_commit(message="Commit.")
4071
remote_branch_url = self.smart_server.get_url() + 'remote'
4072
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4073
local.repository.fetch(remote_branch.repository)
4074
self.hpss_calls = []
4075
remote_branch.copy_content_into(local)
4076
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4078
def test_fetch_everything_needs_just_one_call(self):
4079
local = self.make_branch('local')
4080
builder = self.make_branch_builder('remote')
4081
builder.build_commit(message="Commit.")
4082
remote_branch_url = self.smart_server.get_url() + 'remote'
4083
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4084
self.hpss_calls = []
4085
local.repository.fetch(
4086
remote_branch.repository,
4087
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4088
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4090
def override_verb(self, verb_name, verb):
4091
request_handlers = request.request_handlers
4092
orig_verb = request_handlers.get(verb_name)
4093
orig_info = request_handlers.get_info(verb_name)
4094
request_handlers.register(verb_name, verb, override_existing=True)
4095
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4096
override_existing=True, info=orig_info)
4098
def test_fetch_everything_backwards_compat(self):
4099
"""Can fetch with EverythingResult even with pre 2.4 servers.
4101
Pre-2.4 do not support 'everything' searches with the
4102
Repository.get_stream_1.19 verb.
4105
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4106
"""A version of the Repository.get_stream_1.19 verb patched to
4107
reject 'everything' searches the way 2.3 and earlier do.
4109
def recreate_search(self, repository, search_bytes,
4110
discard_excess=False):
4111
verb_log.append(search_bytes.split('\n', 1)[0])
4112
if search_bytes == 'everything':
4114
request.FailedSmartServerResponse(('BadSearch',)))
4115
return super(OldGetStreamVerb,
4116
self).recreate_search(repository, search_bytes,
4117
discard_excess=discard_excess)
4118
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4119
local = self.make_branch('local')
4120
builder = self.make_branch_builder('remote')
4121
builder.build_commit(message="Commit.")
4122
remote_branch_url = self.smart_server.get_url() + 'remote'
4123
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4124
self.hpss_calls = []
4125
local.repository.fetch(
4126
remote_branch.repository,
4127
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4128
# make sure the overridden verb was used
4129
self.assertLength(1, verb_log)
4130
# more than one HPSS call is needed, but because it's a VFS callback
4131
# its hard to predict exactly how many.
4132
self.assertTrue(len(self.hpss_calls) > 1)
4135
class TestUpdateBoundBranchWithModifiedBoundLocation(
4136
tests.TestCaseWithTransport):
4137
"""Ensure correct handling of bound_location modifications.
4139
This is tested against a smart server as http://pad.lv/786980 was about a
4140
ReadOnlyError (write attempt during a read-only transaction) which can only
4141
happen in this context.
4145
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4146
self.transport_server = test_server.SmartTCPServer_for_testing
4148
def make_master_and_checkout(self, master_name, checkout_name):
4149
# Create the master branch and its associated checkout
4150
self.master = self.make_branch_and_tree(master_name)
4151
self.checkout = self.master.branch.create_checkout(checkout_name)
4152
# Modify the master branch so there is something to update
4153
self.master.commit('add stuff')
4154
self.last_revid = self.master.commit('even more stuff')
4155
self.bound_location = self.checkout.branch.get_bound_location()
4157
def assertUpdateSucceeds(self, new_location):
4158
self.checkout.branch.set_bound_location(new_location)
4159
self.checkout.update()
4160
self.assertEqual(self.last_revid, self.checkout.last_revision())
4162
def test_without_final_slash(self):
4163
self.make_master_and_checkout('master', 'checkout')
4164
# For unclear reasons some users have a bound_location without a final
4165
# '/', simulate that by forcing such a value
4166
self.assertEndsWith(self.bound_location, '/')
4167
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4169
def test_plus_sign(self):
4170
self.make_master_and_checkout('+master', 'checkout')
4171
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4173
def test_tilda(self):
4174
# Embed ~ in the middle of the path just to avoid any $HOME
4176
self.make_master_and_checkout('mas~ter', 'checkout')
4177
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4180
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4182
def test_no_context(self):
4183
class OutOfCoffee(errors.BzrError):
4184
"""A dummy exception for testing."""
4186
def __init__(self, urgency):
4187
self.urgency = urgency
4188
remote.no_context_error_translators.register("OutOfCoffee",
4189
lambda err: OutOfCoffee(err.error_args[0]))
4190
transport = MemoryTransport()
4191
client = FakeClient(transport.base)
4192
client.add_expected_call(
4193
'Branch.get_stacked_on_url', ('quack/',),
4194
'error', ('NotStacked',))
4195
client.add_expected_call(
4196
'Branch.last_revision_info',
4198
'error', ('OutOfCoffee', 'low'))
4199
transport.mkdir('quack')
4200
transport = transport.clone('quack')
4201
branch = self.make_remote_branch(transport, client)
4202
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4203
self.assertFinished(client)
4205
def test_with_context(self):
4206
class OutOfTea(errors.BzrError):
4207
def __init__(self, branch, urgency):
4208
self.branch = branch
4209
self.urgency = urgency
4210
remote.error_translators.register("OutOfTea",
4211
lambda err, find, path: OutOfTea(err.error_args[0],
4213
transport = MemoryTransport()
4214
client = FakeClient(transport.base)
4215
client.add_expected_call(
4216
'Branch.get_stacked_on_url', ('quack/',),
4217
'error', ('NotStacked',))
4218
client.add_expected_call(
4219
'Branch.last_revision_info',
4221
'error', ('OutOfTea', 'low'))
4222
transport.mkdir('quack')
4223
transport = transport.clone('quack')
4224
branch = self.make_remote_branch(transport, client)
4225
self.assertRaises(OutOfTea, branch.last_revision_info)
4226
self.assertFinished(client)
4229
class TestRepositoryPack(TestRemoteRepository):
4231
def test_pack(self):
4232
transport_path = 'quack'
4233
repo, client = self.setup_fake_client_and_repository(transport_path)
4234
client.add_expected_call(
4235
'Repository.lock_write', ('quack/', ''),
4236
'success', ('ok', 'token'))
4237
client.add_expected_call(
4238
'Repository.pack', ('quack/', 'token', 'False'),
4239
'success', ('ok',), )
4240
client.add_expected_call(
4241
'Repository.unlock', ('quack/', 'token'),
4242
'success', ('ok', ))
4245
def test_pack_with_hint(self):
4246
transport_path = 'quack'
4247
repo, client = self.setup_fake_client_and_repository(transport_path)
4248
client.add_expected_call(
4249
'Repository.lock_write', ('quack/', ''),
4250
'success', ('ok', 'token'))
4251
client.add_expected_call(
4252
'Repository.pack', ('quack/', 'token', 'False'),
4253
'success', ('ok',), )
4254
client.add_expected_call(
4255
'Repository.unlock', ('quack/', 'token', 'False'),
4256
'success', ('ok', ))
4257
repo.pack(['hinta', 'hintb'])
4260
class TestRepositoryIterInventories(TestRemoteRepository):
4261
"""Test Repository.iter_inventories."""
4263
def _serialize_inv_delta(self, old_name, new_name, delta):
4264
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4265
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4267
def test_single_empty(self):
4268
transport_path = 'quack'
4269
repo, client = self.setup_fake_client_and_repository(transport_path)
4270
fmt = controldir.format_registry.get('2a')().repository_format
4272
stream = [('inventory-deltas', [
4273
versionedfile.FulltextContentFactory('somerevid', None, None,
4274
self._serialize_inv_delta('null:', 'somerevid', []))])]
4275
client.add_expected_call(
4276
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4277
'success', ('ok', ),
4278
_stream_to_byte_stream(stream, fmt))
4279
ret = list(repo.iter_inventories(["somerevid"]))
4280
self.assertLength(1, ret)
4282
self.assertEqual("somerevid", inv.revision_id)
4284
def test_empty(self):
4285
transport_path = 'quack'
4286
repo, client = self.setup_fake_client_and_repository(transport_path)
4287
ret = list(repo.iter_inventories([]))
4288
self.assertEqual(ret, [])
4290
def test_missing(self):
4291
transport_path = 'quack'
4292
repo, client = self.setup_fake_client_and_repository(transport_path)
4293
client.add_expected_call(
4294
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4295
'success', ('ok', ), iter([]))
4296
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(