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
50
from ..bzr.bzrdir import (
57
from ..bzr.chk_serializer import chk_bencode_serializer
58
from ..bzr.remote import (
64
RemoteRepositoryFormat,
66
from ..bzr import groupcompress_repo, knitpack_repo
67
from ..revision import (
71
from ..sixish import (
74
from ..bzr.smart import medium, request
75
from ..bzr.smart.client import _SmartClient
76
from ..bzr.smart.repository import (
77
SmartServerRepositoryGetParentMap,
78
SmartServerRepositoryGetStream_1_19,
79
_stream_to_byte_stream,
84
from .scenarios import load_tests_apply_scenarios
85
from ..transport.memory import MemoryTransport
86
from ..transport.remote import (
93
load_tests = load_tests_apply_scenarios
96
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
100
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
102
{'transport_server': test_server.SmartTCPServer_for_testing})]
106
super(BasicRemoteObjectTests, self).setUp()
107
self.transport = self.get_transport()
108
# make a branch that can be opened over the smart transport
109
self.local_wt = BzrDir.create_standalone_workingtree('.')
110
self.addCleanup(self.transport.disconnect)
112
def test_create_remote_bzrdir(self):
113
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
114
self.assertIsInstance(b, BzrDir)
116
def test_open_remote_branch(self):
117
# open a standalone branch in the working directory
118
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
119
branch = b.open_branch()
120
self.assertIsInstance(branch, Branch)
122
def test_remote_repository(self):
123
b = BzrDir.open_from_transport(self.transport)
124
repo = b.open_repository()
125
revid = u'\xc823123123'.encode('utf8')
126
self.assertFalse(repo.has_revision(revid))
127
self.local_wt.commit(message='test commit', rev_id=revid)
128
self.assertTrue(repo.has_revision(revid))
130
def test_find_correct_format(self):
131
"""Should open a RemoteBzrDir over a RemoteTransport"""
132
fmt = BzrDirFormat.find_format(self.transport)
133
self.assertTrue(RemoteBzrProber
134
in controldir.ControlDirFormat._server_probers)
135
self.assertIsInstance(fmt, RemoteBzrDirFormat)
137
def test_open_detected_smart_format(self):
138
fmt = BzrDirFormat.find_format(self.transport)
139
d = fmt.open(self.transport)
140
self.assertIsInstance(d, BzrDir)
142
def test_remote_branch_repr(self):
143
b = BzrDir.open_from_transport(self.transport).open_branch()
144
self.assertStartsWith(str(b), 'RemoteBranch(')
146
def test_remote_bzrdir_repr(self):
147
b = BzrDir.open_from_transport(self.transport)
148
self.assertStartsWith(str(b), 'RemoteBzrDir(')
150
def test_remote_branch_format_supports_stacking(self):
152
self.make_branch('unstackable', format='pack-0.92')
153
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
154
self.assertFalse(b._format.supports_stacking())
155
self.make_branch('stackable', format='1.9')
156
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
157
self.assertTrue(b._format.supports_stacking())
159
def test_remote_repo_format_supports_external_references(self):
161
bd = self.make_controldir('unstackable', format='pack-0.92')
162
r = bd.create_repository()
163
self.assertFalse(r._format.supports_external_lookups)
164
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
165
self.assertFalse(r._format.supports_external_lookups)
166
bd = self.make_controldir('stackable', format='1.9')
167
r = bd.create_repository()
168
self.assertTrue(r._format.supports_external_lookups)
169
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
170
self.assertTrue(r._format.supports_external_lookups)
172
def test_remote_branch_set_append_revisions_only(self):
173
# Make a format 1.9 branch, which supports append_revisions_only
174
branch = self.make_branch('branch', format='1.9')
175
branch.set_append_revisions_only(True)
176
config = branch.get_config_stack()
178
True, config.get('append_revisions_only'))
179
branch.set_append_revisions_only(False)
180
config = branch.get_config_stack()
182
False, config.get('append_revisions_only'))
184
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
185
branch = self.make_branch('branch', format='knit')
187
errors.UpgradeRequired, branch.set_append_revisions_only, True)
190
class FakeProtocol(object):
191
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
193
def __init__(self, body, fake_client):
195
self._body_buffer = None
196
self._fake_client = fake_client
198
def read_body_bytes(self, count=-1):
199
if self._body_buffer is None:
200
self._body_buffer = BytesIO(self.body)
201
bytes = self._body_buffer.read(count)
202
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
203
self._fake_client.expecting_body = False
206
def cancel_read_body(self):
207
self._fake_client.expecting_body = False
209
def read_streamed_body(self):
213
class FakeClient(_SmartClient):
214
"""Lookalike for _SmartClient allowing testing."""
216
def __init__(self, fake_medium_base='fake base'):
217
"""Create a FakeClient."""
220
self.expecting_body = False
221
# if non-None, this is the list of expected calls, with only the
222
# method name and arguments included. the body might be hard to
223
# compute so is not included. If a call is None, that call can
225
self._expected_calls = None
226
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
228
def add_expected_call(self, call_name, call_args, response_type,
229
response_args, response_body=None):
230
if self._expected_calls is None:
231
self._expected_calls = []
232
self._expected_calls.append((call_name, call_args))
233
self.responses.append((response_type, response_args, response_body))
235
def add_success_response(self, *args):
236
self.responses.append(('success', args, None))
238
def add_success_response_with_body(self, body, *args):
239
self.responses.append(('success', args, body))
240
if self._expected_calls is not None:
241
self._expected_calls.append(None)
243
def add_error_response(self, *args):
244
self.responses.append(('error', args))
246
def add_unknown_method_response(self, verb):
247
self.responses.append(('unknown', verb))
249
def finished_test(self):
250
if self._expected_calls:
251
raise AssertionError("%r finished but was still expecting %r"
252
% (self, self._expected_calls[0]))
254
def _get_next_response(self):
256
response_tuple = self.responses.pop(0)
257
except IndexError as e:
258
raise AssertionError("%r didn't expect any more calls"
260
if response_tuple[0] == 'unknown':
261
raise errors.UnknownSmartMethod(response_tuple[1])
262
elif response_tuple[0] == 'error':
263
raise errors.ErrorFromSmartServer(response_tuple[1])
264
return response_tuple
266
def _check_call(self, method, args):
267
if self._expected_calls is None:
268
# the test should be updated to say what it expects
271
next_call = self._expected_calls.pop(0)
273
raise AssertionError("%r didn't expect any more calls "
275
% (self, method, args,))
276
if next_call is None:
278
if method != next_call[0] or args != next_call[1]:
279
raise AssertionError("%r expected %r%r "
281
% (self, next_call[0], next_call[1], method, args,))
283
def call(self, method, *args):
284
self._check_call(method, args)
285
self._calls.append(('call', method, args))
286
return self._get_next_response()[1]
288
def call_expecting_body(self, method, *args):
289
self._check_call(method, args)
290
self._calls.append(('call_expecting_body', method, args))
291
result = self._get_next_response()
292
self.expecting_body = True
293
return result[1], FakeProtocol(result[2], self)
295
def call_with_body_bytes(self, method, args, body):
296
self._check_call(method, args)
297
self._calls.append(('call_with_body_bytes', method, args, body))
298
result = self._get_next_response()
299
return result[1], FakeProtocol(result[2], self)
301
def call_with_body_bytes_expecting_body(self, method, args, body):
302
self._check_call(method, args)
303
self._calls.append(('call_with_body_bytes_expecting_body', method,
305
result = self._get_next_response()
306
self.expecting_body = True
307
return result[1], FakeProtocol(result[2], self)
309
def call_with_body_stream(self, args, stream):
310
# Explicitly consume the stream before checking for an error, because
311
# that's what happens a real medium.
312
stream = list(stream)
313
self._check_call(args[0], args[1:])
314
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
315
result = self._get_next_response()
316
# The second value returned from call_with_body_stream is supposed to
317
# be a response_handler object, but so far no tests depend on that.
318
response_handler = None
319
return result[1], response_handler
322
class FakeMedium(medium.SmartClientMedium):
324
def __init__(self, client_calls, base):
325
medium.SmartClientMedium.__init__(self, base)
326
self._client_calls = client_calls
328
def disconnect(self):
329
self._client_calls.append(('disconnect medium',))
332
class TestVfsHas(tests.TestCase):
334
def test_unicode_path(self):
335
client = FakeClient('/')
336
client.add_success_response('yes',)
337
transport = RemoteTransport('bzr://localhost/', _client=client)
338
filename = u'/hell\u00d8'.encode('utf8')
339
result = transport.has(filename)
341
[('call', 'has', (filename,))],
343
self.assertTrue(result)
346
class TestRemote(tests.TestCaseWithMemoryTransport):
348
def get_branch_format(self):
349
reference_bzrdir_format = controldir.format_registry.get('default')()
350
return reference_bzrdir_format.get_branch_format()
352
def get_repo_format(self):
353
reference_bzrdir_format = controldir.format_registry.get('default')()
354
return reference_bzrdir_format.repository_format
356
def assertFinished(self, fake_client):
357
"""Assert that all of a FakeClient's expected calls have occurred."""
358
fake_client.finished_test()
361
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
362
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
364
def assertRemotePath(self, expected, client_base, transport_base):
365
"""Assert that the result of
366
SmartClientMedium.remote_path_from_transport is the expected value for
367
a given client_base and transport_base.
369
client_medium = medium.SmartClientMedium(client_base)
370
t = transport.get_transport(transport_base)
371
result = client_medium.remote_path_from_transport(t)
372
self.assertEqual(expected, result)
374
def test_remote_path_from_transport(self):
375
"""SmartClientMedium.remote_path_from_transport calculates a URL for
376
the given transport relative to the root of the client base URL.
378
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
379
self.assertRemotePath(
380
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
382
def assertRemotePathHTTP(self, expected, transport_base, relpath):
383
"""Assert that the result of
384
HttpTransportBase.remote_path_from_transport is the expected value for
385
a given transport_base and relpath of that transport. (Note that
386
HttpTransportBase is a subclass of SmartClientMedium)
388
base_transport = transport.get_transport(transport_base)
389
client_medium = base_transport.get_smart_medium()
390
cloned_transport = base_transport.clone(relpath)
391
result = client_medium.remote_path_from_transport(cloned_transport)
392
self.assertEqual(expected, result)
394
def test_remote_path_from_transport_http(self):
395
"""Remote paths for HTTP transports are calculated differently to other
396
transports. They are just relative to the client base, not the root
397
directory of the host.
399
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
400
self.assertRemotePathHTTP(
401
'../xyz/', scheme + '//host/path', '../xyz/')
402
self.assertRemotePathHTTP(
403
'xyz/', scheme + '//host/path', 'xyz/')
406
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
407
"""Tests for the behaviour of client_medium.remote_is_at_least."""
409
def test_initially_unlimited(self):
410
"""A fresh medium assumes that the remote side supports all
413
client_medium = medium.SmartClientMedium('dummy base')
414
self.assertFalse(client_medium._is_remote_before((99, 99)))
416
def test__remember_remote_is_before(self):
417
"""Calling _remember_remote_is_before ratchets down the known remote
420
client_medium = medium.SmartClientMedium('dummy base')
421
# Mark the remote side as being less than 1.6. The remote side may
423
client_medium._remember_remote_is_before((1, 6))
424
self.assertTrue(client_medium._is_remote_before((1, 6)))
425
self.assertFalse(client_medium._is_remote_before((1, 5)))
426
# Calling _remember_remote_is_before again with a lower value works.
427
client_medium._remember_remote_is_before((1, 5))
428
self.assertTrue(client_medium._is_remote_before((1, 5)))
429
# If you call _remember_remote_is_before with a higher value it logs a
430
# warning, and continues to remember the lower value.
431
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
432
client_medium._remember_remote_is_before((1, 9))
433
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
434
self.assertTrue(client_medium._is_remote_before((1, 5)))
437
class TestBzrDirCloningMetaDir(TestRemote):
439
def test_backwards_compat(self):
440
self.setup_smart_server_with_call_log()
441
a_dir = self.make_controldir('.')
442
self.reset_smart_call_log()
443
verb = 'BzrDir.cloning_metadir'
444
self.disable_verb(verb)
445
format = a_dir.cloning_metadir()
446
call_count = len([call for call in self.hpss_calls if
447
call.call.method == verb])
448
self.assertEqual(1, call_count)
450
def test_branch_reference(self):
451
transport = self.get_transport('quack')
452
referenced = self.make_branch('referenced')
453
expected = referenced.controldir.cloning_metadir()
454
client = FakeClient(transport.base)
455
client.add_expected_call(
456
'BzrDir.cloning_metadir', ('quack/', 'False'),
457
'error', ('BranchReference',)),
458
client.add_expected_call(
459
'BzrDir.open_branchV3', ('quack/',),
460
'success', ('ref', self.get_url('referenced'))),
461
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
463
result = a_controldir.cloning_metadir()
464
# We should have got a control dir matching the referenced branch.
465
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
466
self.assertEqual(expected._repository_format, result._repository_format)
467
self.assertEqual(expected._branch_format, result._branch_format)
468
self.assertFinished(client)
470
def test_current_server(self):
471
transport = self.get_transport('.')
472
transport = transport.clone('quack')
473
self.make_controldir('quack')
474
client = FakeClient(transport.base)
475
reference_bzrdir_format = controldir.format_registry.get('default')()
476
control_name = reference_bzrdir_format.network_name()
477
client.add_expected_call(
478
'BzrDir.cloning_metadir', ('quack/', 'False'),
479
'success', (control_name, '', ('branch', ''))),
480
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
482
result = a_controldir.cloning_metadir()
483
# We should have got a reference control dir with default branch and
484
# repository formats.
485
# This pokes a little, just to be sure.
486
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
487
self.assertEqual(None, result._repository_format)
488
self.assertEqual(None, result._branch_format)
489
self.assertFinished(client)
491
def test_unknown(self):
492
transport = self.get_transport('quack')
493
referenced = self.make_branch('referenced')
494
expected = referenced.controldir.cloning_metadir()
495
client = FakeClient(transport.base)
496
client.add_expected_call(
497
'BzrDir.cloning_metadir', ('quack/', 'False'),
498
'success', ('unknown', 'unknown', ('branch', ''))),
499
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
501
self.assertRaises(errors.UnknownFormatError, a_controldir.cloning_metadir)
504
class TestBzrDirCheckoutMetaDir(TestRemote):
506
def test__get_checkout_format(self):
507
transport = MemoryTransport()
508
client = FakeClient(transport.base)
509
reference_bzrdir_format = controldir.format_registry.get('default')()
510
control_name = reference_bzrdir_format.network_name()
511
client.add_expected_call(
512
'BzrDir.checkout_metadir', ('quack/', ),
513
'success', (control_name, '', ''))
514
transport.mkdir('quack')
515
transport = transport.clone('quack')
516
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
518
result = a_controldir.checkout_metadir()
519
# We should have got a reference control dir with default branch and
520
# repository formats.
521
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
522
self.assertEqual(None, result._repository_format)
523
self.assertEqual(None, result._branch_format)
524
self.assertFinished(client)
526
def test_unknown_format(self):
527
transport = MemoryTransport()
528
client = FakeClient(transport.base)
529
client.add_expected_call(
530
'BzrDir.checkout_metadir', ('quack/',),
531
'success', ('dontknow', '', ''))
532
transport.mkdir('quack')
533
transport = transport.clone('quack')
534
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
536
self.assertRaises(errors.UnknownFormatError,
537
a_controldir.checkout_metadir)
538
self.assertFinished(client)
541
class TestBzrDirGetBranches(TestRemote):
543
def test_get_branches(self):
544
transport = MemoryTransport()
545
client = FakeClient(transport.base)
546
reference_bzrdir_format = controldir.format_registry.get('default')()
547
branch_name = reference_bzrdir_format.get_branch_format().network_name()
548
client.add_success_response_with_body(
550
"foo": ("branch", branch_name),
551
"": ("branch", branch_name)}), "success")
552
client.add_success_response(
553
'ok', '', 'no', 'no', 'no',
554
reference_bzrdir_format.repository_format.network_name())
555
client.add_error_response('NotStacked')
556
client.add_success_response(
557
'ok', '', 'no', 'no', 'no',
558
reference_bzrdir_format.repository_format.network_name())
559
client.add_error_response('NotStacked')
560
transport.mkdir('quack')
561
transport = transport.clone('quack')
562
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
564
result = a_controldir.get_branches()
565
self.assertEqual({"", "foo"}, set(result.keys()))
567
[('call_expecting_body', 'BzrDir.get_branches', ('quack/',)),
568
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
569
('call', 'Branch.get_stacked_on_url', ('quack/', )),
570
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
571
('call', 'Branch.get_stacked_on_url', ('quack/', ))],
575
class TestBzrDirDestroyBranch(TestRemote):
577
def test_destroy_default(self):
578
transport = self.get_transport('quack')
579
referenced = self.make_branch('referenced')
580
client = FakeClient(transport.base)
581
client.add_expected_call(
582
'BzrDir.destroy_branch', ('quack/', ),
584
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
586
a_controldir.destroy_branch()
587
self.assertFinished(client)
590
class TestBzrDirHasWorkingTree(TestRemote):
592
def test_has_workingtree(self):
593
transport = self.get_transport('quack')
594
client = FakeClient(transport.base)
595
client.add_expected_call(
596
'BzrDir.has_workingtree', ('quack/',),
597
'success', ('yes',)),
598
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
600
self.assertTrue(a_controldir.has_workingtree())
601
self.assertFinished(client)
603
def test_no_workingtree(self):
604
transport = self.get_transport('quack')
605
client = FakeClient(transport.base)
606
client.add_expected_call(
607
'BzrDir.has_workingtree', ('quack/',),
609
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
611
self.assertFalse(a_controldir.has_workingtree())
612
self.assertFinished(client)
615
class TestBzrDirDestroyRepository(TestRemote):
617
def test_destroy_repository(self):
618
transport = self.get_transport('quack')
619
client = FakeClient(transport.base)
620
client.add_expected_call(
621
'BzrDir.destroy_repository', ('quack/',),
623
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
625
a_controldir.destroy_repository()
626
self.assertFinished(client)
629
class TestBzrDirOpen(TestRemote):
631
def make_fake_client_and_transport(self, path='quack'):
632
transport = MemoryTransport()
633
transport.mkdir(path)
634
transport = transport.clone(path)
635
client = FakeClient(transport.base)
636
return client, transport
638
def test_absent(self):
639
client, transport = self.make_fake_client_and_transport()
640
client.add_expected_call(
641
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
642
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
643
RemoteBzrDirFormat(), _client=client, _force_probe=True)
644
self.assertFinished(client)
646
def test_present_without_workingtree(self):
647
client, transport = self.make_fake_client_and_transport()
648
client.add_expected_call(
649
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
650
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
651
_client=client, _force_probe=True)
652
self.assertIsInstance(bd, RemoteBzrDir)
653
self.assertFalse(bd.has_workingtree())
654
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
655
self.assertFinished(client)
657
def test_present_with_workingtree(self):
658
client, transport = self.make_fake_client_and_transport()
659
client.add_expected_call(
660
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
661
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
662
_client=client, _force_probe=True)
663
self.assertIsInstance(bd, RemoteBzrDir)
664
self.assertTrue(bd.has_workingtree())
665
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
666
self.assertFinished(client)
668
def test_backwards_compat(self):
669
client, transport = self.make_fake_client_and_transport()
670
client.add_expected_call(
671
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
672
client.add_expected_call(
673
'BzrDir.open', ('quack/',), 'success', ('yes',))
674
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
675
_client=client, _force_probe=True)
676
self.assertIsInstance(bd, RemoteBzrDir)
677
self.assertFinished(client)
679
def test_backwards_compat_hpss_v2(self):
680
client, transport = self.make_fake_client_and_transport()
681
# Monkey-patch fake client to simulate real-world behaviour with v2
682
# server: upon first RPC call detect the protocol version, and because
683
# the version is 2 also do _remember_remote_is_before((1, 6)) before
684
# continuing with the RPC.
685
orig_check_call = client._check_call
686
def check_call(method, args):
687
client._medium._protocol_version = 2
688
client._medium._remember_remote_is_before((1, 6))
689
client._check_call = orig_check_call
690
client._check_call(method, args)
691
client._check_call = check_call
692
client.add_expected_call(
693
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
694
client.add_expected_call(
695
'BzrDir.open', ('quack/',), 'success', ('yes',))
696
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
697
_client=client, _force_probe=True)
698
self.assertIsInstance(bd, RemoteBzrDir)
699
self.assertFinished(client)
702
class TestBzrDirOpenBranch(TestRemote):
704
def test_backwards_compat(self):
705
self.setup_smart_server_with_call_log()
706
self.make_branch('.')
707
a_dir = BzrDir.open(self.get_url('.'))
708
self.reset_smart_call_log()
709
verb = 'BzrDir.open_branchV3'
710
self.disable_verb(verb)
711
format = a_dir.open_branch()
712
call_count = len([call for call in self.hpss_calls if
713
call.call.method == verb])
714
self.assertEqual(1, call_count)
716
def test_branch_present(self):
717
reference_format = self.get_repo_format()
718
network_name = reference_format.network_name()
719
branch_network_name = self.get_branch_format().network_name()
720
transport = MemoryTransport()
721
transport.mkdir('quack')
722
transport = transport.clone('quack')
723
client = FakeClient(transport.base)
724
client.add_expected_call(
725
'BzrDir.open_branchV3', ('quack/',),
726
'success', ('branch', branch_network_name))
727
client.add_expected_call(
728
'BzrDir.find_repositoryV3', ('quack/',),
729
'success', ('ok', '', 'no', 'no', 'no', network_name))
730
client.add_expected_call(
731
'Branch.get_stacked_on_url', ('quack/',),
732
'error', ('NotStacked',))
733
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
735
result = bzrdir.open_branch()
736
self.assertIsInstance(result, RemoteBranch)
737
self.assertEqual(bzrdir, result.controldir)
738
self.assertFinished(client)
740
def test_branch_missing(self):
741
transport = MemoryTransport()
742
transport.mkdir('quack')
743
transport = transport.clone('quack')
744
client = FakeClient(transport.base)
745
client.add_error_response('nobranch')
746
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
748
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
750
[('call', 'BzrDir.open_branchV3', ('quack/',))],
753
def test__get_tree_branch(self):
754
# _get_tree_branch is a form of open_branch, but it should only ask for
755
# branch opening, not any other network requests.
757
def open_branch(name=None, possible_transports=None):
758
calls.append("Called")
760
transport = MemoryTransport()
761
# no requests on the network - catches other api calls being made.
762
client = FakeClient(transport.base)
763
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
765
# patch the open_branch call to record that it was called.
766
bzrdir.open_branch = open_branch
767
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
768
self.assertEqual(["Called"], calls)
769
self.assertEqual([], client._calls)
771
def test_url_quoting_of_path(self):
772
# Relpaths on the wire should not be URL-escaped. So "~" should be
773
# transmitted as "~", not "%7E".
774
transport = RemoteTCPTransport('bzr://localhost/~hello/')
775
client = FakeClient(transport.base)
776
reference_format = self.get_repo_format()
777
network_name = reference_format.network_name()
778
branch_network_name = self.get_branch_format().network_name()
779
client.add_expected_call(
780
'BzrDir.open_branchV3', ('~hello/',),
781
'success', ('branch', branch_network_name))
782
client.add_expected_call(
783
'BzrDir.find_repositoryV3', ('~hello/',),
784
'success', ('ok', '', 'no', 'no', 'no', network_name))
785
client.add_expected_call(
786
'Branch.get_stacked_on_url', ('~hello/',),
787
'error', ('NotStacked',))
788
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
790
result = bzrdir.open_branch()
791
self.assertFinished(client)
793
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
794
reference_format = self.get_repo_format()
795
network_name = reference_format.network_name()
796
transport = MemoryTransport()
797
transport.mkdir('quack')
798
transport = transport.clone('quack')
800
rich_response = 'yes'
804
subtree_response = 'yes'
806
subtree_response = 'no'
807
client = FakeClient(transport.base)
808
client.add_success_response(
809
'ok', '', rich_response, subtree_response, external_lookup,
811
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
813
result = bzrdir.open_repository()
815
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
817
self.assertIsInstance(result, RemoteRepository)
818
self.assertEqual(bzrdir, result.controldir)
819
self.assertEqual(rich_root, result._format.rich_root_data)
820
self.assertEqual(subtrees, result._format.supports_tree_reference)
822
def test_open_repository_sets_format_attributes(self):
823
self.check_open_repository(True, True)
824
self.check_open_repository(False, True)
825
self.check_open_repository(True, False)
826
self.check_open_repository(False, False)
827
self.check_open_repository(False, False, 'yes')
829
def test_old_server(self):
830
"""RemoteBzrDirFormat should fail to probe if the server version is too
833
self.assertRaises(errors.NotBranchError,
834
RemoteBzrProber.probe_transport, OldServerTransport())
837
class TestBzrDirCreateBranch(TestRemote):
839
def test_backwards_compat(self):
840
self.setup_smart_server_with_call_log()
841
repo = self.make_repository('.')
842
self.reset_smart_call_log()
843
self.disable_verb('BzrDir.create_branch')
844
branch = repo.controldir.create_branch()
845
create_branch_call_count = len([call for call in self.hpss_calls if
846
call.call.method == 'BzrDir.create_branch'])
847
self.assertEqual(1, create_branch_call_count)
849
def test_current_server(self):
850
transport = self.get_transport('.')
851
transport = transport.clone('quack')
852
self.make_repository('quack')
853
client = FakeClient(transport.base)
854
reference_bzrdir_format = controldir.format_registry.get('default')()
855
reference_format = reference_bzrdir_format.get_branch_format()
856
network_name = reference_format.network_name()
857
reference_repo_fmt = reference_bzrdir_format.repository_format
858
reference_repo_name = reference_repo_fmt.network_name()
859
client.add_expected_call(
860
'BzrDir.create_branch', ('quack/', network_name),
861
'success', ('ok', network_name, '', 'no', 'no', 'yes',
862
reference_repo_name))
863
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
865
branch = a_controldir.create_branch()
866
# We should have got a remote branch
867
self.assertIsInstance(branch, remote.RemoteBranch)
868
# its format should have the settings from the response
869
format = branch._format
870
self.assertEqual(network_name, format.network_name())
872
def test_already_open_repo_and_reused_medium(self):
873
"""Bug 726584: create_branch(..., repository=repo) should work
874
regardless of what the smart medium's base URL is.
876
self.transport_server = test_server.SmartTCPServer_for_testing
877
transport = self.get_transport('.')
878
repo = self.make_repository('quack')
879
# Client's medium rooted a transport root (not at the bzrdir)
880
client = FakeClient(transport.base)
881
transport = transport.clone('quack')
882
reference_bzrdir_format = controldir.format_registry.get('default')()
883
reference_format = reference_bzrdir_format.get_branch_format()
884
network_name = reference_format.network_name()
885
reference_repo_fmt = reference_bzrdir_format.repository_format
886
reference_repo_name = reference_repo_fmt.network_name()
887
client.add_expected_call(
888
'BzrDir.create_branch', ('extra/quack/', network_name),
889
'success', ('ok', network_name, '', 'no', 'no', 'yes',
890
reference_repo_name))
891
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
893
branch = a_controldir.create_branch(repository=repo)
894
# We should have got a remote branch
895
self.assertIsInstance(branch, remote.RemoteBranch)
896
# its format should have the settings from the response
897
format = branch._format
898
self.assertEqual(network_name, format.network_name())
901
class TestBzrDirCreateRepository(TestRemote):
903
def test_backwards_compat(self):
904
self.setup_smart_server_with_call_log()
905
bzrdir = self.make_controldir('.')
906
self.reset_smart_call_log()
907
self.disable_verb('BzrDir.create_repository')
908
repo = bzrdir.create_repository()
909
create_repo_call_count = len([call for call in self.hpss_calls if
910
call.call.method == 'BzrDir.create_repository'])
911
self.assertEqual(1, create_repo_call_count)
913
def test_current_server(self):
914
transport = self.get_transport('.')
915
transport = transport.clone('quack')
916
self.make_controldir('quack')
917
client = FakeClient(transport.base)
918
reference_bzrdir_format = controldir.format_registry.get('default')()
919
reference_format = reference_bzrdir_format.repository_format
920
network_name = reference_format.network_name()
921
client.add_expected_call(
922
'BzrDir.create_repository', ('quack/',
923
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
925
'success', ('ok', 'yes', 'yes', 'yes', network_name))
926
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
928
repo = a_controldir.create_repository()
929
# We should have got a remote repository
930
self.assertIsInstance(repo, remote.RemoteRepository)
931
# its format should have the settings from the response
932
format = repo._format
933
self.assertTrue(format.rich_root_data)
934
self.assertTrue(format.supports_tree_reference)
935
self.assertTrue(format.supports_external_lookups)
936
self.assertEqual(network_name, format.network_name())
939
class TestBzrDirOpenRepository(TestRemote):
941
def test_backwards_compat_1_2_3(self):
942
# fallback all the way to the first version.
943
reference_format = self.get_repo_format()
944
network_name = reference_format.network_name()
945
server_url = 'bzr://example.com/'
946
self.permit_url(server_url)
947
client = FakeClient(server_url)
948
client.add_unknown_method_response('BzrDir.find_repositoryV3')
949
client.add_unknown_method_response('BzrDir.find_repositoryV2')
950
client.add_success_response('ok', '', 'no', 'no')
951
# A real repository instance will be created to determine the network
953
client.add_success_response_with_body(
954
"Bazaar-NG meta directory, format 1\n", 'ok')
955
client.add_success_response('stat', '0', '65535')
956
client.add_success_response_with_body(
957
reference_format.get_format_string(), 'ok')
958
# PackRepository wants to do a stat
959
client.add_success_response('stat', '0', '65535')
960
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
962
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
964
repo = bzrdir.open_repository()
966
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
967
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
968
('call', 'BzrDir.find_repository', ('quack/',)),
969
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
970
('call', 'stat', ('/quack/.bzr',)),
971
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
972
('call', 'stat', ('/quack/.bzr/repository',)),
975
self.assertEqual(network_name, repo._format.network_name())
977
def test_backwards_compat_2(self):
978
# fallback to find_repositoryV2
979
reference_format = self.get_repo_format()
980
network_name = reference_format.network_name()
981
server_url = 'bzr://example.com/'
982
self.permit_url(server_url)
983
client = FakeClient(server_url)
984
client.add_unknown_method_response('BzrDir.find_repositoryV3')
985
client.add_success_response('ok', '', 'no', 'no', 'no')
986
# A real repository instance will be created to determine the network
988
client.add_success_response_with_body(
989
"Bazaar-NG meta directory, format 1\n", 'ok')
990
client.add_success_response('stat', '0', '65535')
991
client.add_success_response_with_body(
992
reference_format.get_format_string(), 'ok')
993
# PackRepository wants to do a stat
994
client.add_success_response('stat', '0', '65535')
995
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
997
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
999
repo = bzrdir.open_repository()
1001
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
1002
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
1003
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
1004
('call', 'stat', ('/quack/.bzr',)),
1005
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
1006
('call', 'stat', ('/quack/.bzr/repository',)),
1009
self.assertEqual(network_name, repo._format.network_name())
1011
def test_current_server(self):
1012
reference_format = self.get_repo_format()
1013
network_name = reference_format.network_name()
1014
transport = MemoryTransport()
1015
transport.mkdir('quack')
1016
transport = transport.clone('quack')
1017
client = FakeClient(transport.base)
1018
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
1019
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1021
repo = bzrdir.open_repository()
1023
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
1025
self.assertEqual(network_name, repo._format.network_name())
1028
class TestBzrDirFormatInitializeEx(TestRemote):
1030
def test_success(self):
1031
"""Simple test for typical successful call."""
1032
fmt = RemoteBzrDirFormat()
1033
default_format_name = BzrDirFormat.get_default_format().network_name()
1034
transport = self.get_transport()
1035
client = FakeClient(transport.base)
1036
client.add_expected_call(
1037
'BzrDirFormat.initialize_ex_1.16',
1038
(default_format_name, 'path', 'False', 'False', 'False', '',
1039
'', '', '', 'False'),
1041
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
1042
'bzrdir fmt', 'False', '', '', 'repo lock token'))
1043
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1044
# it's currently hard to test that without supplying a real remote
1045
# transport connected to a real server.
1046
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1047
transport, False, False, False, None, None, None, None, False)
1048
self.assertFinished(client)
1050
def test_error(self):
1051
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1052
corresponding error from the client.
1054
fmt = RemoteBzrDirFormat()
1055
default_format_name = BzrDirFormat.get_default_format().network_name()
1056
transport = self.get_transport()
1057
client = FakeClient(transport.base)
1058
client.add_expected_call(
1059
'BzrDirFormat.initialize_ex_1.16',
1060
(default_format_name, 'path', 'False', 'False', 'False', '',
1061
'', '', '', 'False'),
1063
('PermissionDenied', 'path', 'extra info'))
1064
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1065
# it's currently hard to test that without supplying a real remote
1066
# transport connected to a real server.
1067
err = self.assertRaises(errors.PermissionDenied,
1068
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1069
False, False, False, None, None, None, None, False)
1070
self.assertEqual('path', err.path)
1071
self.assertEqual(': extra info', err.extra)
1072
self.assertFinished(client)
1074
def test_error_from_real_server(self):
1075
"""Integration test for error translation."""
1076
transport = self.make_smart_server('foo')
1077
transport = transport.clone('no-such-path')
1078
fmt = RemoteBzrDirFormat()
1079
err = self.assertRaises(errors.NoSuchFile,
1080
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1083
class OldSmartClient(object):
1084
"""A fake smart client for test_old_version that just returns a version one
1085
response to the 'hello' (query version) command.
1088
def get_request(self):
1089
input_file = BytesIO(b'ok\x011\n')
1090
output_file = BytesIO()
1091
client_medium = medium.SmartSimplePipesClientMedium(
1092
input_file, output_file)
1093
return medium.SmartClientStreamMediumRequest(client_medium)
1095
def protocol_version(self):
1099
class OldServerTransport(object):
1100
"""A fake transport for test_old_server that reports it's smart server
1101
protocol version as version one.
1107
def get_smart_client(self):
1108
return OldSmartClient()
1111
class RemoteBzrDirTestCase(TestRemote):
1113
def make_remote_bzrdir(self, transport, client):
1114
"""Make a RemotebzrDir using 'client' as the _client."""
1115
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1119
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1121
def lock_remote_branch(self, branch):
1122
"""Trick a RemoteBranch into thinking it is locked."""
1123
branch._lock_mode = 'w'
1124
branch._lock_count = 2
1125
branch._lock_token = 'branch token'
1126
branch._repo_lock_token = 'repo token'
1127
branch.repository._lock_mode = 'w'
1128
branch.repository._lock_count = 2
1129
branch.repository._lock_token = 'repo token'
1131
def make_remote_branch(self, transport, client):
1132
"""Make a RemoteBranch using 'client' as its _SmartClient.
1134
A RemoteBzrDir and RemoteRepository will also be created to fill out
1135
the RemoteBranch, albeit with stub values for some of their attributes.
1137
# we do not want bzrdir to make any remote calls, so use False as its
1138
# _client. If it tries to make a remote call, this will fail
1140
bzrdir = self.make_remote_bzrdir(transport, False)
1141
repo = RemoteRepository(bzrdir, None, _client=client)
1142
branch_format = self.get_branch_format()
1143
format = RemoteBranchFormat(network_name=branch_format.network_name())
1144
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1147
class TestBranchBreakLock(RemoteBranchTestCase):
1149
def test_break_lock(self):
1150
transport_path = 'quack'
1151
transport = MemoryTransport()
1152
client = FakeClient(transport.base)
1153
client.add_expected_call(
1154
'Branch.get_stacked_on_url', ('quack/',),
1155
'error', ('NotStacked',))
1156
client.add_expected_call(
1157
'Branch.break_lock', ('quack/',),
1159
transport.mkdir('quack')
1160
transport = transport.clone('quack')
1161
branch = self.make_remote_branch(transport, client)
1163
self.assertFinished(client)
1166
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1168
def test_get_physical_lock_status_yes(self):
1169
transport = MemoryTransport()
1170
client = FakeClient(transport.base)
1171
client.add_expected_call(
1172
'Branch.get_stacked_on_url', ('quack/',),
1173
'error', ('NotStacked',))
1174
client.add_expected_call(
1175
'Branch.get_physical_lock_status', ('quack/',),
1176
'success', ('yes',))
1177
transport.mkdir('quack')
1178
transport = transport.clone('quack')
1179
branch = self.make_remote_branch(transport, client)
1180
result = branch.get_physical_lock_status()
1181
self.assertFinished(client)
1182
self.assertEqual(True, result)
1184
def test_get_physical_lock_status_no(self):
1185
transport = MemoryTransport()
1186
client = FakeClient(transport.base)
1187
client.add_expected_call(
1188
'Branch.get_stacked_on_url', ('quack/',),
1189
'error', ('NotStacked',))
1190
client.add_expected_call(
1191
'Branch.get_physical_lock_status', ('quack/',),
1193
transport.mkdir('quack')
1194
transport = transport.clone('quack')
1195
branch = self.make_remote_branch(transport, client)
1196
result = branch.get_physical_lock_status()
1197
self.assertFinished(client)
1198
self.assertEqual(False, result)
1201
class TestBranchGetParent(RemoteBranchTestCase):
1203
def test_no_parent(self):
1204
# in an empty branch we decode the response properly
1205
transport = MemoryTransport()
1206
client = FakeClient(transport.base)
1207
client.add_expected_call(
1208
'Branch.get_stacked_on_url', ('quack/',),
1209
'error', ('NotStacked',))
1210
client.add_expected_call(
1211
'Branch.get_parent', ('quack/',),
1213
transport.mkdir('quack')
1214
transport = transport.clone('quack')
1215
branch = self.make_remote_branch(transport, client)
1216
result = branch.get_parent()
1217
self.assertFinished(client)
1218
self.assertEqual(None, result)
1220
def test_parent_relative(self):
1221
transport = MemoryTransport()
1222
client = FakeClient(transport.base)
1223
client.add_expected_call(
1224
'Branch.get_stacked_on_url', ('kwaak/',),
1225
'error', ('NotStacked',))
1226
client.add_expected_call(
1227
'Branch.get_parent', ('kwaak/',),
1228
'success', ('../foo/',))
1229
transport.mkdir('kwaak')
1230
transport = transport.clone('kwaak')
1231
branch = self.make_remote_branch(transport, client)
1232
result = branch.get_parent()
1233
self.assertEqual(transport.clone('../foo').base, result)
1235
def test_parent_absolute(self):
1236
transport = MemoryTransport()
1237
client = FakeClient(transport.base)
1238
client.add_expected_call(
1239
'Branch.get_stacked_on_url', ('kwaak/',),
1240
'error', ('NotStacked',))
1241
client.add_expected_call(
1242
'Branch.get_parent', ('kwaak/',),
1243
'success', ('http://foo/',))
1244
transport.mkdir('kwaak')
1245
transport = transport.clone('kwaak')
1246
branch = self.make_remote_branch(transport, client)
1247
result = branch.get_parent()
1248
self.assertEqual('http://foo/', result)
1249
self.assertFinished(client)
1252
class TestBranchSetParentLocation(RemoteBranchTestCase):
1254
def test_no_parent(self):
1255
# We call the verb when setting parent to None
1256
transport = MemoryTransport()
1257
client = FakeClient(transport.base)
1258
client.add_expected_call(
1259
'Branch.get_stacked_on_url', ('quack/',),
1260
'error', ('NotStacked',))
1261
client.add_expected_call(
1262
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1264
transport.mkdir('quack')
1265
transport = transport.clone('quack')
1266
branch = self.make_remote_branch(transport, client)
1267
branch._lock_token = 'b'
1268
branch._repo_lock_token = 'r'
1269
branch._set_parent_location(None)
1270
self.assertFinished(client)
1272
def test_parent(self):
1273
transport = MemoryTransport()
1274
client = FakeClient(transport.base)
1275
client.add_expected_call(
1276
'Branch.get_stacked_on_url', ('kwaak/',),
1277
'error', ('NotStacked',))
1278
client.add_expected_call(
1279
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1281
transport.mkdir('kwaak')
1282
transport = transport.clone('kwaak')
1283
branch = self.make_remote_branch(transport, client)
1284
branch._lock_token = 'b'
1285
branch._repo_lock_token = 'r'
1286
branch._set_parent_location('foo')
1287
self.assertFinished(client)
1289
def test_backwards_compat(self):
1290
self.setup_smart_server_with_call_log()
1291
branch = self.make_branch('.')
1292
self.reset_smart_call_log()
1293
verb = 'Branch.set_parent_location'
1294
self.disable_verb(verb)
1295
branch.set_parent('http://foo/')
1296
self.assertLength(14, self.hpss_calls)
1299
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1301
def test_backwards_compat(self):
1302
self.setup_smart_server_with_call_log()
1303
branch = self.make_branch('.')
1304
self.reset_smart_call_log()
1305
verb = 'Branch.get_tags_bytes'
1306
self.disable_verb(verb)
1307
branch.tags.get_tag_dict()
1308
call_count = len([call for call in self.hpss_calls if
1309
call.call.method == verb])
1310
self.assertEqual(1, call_count)
1312
def test_trivial(self):
1313
transport = MemoryTransport()
1314
client = FakeClient(transport.base)
1315
client.add_expected_call(
1316
'Branch.get_stacked_on_url', ('quack/',),
1317
'error', ('NotStacked',))
1318
client.add_expected_call(
1319
'Branch.get_tags_bytes', ('quack/',),
1321
transport.mkdir('quack')
1322
transport = transport.clone('quack')
1323
branch = self.make_remote_branch(transport, client)
1324
result = branch.tags.get_tag_dict()
1325
self.assertFinished(client)
1326
self.assertEqual({}, result)
1329
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1331
def test_trivial(self):
1332
transport = MemoryTransport()
1333
client = FakeClient(transport.base)
1334
client.add_expected_call(
1335
'Branch.get_stacked_on_url', ('quack/',),
1336
'error', ('NotStacked',))
1337
client.add_expected_call(
1338
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1340
transport.mkdir('quack')
1341
transport = transport.clone('quack')
1342
branch = self.make_remote_branch(transport, client)
1343
self.lock_remote_branch(branch)
1344
branch._set_tags_bytes('tags bytes')
1345
self.assertFinished(client)
1346
self.assertEqual('tags bytes', client._calls[-1][-1])
1348
def test_backwards_compatible(self):
1349
transport = MemoryTransport()
1350
client = FakeClient(transport.base)
1351
client.add_expected_call(
1352
'Branch.get_stacked_on_url', ('quack/',),
1353
'error', ('NotStacked',))
1354
client.add_expected_call(
1355
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1356
'unknown', ('Branch.set_tags_bytes',))
1357
transport.mkdir('quack')
1358
transport = transport.clone('quack')
1359
branch = self.make_remote_branch(transport, client)
1360
self.lock_remote_branch(branch)
1361
class StubRealBranch(object):
1364
def _set_tags_bytes(self, bytes):
1365
self.calls.append(('set_tags_bytes', bytes))
1366
real_branch = StubRealBranch()
1367
branch._real_branch = real_branch
1368
branch._set_tags_bytes('tags bytes')
1369
# Call a second time, to exercise the 'remote version already inferred'
1371
branch._set_tags_bytes('tags bytes')
1372
self.assertFinished(client)
1374
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1377
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1379
def test_uses_last_revision_info_and_tags_by_default(self):
1380
transport = MemoryTransport()
1381
client = FakeClient(transport.base)
1382
client.add_expected_call(
1383
'Branch.get_stacked_on_url', ('quack/',),
1384
'error', ('NotStacked',))
1385
client.add_expected_call(
1386
'Branch.last_revision_info', ('quack/',),
1387
'success', ('ok', '1', 'rev-tip'))
1388
client.add_expected_call(
1389
'Branch.get_config_file', ('quack/',),
1390
'success', ('ok',), '')
1391
transport.mkdir('quack')
1392
transport = transport.clone('quack')
1393
branch = self.make_remote_branch(transport, client)
1394
result = branch.heads_to_fetch()
1395
self.assertFinished(client)
1396
self.assertEqual(({'rev-tip'}, set()), result)
1398
def test_uses_last_revision_info_and_tags_when_set(self):
1399
transport = MemoryTransport()
1400
client = FakeClient(transport.base)
1401
client.add_expected_call(
1402
'Branch.get_stacked_on_url', ('quack/',),
1403
'error', ('NotStacked',))
1404
client.add_expected_call(
1405
'Branch.last_revision_info', ('quack/',),
1406
'success', ('ok', '1', 'rev-tip'))
1407
client.add_expected_call(
1408
'Branch.get_config_file', ('quack/',),
1409
'success', ('ok',), 'branch.fetch_tags = True')
1410
# XXX: this will break if the default format's serialization of tags
1411
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1412
client.add_expected_call(
1413
'Branch.get_tags_bytes', ('quack/',),
1414
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1415
transport.mkdir('quack')
1416
transport = transport.clone('quack')
1417
branch = self.make_remote_branch(transport, client)
1418
result = branch.heads_to_fetch()
1419
self.assertFinished(client)
1421
({'rev-tip'}, {'rev-foo', 'rev-bar'}), result)
1423
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1424
transport = MemoryTransport()
1425
client = FakeClient(transport.base)
1426
client.add_expected_call(
1427
'Branch.get_stacked_on_url', ('quack/',),
1428
'error', ('NotStacked',))
1429
client.add_expected_call(
1430
'Branch.heads_to_fetch', ('quack/',),
1431
'success', (['tip'], ['tagged-1', 'tagged-2']))
1432
transport.mkdir('quack')
1433
transport = transport.clone('quack')
1434
branch = self.make_remote_branch(transport, client)
1435
branch._format._use_default_local_heads_to_fetch = lambda: False
1436
result = branch.heads_to_fetch()
1437
self.assertFinished(client)
1438
self.assertEqual(({'tip'}, {'tagged-1', 'tagged-2'}), result)
1440
def make_branch_with_tags(self):
1441
self.setup_smart_server_with_call_log()
1442
# Make a branch with a single revision.
1443
builder = self.make_branch_builder('foo')
1444
builder.start_series()
1445
builder.build_snapshot('tip', None, [
1446
('add', ('', 'root-id', 'directory', ''))])
1447
builder.finish_series()
1448
branch = builder.get_branch()
1449
# Add two tags to that branch
1450
branch.tags.set_tag('tag-1', 'rev-1')
1451
branch.tags.set_tag('tag-2', 'rev-2')
1454
def test_backwards_compatible(self):
1455
br = self.make_branch_with_tags()
1456
br.get_config_stack().set('branch.fetch_tags', True)
1457
self.addCleanup(br.lock_read().unlock)
1458
# Disable the heads_to_fetch verb
1459
verb = 'Branch.heads_to_fetch'
1460
self.disable_verb(verb)
1461
self.reset_smart_call_log()
1462
result = br.heads_to_fetch()
1463
self.assertEqual(({'tip'}, {'rev-1', 'rev-2'}), result)
1465
['Branch.last_revision_info', 'Branch.get_tags_bytes'],
1466
[call.call.method for call in self.hpss_calls])
1468
def test_backwards_compatible_no_tags(self):
1469
br = self.make_branch_with_tags()
1470
br.get_config_stack().set('branch.fetch_tags', False)
1471
self.addCleanup(br.lock_read().unlock)
1472
# Disable the heads_to_fetch verb
1473
verb = 'Branch.heads_to_fetch'
1474
self.disable_verb(verb)
1475
self.reset_smart_call_log()
1476
result = br.heads_to_fetch()
1477
self.assertEqual(({'tip'}, set()), result)
1479
['Branch.last_revision_info'],
1480
[call.call.method for call in self.hpss_calls])
1483
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1485
def test_empty_branch(self):
1486
# in an empty branch we decode the response properly
1487
transport = MemoryTransport()
1488
client = FakeClient(transport.base)
1489
client.add_expected_call(
1490
'Branch.get_stacked_on_url', ('quack/',),
1491
'error', ('NotStacked',))
1492
client.add_expected_call(
1493
'Branch.last_revision_info', ('quack/',),
1494
'success', ('ok', '0', 'null:'))
1495
transport.mkdir('quack')
1496
transport = transport.clone('quack')
1497
branch = self.make_remote_branch(transport, client)
1498
result = branch.last_revision_info()
1499
self.assertFinished(client)
1500
self.assertEqual((0, NULL_REVISION), result)
1502
def test_non_empty_branch(self):
1503
# in a non-empty branch we also decode the response properly
1504
revid = u'\xc8'.encode('utf8')
1505
transport = MemoryTransport()
1506
client = FakeClient(transport.base)
1507
client.add_expected_call(
1508
'Branch.get_stacked_on_url', ('kwaak/',),
1509
'error', ('NotStacked',))
1510
client.add_expected_call(
1511
'Branch.last_revision_info', ('kwaak/',),
1512
'success', ('ok', '2', revid))
1513
transport.mkdir('kwaak')
1514
transport = transport.clone('kwaak')
1515
branch = self.make_remote_branch(transport, client)
1516
result = branch.last_revision_info()
1517
self.assertEqual((2, revid), result)
1520
class TestBranch_get_stacked_on_url(TestRemote):
1521
"""Test Branch._get_stacked_on_url rpc"""
1523
def test_get_stacked_on_invalid_url(self):
1524
# test that asking for a stacked on url the server can't access works.
1525
# This isn't perfect, but then as we're in the same process there
1526
# really isn't anything we can do to be 100% sure that the server
1527
# doesn't just open in - this test probably needs to be rewritten using
1528
# a spawn()ed server.
1529
stacked_branch = self.make_branch('stacked', format='1.9')
1530
memory_branch = self.make_branch('base', format='1.9')
1531
vfs_url = self.get_vfs_only_url('base')
1532
stacked_branch.set_stacked_on_url(vfs_url)
1533
transport = stacked_branch.controldir.root_transport
1534
client = FakeClient(transport.base)
1535
client.add_expected_call(
1536
'Branch.get_stacked_on_url', ('stacked/',),
1537
'success', ('ok', vfs_url))
1538
# XXX: Multiple calls are bad, this second call documents what is
1540
client.add_expected_call(
1541
'Branch.get_stacked_on_url', ('stacked/',),
1542
'success', ('ok', vfs_url))
1543
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1545
repo_fmt = remote.RemoteRepositoryFormat()
1546
repo_fmt._custom_format = stacked_branch.repository._format
1547
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1549
result = branch.get_stacked_on_url()
1550
self.assertEqual(vfs_url, result)
1552
def test_backwards_compatible(self):
1553
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1554
base_branch = self.make_branch('base', format='1.6')
1555
stacked_branch = self.make_branch('stacked', format='1.6')
1556
stacked_branch.set_stacked_on_url('../base')
1557
client = FakeClient(self.get_url())
1558
branch_network_name = self.get_branch_format().network_name()
1559
client.add_expected_call(
1560
'BzrDir.open_branchV3', ('stacked/',),
1561
'success', ('branch', branch_network_name))
1562
client.add_expected_call(
1563
'BzrDir.find_repositoryV3', ('stacked/',),
1564
'success', ('ok', '', 'no', 'no', 'yes',
1565
stacked_branch.repository._format.network_name()))
1566
# called twice, once from constructor and then again by us
1567
client.add_expected_call(
1568
'Branch.get_stacked_on_url', ('stacked/',),
1569
'unknown', ('Branch.get_stacked_on_url',))
1570
client.add_expected_call(
1571
'Branch.get_stacked_on_url', ('stacked/',),
1572
'unknown', ('Branch.get_stacked_on_url',))
1573
# this will also do vfs access, but that goes direct to the transport
1574
# and isn't seen by the FakeClient.
1575
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1576
RemoteBzrDirFormat(), _client=client)
1577
branch = bzrdir.open_branch()
1578
result = branch.get_stacked_on_url()
1579
self.assertEqual('../base', result)
1580
self.assertFinished(client)
1581
# it's in the fallback list both for the RemoteRepository and its vfs
1583
self.assertEqual(1, len(branch.repository._fallback_repositories))
1585
len(branch.repository._real_repository._fallback_repositories))
1587
def test_get_stacked_on_real_branch(self):
1588
base_branch = self.make_branch('base')
1589
stacked_branch = self.make_branch('stacked')
1590
stacked_branch.set_stacked_on_url('../base')
1591
reference_format = self.get_repo_format()
1592
network_name = reference_format.network_name()
1593
client = FakeClient(self.get_url())
1594
branch_network_name = self.get_branch_format().network_name()
1595
client.add_expected_call(
1596
'BzrDir.open_branchV3', ('stacked/',),
1597
'success', ('branch', branch_network_name))
1598
client.add_expected_call(
1599
'BzrDir.find_repositoryV3', ('stacked/',),
1600
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1601
# called twice, once from constructor and then again by us
1602
client.add_expected_call(
1603
'Branch.get_stacked_on_url', ('stacked/',),
1604
'success', ('ok', '../base'))
1605
client.add_expected_call(
1606
'Branch.get_stacked_on_url', ('stacked/',),
1607
'success', ('ok', '../base'))
1608
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1609
RemoteBzrDirFormat(), _client=client)
1610
branch = bzrdir.open_branch()
1611
result = branch.get_stacked_on_url()
1612
self.assertEqual('../base', result)
1613
self.assertFinished(client)
1614
# it's in the fallback list both for the RemoteRepository.
1615
self.assertEqual(1, len(branch.repository._fallback_repositories))
1616
# And we haven't had to construct a real repository.
1617
self.assertEqual(None, branch.repository._real_repository)
1620
class TestBranchSetLastRevision(RemoteBranchTestCase):
1622
def test_set_empty(self):
1623
# _set_last_revision_info('null:') is translated to calling
1624
# Branch.set_last_revision(path, '') on the wire.
1625
transport = MemoryTransport()
1626
transport.mkdir('branch')
1627
transport = transport.clone('branch')
1629
client = FakeClient(transport.base)
1630
client.add_expected_call(
1631
'Branch.get_stacked_on_url', ('branch/',),
1632
'error', ('NotStacked',))
1633
client.add_expected_call(
1634
'Branch.lock_write', ('branch/', '', ''),
1635
'success', ('ok', 'branch token', 'repo token'))
1636
client.add_expected_call(
1637
'Branch.last_revision_info',
1639
'success', ('ok', '0', 'null:'))
1640
client.add_expected_call(
1641
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1643
client.add_expected_call(
1644
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1646
branch = self.make_remote_branch(transport, client)
1648
result = branch._set_last_revision(NULL_REVISION)
1650
self.assertEqual(None, result)
1651
self.assertFinished(client)
1653
def test_set_nonempty(self):
1654
# set_last_revision_info(N, rev-idN) is translated to calling
1655
# Branch.set_last_revision(path, rev-idN) on the wire.
1656
transport = MemoryTransport()
1657
transport.mkdir('branch')
1658
transport = transport.clone('branch')
1660
client = FakeClient(transport.base)
1661
client.add_expected_call(
1662
'Branch.get_stacked_on_url', ('branch/',),
1663
'error', ('NotStacked',))
1664
client.add_expected_call(
1665
'Branch.lock_write', ('branch/', '', ''),
1666
'success', ('ok', 'branch token', 'repo token'))
1667
client.add_expected_call(
1668
'Branch.last_revision_info',
1670
'success', ('ok', '0', 'null:'))
1672
encoded_body = bz2.compress('\n'.join(lines))
1673
client.add_success_response_with_body(encoded_body, 'ok')
1674
client.add_expected_call(
1675
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1677
client.add_expected_call(
1678
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1680
branch = self.make_remote_branch(transport, client)
1681
# Lock the branch, reset the record of remote calls.
1683
result = branch._set_last_revision('rev-id2')
1685
self.assertEqual(None, result)
1686
self.assertFinished(client)
1688
def test_no_such_revision(self):
1689
transport = MemoryTransport()
1690
transport.mkdir('branch')
1691
transport = transport.clone('branch')
1692
# A response of 'NoSuchRevision' is translated into an exception.
1693
client = FakeClient(transport.base)
1694
client.add_expected_call(
1695
'Branch.get_stacked_on_url', ('branch/',),
1696
'error', ('NotStacked',))
1697
client.add_expected_call(
1698
'Branch.lock_write', ('branch/', '', ''),
1699
'success', ('ok', 'branch token', 'repo token'))
1700
client.add_expected_call(
1701
'Branch.last_revision_info',
1703
'success', ('ok', '0', 'null:'))
1704
# get_graph calls to construct the revision history, for the set_rh
1707
encoded_body = bz2.compress('\n'.join(lines))
1708
client.add_success_response_with_body(encoded_body, 'ok')
1709
client.add_expected_call(
1710
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1711
'error', ('NoSuchRevision', 'rev-id'))
1712
client.add_expected_call(
1713
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1716
branch = self.make_remote_branch(transport, client)
1719
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1721
self.assertFinished(client)
1723
def test_tip_change_rejected(self):
1724
"""TipChangeRejected responses cause a TipChangeRejected exception to
1727
transport = MemoryTransport()
1728
transport.mkdir('branch')
1729
transport = transport.clone('branch')
1730
client = FakeClient(transport.base)
1731
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1732
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1733
client.add_expected_call(
1734
'Branch.get_stacked_on_url', ('branch/',),
1735
'error', ('NotStacked',))
1736
client.add_expected_call(
1737
'Branch.lock_write', ('branch/', '', ''),
1738
'success', ('ok', 'branch token', 'repo token'))
1739
client.add_expected_call(
1740
'Branch.last_revision_info',
1742
'success', ('ok', '0', 'null:'))
1744
encoded_body = bz2.compress('\n'.join(lines))
1745
client.add_success_response_with_body(encoded_body, 'ok')
1746
client.add_expected_call(
1747
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1748
'error', ('TipChangeRejected', rejection_msg_utf8))
1749
client.add_expected_call(
1750
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1752
branch = self.make_remote_branch(transport, client)
1754
# The 'TipChangeRejected' error response triggered by calling
1755
# set_last_revision_info causes a TipChangeRejected exception.
1756
err = self.assertRaises(
1757
errors.TipChangeRejected,
1758
branch._set_last_revision, 'rev-id')
1759
# The UTF-8 message from the response has been decoded into a unicode
1761
self.assertIsInstance(err.msg, unicode)
1762
self.assertEqual(rejection_msg_unicode, err.msg)
1764
self.assertFinished(client)
1767
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1769
def test_set_last_revision_info(self):
1770
# set_last_revision_info(num, 'rev-id') is translated to calling
1771
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1772
transport = MemoryTransport()
1773
transport.mkdir('branch')
1774
transport = transport.clone('branch')
1775
client = FakeClient(transport.base)
1776
# get_stacked_on_url
1777
client.add_error_response('NotStacked')
1779
client.add_success_response('ok', 'branch token', 'repo token')
1780
# query the current revision
1781
client.add_success_response('ok', '0', 'null:')
1783
client.add_success_response('ok')
1785
client.add_success_response('ok')
1787
branch = self.make_remote_branch(transport, client)
1788
# Lock the branch, reset the record of remote calls.
1791
result = branch.set_last_revision_info(1234, 'a-revision-id')
1793
[('call', 'Branch.last_revision_info', ('branch/',)),
1794
('call', 'Branch.set_last_revision_info',
1795
('branch/', 'branch token', 'repo token',
1796
'1234', 'a-revision-id'))],
1798
self.assertEqual(None, result)
1800
def test_no_such_revision(self):
1801
# A response of 'NoSuchRevision' is translated into an exception.
1802
transport = MemoryTransport()
1803
transport.mkdir('branch')
1804
transport = transport.clone('branch')
1805
client = FakeClient(transport.base)
1806
# get_stacked_on_url
1807
client.add_error_response('NotStacked')
1809
client.add_success_response('ok', 'branch token', 'repo token')
1811
client.add_error_response('NoSuchRevision', 'revid')
1813
client.add_success_response('ok')
1815
branch = self.make_remote_branch(transport, client)
1816
# Lock the branch, reset the record of remote calls.
1821
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1824
def test_backwards_compatibility(self):
1825
"""If the server does not support the Branch.set_last_revision_info
1826
verb (which is new in 1.4), then the client falls back to VFS methods.
1828
# This test is a little messy. Unlike most tests in this file, it
1829
# doesn't purely test what a Remote* object sends over the wire, and
1830
# how it reacts to responses from the wire. It instead relies partly
1831
# on asserting that the RemoteBranch will call
1832
# self._real_branch.set_last_revision_info(...).
1834
# First, set up our RemoteBranch with a FakeClient that raises
1835
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1836
transport = MemoryTransport()
1837
transport.mkdir('branch')
1838
transport = transport.clone('branch')
1839
client = FakeClient(transport.base)
1840
client.add_expected_call(
1841
'Branch.get_stacked_on_url', ('branch/',),
1842
'error', ('NotStacked',))
1843
client.add_expected_call(
1844
'Branch.last_revision_info',
1846
'success', ('ok', '0', 'null:'))
1847
client.add_expected_call(
1848
'Branch.set_last_revision_info',
1849
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1850
'unknown', 'Branch.set_last_revision_info')
1852
branch = self.make_remote_branch(transport, client)
1853
class StubRealBranch(object):
1856
def set_last_revision_info(self, revno, revision_id):
1858
('set_last_revision_info', revno, revision_id))
1859
def _clear_cached_state(self):
1861
real_branch = StubRealBranch()
1862
branch._real_branch = real_branch
1863
self.lock_remote_branch(branch)
1865
# Call set_last_revision_info, and verify it behaved as expected.
1866
result = branch.set_last_revision_info(1234, 'a-revision-id')
1868
[('set_last_revision_info', 1234, 'a-revision-id')],
1870
self.assertFinished(client)
1872
def test_unexpected_error(self):
1873
# If the server sends an error the client doesn't understand, it gets
1874
# turned into an UnknownErrorFromSmartServer, which is presented as a
1875
# non-internal error to the user.
1876
transport = MemoryTransport()
1877
transport.mkdir('branch')
1878
transport = transport.clone('branch')
1879
client = FakeClient(transport.base)
1880
# get_stacked_on_url
1881
client.add_error_response('NotStacked')
1883
client.add_success_response('ok', 'branch token', 'repo token')
1885
client.add_error_response('UnexpectedError')
1887
client.add_success_response('ok')
1889
branch = self.make_remote_branch(transport, client)
1890
# Lock the branch, reset the record of remote calls.
1894
err = self.assertRaises(
1895
errors.UnknownErrorFromSmartServer,
1896
branch.set_last_revision_info, 123, 'revid')
1897
self.assertEqual(('UnexpectedError',), err.error_tuple)
1900
def test_tip_change_rejected(self):
1901
"""TipChangeRejected responses cause a TipChangeRejected exception to
1904
transport = MemoryTransport()
1905
transport.mkdir('branch')
1906
transport = transport.clone('branch')
1907
client = FakeClient(transport.base)
1908
# get_stacked_on_url
1909
client.add_error_response('NotStacked')
1911
client.add_success_response('ok', 'branch token', 'repo token')
1913
client.add_error_response('TipChangeRejected', 'rejection message')
1915
client.add_success_response('ok')
1917
branch = self.make_remote_branch(transport, client)
1918
# Lock the branch, reset the record of remote calls.
1920
self.addCleanup(branch.unlock)
1923
# The 'TipChangeRejected' error response triggered by calling
1924
# set_last_revision_info causes a TipChangeRejected exception.
1925
err = self.assertRaises(
1926
errors.TipChangeRejected,
1927
branch.set_last_revision_info, 123, 'revid')
1928
self.assertEqual('rejection message', err.msg)
1931
class TestBranchGetSetConfig(RemoteBranchTestCase):
1933
def test_get_branch_conf(self):
1934
# in an empty branch we decode the response properly
1935
client = FakeClient()
1936
client.add_expected_call(
1937
'Branch.get_stacked_on_url', ('memory:///',),
1938
'error', ('NotStacked',),)
1939
client.add_success_response_with_body('# config file body', 'ok')
1940
transport = MemoryTransport()
1941
branch = self.make_remote_branch(transport, client)
1942
config = branch.get_config()
1943
config.has_explicit_nickname()
1945
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1946
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1949
def test_get_multi_line_branch_conf(self):
1950
# Make sure that multiple-line branch.conf files are supported
1952
# https://bugs.launchpad.net/bzr/+bug/354075
1953
client = FakeClient()
1954
client.add_expected_call(
1955
'Branch.get_stacked_on_url', ('memory:///',),
1956
'error', ('NotStacked',),)
1957
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1958
transport = MemoryTransport()
1959
branch = self.make_remote_branch(transport, client)
1960
config = branch.get_config()
1961
self.assertEqual(u'2', config.get_user_option('b'))
1963
def test_set_option(self):
1964
client = FakeClient()
1965
client.add_expected_call(
1966
'Branch.get_stacked_on_url', ('memory:///',),
1967
'error', ('NotStacked',),)
1968
client.add_expected_call(
1969
'Branch.lock_write', ('memory:///', '', ''),
1970
'success', ('ok', 'branch token', 'repo token'))
1971
client.add_expected_call(
1972
'Branch.set_config_option', ('memory:///', 'branch token',
1973
'repo token', 'foo', 'bar', ''),
1975
client.add_expected_call(
1976
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1978
transport = MemoryTransport()
1979
branch = self.make_remote_branch(transport, client)
1981
config = branch._get_config()
1982
config.set_option('foo', 'bar')
1984
self.assertFinished(client)
1986
def test_set_option_with_dict(self):
1987
client = FakeClient()
1988
client.add_expected_call(
1989
'Branch.get_stacked_on_url', ('memory:///',),
1990
'error', ('NotStacked',),)
1991
client.add_expected_call(
1992
'Branch.lock_write', ('memory:///', '', ''),
1993
'success', ('ok', 'branch token', 'repo token'))
1994
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1995
client.add_expected_call(
1996
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1997
'repo token', encoded_dict_value, 'foo', ''),
1999
client.add_expected_call(
2000
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2002
transport = MemoryTransport()
2003
branch = self.make_remote_branch(transport, client)
2005
config = branch._get_config()
2007
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2010
self.assertFinished(client)
2012
def test_backwards_compat_set_option(self):
2013
self.setup_smart_server_with_call_log()
2014
branch = self.make_branch('.')
2015
verb = 'Branch.set_config_option'
2016
self.disable_verb(verb)
2018
self.addCleanup(branch.unlock)
2019
self.reset_smart_call_log()
2020
branch._get_config().set_option('value', 'name')
2021
self.assertLength(11, self.hpss_calls)
2022
self.assertEqual('value', branch._get_config().get_option('name'))
2024
def test_backwards_compat_set_option_with_dict(self):
2025
self.setup_smart_server_with_call_log()
2026
branch = self.make_branch('.')
2027
verb = 'Branch.set_config_option_dict'
2028
self.disable_verb(verb)
2030
self.addCleanup(branch.unlock)
2031
self.reset_smart_call_log()
2032
config = branch._get_config()
2033
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2034
config.set_option(value_dict, 'name')
2035
self.assertLength(11, self.hpss_calls)
2036
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2039
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2041
def test_get_branch_conf(self):
2042
# in an empty branch we decode the response properly
2043
client = FakeClient()
2044
client.add_expected_call(
2045
'Branch.get_stacked_on_url', ('memory:///',),
2046
'error', ('NotStacked',),)
2047
client.add_success_response_with_body('# config file body', 'ok')
2048
transport = MemoryTransport()
2049
branch = self.make_remote_branch(transport, client)
2050
config = branch.get_config_stack()
2052
config.get("log_format")
2054
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2055
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2058
def test_set_branch_conf(self):
2059
client = FakeClient()
2060
client.add_expected_call(
2061
'Branch.get_stacked_on_url', ('memory:///',),
2062
'error', ('NotStacked',),)
2063
client.add_expected_call(
2064
'Branch.lock_write', ('memory:///', '', ''),
2065
'success', ('ok', 'branch token', 'repo token'))
2066
client.add_expected_call(
2067
'Branch.get_config_file', ('memory:///', ),
2068
'success', ('ok', ), "# line 1\n")
2069
client.add_expected_call(
2070
'Branch.get_config_file', ('memory:///', ),
2071
'success', ('ok', ), "# line 1\n")
2072
client.add_expected_call(
2073
'Branch.put_config_file', ('memory:///', 'branch token',
2076
client.add_expected_call(
2077
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2079
transport = MemoryTransport()
2080
branch = self.make_remote_branch(transport, client)
2082
config = branch.get_config_stack()
2083
config.set('email', 'The Dude <lebowski@example.com>')
2085
self.assertFinished(client)
2087
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2088
('call', 'Branch.lock_write', ('memory:///', '', '')),
2089
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2090
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2091
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2092
('memory:///', 'branch token', 'repo token'),
2093
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2094
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2098
class TestBranchLockWrite(RemoteBranchTestCase):
2100
def test_lock_write_unlockable(self):
2101
transport = MemoryTransport()
2102
client = FakeClient(transport.base)
2103
client.add_expected_call(
2104
'Branch.get_stacked_on_url', ('quack/',),
2105
'error', ('NotStacked',),)
2106
client.add_expected_call(
2107
'Branch.lock_write', ('quack/', '', ''),
2108
'error', ('UnlockableTransport',))
2109
transport.mkdir('quack')
2110
transport = transport.clone('quack')
2111
branch = self.make_remote_branch(transport, client)
2112
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2113
self.assertFinished(client)
2116
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2118
def test_simple(self):
2119
transport = MemoryTransport()
2120
client = FakeClient(transport.base)
2121
client.add_expected_call(
2122
'Branch.get_stacked_on_url', ('quack/',),
2123
'error', ('NotStacked',),)
2124
client.add_expected_call(
2125
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2126
'success', ('ok', '0',),)
2127
client.add_expected_call(
2128
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2129
'error', ('NoSuchRevision', 'unknown',),)
2130
transport.mkdir('quack')
2131
transport = transport.clone('quack')
2132
branch = self.make_remote_branch(transport, client)
2133
self.assertEqual(0, branch.revision_id_to_revno('null:'))
2134
self.assertRaises(errors.NoSuchRevision,
2135
branch.revision_id_to_revno, 'unknown')
2136
self.assertFinished(client)
2138
def test_dotted(self):
2139
transport = MemoryTransport()
2140
client = FakeClient(transport.base)
2141
client.add_expected_call(
2142
'Branch.get_stacked_on_url', ('quack/',),
2143
'error', ('NotStacked',),)
2144
client.add_expected_call(
2145
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2146
'success', ('ok', '0',),)
2147
client.add_expected_call(
2148
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2149
'error', ('NoSuchRevision', 'unknown',),)
2150
transport.mkdir('quack')
2151
transport = transport.clone('quack')
2152
branch = self.make_remote_branch(transport, client)
2153
self.assertEqual((0, ), branch.revision_id_to_dotted_revno('null:'))
2154
self.assertRaises(errors.NoSuchRevision,
2155
branch.revision_id_to_dotted_revno, 'unknown')
2156
self.assertFinished(client)
2158
def test_dotted_no_smart_verb(self):
2159
self.setup_smart_server_with_call_log()
2160
branch = self.make_branch('.')
2161
self.disable_verb('Branch.revision_id_to_revno')
2162
self.reset_smart_call_log()
2163
self.assertEqual((0, ),
2164
branch.revision_id_to_dotted_revno('null:'))
2165
self.assertLength(8, self.hpss_calls)
2168
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2170
def test__get_config(self):
2171
client = FakeClient()
2172
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2173
transport = MemoryTransport()
2174
bzrdir = self.make_remote_bzrdir(transport, client)
2175
config = bzrdir.get_config()
2176
self.assertEqual('/', config.get_default_stack_on())
2178
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2181
def test_set_option_uses_vfs(self):
2182
self.setup_smart_server_with_call_log()
2183
bzrdir = self.make_controldir('.')
2184
self.reset_smart_call_log()
2185
config = bzrdir.get_config()
2186
config.set_default_stack_on('/')
2187
self.assertLength(4, self.hpss_calls)
2189
def test_backwards_compat_get_option(self):
2190
self.setup_smart_server_with_call_log()
2191
bzrdir = self.make_controldir('.')
2192
verb = 'BzrDir.get_config_file'
2193
self.disable_verb(verb)
2194
self.reset_smart_call_log()
2195
self.assertEqual(None,
2196
bzrdir._get_config().get_option('default_stack_on'))
2197
self.assertLength(4, self.hpss_calls)
2200
class TestTransportIsReadonly(tests.TestCase):
2202
def test_true(self):
2203
client = FakeClient()
2204
client.add_success_response('yes')
2205
transport = RemoteTransport('bzr://example.com/', medium=False,
2207
self.assertEqual(True, transport.is_readonly())
2209
[('call', 'Transport.is_readonly', ())],
2212
def test_false(self):
2213
client = FakeClient()
2214
client.add_success_response('no')
2215
transport = RemoteTransport('bzr://example.com/', medium=False,
2217
self.assertEqual(False, transport.is_readonly())
2219
[('call', 'Transport.is_readonly', ())],
2222
def test_error_from_old_server(self):
2223
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2225
Clients should treat it as a "no" response, because is_readonly is only
2226
advisory anyway (a transport could be read-write, but then the
2227
underlying filesystem could be readonly anyway).
2229
client = FakeClient()
2230
client.add_unknown_method_response('Transport.is_readonly')
2231
transport = RemoteTransport('bzr://example.com/', medium=False,
2233
self.assertEqual(False, transport.is_readonly())
2235
[('call', 'Transport.is_readonly', ())],
2239
class TestTransportMkdir(tests.TestCase):
2241
def test_permissiondenied(self):
2242
client = FakeClient()
2243
client.add_error_response('PermissionDenied', 'remote path', 'extra')
2244
transport = RemoteTransport('bzr://example.com/', medium=False,
2246
exc = self.assertRaises(
2247
errors.PermissionDenied, transport.mkdir, 'client path')
2248
expected_error = errors.PermissionDenied('/client path', 'extra')
2249
self.assertEqual(expected_error, exc)
2252
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2254
def test_defaults_to_none(self):
2255
t = RemoteSSHTransport('bzr+ssh://example.com')
2256
self.assertIs(None, t._get_credentials()[0])
2258
def test_uses_authentication_config(self):
2259
conf = config.AuthenticationConfig()
2260
conf._get_config().update(
2261
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2264
t = RemoteSSHTransport('bzr+ssh://example.com')
2265
self.assertEqual('bar', t._get_credentials()[0])
2268
class TestRemoteRepository(TestRemote):
2269
"""Base for testing RemoteRepository protocol usage.
2271
These tests contain frozen requests and responses. We want any changes to
2272
what is sent or expected to be require a thoughtful update to these tests
2273
because they might break compatibility with different-versioned servers.
2276
def setup_fake_client_and_repository(self, transport_path):
2277
"""Create the fake client and repository for testing with.
2279
There's no real server here; we just have canned responses sent
2282
:param transport_path: Path below the root of the MemoryTransport
2283
where the repository will be created.
2285
transport = MemoryTransport()
2286
transport.mkdir(transport_path)
2287
client = FakeClient(transport.base)
2288
transport = transport.clone(transport_path)
2289
# we do not want bzrdir to make any remote calls
2290
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2292
repo = RemoteRepository(bzrdir, None, _client=client)
2296
def remoted_description(format):
2297
return 'Remote: ' + format.get_format_description()
2300
class TestBranchFormat(tests.TestCase):
2302
def test_get_format_description(self):
2303
remote_format = RemoteBranchFormat()
2304
real_format = branch.format_registry.get_default()
2305
remote_format._network_name = real_format.network_name()
2306
self.assertEqual(remoted_description(real_format),
2307
remote_format.get_format_description())
2310
class TestRepositoryFormat(TestRemoteRepository):
2312
def test_fast_delta(self):
2313
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2314
true_format = RemoteRepositoryFormat()
2315
true_format._network_name = true_name
2316
self.assertEqual(True, true_format.fast_deltas)
2317
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2318
false_format = RemoteRepositoryFormat()
2319
false_format._network_name = false_name
2320
self.assertEqual(False, false_format.fast_deltas)
2322
def test_get_format_description(self):
2323
remote_repo_format = RemoteRepositoryFormat()
2324
real_format = repository.format_registry.get_default()
2325
remote_repo_format._network_name = real_format.network_name()
2326
self.assertEqual(remoted_description(real_format),
2327
remote_repo_format.get_format_description())
2330
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2332
def test_empty(self):
2333
transport_path = 'quack'
2334
repo, client = self.setup_fake_client_and_repository(transport_path)
2335
client.add_success_response_with_body('', 'ok')
2336
self.assertEqual([], repo.all_revision_ids())
2338
[('call_expecting_body', 'Repository.all_revision_ids',
2342
def test_with_some_content(self):
2343
transport_path = 'quack'
2344
repo, client = self.setup_fake_client_and_repository(transport_path)
2345
client.add_success_response_with_body(
2346
'rev1\nrev2\nanotherrev\n', 'ok')
2347
self.assertEqual(["rev1", "rev2", "anotherrev"],
2348
repo.all_revision_ids())
2350
[('call_expecting_body', 'Repository.all_revision_ids',
2355
class TestRepositoryGatherStats(TestRemoteRepository):
2357
def test_revid_none(self):
2358
# ('ok',), body with revisions and size
2359
transport_path = 'quack'
2360
repo, client = self.setup_fake_client_and_repository(transport_path)
2361
client.add_success_response_with_body(
2362
'revisions: 2\nsize: 18\n', 'ok')
2363
result = repo.gather_stats(None)
2365
[('call_expecting_body', 'Repository.gather_stats',
2366
('quack/','','no'))],
2368
self.assertEqual({'revisions': 2, 'size': 18}, result)
2370
def test_revid_no_committers(self):
2371
# ('ok',), body without committers
2372
body = ('firstrev: 123456.300 3600\n'
2373
'latestrev: 654231.400 0\n'
2376
transport_path = 'quick'
2377
revid = u'\xc8'.encode('utf8')
2378
repo, client = self.setup_fake_client_and_repository(transport_path)
2379
client.add_success_response_with_body(body, 'ok')
2380
result = repo.gather_stats(revid)
2382
[('call_expecting_body', 'Repository.gather_stats',
2383
('quick/', revid, 'no'))],
2385
self.assertEqual({'revisions': 2, 'size': 18,
2386
'firstrev': (123456.300, 3600),
2387
'latestrev': (654231.400, 0),},
2390
def test_revid_with_committers(self):
2391
# ('ok',), body with committers
2392
body = ('committers: 128\n'
2393
'firstrev: 123456.300 3600\n'
2394
'latestrev: 654231.400 0\n'
2397
transport_path = 'buick'
2398
revid = u'\xc8'.encode('utf8')
2399
repo, client = self.setup_fake_client_and_repository(transport_path)
2400
client.add_success_response_with_body(body, 'ok')
2401
result = repo.gather_stats(revid, True)
2403
[('call_expecting_body', 'Repository.gather_stats',
2404
('buick/', revid, 'yes'))],
2406
self.assertEqual({'revisions': 2, 'size': 18,
2408
'firstrev': (123456.300, 3600),
2409
'latestrev': (654231.400, 0),},
2413
class TestRepositoryBreakLock(TestRemoteRepository):
2415
def test_break_lock(self):
2416
transport_path = 'quack'
2417
repo, client = self.setup_fake_client_and_repository(transport_path)
2418
client.add_success_response('ok')
2421
[('call', 'Repository.break_lock', ('quack/',))],
2425
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2427
def test_get_serializer_format(self):
2428
transport_path = 'hill'
2429
repo, client = self.setup_fake_client_and_repository(transport_path)
2430
client.add_success_response('ok', '7')
2431
self.assertEqual('7', repo.get_serializer_format())
2433
[('call', 'VersionedFileRepository.get_serializer_format',
2438
class TestRepositoryReconcile(TestRemoteRepository):
2440
def test_reconcile(self):
2441
transport_path = 'hill'
2442
repo, client = self.setup_fake_client_and_repository(transport_path)
2443
body = ("garbage_inventories: 2\n"
2444
"inconsistent_parents: 3\n")
2445
client.add_expected_call(
2446
'Repository.lock_write', ('hill/', ''),
2447
'success', ('ok', 'a token'))
2448
client.add_success_response_with_body(body, 'ok')
2449
reconciler = repo.reconcile()
2451
[('call', 'Repository.lock_write', ('hill/', '')),
2452
('call_expecting_body', 'Repository.reconcile',
2453
('hill/', 'a token'))],
2455
self.assertEqual(2, reconciler.garbage_inventories)
2456
self.assertEqual(3, reconciler.inconsistent_parents)
2459
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2461
def test_text(self):
2462
# ('ok',), body with signature text
2463
transport_path = 'quack'
2464
repo, client = self.setup_fake_client_and_repository(transport_path)
2465
client.add_success_response_with_body(
2467
self.assertEqual("THETEXT", repo.get_signature_text("revid"))
2469
[('call_expecting_body', 'Repository.get_revision_signature_text',
2470
('quack/', 'revid'))],
2473
def test_no_signature(self):
2474
transport_path = 'quick'
2475
repo, client = self.setup_fake_client_and_repository(transport_path)
2476
client.add_error_response('nosuchrevision', 'unknown')
2477
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2480
[('call_expecting_body', 'Repository.get_revision_signature_text',
2481
('quick/', 'unknown'))],
2485
class TestRepositoryGetGraph(TestRemoteRepository):
2487
def test_get_graph(self):
2488
# get_graph returns a graph with a custom parents provider.
2489
transport_path = 'quack'
2490
repo, client = self.setup_fake_client_and_repository(transport_path)
2491
graph = repo.get_graph()
2492
self.assertNotEqual(graph._parents_provider, repo)
2495
class TestRepositoryAddSignatureText(TestRemoteRepository):
2497
def test_add_signature_text(self):
2498
transport_path = 'quack'
2499
repo, client = self.setup_fake_client_and_repository(transport_path)
2500
client.add_expected_call(
2501
'Repository.lock_write', ('quack/', ''),
2502
'success', ('ok', 'a token'))
2503
client.add_expected_call(
2504
'Repository.start_write_group', ('quack/', 'a token'),
2505
'success', ('ok', ('token1', )))
2506
client.add_expected_call(
2507
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2509
'success', ('ok', ), None)
2511
repo.start_write_group()
2513
repo.add_signature_text("rev1", "every bloody emperor"))
2515
('call_with_body_bytes_expecting_body',
2516
'Repository.add_signature_text',
2517
('quack/', 'a token', 'rev1', 'token1'),
2518
'every bloody emperor'),
2522
class TestRepositoryGetParentMap(TestRemoteRepository):
2524
def test_get_parent_map_caching(self):
2525
# get_parent_map returns from cache until unlock()
2526
# setup a reponse with two revisions
2527
r1 = u'\u0e33'.encode('utf8')
2528
r2 = u'\u0dab'.encode('utf8')
2529
lines = [' '.join([r2, r1]), r1]
2530
encoded_body = bz2.compress('\n'.join(lines))
2532
transport_path = 'quack'
2533
repo, client = self.setup_fake_client_and_repository(transport_path)
2534
client.add_success_response_with_body(encoded_body, 'ok')
2535
client.add_success_response_with_body(encoded_body, 'ok')
2537
graph = repo.get_graph()
2538
parents = graph.get_parent_map([r2])
2539
self.assertEqual({r2: (r1,)}, parents)
2540
# locking and unlocking deeper should not reset
2543
parents = graph.get_parent_map([r1])
2544
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2546
[('call_with_body_bytes_expecting_body',
2547
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2551
# now we call again, and it should use the second response.
2553
graph = repo.get_graph()
2554
parents = graph.get_parent_map([r1])
2555
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2557
[('call_with_body_bytes_expecting_body',
2558
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2560
('call_with_body_bytes_expecting_body',
2561
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2567
def test_get_parent_map_reconnects_if_unknown_method(self):
2568
transport_path = 'quack'
2569
rev_id = 'revision-id'
2570
repo, client = self.setup_fake_client_and_repository(transport_path)
2571
client.add_unknown_method_response('Repository.get_parent_map')
2572
client.add_success_response_with_body(rev_id, 'ok')
2573
self.assertFalse(client._medium._is_remote_before((1, 2)))
2574
parents = repo.get_parent_map([rev_id])
2576
[('call_with_body_bytes_expecting_body',
2577
'Repository.get_parent_map',
2578
('quack/', 'include-missing:', rev_id), '\n\n0'),
2579
('disconnect medium',),
2580
('call_expecting_body', 'Repository.get_revision_graph',
2583
# The medium is now marked as being connected to an older server
2584
self.assertTrue(client._medium._is_remote_before((1, 2)))
2585
self.assertEqual({rev_id: ('null:',)}, parents)
2587
def test_get_parent_map_fallback_parentless_node(self):
2588
"""get_parent_map falls back to get_revision_graph on old servers. The
2589
results from get_revision_graph are tweaked to match the get_parent_map
2592
Specifically, a {key: ()} result from get_revision_graph means "no
2593
parents" for that key, which in get_parent_map results should be
2594
represented as {key: ('null:',)}.
2596
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2598
rev_id = 'revision-id'
2599
transport_path = 'quack'
2600
repo, client = self.setup_fake_client_and_repository(transport_path)
2601
client.add_success_response_with_body(rev_id, 'ok')
2602
client._medium._remember_remote_is_before((1, 2))
2603
parents = repo.get_parent_map([rev_id])
2605
[('call_expecting_body', 'Repository.get_revision_graph',
2608
self.assertEqual({rev_id: ('null:',)}, parents)
2610
def test_get_parent_map_unexpected_response(self):
2611
repo, client = self.setup_fake_client_and_repository('path')
2612
client.add_success_response('something unexpected!')
2614
errors.UnexpectedSmartServerResponse,
2615
repo.get_parent_map, ['a-revision-id'])
2617
def test_get_parent_map_negative_caches_missing_keys(self):
2618
self.setup_smart_server_with_call_log()
2619
repo = self.make_repository('foo')
2620
self.assertIsInstance(repo, RemoteRepository)
2622
self.addCleanup(repo.unlock)
2623
self.reset_smart_call_log()
2624
graph = repo.get_graph()
2625
self.assertEqual({},
2626
graph.get_parent_map(['some-missing', 'other-missing']))
2627
self.assertLength(1, self.hpss_calls)
2628
# No call if we repeat this
2629
self.reset_smart_call_log()
2630
graph = repo.get_graph()
2631
self.assertEqual({},
2632
graph.get_parent_map(['some-missing', 'other-missing']))
2633
self.assertLength(0, self.hpss_calls)
2634
# Asking for more unknown keys makes a request.
2635
self.reset_smart_call_log()
2636
graph = repo.get_graph()
2637
self.assertEqual({},
2638
graph.get_parent_map(['some-missing', 'other-missing',
2640
self.assertLength(1, self.hpss_calls)
2642
def disableExtraResults(self):
2643
self.overrideAttr(SmartServerRepositoryGetParentMap,
2644
'no_extra_results', True)
2646
def test_null_cached_missing_and_stop_key(self):
2647
self.setup_smart_server_with_call_log()
2648
# Make a branch with a single revision.
2649
builder = self.make_branch_builder('foo')
2650
builder.start_series()
2651
builder.build_snapshot('first', None, [
2652
('add', ('', 'root-id', 'directory', ''))])
2653
builder.finish_series()
2654
branch = builder.get_branch()
2655
repo = branch.repository
2656
self.assertIsInstance(repo, RemoteRepository)
2657
# Stop the server from sending extra results.
2658
self.disableExtraResults()
2660
self.addCleanup(repo.unlock)
2661
self.reset_smart_call_log()
2662
graph = repo.get_graph()
2663
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2664
# 'first' it will be a candidate for the stop_keys of subsequent
2665
# requests, and because 'null:' was queried but not returned it will be
2666
# cached as missing.
2667
self.assertEqual({'first': ('null:',)},
2668
graph.get_parent_map(['first', 'null:']))
2669
# Now query for another key. This request will pass along a recipe of
2670
# start and stop keys describing the already cached results, and this
2671
# recipe's revision count must be correct (or else it will trigger an
2672
# error from the server).
2673
self.assertEqual({}, graph.get_parent_map(['another-key']))
2674
# This assertion guards against disableExtraResults silently failing to
2675
# work, thus invalidating the test.
2676
self.assertLength(2, self.hpss_calls)
2678
def test_get_parent_map_gets_ghosts_from_result(self):
2679
# asking for a revision should negatively cache close ghosts in its
2681
self.setup_smart_server_with_call_log()
2682
tree = self.make_branch_and_memory_tree('foo')
2685
builder = treebuilder.TreeBuilder()
2686
builder.start_tree(tree)
2688
builder.finish_tree()
2689
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2690
rev_id = tree.commit('')
2694
self.addCleanup(tree.unlock)
2695
repo = tree.branch.repository
2696
self.assertIsInstance(repo, RemoteRepository)
2698
repo.get_parent_map([rev_id])
2699
self.reset_smart_call_log()
2700
# Now asking for rev_id's ghost parent should not make calls
2701
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2702
self.assertLength(0, self.hpss_calls)
2704
def test_exposes_get_cached_parent_map(self):
2705
"""RemoteRepository exposes get_cached_parent_map from
2708
r1 = u'\u0e33'.encode('utf8')
2709
r2 = u'\u0dab'.encode('utf8')
2710
lines = [' '.join([r2, r1]), r1]
2711
encoded_body = bz2.compress('\n'.join(lines))
2713
transport_path = 'quack'
2714
repo, client = self.setup_fake_client_and_repository(transport_path)
2715
client.add_success_response_with_body(encoded_body, 'ok')
2717
# get_cached_parent_map should *not* trigger an RPC
2718
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2719
self.assertEqual([], client._calls)
2720
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2721
self.assertEqual({r1: (NULL_REVISION,)},
2722
repo.get_cached_parent_map([r1]))
2724
[('call_with_body_bytes_expecting_body',
2725
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2731
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2733
def test_allows_new_revisions(self):
2734
"""get_parent_map's results can be updated by commit."""
2735
smart_server = test_server.SmartTCPServer_for_testing()
2736
self.start_server(smart_server)
2737
self.make_branch('branch')
2738
branch = Branch.open(smart_server.get_url() + '/branch')
2739
tree = branch.create_checkout('tree', lightweight=True)
2741
self.addCleanup(tree.unlock)
2742
graph = tree.branch.repository.get_graph()
2743
# This provides an opportunity for the missing rev-id to be cached.
2744
self.assertEqual({}, graph.get_parent_map(['rev1']))
2745
tree.commit('message', rev_id='rev1')
2746
graph = tree.branch.repository.get_graph()
2747
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2750
class TestRepositoryGetRevisions(TestRemoteRepository):
2752
def test_hpss_missing_revision(self):
2753
transport_path = 'quack'
2754
repo, client = self.setup_fake_client_and_repository(transport_path)
2755
client.add_success_response_with_body(
2757
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2758
['somerev1', 'anotherrev2'])
2760
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2761
('quack/', ), "somerev1\nanotherrev2")],
2764
def test_hpss_get_single_revision(self):
2765
transport_path = 'quack'
2766
repo, client = self.setup_fake_client_and_repository(transport_path)
2767
somerev1 = Revision("somerev1")
2768
somerev1.committer = "Joe Committer <joe@example.com>"
2769
somerev1.timestamp = 1321828927
2770
somerev1.timezone = -60
2771
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2772
somerev1.message = "Message"
2773
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2775
# Split up body into two bits to make sure the zlib compression object
2776
# gets data fed twice.
2777
client.add_success_response_with_body(
2778
[body[:10], body[10:]], 'ok', '10')
2779
revs = repo.get_revisions(['somerev1'])
2780
self.assertEqual(revs, [somerev1])
2782
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2783
('quack/', ), "somerev1")],
2787
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2789
def test_null_revision(self):
2790
# a null revision has the predictable result {}, we should have no wire
2791
# traffic when calling it with this argument
2792
transport_path = 'empty'
2793
repo, client = self.setup_fake_client_and_repository(transport_path)
2794
client.add_success_response('notused')
2795
# actual RemoteRepository.get_revision_graph is gone, but there's an
2796
# equivalent private method for testing
2797
result = repo._get_revision_graph(NULL_REVISION)
2798
self.assertEqual([], client._calls)
2799
self.assertEqual({}, result)
2801
def test_none_revision(self):
2802
# with none we want the entire graph
2803
r1 = u'\u0e33'.encode('utf8')
2804
r2 = u'\u0dab'.encode('utf8')
2805
lines = [' '.join([r2, r1]), r1]
2806
encoded_body = '\n'.join(lines)
2808
transport_path = 'sinhala'
2809
repo, client = self.setup_fake_client_and_repository(transport_path)
2810
client.add_success_response_with_body(encoded_body, 'ok')
2811
# actual RemoteRepository.get_revision_graph is gone, but there's an
2812
# equivalent private method for testing
2813
result = repo._get_revision_graph(None)
2815
[('call_expecting_body', 'Repository.get_revision_graph',
2818
self.assertEqual({r1: (), r2: (r1, )}, result)
2820
def test_specific_revision(self):
2821
# with a specific revision we want the graph for that
2822
# with none we want the entire graph
2823
r11 = u'\u0e33'.encode('utf8')
2824
r12 = u'\xc9'.encode('utf8')
2825
r2 = u'\u0dab'.encode('utf8')
2826
lines = [' '.join([r2, r11, r12]), r11, r12]
2827
encoded_body = '\n'.join(lines)
2829
transport_path = 'sinhala'
2830
repo, client = self.setup_fake_client_and_repository(transport_path)
2831
client.add_success_response_with_body(encoded_body, 'ok')
2832
result = repo._get_revision_graph(r2)
2834
[('call_expecting_body', 'Repository.get_revision_graph',
2837
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2839
def test_no_such_revision(self):
2841
transport_path = 'sinhala'
2842
repo, client = self.setup_fake_client_and_repository(transport_path)
2843
client.add_error_response('nosuchrevision', revid)
2844
# also check that the right revision is reported in the error
2845
self.assertRaises(errors.NoSuchRevision,
2846
repo._get_revision_graph, revid)
2848
[('call_expecting_body', 'Repository.get_revision_graph',
2849
('sinhala/', revid))],
2852
def test_unexpected_error(self):
2854
transport_path = 'sinhala'
2855
repo, client = self.setup_fake_client_and_repository(transport_path)
2856
client.add_error_response('AnUnexpectedError')
2857
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2858
repo._get_revision_graph, revid)
2859
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2862
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2865
repo, client = self.setup_fake_client_and_repository('quack')
2866
client.add_expected_call(
2867
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2868
'success', ('ok', 'rev-five'))
2869
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2870
self.assertEqual((True, 'rev-five'), result)
2871
self.assertFinished(client)
2873
def test_history_incomplete(self):
2874
repo, client = self.setup_fake_client_and_repository('quack')
2875
client.add_expected_call(
2876
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2877
'success', ('history-incomplete', 10, 'rev-ten'))
2878
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2879
self.assertEqual((False, (10, 'rev-ten')), result)
2880
self.assertFinished(client)
2882
def test_history_incomplete_with_fallback(self):
2883
"""A 'history-incomplete' response causes the fallback repository to be
2884
queried too, if one is set.
2886
# Make a repo with a fallback repo, both using a FakeClient.
2887
format = remote.response_tuple_to_repo_format(
2888
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2889
repo, client = self.setup_fake_client_and_repository('quack')
2890
repo._format = format
2891
fallback_repo, ignored = self.setup_fake_client_and_repository(
2893
fallback_repo._client = client
2894
fallback_repo._format = format
2895
repo.add_fallback_repository(fallback_repo)
2896
# First the client should ask the primary repo
2897
client.add_expected_call(
2898
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2899
'success', ('history-incomplete', 2, 'rev-two'))
2900
# Then it should ask the fallback, using revno/revid from the
2901
# history-incomplete response as the known revno/revid.
2902
client.add_expected_call(
2903
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2904
'success', ('ok', 'rev-one'))
2905
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2906
self.assertEqual((True, 'rev-one'), result)
2907
self.assertFinished(client)
2909
def test_nosuchrevision(self):
2910
# 'nosuchrevision' is returned when the known-revid is not found in the
2911
# remote repo. The client translates that response to NoSuchRevision.
2912
repo, client = self.setup_fake_client_and_repository('quack')
2913
client.add_expected_call(
2914
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2915
'error', ('nosuchrevision', 'rev-foo'))
2917
errors.NoSuchRevision,
2918
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2919
self.assertFinished(client)
2921
def test_branch_fallback_locking(self):
2922
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2923
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2924
will be invoked, which will fail if the repo is unlocked.
2926
self.setup_smart_server_with_call_log()
2927
tree = self.make_branch_and_memory_tree('.')
2930
rev1 = tree.commit('First')
2931
rev2 = tree.commit('Second')
2933
branch = tree.branch
2934
self.assertFalse(branch.is_locked())
2935
self.reset_smart_call_log()
2936
verb = 'Repository.get_rev_id_for_revno'
2937
self.disable_verb(verb)
2938
self.assertEqual(rev1, branch.get_rev_id(1))
2939
self.assertLength(1, [call for call in self.hpss_calls if
2940
call.call.method == verb])
2943
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2945
def test_has_signature_for_revision_id(self):
2946
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2947
transport_path = 'quack'
2948
repo, client = self.setup_fake_client_and_repository(transport_path)
2949
client.add_success_response('yes')
2950
result = repo.has_signature_for_revision_id('A')
2952
[('call', 'Repository.has_signature_for_revision_id',
2955
self.assertEqual(True, result)
2957
def test_is_not_shared(self):
2958
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2959
transport_path = 'qwack'
2960
repo, client = self.setup_fake_client_and_repository(transport_path)
2961
client.add_success_response('no')
2962
result = repo.has_signature_for_revision_id('A')
2964
[('call', 'Repository.has_signature_for_revision_id',
2967
self.assertEqual(False, result)
2970
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2972
def test_get_physical_lock_status_yes(self):
2973
transport_path = 'qwack'
2974
repo, client = self.setup_fake_client_and_repository(transport_path)
2975
client.add_success_response('yes')
2976
result = repo.get_physical_lock_status()
2978
[('call', 'Repository.get_physical_lock_status',
2981
self.assertEqual(True, result)
2983
def test_get_physical_lock_status_no(self):
2984
transport_path = 'qwack'
2985
repo, client = self.setup_fake_client_and_repository(transport_path)
2986
client.add_success_response('no')
2987
result = repo.get_physical_lock_status()
2989
[('call', 'Repository.get_physical_lock_status',
2992
self.assertEqual(False, result)
2995
class TestRepositoryIsShared(TestRemoteRepository):
2997
def test_is_shared(self):
2998
# ('yes', ) for Repository.is_shared -> 'True'.
2999
transport_path = 'quack'
3000
repo, client = self.setup_fake_client_and_repository(transport_path)
3001
client.add_success_response('yes')
3002
result = repo.is_shared()
3004
[('call', 'Repository.is_shared', ('quack/',))],
3006
self.assertEqual(True, result)
3008
def test_is_not_shared(self):
3009
# ('no', ) for Repository.is_shared -> 'False'.
3010
transport_path = 'qwack'
3011
repo, client = self.setup_fake_client_and_repository(transport_path)
3012
client.add_success_response('no')
3013
result = repo.is_shared()
3015
[('call', 'Repository.is_shared', ('qwack/',))],
3017
self.assertEqual(False, result)
3020
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3022
def test_make_working_trees(self):
3023
# ('yes', ) for Repository.make_working_trees -> 'True'.
3024
transport_path = 'quack'
3025
repo, client = self.setup_fake_client_and_repository(transport_path)
3026
client.add_success_response('yes')
3027
result = repo.make_working_trees()
3029
[('call', 'Repository.make_working_trees', ('quack/',))],
3031
self.assertEqual(True, result)
3033
def test_no_working_trees(self):
3034
# ('no', ) for Repository.make_working_trees -> 'False'.
3035
transport_path = 'qwack'
3036
repo, client = self.setup_fake_client_and_repository(transport_path)
3037
client.add_success_response('no')
3038
result = repo.make_working_trees()
3040
[('call', 'Repository.make_working_trees', ('qwack/',))],
3042
self.assertEqual(False, result)
3045
class TestRepositoryLockWrite(TestRemoteRepository):
3047
def test_lock_write(self):
3048
transport_path = 'quack'
3049
repo, client = self.setup_fake_client_and_repository(transport_path)
3050
client.add_success_response('ok', 'a token')
3051
token = repo.lock_write().repository_token
3053
[('call', 'Repository.lock_write', ('quack/', ''))],
3055
self.assertEqual('a token', token)
3057
def test_lock_write_already_locked(self):
3058
transport_path = 'quack'
3059
repo, client = self.setup_fake_client_and_repository(transport_path)
3060
client.add_error_response('LockContention')
3061
self.assertRaises(errors.LockContention, repo.lock_write)
3063
[('call', 'Repository.lock_write', ('quack/', ''))],
3066
def test_lock_write_unlockable(self):
3067
transport_path = 'quack'
3068
repo, client = self.setup_fake_client_and_repository(transport_path)
3069
client.add_error_response('UnlockableTransport')
3070
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3072
[('call', 'Repository.lock_write', ('quack/', ''))],
3076
class TestRepositoryWriteGroups(TestRemoteRepository):
3078
def test_start_write_group(self):
3079
transport_path = 'quack'
3080
repo, client = self.setup_fake_client_and_repository(transport_path)
3081
client.add_expected_call(
3082
'Repository.lock_write', ('quack/', ''),
3083
'success', ('ok', 'a token'))
3084
client.add_expected_call(
3085
'Repository.start_write_group', ('quack/', 'a token'),
3086
'success', ('ok', ('token1', )))
3088
repo.start_write_group()
3090
def test_start_write_group_unsuspendable(self):
3091
# Some repositories do not support suspending write
3092
# groups. For those, fall back to the "real" repository.
3093
transport_path = 'quack'
3094
repo, client = self.setup_fake_client_and_repository(transport_path)
3095
def stub_ensure_real():
3096
client._calls.append(('_ensure_real',))
3097
repo._real_repository = _StubRealPackRepository(client._calls)
3098
repo._ensure_real = stub_ensure_real
3099
client.add_expected_call(
3100
'Repository.lock_write', ('quack/', ''),
3101
'success', ('ok', 'a token'))
3102
client.add_expected_call(
3103
'Repository.start_write_group', ('quack/', 'a token'),
3104
'error', ('UnsuspendableWriteGroup',))
3106
repo.start_write_group()
3107
self.assertEqual(client._calls[-2:], [
3109
('start_write_group',)])
3111
def test_commit_write_group(self):
3112
transport_path = 'quack'
3113
repo, client = self.setup_fake_client_and_repository(transport_path)
3114
client.add_expected_call(
3115
'Repository.lock_write', ('quack/', ''),
3116
'success', ('ok', 'a token'))
3117
client.add_expected_call(
3118
'Repository.start_write_group', ('quack/', 'a token'),
3119
'success', ('ok', ['token1']))
3120
client.add_expected_call(
3121
'Repository.commit_write_group', ('quack/', 'a token', ['token1']),
3124
repo.start_write_group()
3125
repo.commit_write_group()
3127
def test_abort_write_group(self):
3128
transport_path = 'quack'
3129
repo, client = self.setup_fake_client_and_repository(transport_path)
3130
client.add_expected_call(
3131
'Repository.lock_write', ('quack/', ''),
3132
'success', ('ok', 'a token'))
3133
client.add_expected_call(
3134
'Repository.start_write_group', ('quack/', 'a token'),
3135
'success', ('ok', ['token1']))
3136
client.add_expected_call(
3137
'Repository.abort_write_group', ('quack/', 'a token', ['token1']),
3140
repo.start_write_group()
3141
repo.abort_write_group(False)
3143
def test_suspend_write_group(self):
3144
transport_path = 'quack'
3145
repo, client = self.setup_fake_client_and_repository(transport_path)
3146
self.assertEqual([], repo.suspend_write_group())
3148
def test_resume_write_group(self):
3149
transport_path = 'quack'
3150
repo, client = self.setup_fake_client_and_repository(transport_path)
3151
client.add_expected_call(
3152
'Repository.lock_write', ('quack/', ''),
3153
'success', ('ok', 'a token'))
3154
client.add_expected_call(
3155
'Repository.check_write_group', ('quack/', 'a token', ['token1']),
3158
repo.resume_write_group(['token1'])
3161
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3163
def test_backwards_compat(self):
3164
self.setup_smart_server_with_call_log()
3165
repo = self.make_repository('.')
3166
self.reset_smart_call_log()
3167
verb = 'Repository.set_make_working_trees'
3168
self.disable_verb(verb)
3169
repo.set_make_working_trees(True)
3170
call_count = len([call for call in self.hpss_calls if
3171
call.call.method == verb])
3172
self.assertEqual(1, call_count)
3174
def test_current(self):
3175
transport_path = 'quack'
3176
repo, client = self.setup_fake_client_and_repository(transport_path)
3177
client.add_expected_call(
3178
'Repository.set_make_working_trees', ('quack/', 'True'),
3180
client.add_expected_call(
3181
'Repository.set_make_working_trees', ('quack/', 'False'),
3183
repo.set_make_working_trees(True)
3184
repo.set_make_working_trees(False)
3187
class TestRepositoryUnlock(TestRemoteRepository):
3189
def test_unlock(self):
3190
transport_path = 'quack'
3191
repo, client = self.setup_fake_client_and_repository(transport_path)
3192
client.add_success_response('ok', 'a token')
3193
client.add_success_response('ok')
3197
[('call', 'Repository.lock_write', ('quack/', '')),
3198
('call', 'Repository.unlock', ('quack/', 'a token'))],
3201
def test_unlock_wrong_token(self):
3202
# If somehow the token is wrong, unlock will raise TokenMismatch.
3203
transport_path = 'quack'
3204
repo, client = self.setup_fake_client_and_repository(transport_path)
3205
client.add_success_response('ok', 'a token')
3206
client.add_error_response('TokenMismatch')
3208
self.assertRaises(errors.TokenMismatch, repo.unlock)
3211
class TestRepositoryHasRevision(TestRemoteRepository):
3213
def test_none(self):
3214
# repo.has_revision(None) should not cause any traffic.
3215
transport_path = 'quack'
3216
repo, client = self.setup_fake_client_and_repository(transport_path)
3218
# The null revision is always there, so has_revision(None) == True.
3219
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3221
# The remote repo shouldn't be accessed.
3222
self.assertEqual([], client._calls)
3225
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3226
"""Test Repository.iter_file_bytes."""
3228
def test_single(self):
3229
transport_path = 'quack'
3230
repo, client = self.setup_fake_client_and_repository(transport_path)
3231
client.add_expected_call(
3232
'Repository.iter_files_bytes', ('quack/', ),
3233
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3234
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3235
"somerev", "myid")]):
3236
self.assertEqual("myid", identifier)
3237
self.assertEqual("".join(byte_stream), "mydata" * 10)
3239
def test_missing(self):
3240
transport_path = 'quack'
3241
repo, client = self.setup_fake_client_and_repository(transport_path)
3242
client.add_expected_call(
3243
'Repository.iter_files_bytes',
3245
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3246
iter(["absent\0somefile\0somerev\n"]))
3247
self.assertRaises(errors.RevisionNotPresent, list,
3248
repo.iter_files_bytes(
3249
[("somefile", "somerev", "myid")]))
3252
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3253
"""Base class for Repository.insert_stream and .insert_stream_1.19
3257
def checkInsertEmptyStream(self, repo, client):
3258
"""Insert an empty stream, checking the result.
3260
This checks that there are no resume_tokens or missing_keys, and that
3261
the client is finished.
3263
sink = repo._get_sink()
3264
fmt = repository.format_registry.get_default()
3265
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3266
self.assertEqual([], resume_tokens)
3267
self.assertEqual(set(), missing_keys)
3268
self.assertFinished(client)
3271
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3272
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3275
This test case is very similar to TestRepositoryInsertStream_1_19.
3279
super(TestRepositoryInsertStream, self).setUp()
3280
self.disable_verb('Repository.insert_stream_1.19')
3282
def test_unlocked_repo(self):
3283
transport_path = 'quack'
3284
repo, client = self.setup_fake_client_and_repository(transport_path)
3285
client.add_expected_call(
3286
'Repository.insert_stream_1.19', ('quack/', ''),
3287
'unknown', ('Repository.insert_stream_1.19',))
3288
client.add_expected_call(
3289
'Repository.insert_stream', ('quack/', ''),
3291
client.add_expected_call(
3292
'Repository.insert_stream', ('quack/', ''),
3294
self.checkInsertEmptyStream(repo, client)
3296
def test_locked_repo_with_no_lock_token(self):
3297
transport_path = 'quack'
3298
repo, client = self.setup_fake_client_and_repository(transport_path)
3299
client.add_expected_call(
3300
'Repository.lock_write', ('quack/', ''),
3301
'success', ('ok', ''))
3302
client.add_expected_call(
3303
'Repository.insert_stream_1.19', ('quack/', ''),
3304
'unknown', ('Repository.insert_stream_1.19',))
3305
client.add_expected_call(
3306
'Repository.insert_stream', ('quack/', ''),
3308
client.add_expected_call(
3309
'Repository.insert_stream', ('quack/', ''),
3312
self.checkInsertEmptyStream(repo, client)
3314
def test_locked_repo_with_lock_token(self):
3315
transport_path = 'quack'
3316
repo, client = self.setup_fake_client_and_repository(transport_path)
3317
client.add_expected_call(
3318
'Repository.lock_write', ('quack/', ''),
3319
'success', ('ok', 'a token'))
3320
client.add_expected_call(
3321
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3322
'unknown', ('Repository.insert_stream_1.19',))
3323
client.add_expected_call(
3324
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3326
client.add_expected_call(
3327
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3330
self.checkInsertEmptyStream(repo, client)
3332
def test_stream_with_inventory_deltas(self):
3333
"""'inventory-deltas' substreams cannot be sent to the
3334
Repository.insert_stream verb, because not all servers that implement
3335
that verb will accept them. So when one is encountered the RemoteSink
3336
immediately stops using that verb and falls back to VFS insert_stream.
3338
transport_path = 'quack'
3339
repo, client = self.setup_fake_client_and_repository(transport_path)
3340
client.add_expected_call(
3341
'Repository.insert_stream_1.19', ('quack/', ''),
3342
'unknown', ('Repository.insert_stream_1.19',))
3343
client.add_expected_call(
3344
'Repository.insert_stream', ('quack/', ''),
3346
client.add_expected_call(
3347
'Repository.insert_stream', ('quack/', ''),
3349
# Create a fake real repository for insert_stream to fall back on, so
3350
# that we can directly see the records the RemoteSink passes to the
3355
def insert_stream(self, stream, src_format, resume_tokens):
3356
for substream_kind, substream in stream:
3357
self.records.append(
3358
(substream_kind, [record.key for record in substream]))
3359
return ['fake tokens'], ['fake missing keys']
3360
fake_real_sink = FakeRealSink()
3361
class FakeRealRepository:
3362
def _get_sink(self):
3363
return fake_real_sink
3364
def is_in_write_group(self):
3366
def refresh_data(self):
3368
repo._real_repository = FakeRealRepository()
3369
sink = repo._get_sink()
3370
fmt = repository.format_registry.get_default()
3371
stream = self.make_stream_with_inv_deltas(fmt)
3372
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3373
# Every record from the first inventory delta should have been sent to
3375
expected_records = [
3376
('inventory-deltas', [('rev2',), ('rev3',)]),
3377
('texts', [('some-rev', 'some-file')])]
3378
self.assertEqual(expected_records, fake_real_sink.records)
3379
# The return values from the real sink's insert_stream are propagated
3380
# back to the original caller.
3381
self.assertEqual(['fake tokens'], resume_tokens)
3382
self.assertEqual(['fake missing keys'], missing_keys)
3383
self.assertFinished(client)
3385
def make_stream_with_inv_deltas(self, fmt):
3386
"""Make a simple stream with an inventory delta followed by more
3387
records and more substreams to test that all records and substreams
3388
from that point on are used.
3390
This sends, in order:
3391
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3393
* texts substream: (some-rev, some-file)
3395
# Define a stream using generators so that it isn't rewindable.
3396
inv = inventory.Inventory(revision_id='rev1')
3397
inv.root.revision = 'rev1'
3398
def stream_with_inv_delta():
3399
yield ('inventories', inventories_substream())
3400
yield ('inventory-deltas', inventory_delta_substream())
3402
versionedfile.FulltextContentFactory(
3403
('some-rev', 'some-file'), (), None, 'content')])
3404
def inventories_substream():
3405
# An empty inventory fulltext. This will be streamed normally.
3406
text = fmt._serializer.write_inventory_to_string(inv)
3407
yield versionedfile.FulltextContentFactory(
3408
('rev1',), (), None, text)
3409
def inventory_delta_substream():
3410
# An inventory delta. This can't be streamed via this verb, so it
3411
# will trigger a fallback to VFS insert_stream.
3412
entry = inv.make_entry(
3413
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3414
entry.revision = 'ghost'
3415
delta = [(None, 'newdir', 'newdir-id', entry)]
3416
serializer = inventory_delta.InventoryDeltaSerializer(
3417
versioned_root=True, tree_references=False)
3418
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3419
yield versionedfile.ChunkedContentFactory(
3420
('rev2',), (('rev1',)), None, lines)
3422
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3423
yield versionedfile.ChunkedContentFactory(
3424
('rev3',), (('rev1',)), None, lines)
3425
return stream_with_inv_delta()
3428
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3430
def test_unlocked_repo(self):
3431
transport_path = 'quack'
3432
repo, client = self.setup_fake_client_and_repository(transport_path)
3433
client.add_expected_call(
3434
'Repository.insert_stream_1.19', ('quack/', ''),
3436
client.add_expected_call(
3437
'Repository.insert_stream_1.19', ('quack/', ''),
3439
self.checkInsertEmptyStream(repo, client)
3441
def test_locked_repo_with_no_lock_token(self):
3442
transport_path = 'quack'
3443
repo, client = self.setup_fake_client_and_repository(transport_path)
3444
client.add_expected_call(
3445
'Repository.lock_write', ('quack/', ''),
3446
'success', ('ok', ''))
3447
client.add_expected_call(
3448
'Repository.insert_stream_1.19', ('quack/', ''),
3450
client.add_expected_call(
3451
'Repository.insert_stream_1.19', ('quack/', ''),
3454
self.checkInsertEmptyStream(repo, client)
3456
def test_locked_repo_with_lock_token(self):
3457
transport_path = 'quack'
3458
repo, client = self.setup_fake_client_and_repository(transport_path)
3459
client.add_expected_call(
3460
'Repository.lock_write', ('quack/', ''),
3461
'success', ('ok', 'a token'))
3462
client.add_expected_call(
3463
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3465
client.add_expected_call(
3466
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3469
self.checkInsertEmptyStream(repo, client)
3472
class TestRepositoryTarball(TestRemoteRepository):
3474
# This is a canned tarball reponse we can validate against
3475
tarball_content = base64.b64decode(
3476
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3477
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3478
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3479
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3480
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3481
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3482
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3483
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3484
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3485
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3486
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3487
'nWQ7QH/F3JFOFCQ0aSPfA='
3490
def test_repository_tarball(self):
3491
# Test that Repository.tarball generates the right operations
3492
transport_path = 'repo'
3493
expected_calls = [('call_expecting_body', 'Repository.tarball',
3494
('repo/', 'bz2',),),
3496
repo, client = self.setup_fake_client_and_repository(transport_path)
3497
client.add_success_response_with_body(self.tarball_content, 'ok')
3498
# Now actually ask for the tarball
3499
tarball_file = repo._get_tarball('bz2')
3501
self.assertEqual(expected_calls, client._calls)
3502
self.assertEqual(self.tarball_content, tarball_file.read())
3504
tarball_file.close()
3507
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3508
"""RemoteRepository.copy_content_into optimizations"""
3510
def test_copy_content_remote_to_local(self):
3511
self.transport_server = test_server.SmartTCPServer_for_testing
3512
src_repo = self.make_repository('repo1')
3513
src_repo = repository.Repository.open(self.get_url('repo1'))
3514
# At the moment the tarball-based copy_content_into can't write back
3515
# into a smart server. It would be good if it could upload the
3516
# tarball; once that works we'd have to create repositories of
3517
# different formats. -- mbp 20070410
3518
dest_url = self.get_vfs_only_url('repo2')
3519
dest_bzrdir = BzrDir.create(dest_url)
3520
dest_repo = dest_bzrdir.create_repository()
3521
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3522
self.assertTrue(isinstance(src_repo, RemoteRepository))
3523
src_repo.copy_content_into(dest_repo)
3526
class _StubRealPackRepository(object):
3528
def __init__(self, calls):
3530
self._pack_collection = _StubPackCollection(calls)
3532
def start_write_group(self):
3533
self.calls.append(('start_write_group',))
3535
def is_in_write_group(self):
3538
def refresh_data(self):
3539
self.calls.append(('pack collection reload_pack_names',))
3542
class _StubPackCollection(object):
3544
def __init__(self, calls):
3548
self.calls.append(('pack collection autopack',))
3551
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3552
"""Tests for RemoteRepository.autopack implementation."""
3555
"""When the server returns 'ok' and there's no _real_repository, then
3556
nothing else happens: the autopack method is done.
3558
transport_path = 'quack'
3559
repo, client = self.setup_fake_client_and_repository(transport_path)
3560
client.add_expected_call(
3561
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3563
self.assertFinished(client)
3565
def test_ok_with_real_repo(self):
3566
"""When the server returns 'ok' and there is a _real_repository, then
3567
the _real_repository's reload_pack_name's method will be called.
3569
transport_path = 'quack'
3570
repo, client = self.setup_fake_client_and_repository(transport_path)
3571
client.add_expected_call(
3572
'PackRepository.autopack', ('quack/',),
3574
repo._real_repository = _StubRealPackRepository(client._calls)
3577
[('call', 'PackRepository.autopack', ('quack/',)),
3578
('pack collection reload_pack_names',)],
3581
def test_backwards_compatibility(self):
3582
"""If the server does not recognise the PackRepository.autopack verb,
3583
fallback to the real_repository's implementation.
3585
transport_path = 'quack'
3586
repo, client = self.setup_fake_client_and_repository(transport_path)
3587
client.add_unknown_method_response('PackRepository.autopack')
3588
def stub_ensure_real():
3589
client._calls.append(('_ensure_real',))
3590
repo._real_repository = _StubRealPackRepository(client._calls)
3591
repo._ensure_real = stub_ensure_real
3594
[('call', 'PackRepository.autopack', ('quack/',)),
3596
('pack collection autopack',)],
3599
def test_oom_error_reporting(self):
3600
"""An out-of-memory condition on the server is reported clearly"""
3601
transport_path = 'quack'
3602
repo, client = self.setup_fake_client_and_repository(transport_path)
3603
client.add_expected_call(
3604
'PackRepository.autopack', ('quack/',),
3605
'error', ('MemoryError',))
3606
err = self.assertRaises(errors.BzrError, repo.autopack)
3607
self.assertContainsRe(str(err), "^remote server out of mem")
3610
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3611
"""Base class for unit tests for breezy.bzr.remote._translate_error."""
3613
def translateTuple(self, error_tuple, **context):
3614
"""Call _translate_error with an ErrorFromSmartServer built from the
3617
:param error_tuple: A tuple of a smart server response, as would be
3618
passed to an ErrorFromSmartServer.
3619
:kwargs context: context items to call _translate_error with.
3621
:returns: The error raised by _translate_error.
3623
# Raise the ErrorFromSmartServer before passing it as an argument,
3624
# because _translate_error may need to re-raise it with a bare 'raise'
3626
server_error = errors.ErrorFromSmartServer(error_tuple)
3627
translated_error = self.translateErrorFromSmartServer(
3628
server_error, **context)
3629
return translated_error
3631
def translateErrorFromSmartServer(self, error_object, **context):
3632
"""Like translateTuple, but takes an already constructed
3633
ErrorFromSmartServer rather than a tuple.
3637
except errors.ErrorFromSmartServer as server_error:
3638
translated_error = self.assertRaises(
3639
errors.BzrError, remote._translate_error, server_error,
3641
return translated_error
3644
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3645
"""Unit tests for breezy.bzr.remote._translate_error.
3647
Given an ErrorFromSmartServer (which has an error tuple from a smart
3648
server) and some context, _translate_error raises more specific errors from
3651
This test case covers the cases where _translate_error succeeds in
3652
translating an ErrorFromSmartServer to something better. See
3653
TestErrorTranslationRobustness for other cases.
3656
def test_NoSuchRevision(self):
3657
branch = self.make_branch('')
3659
translated_error = self.translateTuple(
3660
('NoSuchRevision', revid), branch=branch)
3661
expected_error = errors.NoSuchRevision(branch, revid)
3662
self.assertEqual(expected_error, translated_error)
3664
def test_nosuchrevision(self):
3665
repository = self.make_repository('')
3667
translated_error = self.translateTuple(
3668
('nosuchrevision', revid), repository=repository)
3669
expected_error = errors.NoSuchRevision(repository, revid)
3670
self.assertEqual(expected_error, translated_error)
3672
def test_nobranch(self):
3673
bzrdir = self.make_controldir('')
3674
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3675
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3676
self.assertEqual(expected_error, translated_error)
3678
def test_nobranch_one_arg(self):
3679
bzrdir = self.make_controldir('')
3680
translated_error = self.translateTuple(
3681
('nobranch', 'extra detail'), bzrdir=bzrdir)
3682
expected_error = errors.NotBranchError(
3683
path=bzrdir.root_transport.base,
3684
detail='extra detail')
3685
self.assertEqual(expected_error, translated_error)
3687
def test_norepository(self):
3688
bzrdir = self.make_controldir('')
3689
translated_error = self.translateTuple(('norepository',),
3691
expected_error = errors.NoRepositoryPresent(bzrdir)
3692
self.assertEqual(expected_error, translated_error)
3694
def test_LockContention(self):
3695
translated_error = self.translateTuple(('LockContention',))
3696
expected_error = errors.LockContention('(remote lock)')
3697
self.assertEqual(expected_error, translated_error)
3699
def test_UnlockableTransport(self):
3700
bzrdir = self.make_controldir('')
3701
translated_error = self.translateTuple(
3702
('UnlockableTransport',), bzrdir=bzrdir)
3703
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3704
self.assertEqual(expected_error, translated_error)
3706
def test_LockFailed(self):
3707
lock = 'str() of a server lock'
3708
why = 'str() of why'
3709
translated_error = self.translateTuple(('LockFailed', lock, why))
3710
expected_error = errors.LockFailed(lock, why)
3711
self.assertEqual(expected_error, translated_error)
3713
def test_TokenMismatch(self):
3714
token = 'a lock token'
3715
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3716
expected_error = errors.TokenMismatch(token, '(remote token)')
3717
self.assertEqual(expected_error, translated_error)
3719
def test_Diverged(self):
3720
branch = self.make_branch('a')
3721
other_branch = self.make_branch('b')
3722
translated_error = self.translateTuple(
3723
('Diverged',), branch=branch, other_branch=other_branch)
3724
expected_error = errors.DivergedBranches(branch, other_branch)
3725
self.assertEqual(expected_error, translated_error)
3727
def test_NotStacked(self):
3728
branch = self.make_branch('')
3729
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3730
expected_error = errors.NotStacked(branch)
3731
self.assertEqual(expected_error, translated_error)
3733
def test_ReadError_no_args(self):
3735
translated_error = self.translateTuple(('ReadError',), path=path)
3736
expected_error = errors.ReadError(path)
3737
self.assertEqual(expected_error, translated_error)
3739
def test_ReadError(self):
3741
translated_error = self.translateTuple(('ReadError', path))
3742
expected_error = errors.ReadError(path)
3743
self.assertEqual(expected_error, translated_error)
3745
def test_IncompatibleRepositories(self):
3746
translated_error = self.translateTuple(('IncompatibleRepositories',
3747
"repo1", "repo2", "details here"))
3748
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3750
self.assertEqual(expected_error, translated_error)
3752
def test_PermissionDenied_no_args(self):
3754
translated_error = self.translateTuple(('PermissionDenied',),
3756
expected_error = errors.PermissionDenied(path)
3757
self.assertEqual(expected_error, translated_error)
3759
def test_PermissionDenied_one_arg(self):
3761
translated_error = self.translateTuple(('PermissionDenied', path))
3762
expected_error = errors.PermissionDenied(path)
3763
self.assertEqual(expected_error, translated_error)
3765
def test_PermissionDenied_one_arg_and_context(self):
3766
"""Given a choice between a path from the local context and a path on
3767
the wire, _translate_error prefers the path from the local context.
3769
local_path = 'local path'
3770
remote_path = 'remote path'
3771
translated_error = self.translateTuple(
3772
('PermissionDenied', remote_path), path=local_path)
3773
expected_error = errors.PermissionDenied(local_path)
3774
self.assertEqual(expected_error, translated_error)
3776
def test_PermissionDenied_two_args(self):
3778
extra = 'a string with extra info'
3779
translated_error = self.translateTuple(
3780
('PermissionDenied', path, extra))
3781
expected_error = errors.PermissionDenied(path, extra)
3782
self.assertEqual(expected_error, translated_error)
3784
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3786
def test_NoSuchFile_context_path(self):
3787
local_path = "local path"
3788
translated_error = self.translateTuple(('ReadError', "remote path"),
3790
expected_error = errors.ReadError(local_path)
3791
self.assertEqual(expected_error, translated_error)
3793
def test_NoSuchFile_without_context(self):
3794
remote_path = "remote path"
3795
translated_error = self.translateTuple(('ReadError', remote_path))
3796
expected_error = errors.ReadError(remote_path)
3797
self.assertEqual(expected_error, translated_error)
3799
def test_ReadOnlyError(self):
3800
translated_error = self.translateTuple(('ReadOnlyError',))
3801
expected_error = errors.TransportNotPossible("readonly transport")
3802
self.assertEqual(expected_error, translated_error)
3804
def test_MemoryError(self):
3805
translated_error = self.translateTuple(('MemoryError',))
3806
self.assertStartsWith(str(translated_error),
3807
"remote server out of memory")
3809
def test_generic_IndexError_no_classname(self):
3810
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3811
translated_error = self.translateErrorFromSmartServer(err)
3812
expected_error = errors.UnknownErrorFromSmartServer(err)
3813
self.assertEqual(expected_error, translated_error)
3815
# GZ 2011-03-02: TODO test generic non-ascii error string
3817
def test_generic_KeyError(self):
3818
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3819
translated_error = self.translateErrorFromSmartServer(err)
3820
expected_error = errors.UnknownErrorFromSmartServer(err)
3821
self.assertEqual(expected_error, translated_error)
3824
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3825
"""Unit tests for breezy.bzr.remote._translate_error's robustness.
3827
TestErrorTranslationSuccess is for cases where _translate_error can
3828
translate successfully. This class about how _translate_err behaves when
3829
it fails to translate: it re-raises the original error.
3832
def test_unrecognised_server_error(self):
3833
"""If the error code from the server is not recognised, the original
3834
ErrorFromSmartServer is propagated unmodified.
3836
error_tuple = ('An unknown error tuple',)
3837
server_error = errors.ErrorFromSmartServer(error_tuple)
3838
translated_error = self.translateErrorFromSmartServer(server_error)
3839
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3840
self.assertEqual(expected_error, translated_error)
3842
def test_context_missing_a_key(self):
3843
"""In case of a bug in the client, or perhaps an unexpected response
3844
from a server, _translate_error returns the original error tuple from
3845
the server and mutters a warning.
3847
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3848
# in the context dict. So let's give it an empty context dict instead
3849
# to exercise its error recovery.
3851
error_tuple = ('NoSuchRevision', 'revid')
3852
server_error = errors.ErrorFromSmartServer(error_tuple)
3853
translated_error = self.translateErrorFromSmartServer(server_error)
3854
self.assertEqual(server_error, translated_error)
3855
# In addition to re-raising ErrorFromSmartServer, some debug info has
3856
# been muttered to the log file for developer to look at.
3857
self.assertContainsRe(
3859
"Missing key 'branch' in context")
3861
def test_path_missing(self):
3862
"""Some translations (PermissionDenied, ReadError) can determine the
3863
'path' variable from either the wire or the local context. If neither
3864
has it, then an error is raised.
3866
error_tuple = ('ReadError',)
3867
server_error = errors.ErrorFromSmartServer(error_tuple)
3868
translated_error = self.translateErrorFromSmartServer(server_error)
3869
self.assertEqual(server_error, translated_error)
3870
# In addition to re-raising ErrorFromSmartServer, some debug info has
3871
# been muttered to the log file for developer to look at.
3872
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3875
class TestStacking(tests.TestCaseWithTransport):
3876
"""Tests for operations on stacked remote repositories.
3878
The underlying format type must support stacking.
3881
def test_access_stacked_remote(self):
3882
# based on <http://launchpad.net/bugs/261315>
3883
# make a branch stacked on another repository containing an empty
3884
# revision, then open it over hpss - we should be able to see that
3886
base_transport = self.get_transport()
3887
base_builder = self.make_branch_builder('base', format='1.9')
3888
base_builder.start_series()
3889
base_revid = base_builder.build_snapshot('rev-id', None,
3890
[('add', ('', None, 'directory', None))],
3892
base_builder.finish_series()
3893
stacked_branch = self.make_branch('stacked', format='1.9')
3894
stacked_branch.set_stacked_on_url('../base')
3895
# start a server looking at this
3896
smart_server = test_server.SmartTCPServer_for_testing()
3897
self.start_server(smart_server)
3898
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3899
# can get its branch and repository
3900
remote_branch = remote_bzrdir.open_branch()
3901
remote_repo = remote_branch.repository
3902
remote_repo.lock_read()
3904
# it should have an appropriate fallback repository, which should also
3905
# be a RemoteRepository
3906
self.assertLength(1, remote_repo._fallback_repositories)
3907
self.assertIsInstance(remote_repo._fallback_repositories[0],
3909
# and it has the revision committed to the underlying repository;
3910
# these have varying implementations so we try several of them
3911
self.assertTrue(remote_repo.has_revisions([base_revid]))
3912
self.assertTrue(remote_repo.has_revision(base_revid))
3913
self.assertEqual(remote_repo.get_revision(base_revid).message,
3916
remote_repo.unlock()
3918
def prepare_stacked_remote_branch(self):
3919
"""Get stacked_upon and stacked branches with content in each."""
3920
self.setup_smart_server_with_call_log()
3921
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3922
tree1.commit('rev1', rev_id='rev1')
3923
tree2 = tree1.branch.controldir.sprout('tree2', stacked=True
3924
).open_workingtree()
3925
local_tree = tree2.branch.create_checkout('local')
3926
local_tree.commit('local changes make me feel good.')
3927
branch2 = Branch.open(self.get_url('tree2'))
3929
self.addCleanup(branch2.unlock)
3930
return tree1.branch, branch2
3932
def test_stacked_get_parent_map(self):
3933
# the public implementation of get_parent_map obeys stacking
3934
_, branch = self.prepare_stacked_remote_branch()
3935
repo = branch.repository
3936
self.assertEqual({'rev1'}, set(repo.get_parent_map(['rev1'])))
3938
def test_unstacked_get_parent_map(self):
3939
# _unstacked_provider.get_parent_map ignores stacking
3940
_, branch = self.prepare_stacked_remote_branch()
3941
provider = branch.repository._unstacked_provider
3942
self.assertEqual(set(), set(provider.get_parent_map(['rev1'])))
3944
def fetch_stream_to_rev_order(self, stream):
3946
for kind, substream in stream:
3947
if not kind == 'revisions':
3950
for content in substream:
3951
result.append(content.key[-1])
3954
def get_ordered_revs(self, format, order, branch_factory=None):
3955
"""Get a list of the revisions in a stream to format format.
3957
:param format: The format of the target.
3958
:param order: the order that target should have requested.
3959
:param branch_factory: A callable to create a trunk and stacked branch
3960
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3961
:result: The revision ids in the stream, in the order seen,
3962
the topological order of revisions in the source.
3964
unordered_format = controldir.format_registry.get(format)()
3965
target_repository_format = unordered_format.repository_format
3967
self.assertEqual(order, target_repository_format._fetch_order)
3968
if branch_factory is None:
3969
branch_factory = self.prepare_stacked_remote_branch
3970
_, stacked = branch_factory()
3971
source = stacked.repository._get_source(target_repository_format)
3972
tip = stacked.last_revision()
3973
stacked.repository._ensure_real()
3974
graph = stacked.repository.get_graph()
3975
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3976
if r != NULL_REVISION]
3978
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3979
self.reset_smart_call_log()
3980
stream = source.get_stream(search)
3981
# We trust that if a revision is in the stream the rest of the new
3982
# content for it is too, as per our main fetch tests; here we are
3983
# checking that the revisions are actually included at all, and their
3985
return self.fetch_stream_to_rev_order(stream), revs
3987
def test_stacked_get_stream_unordered(self):
3988
# Repository._get_source.get_stream() from a stacked repository with
3989
# unordered yields the full data from both stacked and stacked upon
3991
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3992
self.assertEqual(set(expected_revs), set(rev_ord))
3993
# Getting unordered results should have made a streaming data request
3994
# from the server, then one from the backing branch.
3995
self.assertLength(2, self.hpss_calls)
3997
def test_stacked_on_stacked_get_stream_unordered(self):
3998
# Repository._get_source.get_stream() from a stacked repository which
3999
# is itself stacked yields the full data from all three sources.
4000
def make_stacked_stacked():
4001
_, stacked = self.prepare_stacked_remote_branch()
4002
tree = stacked.controldir.sprout('tree3', stacked=True
4003
).open_workingtree()
4004
local_tree = tree.branch.create_checkout('local-tree3')
4005
local_tree.commit('more local changes are better')
4006
branch = Branch.open(self.get_url('tree3'))
4008
self.addCleanup(branch.unlock)
4010
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
4011
branch_factory=make_stacked_stacked)
4012
self.assertEqual(set(expected_revs), set(rev_ord))
4013
# Getting unordered results should have made a streaming data request
4014
# from the server, and one from each backing repo
4015
self.assertLength(3, self.hpss_calls)
4017
def test_stacked_get_stream_topological(self):
4018
# Repository._get_source.get_stream() from a stacked repository with
4019
# topological sorting yields the full data from both stacked and
4020
# stacked upon sources in topological order.
4021
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4022
self.assertEqual(expected_revs, rev_ord)
4023
# Getting topological sort requires VFS calls still - one of which is
4024
# pushing up from the bound branch.
4025
self.assertLength(14, self.hpss_calls)
4027
def test_stacked_get_stream_groupcompress(self):
4028
# Repository._get_source.get_stream() from a stacked repository with
4029
# groupcompress sorting yields the full data from both stacked and
4030
# stacked upon sources in groupcompress order.
4031
raise tests.TestSkipped('No groupcompress ordered format available')
4032
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4033
self.assertEqual(expected_revs, reversed(rev_ord))
4034
# Getting unordered results should have made a streaming data request
4035
# from the backing branch, and one from the stacked on branch.
4036
self.assertLength(2, self.hpss_calls)
4038
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4039
# When pulling some fixed amount of content that is more than the
4040
# source has (because some is coming from a fallback branch, no error
4041
# should be received. This was reported as bug 360791.
4042
# Need three branches: a trunk, a stacked branch, and a preexisting
4043
# branch pulling content from stacked and trunk.
4044
self.setup_smart_server_with_call_log()
4045
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4046
r1 = trunk.commit('start')
4047
stacked_branch = trunk.branch.create_clone_on_transport(
4048
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4049
local = self.make_branch('local', format='1.9-rich-root')
4050
local.repository.fetch(stacked_branch.repository,
4051
stacked_branch.last_revision())
4054
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4057
super(TestRemoteBranchEffort, self).setUp()
4058
# Create a smart server that publishes whatever the backing VFS server
4060
self.smart_server = test_server.SmartTCPServer_for_testing()
4061
self.start_server(self.smart_server, self.get_server())
4062
# Log all HPSS calls into self.hpss_calls.
4063
_SmartClient.hooks.install_named_hook(
4064
'call', self.capture_hpss_call, None)
4065
self.hpss_calls = []
4067
def capture_hpss_call(self, params):
4068
self.hpss_calls.append(params.method)
4070
def test_copy_content_into_avoids_revision_history(self):
4071
local = self.make_branch('local')
4072
builder = self.make_branch_builder('remote')
4073
builder.build_commit(message="Commit.")
4074
remote_branch_url = self.smart_server.get_url() + 'remote'
4075
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4076
local.repository.fetch(remote_branch.repository)
4077
self.hpss_calls = []
4078
remote_branch.copy_content_into(local)
4079
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4081
def test_fetch_everything_needs_just_one_call(self):
4082
local = self.make_branch('local')
4083
builder = self.make_branch_builder('remote')
4084
builder.build_commit(message="Commit.")
4085
remote_branch_url = self.smart_server.get_url() + 'remote'
4086
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4087
self.hpss_calls = []
4088
local.repository.fetch(
4089
remote_branch.repository,
4090
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4091
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4093
def override_verb(self, verb_name, verb):
4094
request_handlers = request.request_handlers
4095
orig_verb = request_handlers.get(verb_name)
4096
orig_info = request_handlers.get_info(verb_name)
4097
request_handlers.register(verb_name, verb, override_existing=True)
4098
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4099
override_existing=True, info=orig_info)
4101
def test_fetch_everything_backwards_compat(self):
4102
"""Can fetch with EverythingResult even with pre 2.4 servers.
4104
Pre-2.4 do not support 'everything' searches with the
4105
Repository.get_stream_1.19 verb.
4108
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4109
"""A version of the Repository.get_stream_1.19 verb patched to
4110
reject 'everything' searches the way 2.3 and earlier do.
4112
def recreate_search(self, repository, search_bytes,
4113
discard_excess=False):
4114
verb_log.append(search_bytes.split('\n', 1)[0])
4115
if search_bytes == 'everything':
4117
request.FailedSmartServerResponse(('BadSearch',)))
4118
return super(OldGetStreamVerb,
4119
self).recreate_search(repository, search_bytes,
4120
discard_excess=discard_excess)
4121
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4122
local = self.make_branch('local')
4123
builder = self.make_branch_builder('remote')
4124
builder.build_commit(message="Commit.")
4125
remote_branch_url = self.smart_server.get_url() + 'remote'
4126
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4127
self.hpss_calls = []
4128
local.repository.fetch(
4129
remote_branch.repository,
4130
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4131
# make sure the overridden verb was used
4132
self.assertLength(1, verb_log)
4133
# more than one HPSS call is needed, but because it's a VFS callback
4134
# its hard to predict exactly how many.
4135
self.assertTrue(len(self.hpss_calls) > 1)
4138
class TestUpdateBoundBranchWithModifiedBoundLocation(
4139
tests.TestCaseWithTransport):
4140
"""Ensure correct handling of bound_location modifications.
4142
This is tested against a smart server as http://pad.lv/786980 was about a
4143
ReadOnlyError (write attempt during a read-only transaction) which can only
4144
happen in this context.
4148
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4149
self.transport_server = test_server.SmartTCPServer_for_testing
4151
def make_master_and_checkout(self, master_name, checkout_name):
4152
# Create the master branch and its associated checkout
4153
self.master = self.make_branch_and_tree(master_name)
4154
self.checkout = self.master.branch.create_checkout(checkout_name)
4155
# Modify the master branch so there is something to update
4156
self.master.commit('add stuff')
4157
self.last_revid = self.master.commit('even more stuff')
4158
self.bound_location = self.checkout.branch.get_bound_location()
4160
def assertUpdateSucceeds(self, new_location):
4161
self.checkout.branch.set_bound_location(new_location)
4162
self.checkout.update()
4163
self.assertEqual(self.last_revid, self.checkout.last_revision())
4165
def test_without_final_slash(self):
4166
self.make_master_and_checkout('master', 'checkout')
4167
# For unclear reasons some users have a bound_location without a final
4168
# '/', simulate that by forcing such a value
4169
self.assertEndsWith(self.bound_location, '/')
4170
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4172
def test_plus_sign(self):
4173
self.make_master_and_checkout('+master', 'checkout')
4174
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4176
def test_tilda(self):
4177
# Embed ~ in the middle of the path just to avoid any $HOME
4179
self.make_master_and_checkout('mas~ter', 'checkout')
4180
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4183
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4185
def test_no_context(self):
4186
class OutOfCoffee(errors.BzrError):
4187
"""A dummy exception for testing."""
4189
def __init__(self, urgency):
4190
self.urgency = urgency
4191
remote.no_context_error_translators.register("OutOfCoffee",
4192
lambda err: OutOfCoffee(err.error_args[0]))
4193
transport = MemoryTransport()
4194
client = FakeClient(transport.base)
4195
client.add_expected_call(
4196
'Branch.get_stacked_on_url', ('quack/',),
4197
'error', ('NotStacked',))
4198
client.add_expected_call(
4199
'Branch.last_revision_info',
4201
'error', ('OutOfCoffee', 'low'))
4202
transport.mkdir('quack')
4203
transport = transport.clone('quack')
4204
branch = self.make_remote_branch(transport, client)
4205
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4206
self.assertFinished(client)
4208
def test_with_context(self):
4209
class OutOfTea(errors.BzrError):
4210
def __init__(self, branch, urgency):
4211
self.branch = branch
4212
self.urgency = urgency
4213
remote.error_translators.register("OutOfTea",
4214
lambda err, find, path: OutOfTea(err.error_args[0],
4216
transport = MemoryTransport()
4217
client = FakeClient(transport.base)
4218
client.add_expected_call(
4219
'Branch.get_stacked_on_url', ('quack/',),
4220
'error', ('NotStacked',))
4221
client.add_expected_call(
4222
'Branch.last_revision_info',
4224
'error', ('OutOfTea', 'low'))
4225
transport.mkdir('quack')
4226
transport = transport.clone('quack')
4227
branch = self.make_remote_branch(transport, client)
4228
self.assertRaises(OutOfTea, branch.last_revision_info)
4229
self.assertFinished(client)
4232
class TestRepositoryPack(TestRemoteRepository):
4234
def test_pack(self):
4235
transport_path = 'quack'
4236
repo, client = self.setup_fake_client_and_repository(transport_path)
4237
client.add_expected_call(
4238
'Repository.lock_write', ('quack/', ''),
4239
'success', ('ok', 'token'))
4240
client.add_expected_call(
4241
'Repository.pack', ('quack/', 'token', 'False'),
4242
'success', ('ok',), )
4243
client.add_expected_call(
4244
'Repository.unlock', ('quack/', 'token'),
4245
'success', ('ok', ))
4248
def test_pack_with_hint(self):
4249
transport_path = 'quack'
4250
repo, client = self.setup_fake_client_and_repository(transport_path)
4251
client.add_expected_call(
4252
'Repository.lock_write', ('quack/', ''),
4253
'success', ('ok', 'token'))
4254
client.add_expected_call(
4255
'Repository.pack', ('quack/', 'token', 'False'),
4256
'success', ('ok',), )
4257
client.add_expected_call(
4258
'Repository.unlock', ('quack/', 'token', 'False'),
4259
'success', ('ok', ))
4260
repo.pack(['hinta', 'hintb'])
4263
class TestRepositoryIterInventories(TestRemoteRepository):
4264
"""Test Repository.iter_inventories."""
4266
def _serialize_inv_delta(self, old_name, new_name, delta):
4267
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4268
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4270
def test_single_empty(self):
4271
transport_path = 'quack'
4272
repo, client = self.setup_fake_client_and_repository(transport_path)
4273
fmt = controldir.format_registry.get('2a')().repository_format
4275
stream = [('inventory-deltas', [
4276
versionedfile.FulltextContentFactory('somerevid', None, None,
4277
self._serialize_inv_delta('null:', 'somerevid', []))])]
4278
client.add_expected_call(
4279
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4280
'success', ('ok', ),
4281
_stream_to_byte_stream(stream, fmt))
4282
ret = list(repo.iter_inventories(["somerevid"]))
4283
self.assertLength(1, ret)
4285
self.assertEqual("somerevid", inv.revision_id)
4287
def test_empty(self):
4288
transport_path = 'quack'
4289
repo, client = self.setup_fake_client_and_repository(transport_path)
4290
ret = list(repo.iter_inventories([]))
4291
self.assertEqual(ret, [])
4293
def test_missing(self):
4294
transport_path = 'quack'
4295
repo, client = self.setup_fake_client_and_repository(transport_path)
4296
client.add_expected_call(
4297
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4298
'success', ('ok', ), iter([]))
4299
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(