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 (
75
from ..bzr.smart import medium, request
76
from ..bzr.smart.client import _SmartClient
77
from ..bzr.smart.repository import (
78
SmartServerRepositoryGetParentMap,
79
SmartServerRepositoryGetStream_1_19,
80
_stream_to_byte_stream,
85
from .scenarios import load_tests_apply_scenarios
86
from ..transport.memory import MemoryTransport
87
from ..transport.remote import (
94
load_tests = load_tests_apply_scenarios
97
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
101
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
103
{'transport_server': test_server.SmartTCPServer_for_testing})]
107
super(BasicRemoteObjectTests, self).setUp()
108
self.transport = self.get_transport()
109
# make a branch that can be opened over the smart transport
110
self.local_wt = BzrDir.create_standalone_workingtree('.')
111
self.addCleanup(self.transport.disconnect)
113
def test_create_remote_bzrdir(self):
114
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
115
self.assertIsInstance(b, BzrDir)
117
def test_open_remote_branch(self):
118
# open a standalone branch in the working directory
119
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
120
branch = b.open_branch()
121
self.assertIsInstance(branch, Branch)
123
def test_remote_repository(self):
124
b = BzrDir.open_from_transport(self.transport)
125
repo = b.open_repository()
126
revid = u'\xc823123123'.encode('utf8')
127
self.assertFalse(repo.has_revision(revid))
128
self.local_wt.commit(message='test commit', rev_id=revid)
129
self.assertTrue(repo.has_revision(revid))
131
def test_find_correct_format(self):
132
"""Should open a RemoteBzrDir over a RemoteTransport"""
133
fmt = BzrDirFormat.find_format(self.transport)
134
self.assertTrue(RemoteBzrProber
135
in controldir.ControlDirFormat._server_probers)
136
self.assertIsInstance(fmt, RemoteBzrDirFormat)
138
def test_open_detected_smart_format(self):
139
fmt = BzrDirFormat.find_format(self.transport)
140
d = fmt.open(self.transport)
141
self.assertIsInstance(d, BzrDir)
143
def test_remote_branch_repr(self):
144
b = BzrDir.open_from_transport(self.transport).open_branch()
145
self.assertStartsWith(str(b), 'RemoteBranch(')
147
def test_remote_bzrdir_repr(self):
148
b = BzrDir.open_from_transport(self.transport)
149
self.assertStartsWith(str(b), 'RemoteBzrDir(')
151
def test_remote_branch_format_supports_stacking(self):
153
self.make_branch('unstackable', format='pack-0.92')
154
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
155
self.assertFalse(b._format.supports_stacking())
156
self.make_branch('stackable', format='1.9')
157
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
158
self.assertTrue(b._format.supports_stacking())
160
def test_remote_repo_format_supports_external_references(self):
162
bd = self.make_controldir('unstackable', format='pack-0.92')
163
r = bd.create_repository()
164
self.assertFalse(r._format.supports_external_lookups)
165
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
166
self.assertFalse(r._format.supports_external_lookups)
167
bd = self.make_controldir('stackable', format='1.9')
168
r = bd.create_repository()
169
self.assertTrue(r._format.supports_external_lookups)
170
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
171
self.assertTrue(r._format.supports_external_lookups)
173
def test_remote_branch_set_append_revisions_only(self):
174
# Make a format 1.9 branch, which supports append_revisions_only
175
branch = self.make_branch('branch', format='1.9')
176
branch.set_append_revisions_only(True)
177
config = branch.get_config_stack()
179
True, config.get('append_revisions_only'))
180
branch.set_append_revisions_only(False)
181
config = branch.get_config_stack()
183
False, config.get('append_revisions_only'))
185
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
186
branch = self.make_branch('branch', format='knit')
188
errors.UpgradeRequired, branch.set_append_revisions_only, True)
191
class FakeProtocol(object):
192
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
194
def __init__(self, body, fake_client):
196
self._body_buffer = None
197
self._fake_client = fake_client
199
def read_body_bytes(self, count=-1):
200
if self._body_buffer is None:
201
self._body_buffer = BytesIO(self.body)
202
bytes = self._body_buffer.read(count)
203
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
204
self._fake_client.expecting_body = False
207
def cancel_read_body(self):
208
self._fake_client.expecting_body = False
210
def read_streamed_body(self):
214
class FakeClient(_SmartClient):
215
"""Lookalike for _SmartClient allowing testing."""
217
def __init__(self, fake_medium_base='fake base'):
218
"""Create a FakeClient."""
221
self.expecting_body = False
222
# if non-None, this is the list of expected calls, with only the
223
# method name and arguments included. the body might be hard to
224
# compute so is not included. If a call is None, that call can
226
self._expected_calls = None
227
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
229
def add_expected_call(self, call_name, call_args, response_type,
230
response_args, response_body=None):
231
if self._expected_calls is None:
232
self._expected_calls = []
233
self._expected_calls.append((call_name, call_args))
234
self.responses.append((response_type, response_args, response_body))
236
def add_success_response(self, *args):
237
self.responses.append((b'success', args, None))
239
def add_success_response_with_body(self, body, *args):
240
self.responses.append((b'success', args, body))
241
if self._expected_calls is not None:
242
self._expected_calls.append(None)
244
def add_error_response(self, *args):
245
self.responses.append((b'error', args))
247
def add_unknown_method_response(self, verb):
248
self.responses.append((b'unknown', verb))
250
def finished_test(self):
251
if self._expected_calls:
252
raise AssertionError("%r finished but was still expecting %r"
253
% (self, self._expected_calls[0]))
255
def _get_next_response(self):
257
response_tuple = self.responses.pop(0)
258
except IndexError as e:
259
raise AssertionError("%r didn't expect any more calls"
261
if response_tuple[0] == b'unknown':
262
raise errors.UnknownSmartMethod(response_tuple[1])
263
elif response_tuple[0] == b'error':
264
raise errors.ErrorFromSmartServer(response_tuple[1])
265
return response_tuple
267
def _check_call(self, method, args):
268
if self._expected_calls is None:
269
# the test should be updated to say what it expects
272
next_call = self._expected_calls.pop(0)
274
raise AssertionError("%r didn't expect any more calls "
276
% (self, method, args,))
277
if next_call is None:
279
if method != next_call[0] or args != next_call[1]:
280
raise AssertionError("%r expected %r%r "
282
% (self, next_call[0], next_call[1], method, args,))
284
def call(self, method, *args):
285
self._check_call(method, args)
286
self._calls.append(('call', method, args))
287
return self._get_next_response()[1]
289
def call_expecting_body(self, method, *args):
290
self._check_call(method, args)
291
self._calls.append(('call_expecting_body', method, args))
292
result = self._get_next_response()
293
self.expecting_body = True
294
return result[1], FakeProtocol(result[2], self)
296
def call_with_body_bytes(self, method, args, body):
297
self._check_call(method, args)
298
self._calls.append(('call_with_body_bytes', method, args, body))
299
result = self._get_next_response()
300
return result[1], FakeProtocol(result[2], self)
302
def call_with_body_bytes_expecting_body(self, method, args, body):
303
self._check_call(method, args)
304
self._calls.append(('call_with_body_bytes_expecting_body', method,
306
result = self._get_next_response()
307
self.expecting_body = True
308
return result[1], FakeProtocol(result[2], self)
310
def call_with_body_stream(self, args, stream):
311
# Explicitly consume the stream before checking for an error, because
312
# that's what happens a real medium.
313
stream = list(stream)
314
self._check_call(args[0], args[1:])
315
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
316
result = self._get_next_response()
317
# The second value returned from call_with_body_stream is supposed to
318
# be a response_handler object, but so far no tests depend on that.
319
response_handler = None
320
return result[1], response_handler
323
class FakeMedium(medium.SmartClientMedium):
325
def __init__(self, client_calls, base):
326
medium.SmartClientMedium.__init__(self, base)
327
self._client_calls = client_calls
329
def disconnect(self):
330
self._client_calls.append(('disconnect medium',))
333
class TestVfsHas(tests.TestCase):
335
def test_unicode_path(self):
336
client = FakeClient('/')
337
client.add_success_response(b'yes',)
338
transport = RemoteTransport('bzr://localhost/', _client=client)
339
filename = u'/hell\u00d8'.encode('utf-8')
340
result = transport.has(filename)
342
[('call', b'has', (filename,))],
344
self.assertTrue(result)
347
class TestRemote(tests.TestCaseWithMemoryTransport):
349
def get_branch_format(self):
350
reference_bzrdir_format = controldir.format_registry.get('default')()
351
return reference_bzrdir_format.get_branch_format()
353
def get_repo_format(self):
354
reference_bzrdir_format = controldir.format_registry.get('default')()
355
return reference_bzrdir_format.repository_format
357
def assertFinished(self, fake_client):
358
"""Assert that all of a FakeClient's expected calls have occurred."""
359
fake_client.finished_test()
362
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
363
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
365
def assertRemotePath(self, expected, client_base, transport_base):
366
"""Assert that the result of
367
SmartClientMedium.remote_path_from_transport is the expected value for
368
a given client_base and transport_base.
370
client_medium = medium.SmartClientMedium(client_base)
371
t = transport.get_transport(transport_base)
372
result = client_medium.remote_path_from_transport(t)
373
self.assertEqual(expected, result)
375
def test_remote_path_from_transport(self):
376
"""SmartClientMedium.remote_path_from_transport calculates a URL for
377
the given transport relative to the root of the client base URL.
379
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
380
self.assertRemotePath(
381
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
383
def assertRemotePathHTTP(self, expected, transport_base, relpath):
384
"""Assert that the result of
385
HttpTransportBase.remote_path_from_transport is the expected value for
386
a given transport_base and relpath of that transport. (Note that
387
HttpTransportBase is a subclass of SmartClientMedium)
389
base_transport = transport.get_transport(transport_base)
390
client_medium = base_transport.get_smart_medium()
391
cloned_transport = base_transport.clone(relpath)
392
result = client_medium.remote_path_from_transport(cloned_transport)
393
self.assertEqual(expected, result)
395
def test_remote_path_from_transport_http(self):
396
"""Remote paths for HTTP transports are calculated differently to other
397
transports. They are just relative to the client base, not the root
398
directory of the host.
400
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
401
self.assertRemotePathHTTP(
402
'../xyz/', scheme + '//host/path', '../xyz/')
403
self.assertRemotePathHTTP(
404
'xyz/', scheme + '//host/path', 'xyz/')
407
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
408
"""Tests for the behaviour of client_medium.remote_is_at_least."""
410
def test_initially_unlimited(self):
411
"""A fresh medium assumes that the remote side supports all
414
client_medium = medium.SmartClientMedium('dummy base')
415
self.assertFalse(client_medium._is_remote_before((99, 99)))
417
def test__remember_remote_is_before(self):
418
"""Calling _remember_remote_is_before ratchets down the known remote
421
client_medium = medium.SmartClientMedium('dummy base')
422
# Mark the remote side as being less than 1.6. The remote side may
424
client_medium._remember_remote_is_before((1, 6))
425
self.assertTrue(client_medium._is_remote_before((1, 6)))
426
self.assertFalse(client_medium._is_remote_before((1, 5)))
427
# Calling _remember_remote_is_before again with a lower value works.
428
client_medium._remember_remote_is_before((1, 5))
429
self.assertTrue(client_medium._is_remote_before((1, 5)))
430
# If you call _remember_remote_is_before with a higher value it logs a
431
# warning, and continues to remember the lower value.
432
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
433
client_medium._remember_remote_is_before((1, 9))
434
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
435
self.assertTrue(client_medium._is_remote_before((1, 5)))
438
class TestBzrDirCloningMetaDir(TestRemote):
440
def test_backwards_compat(self):
441
self.setup_smart_server_with_call_log()
442
a_dir = self.make_controldir('.')
443
self.reset_smart_call_log()
444
verb = b'BzrDir.cloning_metadir'
445
self.disable_verb(verb)
446
format = a_dir.cloning_metadir()
447
call_count = len([call for call in self.hpss_calls if
448
call.call.method == verb])
449
self.assertEqual(1, call_count)
451
def test_branch_reference(self):
452
transport = self.get_transport('quack')
453
referenced = self.make_branch('referenced')
454
expected = referenced.controldir.cloning_metadir()
455
client = FakeClient(transport.base)
456
client.add_expected_call(
457
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
458
b'error', (b'BranchReference',)),
459
client.add_expected_call(
460
b'BzrDir.open_branchV3', (b'quack/',),
461
b'success', (b'ref', self.get_url('referenced'))),
462
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
464
result = a_controldir.cloning_metadir()
465
# We should have got a control dir matching the referenced branch.
466
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
467
self.assertEqual(expected._repository_format, result._repository_format)
468
self.assertEqual(expected._branch_format, result._branch_format)
469
self.assertFinished(client)
471
def test_current_server(self):
472
transport = self.get_transport('.')
473
transport = transport.clone('quack')
474
self.make_controldir('quack')
475
client = FakeClient(transport.base)
476
reference_bzrdir_format = controldir.format_registry.get('default')()
477
control_name = reference_bzrdir_format.network_name()
478
client.add_expected_call(
479
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
480
b'success', (control_name, b'', (b'branch', b''))),
481
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
483
result = a_controldir.cloning_metadir()
484
# We should have got a reference control dir with default branch and
485
# repository formats.
486
# This pokes a little, just to be sure.
487
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
488
self.assertEqual(None, result._repository_format)
489
self.assertEqual(None, result._branch_format)
490
self.assertFinished(client)
492
def test_unknown(self):
493
transport = self.get_transport('quack')
494
referenced = self.make_branch('referenced')
495
expected = referenced.controldir.cloning_metadir()
496
client = FakeClient(transport.base)
497
client.add_expected_call(
498
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
499
b'success', (b'unknown', b'unknown', (b'branch', b''))),
500
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
502
self.assertRaises(errors.UnknownFormatError, a_controldir.cloning_metadir)
505
class TestBzrDirCheckoutMetaDir(TestRemote):
507
def test__get_checkout_format(self):
508
transport = MemoryTransport()
509
client = FakeClient(transport.base)
510
reference_bzrdir_format = controldir.format_registry.get('default')()
511
control_name = reference_bzrdir_format.network_name()
512
client.add_expected_call(
513
b'BzrDir.checkout_metadir', (b'quack/', ),
514
b'success', (control_name, b'', b''))
515
transport.mkdir('quack')
516
transport = transport.clone('quack')
517
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
519
result = a_controldir.checkout_metadir()
520
# We should have got a reference control dir with default branch and
521
# repository formats.
522
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
523
self.assertEqual(None, result._repository_format)
524
self.assertEqual(None, result._branch_format)
525
self.assertFinished(client)
527
def test_unknown_format(self):
528
transport = MemoryTransport()
529
client = FakeClient(transport.base)
530
client.add_expected_call(
531
b'BzrDir.checkout_metadir', (b'quack/',),
532
b'success', (b'dontknow', b'', b''))
533
transport.mkdir('quack')
534
transport = transport.clone('quack')
535
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
537
self.assertRaises(errors.UnknownFormatError,
538
a_controldir.checkout_metadir)
539
self.assertFinished(client)
542
class TestBzrDirGetBranches(TestRemote):
544
def test_get_branches(self):
545
transport = MemoryTransport()
546
client = FakeClient(transport.base)
547
reference_bzrdir_format = controldir.format_registry.get('default')()
548
branch_name = reference_bzrdir_format.get_branch_format().network_name()
549
client.add_success_response_with_body(
551
b"foo": (b"branch", branch_name),
552
b"": (b"branch", branch_name)}), b"success")
553
client.add_success_response(
554
b'ok', b'', b'no', b'no', b'no',
555
reference_bzrdir_format.repository_format.network_name())
556
client.add_error_response(b'NotStacked')
557
client.add_success_response(
558
b'ok', b'', b'no', b'no', b'no',
559
reference_bzrdir_format.repository_format.network_name())
560
client.add_error_response(b'NotStacked')
561
transport.mkdir('quack')
562
transport = transport.clone('quack')
563
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
565
result = a_controldir.get_branches()
566
self.assertEqual({b"", b"foo"}, set(result.keys()))
568
[('call_expecting_body', b'BzrDir.get_branches', (b'quack/',)),
569
('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
570
('call', b'Branch.get_stacked_on_url', (b'quack/', )),
571
('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
572
('call', b'Branch.get_stacked_on_url', (b'quack/', ))],
576
class TestBzrDirDestroyBranch(TestRemote):
578
def test_destroy_default(self):
579
transport = self.get_transport('quack')
580
referenced = self.make_branch('referenced')
581
client = FakeClient(transport.base)
582
client.add_expected_call(
583
b'BzrDir.destroy_branch', (b'quack/', ),
584
b'success', (b'ok',)),
585
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
587
a_controldir.destroy_branch()
588
self.assertFinished(client)
591
class TestBzrDirHasWorkingTree(TestRemote):
593
def test_has_workingtree(self):
594
transport = self.get_transport('quack')
595
client = FakeClient(transport.base)
596
client.add_expected_call(
597
b'BzrDir.has_workingtree', (b'quack/',),
598
b'success', (b'yes',)),
599
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
601
self.assertTrue(a_controldir.has_workingtree())
602
self.assertFinished(client)
604
def test_no_workingtree(self):
605
transport = self.get_transport('quack')
606
client = FakeClient(transport.base)
607
client.add_expected_call(
608
b'BzrDir.has_workingtree', (b'quack/',),
609
b'success', (b'no',)),
610
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
612
self.assertFalse(a_controldir.has_workingtree())
613
self.assertFinished(client)
616
class TestBzrDirDestroyRepository(TestRemote):
618
def test_destroy_repository(self):
619
transport = self.get_transport('quack')
620
client = FakeClient(transport.base)
621
client.add_expected_call(
622
b'BzrDir.destroy_repository', (b'quack/',),
623
b'success', (b'ok',)),
624
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
626
a_controldir.destroy_repository()
627
self.assertFinished(client)
630
class TestBzrDirOpen(TestRemote):
632
def make_fake_client_and_transport(self, path='quack'):
633
transport = MemoryTransport()
634
transport.mkdir(path)
635
transport = transport.clone(path)
636
client = FakeClient(transport.base)
637
return client, transport
639
def test_absent(self):
640
client, transport = self.make_fake_client_and_transport()
641
client.add_expected_call(
642
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'no',))
643
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
644
RemoteBzrDirFormat(), _client=client, _force_probe=True)
645
self.assertFinished(client)
647
def test_present_without_workingtree(self):
648
client, transport = self.make_fake_client_and_transport()
649
client.add_expected_call(
650
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'no'))
651
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
652
_client=client, _force_probe=True)
653
self.assertIsInstance(bd, RemoteBzrDir)
654
self.assertFalse(bd.has_workingtree())
655
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
656
self.assertFinished(client)
658
def test_present_with_workingtree(self):
659
client, transport = self.make_fake_client_and_transport()
660
client.add_expected_call(
661
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'yes'))
662
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
663
_client=client, _force_probe=True)
664
self.assertIsInstance(bd, RemoteBzrDir)
665
self.assertTrue(bd.has_workingtree())
666
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
667
self.assertFinished(client)
669
def test_backwards_compat(self):
670
client, transport = self.make_fake_client_and_transport()
671
client.add_expected_call(
672
b'BzrDir.open_2.1', (b'quack/',), b'unknown', (b'BzrDir.open_2.1',))
673
client.add_expected_call(
674
b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
675
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
676
_client=client, _force_probe=True)
677
self.assertIsInstance(bd, RemoteBzrDir)
678
self.assertFinished(client)
680
def test_backwards_compat_hpss_v2(self):
681
client, transport = self.make_fake_client_and_transport()
682
# Monkey-patch fake client to simulate real-world behaviour with v2
683
# server: upon first RPC call detect the protocol version, and because
684
# the version is 2 also do _remember_remote_is_before((1, 6)) before
685
# continuing with the RPC.
686
orig_check_call = client._check_call
687
def check_call(method, args):
688
client._medium._protocol_version = 2
689
client._medium._remember_remote_is_before((1, 6))
690
client._check_call = orig_check_call
691
client._check_call(method, args)
692
client._check_call = check_call
693
client.add_expected_call(
694
b'BzrDir.open_2.1', (b'quack/',), b'unknown', (b'BzrDir.open_2.1',))
695
client.add_expected_call(
696
b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
697
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
698
_client=client, _force_probe=True)
699
self.assertIsInstance(bd, RemoteBzrDir)
700
self.assertFinished(client)
703
class TestBzrDirOpenBranch(TestRemote):
705
def test_backwards_compat(self):
706
self.setup_smart_server_with_call_log()
707
self.make_branch('.')
708
a_dir = BzrDir.open(self.get_url('.'))
709
self.reset_smart_call_log()
710
verb = b'BzrDir.open_branchV3'
711
self.disable_verb(verb)
712
format = a_dir.open_branch()
713
call_count = len([call for call in self.hpss_calls if
714
call.call.method == verb])
715
self.assertEqual(1, call_count)
717
def test_branch_present(self):
718
reference_format = self.get_repo_format()
719
network_name = reference_format.network_name()
720
branch_network_name = self.get_branch_format().network_name()
721
transport = MemoryTransport()
722
transport.mkdir('quack')
723
transport = transport.clone('quack')
724
client = FakeClient(transport.base)
725
client.add_expected_call(
726
b'BzrDir.open_branchV3', (b'quack/',),
727
b'success', (b'branch', branch_network_name))
728
client.add_expected_call(
729
b'BzrDir.find_repositoryV3', (b'quack/',),
730
b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
731
client.add_expected_call(
732
b'Branch.get_stacked_on_url', (b'quack/',),
733
b'error', (b'NotStacked',))
734
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
736
result = bzrdir.open_branch()
737
self.assertIsInstance(result, RemoteBranch)
738
self.assertEqual(bzrdir, result.controldir)
739
self.assertFinished(client)
741
def test_branch_missing(self):
742
transport = MemoryTransport()
743
transport.mkdir('quack')
744
transport = transport.clone('quack')
745
client = FakeClient(transport.base)
746
client.add_error_response(b'nobranch')
747
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
749
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
751
[('call', b'BzrDir.open_branchV3', (b'quack/',))],
754
def test__get_tree_branch(self):
755
# _get_tree_branch is a form of open_branch, but it should only ask for
756
# branch opening, not any other network requests.
758
def open_branch(name=None, possible_transports=None):
759
calls.append("Called")
761
transport = MemoryTransport()
762
# no requests on the network - catches other api calls being made.
763
client = FakeClient(transport.base)
764
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
766
# patch the open_branch call to record that it was called.
767
bzrdir.open_branch = open_branch
768
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
769
self.assertEqual(["Called"], calls)
770
self.assertEqual([], client._calls)
772
def test_url_quoting_of_path(self):
773
# Relpaths on the wire should not be URL-escaped. So "~" should be
774
# transmitted as "~", not "%7E".
775
transport = RemoteTCPTransport('bzr://localhost/~hello/')
776
client = FakeClient(transport.base)
777
reference_format = self.get_repo_format()
778
network_name = reference_format.network_name()
779
branch_network_name = self.get_branch_format().network_name()
780
client.add_expected_call(
781
b'BzrDir.open_branchV3', (b'~hello/',),
782
b'success', (b'branch', branch_network_name))
783
client.add_expected_call(
784
b'BzrDir.find_repositoryV3', (b'~hello/',),
785
b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
786
client.add_expected_call(
787
b'Branch.get_stacked_on_url', (b'~hello/',),
788
b'error', (b'NotStacked',))
789
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
791
result = bzrdir.open_branch()
792
self.assertFinished(client)
794
def check_open_repository(self, rich_root, subtrees, external_lookup=b'no'):
795
reference_format = self.get_repo_format()
796
network_name = reference_format.network_name()
797
transport = MemoryTransport()
798
transport.mkdir('quack')
799
transport = transport.clone('quack')
801
rich_response = b'yes'
803
rich_response = b'no'
805
subtree_response = b'yes'
807
subtree_response = b'no'
808
client = FakeClient(transport.base)
809
client.add_success_response(
810
b'ok', b'', rich_response, subtree_response, external_lookup,
812
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
814
result = bzrdir.open_repository()
816
[('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
818
self.assertIsInstance(result, RemoteRepository)
819
self.assertEqual(bzrdir, result.controldir)
820
self.assertEqual(rich_root, result._format.rich_root_data)
821
self.assertEqual(subtrees, result._format.supports_tree_reference)
823
def test_open_repository_sets_format_attributes(self):
824
self.check_open_repository(True, True)
825
self.check_open_repository(False, True)
826
self.check_open_repository(True, False)
827
self.check_open_repository(False, False)
828
self.check_open_repository(False, False, b'yes')
830
def test_old_server(self):
831
"""RemoteBzrDirFormat should fail to probe if the server version is too
834
self.assertRaises(errors.NotBranchError,
835
RemoteBzrProber.probe_transport, OldServerTransport())
838
class TestBzrDirCreateBranch(TestRemote):
840
def test_backwards_compat(self):
841
self.setup_smart_server_with_call_log()
842
repo = self.make_repository('.')
843
self.reset_smart_call_log()
844
self.disable_verb(b'BzrDir.create_branch')
845
branch = repo.controldir.create_branch()
846
create_branch_call_count = len([call for call in self.hpss_calls if
847
call.call.method == b'BzrDir.create_branch'])
848
self.assertEqual(1, create_branch_call_count)
850
def test_current_server(self):
851
transport = self.get_transport('.')
852
transport = transport.clone('quack')
853
self.make_repository('quack')
854
client = FakeClient(transport.base)
855
reference_bzrdir_format = controldir.format_registry.get('default')()
856
reference_format = reference_bzrdir_format.get_branch_format()
857
network_name = reference_format.network_name()
858
reference_repo_fmt = reference_bzrdir_format.repository_format
859
reference_repo_name = reference_repo_fmt.network_name()
860
client.add_expected_call(
861
b'BzrDir.create_branch', (b'quack/', network_name),
862
b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
863
reference_repo_name))
864
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
866
branch = a_controldir.create_branch()
867
# We should have got a remote branch
868
self.assertIsInstance(branch, remote.RemoteBranch)
869
# its format should have the settings from the response
870
format = branch._format
871
self.assertEqual(network_name, format.network_name())
873
def test_already_open_repo_and_reused_medium(self):
874
"""Bug 726584: create_branch(..., repository=repo) should work
875
regardless of what the smart medium's base URL is.
877
self.transport_server = test_server.SmartTCPServer_for_testing
878
transport = self.get_transport('.')
879
repo = self.make_repository('quack')
880
# Client's medium rooted a transport root (not at the bzrdir)
881
client = FakeClient(transport.base)
882
transport = transport.clone('quack')
883
reference_bzrdir_format = controldir.format_registry.get('default')()
884
reference_format = reference_bzrdir_format.get_branch_format()
885
network_name = reference_format.network_name()
886
reference_repo_fmt = reference_bzrdir_format.repository_format
887
reference_repo_name = reference_repo_fmt.network_name()
888
client.add_expected_call(
889
b'BzrDir.create_branch', (b'extra/quack/', network_name),
890
b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
891
reference_repo_name))
892
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
894
branch = a_controldir.create_branch(repository=repo)
895
# We should have got a remote branch
896
self.assertIsInstance(branch, remote.RemoteBranch)
897
# its format should have the settings from the response
898
format = branch._format
899
self.assertEqual(network_name, format.network_name())
902
class TestBzrDirCreateRepository(TestRemote):
904
def test_backwards_compat(self):
905
self.setup_smart_server_with_call_log()
906
bzrdir = self.make_controldir('.')
907
self.reset_smart_call_log()
908
self.disable_verb(b'BzrDir.create_repository')
909
repo = bzrdir.create_repository()
910
create_repo_call_count = len([call for call in self.hpss_calls if
911
call.call.method == b'BzrDir.create_repository'])
912
self.assertEqual(1, create_repo_call_count)
914
def test_current_server(self):
915
transport = self.get_transport('.')
916
transport = transport.clone('quack')
917
self.make_controldir('quack')
918
client = FakeClient(transport.base)
919
reference_bzrdir_format = controldir.format_registry.get('default')()
920
reference_format = reference_bzrdir_format.repository_format
921
network_name = reference_format.network_name()
922
client.add_expected_call(
923
b'BzrDir.create_repository', (b'quack/',
924
b'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
926
b'success', (b'ok', b'yes', b'yes', b'yes', network_name))
927
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
929
repo = a_controldir.create_repository()
930
# We should have got a remote repository
931
self.assertIsInstance(repo, remote.RemoteRepository)
932
# its format should have the settings from the response
933
format = repo._format
934
self.assertTrue(format.rich_root_data)
935
self.assertTrue(format.supports_tree_reference)
936
self.assertTrue(format.supports_external_lookups)
937
self.assertEqual(network_name, format.network_name())
940
class TestBzrDirOpenRepository(TestRemote):
942
def test_backwards_compat_1_2_3(self):
943
# fallback all the way to the first version.
944
reference_format = self.get_repo_format()
945
network_name = reference_format.network_name()
946
server_url = 'bzr://example.com/'
947
self.permit_url(server_url)
948
client = FakeClient(server_url)
949
client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
950
client.add_unknown_method_response(b'BzrDir.find_repositoryV2')
951
client.add_success_response(b'ok', b'', b'no', b'no')
952
# A real repository instance will be created to determine the network
954
client.add_success_response_with_body(
955
b"Bazaar-NG meta directory, format 1\n", b'ok')
956
client.add_success_response(b'stat', b'0', b'65535')
957
client.add_success_response_with_body(
958
reference_format.get_format_string(), b'ok')
959
# PackRepository wants to do a stat
960
client.add_success_response(b'stat', b'0', b'65535')
961
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
963
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
965
repo = bzrdir.open_repository()
967
[('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
968
('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
969
('call', b'BzrDir.find_repository', (b'quack/',)),
970
('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
971
('call', b'stat', (b'/quack/.bzr',)),
972
('call_expecting_body', b'get', (b'/quack/.bzr/repository/format',)),
973
('call', b'stat', (b'/quack/.bzr/repository',)),
976
self.assertEqual(network_name, repo._format.network_name())
978
def test_backwards_compat_2(self):
979
# fallback to find_repositoryV2
980
reference_format = self.get_repo_format()
981
network_name = reference_format.network_name()
982
server_url = 'bzr://example.com/'
983
self.permit_url(server_url)
984
client = FakeClient(server_url)
985
client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
986
client.add_success_response(b'ok', b'', b'no', b'no', b'no')
987
# A real repository instance will be created to determine the network
989
client.add_success_response_with_body(
990
b"Bazaar-NG meta directory, format 1\n", b'ok')
991
client.add_success_response(b'stat', b'0', b'65535')
992
client.add_success_response_with_body(
993
reference_format.get_format_string(), b'ok')
994
# PackRepository wants to do a stat
995
client.add_success_response(b'stat', b'0', b'65535')
996
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
998
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
1000
repo = bzrdir.open_repository()
1002
[('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
1003
('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
1004
('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
1005
('call', b'stat', (b'/quack/.bzr',)),
1006
('call_expecting_body', b'get', (b'/quack/.bzr/repository/format',)),
1007
('call', b'stat', (b'/quack/.bzr/repository',)),
1010
self.assertEqual(network_name, repo._format.network_name())
1012
def test_current_server(self):
1013
reference_format = self.get_repo_format()
1014
network_name = reference_format.network_name()
1015
transport = MemoryTransport()
1016
transport.mkdir('quack')
1017
transport = transport.clone('quack')
1018
client = FakeClient(transport.base)
1019
client.add_success_response(b'ok', b'', b'no', b'no', b'no', network_name)
1020
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1022
repo = bzrdir.open_repository()
1024
[('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
1026
self.assertEqual(network_name, repo._format.network_name())
1029
class TestBzrDirFormatInitializeEx(TestRemote):
1031
def test_success(self):
1032
"""Simple test for typical successful call."""
1033
fmt = RemoteBzrDirFormat()
1034
default_format_name = BzrDirFormat.get_default_format().network_name()
1035
transport = self.get_transport()
1036
client = FakeClient(transport.base)
1037
client.add_expected_call(
1038
b'BzrDirFormat.initialize_ex_1.16',
1039
(default_format_name, b'path', b'False', b'False', b'False', b'',
1040
b'', b'', b'', b'False'),
1042
(b'.', b'no', b'no', b'yes', b'repo fmt', b'repo bzrdir fmt',
1043
b'bzrdir fmt', b'False', b'', b'', b'repo lock token'))
1044
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1045
# it's currently hard to test that without supplying a real remote
1046
# transport connected to a real server.
1047
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1048
transport, False, False, False, None, None, None, None, False)
1049
self.assertFinished(client)
1051
def test_error(self):
1052
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1053
corresponding error from the client.
1055
fmt = RemoteBzrDirFormat()
1056
default_format_name = BzrDirFormat.get_default_format().network_name()
1057
transport = self.get_transport()
1058
client = FakeClient(transport.base)
1059
client.add_expected_call(
1060
b'BzrDirFormat.initialize_ex_1.16',
1061
(default_format_name, b'path', b'False', b'False', b'False', b'',
1062
b'', b'', b'', b'False'),
1064
(b'PermissionDenied', b'path', b'extra info'))
1065
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1066
# it's currently hard to test that without supplying a real remote
1067
# transport connected to a real server.
1068
err = self.assertRaises(errors.PermissionDenied,
1069
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1070
False, False, False, None, None, None, None, False)
1071
self.assertEqual('path', err.path)
1072
self.assertEqual(': extra info', err.extra)
1073
self.assertFinished(client)
1075
def test_error_from_real_server(self):
1076
"""Integration test for error translation."""
1077
transport = self.make_smart_server('foo')
1078
transport = transport.clone('no-such-path')
1079
fmt = RemoteBzrDirFormat()
1080
err = self.assertRaises(errors.NoSuchFile,
1081
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1084
class OldSmartClient(object):
1085
"""A fake smart client for test_old_version that just returns a version one
1086
response to the 'hello' (query version) command.
1089
def get_request(self):
1090
input_file = BytesIO(b'ok\x011\n')
1091
output_file = BytesIO()
1092
client_medium = medium.SmartSimplePipesClientMedium(
1093
input_file, output_file)
1094
return medium.SmartClientStreamMediumRequest(client_medium)
1096
def protocol_version(self):
1100
class OldServerTransport(object):
1101
"""A fake transport for test_old_server that reports it's smart server
1102
protocol version as version one.
1108
def get_smart_client(self):
1109
return OldSmartClient()
1112
class RemoteBzrDirTestCase(TestRemote):
1114
def make_remote_bzrdir(self, transport, client):
1115
"""Make a RemotebzrDir using 'client' as the _client."""
1116
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1120
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1122
def lock_remote_branch(self, branch):
1123
"""Trick a RemoteBranch into thinking it is locked."""
1124
branch._lock_mode = 'w'
1125
branch._lock_count = 2
1126
branch._lock_token = b'branch token'
1127
branch._repo_lock_token = b'repo token'
1128
branch.repository._lock_mode = 'w'
1129
branch.repository._lock_count = 2
1130
branch.repository._lock_token = b'repo token'
1132
def make_remote_branch(self, transport, client):
1133
"""Make a RemoteBranch using 'client' as its _SmartClient.
1135
A RemoteBzrDir and RemoteRepository will also be created to fill out
1136
the RemoteBranch, albeit with stub values for some of their attributes.
1138
# we do not want bzrdir to make any remote calls, so use False as its
1139
# _client. If it tries to make a remote call, this will fail
1141
bzrdir = self.make_remote_bzrdir(transport, False)
1142
repo = RemoteRepository(bzrdir, None, _client=client)
1143
branch_format = self.get_branch_format()
1144
format = RemoteBranchFormat(network_name=branch_format.network_name())
1145
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1148
class TestBranchBreakLock(RemoteBranchTestCase):
1150
def test_break_lock(self):
1151
transport_path = 'quack'
1152
transport = MemoryTransport()
1153
client = FakeClient(transport.base)
1154
client.add_expected_call(
1155
b'Branch.get_stacked_on_url', (b'quack/',),
1156
b'error', (b'NotStacked',))
1157
client.add_expected_call(
1158
b'Branch.break_lock', (b'quack/',),
1159
b'success', (b'ok',))
1160
transport.mkdir('quack')
1161
transport = transport.clone('quack')
1162
branch = self.make_remote_branch(transport, client)
1164
self.assertFinished(client)
1167
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1169
def test_get_physical_lock_status_yes(self):
1170
transport = MemoryTransport()
1171
client = FakeClient(transport.base)
1172
client.add_expected_call(
1173
b'Branch.get_stacked_on_url', (b'quack/',),
1174
b'error', (b'NotStacked',))
1175
client.add_expected_call(
1176
b'Branch.get_physical_lock_status', (b'quack/',),
1177
b'success', (b'yes',))
1178
transport.mkdir('quack')
1179
transport = transport.clone('quack')
1180
branch = self.make_remote_branch(transport, client)
1181
result = branch.get_physical_lock_status()
1182
self.assertFinished(client)
1183
self.assertEqual(True, result)
1185
def test_get_physical_lock_status_no(self):
1186
transport = MemoryTransport()
1187
client = FakeClient(transport.base)
1188
client.add_expected_call(
1189
b'Branch.get_stacked_on_url', (b'quack/',),
1190
b'error', (b'NotStacked',))
1191
client.add_expected_call(
1192
b'Branch.get_physical_lock_status', (b'quack/',),
1193
b'success', (b'no',))
1194
transport.mkdir('quack')
1195
transport = transport.clone('quack')
1196
branch = self.make_remote_branch(transport, client)
1197
result = branch.get_physical_lock_status()
1198
self.assertFinished(client)
1199
self.assertEqual(False, result)
1202
class TestBranchGetParent(RemoteBranchTestCase):
1204
def test_no_parent(self):
1205
# in an empty branch we decode the response properly
1206
transport = MemoryTransport()
1207
client = FakeClient(transport.base)
1208
client.add_expected_call(
1209
b'Branch.get_stacked_on_url', (b'quack/',),
1210
b'error', (b'NotStacked',))
1211
client.add_expected_call(
1212
b'Branch.get_parent', (b'quack/',),
1214
transport.mkdir('quack')
1215
transport = transport.clone('quack')
1216
branch = self.make_remote_branch(transport, client)
1217
result = branch.get_parent()
1218
self.assertFinished(client)
1219
self.assertEqual(None, result)
1221
def test_parent_relative(self):
1222
transport = MemoryTransport()
1223
client = FakeClient(transport.base)
1224
client.add_expected_call(
1225
b'Branch.get_stacked_on_url', (b'kwaak/',),
1226
b'error', (b'NotStacked',))
1227
client.add_expected_call(
1228
b'Branch.get_parent', (b'kwaak/',),
1229
b'success', (b'../foo/',))
1230
transport.mkdir('kwaak')
1231
transport = transport.clone('kwaak')
1232
branch = self.make_remote_branch(transport, client)
1233
result = branch.get_parent()
1234
self.assertEqual(transport.clone('../foo').base, result)
1236
def test_parent_absolute(self):
1237
transport = MemoryTransport()
1238
client = FakeClient(transport.base)
1239
client.add_expected_call(
1240
b'Branch.get_stacked_on_url', (b'kwaak/',),
1241
b'error', (b'NotStacked',))
1242
client.add_expected_call(
1243
b'Branch.get_parent', (b'kwaak/',),
1244
b'success', (b'http://foo/',))
1245
transport.mkdir('kwaak')
1246
transport = transport.clone('kwaak')
1247
branch = self.make_remote_branch(transport, client)
1248
result = branch.get_parent()
1249
self.assertEqual('http://foo/', result)
1250
self.assertFinished(client)
1253
class TestBranchSetParentLocation(RemoteBranchTestCase):
1255
def test_no_parent(self):
1256
# We call the verb when setting parent to None
1257
transport = MemoryTransport()
1258
client = FakeClient(transport.base)
1259
client.add_expected_call(
1260
b'Branch.get_stacked_on_url', (b'quack/',),
1261
b'error', (b'NotStacked',))
1262
client.add_expected_call(
1263
b'Branch.set_parent_location', (b'quack/', b'b', b'r', b''),
1265
transport.mkdir('quack')
1266
transport = transport.clone('quack')
1267
branch = self.make_remote_branch(transport, client)
1268
branch._lock_token = b'b'
1269
branch._repo_lock_token = b'r'
1270
branch._set_parent_location(None)
1271
self.assertFinished(client)
1273
def test_parent(self):
1274
transport = MemoryTransport()
1275
client = FakeClient(transport.base)
1276
client.add_expected_call(
1277
b'Branch.get_stacked_on_url', (b'kwaak/',),
1278
b'error', (b'NotStacked',))
1279
client.add_expected_call(
1280
b'Branch.set_parent_location', (b'kwaak/', b'b', b'r', b'foo'),
1282
transport.mkdir('kwaak')
1283
transport = transport.clone('kwaak')
1284
branch = self.make_remote_branch(transport, client)
1285
branch._lock_token = b'b'
1286
branch._repo_lock_token = b'r'
1287
branch._set_parent_location('foo')
1288
self.assertFinished(client)
1290
def test_backwards_compat(self):
1291
self.setup_smart_server_with_call_log()
1292
branch = self.make_branch('.')
1293
self.reset_smart_call_log()
1294
verb = b'Branch.set_parent_location'
1295
self.disable_verb(verb)
1296
branch.set_parent('http://foo/')
1297
self.assertLength(14, self.hpss_calls)
1300
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1302
def test_backwards_compat(self):
1303
self.setup_smart_server_with_call_log()
1304
branch = self.make_branch('.')
1305
self.reset_smart_call_log()
1306
verb = b'Branch.get_tags_bytes'
1307
self.disable_verb(verb)
1308
branch.tags.get_tag_dict()
1309
call_count = len([call for call in self.hpss_calls if
1310
call.call.method == verb])
1311
self.assertEqual(1, call_count)
1313
def test_trivial(self):
1314
transport = MemoryTransport()
1315
client = FakeClient(transport.base)
1316
client.add_expected_call(
1317
b'Branch.get_stacked_on_url', (b'quack/',),
1318
b'error', (b'NotStacked',))
1319
client.add_expected_call(
1320
b'Branch.get_tags_bytes', (b'quack/',),
1322
transport.mkdir('quack')
1323
transport = transport.clone('quack')
1324
branch = self.make_remote_branch(transport, client)
1325
result = branch.tags.get_tag_dict()
1326
self.assertFinished(client)
1327
self.assertEqual({}, result)
1330
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1332
def test_trivial(self):
1333
transport = MemoryTransport()
1334
client = FakeClient(transport.base)
1335
client.add_expected_call(
1336
b'Branch.get_stacked_on_url', (b'quack/',),
1337
b'error', (b'NotStacked',))
1338
client.add_expected_call(
1339
b'Branch.set_tags_bytes', (b'quack/', b'branch token', b'repo token'),
1341
transport.mkdir('quack')
1342
transport = transport.clone('quack')
1343
branch = self.make_remote_branch(transport, client)
1344
self.lock_remote_branch(branch)
1345
branch._set_tags_bytes(b'tags bytes')
1346
self.assertFinished(client)
1347
self.assertEqual(b'tags bytes', client._calls[-1][-1])
1349
def test_backwards_compatible(self):
1350
transport = MemoryTransport()
1351
client = FakeClient(transport.base)
1352
client.add_expected_call(
1353
b'Branch.get_stacked_on_url', (b'quack/',),
1354
b'error', (b'NotStacked',))
1355
client.add_expected_call(
1356
b'Branch.set_tags_bytes', (b'quack/', b'branch token', b'repo token'),
1357
b'unknown', (b'Branch.set_tags_bytes',))
1358
transport.mkdir('quack')
1359
transport = transport.clone('quack')
1360
branch = self.make_remote_branch(transport, client)
1361
self.lock_remote_branch(branch)
1362
class StubRealBranch(object):
1365
def _set_tags_bytes(self, bytes):
1366
self.calls.append(('set_tags_bytes', bytes))
1367
real_branch = StubRealBranch()
1368
branch._real_branch = real_branch
1369
branch._set_tags_bytes(b'tags bytes')
1370
# Call a second time, to exercise the 'remote version already inferred'
1372
branch._set_tags_bytes(b'tags bytes')
1373
self.assertFinished(client)
1375
[('set_tags_bytes', b'tags bytes')] * 2, real_branch.calls)
1378
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1380
def test_uses_last_revision_info_and_tags_by_default(self):
1381
transport = MemoryTransport()
1382
client = FakeClient(transport.base)
1383
client.add_expected_call(
1384
b'Branch.get_stacked_on_url', (b'quack/',),
1385
b'error', (b'NotStacked',))
1386
client.add_expected_call(
1387
b'Branch.last_revision_info', (b'quack/',),
1388
b'success', (b'ok', b'1', b'rev-tip'))
1389
client.add_expected_call(
1390
b'Branch.get_config_file', (b'quack/',),
1391
b'success', (b'ok',), '')
1392
transport.mkdir('quack')
1393
transport = transport.clone('quack')
1394
branch = self.make_remote_branch(transport, client)
1395
result = branch.heads_to_fetch()
1396
self.assertFinished(client)
1397
self.assertEqual(({b'rev-tip'}, set()), result)
1399
def test_uses_last_revision_info_and_tags_when_set(self):
1400
transport = MemoryTransport()
1401
client = FakeClient(transport.base)
1402
client.add_expected_call(
1403
b'Branch.get_stacked_on_url', (b'quack/',),
1404
b'error', (b'NotStacked',))
1405
client.add_expected_call(
1406
b'Branch.last_revision_info', (b'quack/',),
1407
b'success', (b'ok', b'1', b'rev-tip'))
1408
client.add_expected_call(
1409
b'Branch.get_config_file', (b'quack/',),
1410
b'success', (b'ok',), b'branch.fetch_tags = True')
1411
# XXX: this will break if the default format's serialization of tags
1412
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1413
client.add_expected_call(
1414
b'Branch.get_tags_bytes', (b'quack/',),
1415
b'success', (b'd5:tag-17:rev-foo5:tag-27:rev-bare',))
1416
transport.mkdir('quack')
1417
transport = transport.clone('quack')
1418
branch = self.make_remote_branch(transport, client)
1419
result = branch.heads_to_fetch()
1420
self.assertFinished(client)
1422
({b'rev-tip'}, {b'rev-foo', b'rev-bar'}), result)
1424
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1425
transport = MemoryTransport()
1426
client = FakeClient(transport.base)
1427
client.add_expected_call(
1428
b'Branch.get_stacked_on_url', (b'quack/',),
1429
b'error', (b'NotStacked',))
1430
client.add_expected_call(
1431
b'Branch.heads_to_fetch', (b'quack/',),
1432
b'success', ([b'tip'], [b'tagged-1', b'tagged-2']))
1433
transport.mkdir('quack')
1434
transport = transport.clone('quack')
1435
branch = self.make_remote_branch(transport, client)
1436
branch._format._use_default_local_heads_to_fetch = lambda: False
1437
result = branch.heads_to_fetch()
1438
self.assertFinished(client)
1439
self.assertEqual(({b'tip'}, {b'tagged-1', b'tagged-2'}), result)
1441
def make_branch_with_tags(self):
1442
self.setup_smart_server_with_call_log()
1443
# Make a branch with a single revision.
1444
builder = self.make_branch_builder('foo')
1445
builder.start_series()
1446
builder.build_snapshot(None, [
1447
('add', ('', b'root-id', 'directory', ''))],
1449
builder.finish_series()
1450
branch = builder.get_branch()
1451
# Add two tags to that branch
1452
branch.tags.set_tag('tag-1', b'rev-1')
1453
branch.tags.set_tag('tag-2', b'rev-2')
1456
def test_backwards_compatible(self):
1457
br = self.make_branch_with_tags()
1458
br.get_config_stack().set('branch.fetch_tags', True)
1459
self.addCleanup(br.lock_read().unlock)
1460
# Disable the heads_to_fetch verb
1461
verb = b'Branch.heads_to_fetch'
1462
self.disable_verb(verb)
1463
self.reset_smart_call_log()
1464
result = br.heads_to_fetch()
1465
self.assertEqual(({b'tip'}, {b'rev-1', b'rev-2'}), result)
1467
[b'Branch.last_revision_info', b'Branch.get_tags_bytes'],
1468
[call.call.method for call in self.hpss_calls])
1470
def test_backwards_compatible_no_tags(self):
1471
br = self.make_branch_with_tags()
1472
br.get_config_stack().set('branch.fetch_tags', False)
1473
self.addCleanup(br.lock_read().unlock)
1474
# Disable the heads_to_fetch verb
1475
verb = b'Branch.heads_to_fetch'
1476
self.disable_verb(verb)
1477
self.reset_smart_call_log()
1478
result = br.heads_to_fetch()
1479
self.assertEqual(({b'tip'}, set()), result)
1481
[b'Branch.last_revision_info'],
1482
[call.call.method for call in self.hpss_calls])
1485
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1487
def test_empty_branch(self):
1488
# in an empty branch we decode the response properly
1489
transport = MemoryTransport()
1490
client = FakeClient(transport.base)
1491
client.add_expected_call(
1492
b'Branch.get_stacked_on_url', (b'quack/',),
1493
b'error', (b'NotStacked',))
1494
client.add_expected_call(
1495
b'Branch.last_revision_info', (b'quack/',),
1496
b'success', (b'ok', b'0', b'null:'))
1497
transport.mkdir('quack')
1498
transport = transport.clone('quack')
1499
branch = self.make_remote_branch(transport, client)
1500
result = branch.last_revision_info()
1501
self.assertFinished(client)
1502
self.assertEqual((0, NULL_REVISION), result)
1504
def test_non_empty_branch(self):
1505
# in a non-empty branch we also decode the response properly
1506
revid = u'\xc8'.encode('utf8')
1507
transport = MemoryTransport()
1508
client = FakeClient(transport.base)
1509
client.add_expected_call(
1510
b'Branch.get_stacked_on_url', (b'kwaak/',),
1511
b'error', (b'NotStacked',))
1512
client.add_expected_call(
1513
b'Branch.last_revision_info', (b'kwaak/',),
1514
b'success', (b'ok', b'2', revid))
1515
transport.mkdir('kwaak')
1516
transport = transport.clone('kwaak')
1517
branch = self.make_remote_branch(transport, client)
1518
result = branch.last_revision_info()
1519
self.assertEqual((2, revid), result)
1522
class TestBranch_get_stacked_on_url(TestRemote):
1523
"""Test Branch._get_stacked_on_url rpc"""
1525
def test_get_stacked_on_invalid_url(self):
1526
# test that asking for a stacked on url the server can't access works.
1527
# This isn't perfect, but then as we're in the same process there
1528
# really isn't anything we can do to be 100% sure that the server
1529
# doesn't just open in - this test probably needs to be rewritten using
1530
# a spawn()ed server.
1531
stacked_branch = self.make_branch('stacked', format='1.9')
1532
memory_branch = self.make_branch('base', format='1.9')
1533
vfs_url = self.get_vfs_only_url('base')
1534
stacked_branch.set_stacked_on_url(vfs_url)
1535
transport = stacked_branch.controldir.root_transport
1536
client = FakeClient(transport.base)
1537
client.add_expected_call(
1538
b'Branch.get_stacked_on_url', (b'stacked/',),
1539
b'success', (b'ok', vfs_url))
1540
# XXX: Multiple calls are bad, this second call documents what is
1542
client.add_expected_call(
1543
b'Branch.get_stacked_on_url', (b'stacked/',),
1544
b'success', (b'ok', vfs_url))
1545
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1547
repo_fmt = remote.RemoteRepositoryFormat()
1548
repo_fmt._custom_format = stacked_branch.repository._format
1549
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1551
result = branch.get_stacked_on_url()
1552
self.assertEqual(vfs_url, result)
1554
def test_backwards_compatible(self):
1555
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1556
base_branch = self.make_branch('base', format='1.6')
1557
stacked_branch = self.make_branch('stacked', format='1.6')
1558
stacked_branch.set_stacked_on_url('../base')
1559
client = FakeClient(self.get_url())
1560
branch_network_name = self.get_branch_format().network_name()
1561
client.add_expected_call(
1562
b'BzrDir.open_branchV3', (b'stacked/',),
1563
b'success', (b'branch', branch_network_name))
1564
client.add_expected_call(
1565
b'BzrDir.find_repositoryV3', (b'stacked/',),
1566
b'success', (b'ok', b'', b'no', b'no', b'yes',
1567
stacked_branch.repository._format.network_name()))
1568
# called twice, once from constructor and then again by us
1569
client.add_expected_call(
1570
b'Branch.get_stacked_on_url', (b'stacked/',),
1571
b'unknown', (b'Branch.get_stacked_on_url',))
1572
client.add_expected_call(
1573
b'Branch.get_stacked_on_url', (b'stacked/',),
1574
b'unknown', (b'Branch.get_stacked_on_url',))
1575
# this will also do vfs access, but that goes direct to the transport
1576
# and isn't seen by the FakeClient.
1577
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1578
RemoteBzrDirFormat(), _client=client)
1579
branch = bzrdir.open_branch()
1580
result = branch.get_stacked_on_url()
1581
self.assertEqual('../base', result)
1582
self.assertFinished(client)
1583
# it's in the fallback list both for the RemoteRepository and its vfs
1585
self.assertEqual(1, len(branch.repository._fallback_repositories))
1587
len(branch.repository._real_repository._fallback_repositories))
1589
def test_get_stacked_on_real_branch(self):
1590
base_branch = self.make_branch('base')
1591
stacked_branch = self.make_branch('stacked')
1592
stacked_branch.set_stacked_on_url('../base')
1593
reference_format = self.get_repo_format()
1594
network_name = reference_format.network_name()
1595
client = FakeClient(self.get_url())
1596
branch_network_name = self.get_branch_format().network_name()
1597
client.add_expected_call(
1598
b'BzrDir.open_branchV3', ('stacked/',),
1599
b'success', ('branch', branch_network_name))
1600
client.add_expected_call(
1601
b'BzrDir.find_repositoryV3', (b'stacked/',),
1602
b'success', (b'ok', b'', b'yes', b'no', b'yes', network_name))
1603
# called twice, once from constructor and then again by us
1604
client.add_expected_call(
1605
b'Branch.get_stacked_on_url', (b'stacked/',),
1606
b'success', (b'ok', b'../base'))
1607
client.add_expected_call(
1608
b'Branch.get_stacked_on_url', (b'stacked/',),
1609
b'success', (b'ok', b'../base'))
1610
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1611
RemoteBzrDirFormat(), _client=client)
1612
branch = bzrdir.open_branch()
1613
result = branch.get_stacked_on_url()
1614
self.assertEqual('../base', result)
1615
self.assertFinished(client)
1616
# it's in the fallback list both for the RemoteRepository.
1617
self.assertEqual(1, len(branch.repository._fallback_repositories))
1618
# And we haven't had to construct a real repository.
1619
self.assertEqual(None, branch.repository._real_repository)
1622
class TestBranchSetLastRevision(RemoteBranchTestCase):
1624
def test_set_empty(self):
1625
# _set_last_revision_info('null:') is translated to calling
1626
# Branch.set_last_revision(path, '') on the wire.
1627
transport = MemoryTransport()
1628
transport.mkdir('branch')
1629
transport = transport.clone('branch')
1631
client = FakeClient(transport.base)
1632
client.add_expected_call(
1633
b'Branch.get_stacked_on_url', (b'branch/',),
1634
b'error', (b'NotStacked',))
1635
client.add_expected_call(
1636
b'Branch.lock_write', (b'branch/', b'', b''),
1637
b'success', (b'ok', b'branch token', b'repo token'))
1638
client.add_expected_call(
1639
b'Branch.last_revision_info',
1641
b'success', (b'ok', b'0', b'null:'))
1642
client.add_expected_call(
1643
b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'null:',),
1644
b'success', (b'ok',))
1645
client.add_expected_call(
1646
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1647
b'success', (b'ok',))
1648
branch = self.make_remote_branch(transport, client)
1650
result = branch._set_last_revision(NULL_REVISION)
1652
self.assertEqual(None, result)
1653
self.assertFinished(client)
1655
def test_set_nonempty(self):
1656
# set_last_revision_info(N, rev-idN) is translated to calling
1657
# Branch.set_last_revision(path, rev-idN) on the wire.
1658
transport = MemoryTransport()
1659
transport.mkdir('branch')
1660
transport = transport.clone('branch')
1662
client = FakeClient(transport.base)
1663
client.add_expected_call(
1664
b'Branch.get_stacked_on_url', (b'branch/',),
1665
b'error', (b'NotStacked',))
1666
client.add_expected_call(
1667
b'Branch.lock_write', (b'branch/', b'', b''),
1668
b'success', (b'ok', b'branch token', b'repo token'))
1669
client.add_expected_call(
1670
b'Branch.last_revision_info',
1672
b'success', (b'ok', b'0', b'null:'))
1673
lines = [b'rev-id2']
1674
encoded_body = bz2.compress(b'\n'.join(lines))
1675
client.add_success_response_with_body(encoded_body, b'ok')
1676
client.add_expected_call(
1677
b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'rev-id2',),
1678
b'success', (b'ok',))
1679
client.add_expected_call(
1680
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1681
b'success', (b'ok',))
1682
branch = self.make_remote_branch(transport, client)
1683
# Lock the branch, reset the record of remote calls.
1685
result = branch._set_last_revision(b'rev-id2')
1687
self.assertEqual(None, result)
1688
self.assertFinished(client)
1690
def test_no_such_revision(self):
1691
transport = MemoryTransport()
1692
transport.mkdir('branch')
1693
transport = transport.clone('branch')
1694
# A response of 'NoSuchRevision' is translated into an exception.
1695
client = FakeClient(transport.base)
1696
client.add_expected_call(
1697
b'Branch.get_stacked_on_url', (b'branch/',),
1698
b'error', (b'NotStacked',))
1699
client.add_expected_call(
1700
b'Branch.lock_write', (b'branch/', b'', b''),
1701
b'success', (b'ok', b'branch token', b'repo token'))
1702
client.add_expected_call(
1703
b'Branch.last_revision_info',
1705
b'success', (b'ok', b'0', b'null:'))
1706
# get_graph calls to construct the revision history, for the set_rh
1709
encoded_body = bz2.compress(b'\n'.join(lines))
1710
client.add_success_response_with_body(encoded_body, b'ok')
1711
client.add_expected_call(
1712
b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'rev-id',),
1713
b'error', (b'NoSuchRevision', b'rev-id'))
1714
client.add_expected_call(
1715
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1716
b'success', (b'ok',))
1718
branch = self.make_remote_branch(transport, client)
1721
errors.NoSuchRevision, branch._set_last_revision, b'rev-id')
1723
self.assertFinished(client)
1725
def test_tip_change_rejected(self):
1726
"""TipChangeRejected responses cause a TipChangeRejected exception to
1729
transport = MemoryTransport()
1730
transport.mkdir('branch')
1731
transport = transport.clone('branch')
1732
client = FakeClient(transport.base)
1733
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1734
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1735
client.add_expected_call(
1736
b'Branch.get_stacked_on_url', (b'branch/',),
1737
b'error', (b'NotStacked',))
1738
client.add_expected_call(
1739
b'Branch.lock_write', (b'branch/', b'', b''),
1740
b'success', (b'ok', b'branch token', b'repo token'))
1741
client.add_expected_call(
1742
b'Branch.last_revision_info',
1744
b'success', (b'ok', b'0', b'null:'))
1746
encoded_body = bz2.compress(b'\n'.join(lines))
1747
client.add_success_response_with_body(encoded_body, b'ok')
1748
client.add_expected_call(
1749
b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'rev-id',),
1750
b'error', (b'TipChangeRejected', rejection_msg_utf8))
1751
client.add_expected_call(
1752
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1753
b'success', (b'ok',))
1754
branch = self.make_remote_branch(transport, client)
1756
# The 'TipChangeRejected' error response triggered by calling
1757
# set_last_revision_info causes a TipChangeRejected exception.
1758
err = self.assertRaises(
1759
errors.TipChangeRejected,
1760
branch._set_last_revision, b'rev-id')
1761
# The UTF-8 message from the response has been decoded into a unicode
1763
self.assertIsInstance(err.msg, text_type)
1764
self.assertEqual(rejection_msg_unicode, err.msg)
1766
self.assertFinished(client)
1769
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1771
def test_set_last_revision_info(self):
1772
# set_last_revision_info(num, 'rev-id') is translated to calling
1773
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1774
transport = MemoryTransport()
1775
transport.mkdir('branch')
1776
transport = transport.clone('branch')
1777
client = FakeClient(transport.base)
1778
# get_stacked_on_url
1779
client.add_error_response(b'NotStacked')
1781
client.add_success_response(b'ok', b'branch token', b'repo token')
1782
# query the current revision
1783
client.add_success_response(b'ok', b'0', b'null:')
1785
client.add_success_response(b'ok')
1787
client.add_success_response(b'ok')
1789
branch = self.make_remote_branch(transport, client)
1790
# Lock the branch, reset the record of remote calls.
1793
result = branch.set_last_revision_info(1234, b'a-revision-id')
1795
[('call', b'Branch.last_revision_info', (b'branch/',)),
1796
('call', b'Branch.set_last_revision_info',
1797
(b'branch/', b'branch token', b'repo token',
1798
b'1234', b'a-revision-id'))],
1800
self.assertEqual(None, result)
1802
def test_no_such_revision(self):
1803
# A response of 'NoSuchRevision' is translated into an exception.
1804
transport = MemoryTransport()
1805
transport.mkdir('branch')
1806
transport = transport.clone('branch')
1807
client = FakeClient(transport.base)
1808
# get_stacked_on_url
1809
client.add_error_response(b'NotStacked')
1811
client.add_success_response(b'ok', b'branch token', b'repo token')
1813
client.add_error_response(b'NoSuchRevision', b'revid')
1815
client.add_success_response(b'ok')
1817
branch = self.make_remote_branch(transport, client)
1818
# Lock the branch, reset the record of remote calls.
1823
errors.NoSuchRevision, branch.set_last_revision_info, 123, b'revid')
1826
def test_backwards_compatibility(self):
1827
"""If the server does not support the Branch.set_last_revision_info
1828
verb (which is new in 1.4), then the client falls back to VFS methods.
1830
# This test is a little messy. Unlike most tests in this file, it
1831
# doesn't purely test what a Remote* object sends over the wire, and
1832
# how it reacts to responses from the wire. It instead relies partly
1833
# on asserting that the RemoteBranch will call
1834
# self._real_branch.set_last_revision_info(...).
1836
# First, set up our RemoteBranch with a FakeClient that raises
1837
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1838
transport = MemoryTransport()
1839
transport.mkdir('branch')
1840
transport = transport.clone('branch')
1841
client = FakeClient(transport.base)
1842
client.add_expected_call(
1843
b'Branch.get_stacked_on_url', (b'branch/',),
1844
b'error', (b'NotStacked',))
1845
client.add_expected_call(
1846
b'Branch.last_revision_info',
1848
b'success', (b'ok', b'0', b'null:'))
1849
client.add_expected_call(
1850
b'Branch.set_last_revision_info',
1851
(b'branch/', b'branch token', b'repo token', b'1234', b'a-revision-id',),
1852
b'unknown', b'Branch.set_last_revision_info')
1854
branch = self.make_remote_branch(transport, client)
1855
class StubRealBranch(object):
1858
def set_last_revision_info(self, revno, revision_id):
1860
('set_last_revision_info', revno, revision_id))
1861
def _clear_cached_state(self):
1863
real_branch = StubRealBranch()
1864
branch._real_branch = real_branch
1865
self.lock_remote_branch(branch)
1867
# Call set_last_revision_info, and verify it behaved as expected.
1868
result = branch.set_last_revision_info(1234, b'a-revision-id')
1870
[('set_last_revision_info', 1234, b'a-revision-id')],
1872
self.assertFinished(client)
1874
def test_unexpected_error(self):
1875
# If the server sends an error the client doesn't understand, it gets
1876
# turned into an UnknownErrorFromSmartServer, which is presented as a
1877
# non-internal error to the user.
1878
transport = MemoryTransport()
1879
transport.mkdir('branch')
1880
transport = transport.clone('branch')
1881
client = FakeClient(transport.base)
1882
# get_stacked_on_url
1883
client.add_error_response(b'NotStacked')
1885
client.add_success_response(b'ok', b'branch token', b'repo token')
1887
client.add_error_response(b'UnexpectedError')
1889
client.add_success_response(b'ok')
1891
branch = self.make_remote_branch(transport, client)
1892
# Lock the branch, reset the record of remote calls.
1896
err = self.assertRaises(
1897
errors.UnknownErrorFromSmartServer,
1898
branch.set_last_revision_info, 123, b'revid')
1899
self.assertEqual((b'UnexpectedError',), err.error_tuple)
1902
def test_tip_change_rejected(self):
1903
"""TipChangeRejected responses cause a TipChangeRejected exception to
1906
transport = MemoryTransport()
1907
transport.mkdir('branch')
1908
transport = transport.clone('branch')
1909
client = FakeClient(transport.base)
1910
# get_stacked_on_url
1911
client.add_error_response(b'NotStacked')
1913
client.add_success_response(b'ok', b'branch token', b'repo token')
1915
client.add_error_response(b'TipChangeRejected', b'rejection message')
1917
client.add_success_response(b'ok')
1919
branch = self.make_remote_branch(transport, client)
1920
# Lock the branch, reset the record of remote calls.
1922
self.addCleanup(branch.unlock)
1925
# The 'TipChangeRejected' error response triggered by calling
1926
# set_last_revision_info causes a TipChangeRejected exception.
1927
err = self.assertRaises(
1928
errors.TipChangeRejected,
1929
branch.set_last_revision_info, 123, b'revid')
1930
self.assertEqual('rejection message', err.msg)
1933
class TestBranchGetSetConfig(RemoteBranchTestCase):
1935
def test_get_branch_conf(self):
1936
# in an empty branch we decode the response properly
1937
client = FakeClient()
1938
client.add_expected_call(
1939
b'Branch.get_stacked_on_url', (b'memory:///',),
1940
b'error', (b'NotStacked',),)
1941
client.add_success_response_with_body(b'# config file body', b'ok')
1942
transport = MemoryTransport()
1943
branch = self.make_remote_branch(transport, client)
1944
config = branch.get_config()
1945
config.has_explicit_nickname()
1947
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
1948
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
1951
def test_get_multi_line_branch_conf(self):
1952
# Make sure that multiple-line branch.conf files are supported
1954
# https://bugs.launchpad.net/bzr/+bug/354075
1955
client = FakeClient()
1956
client.add_expected_call(
1957
b'Branch.get_stacked_on_url', (b'memory:///',),
1958
b'error', (b'NotStacked',),)
1959
client.add_success_response_with_body(b'a = 1\nb = 2\nc = 3\n', b'ok')
1960
transport = MemoryTransport()
1961
branch = self.make_remote_branch(transport, client)
1962
config = branch.get_config()
1963
self.assertEqual(u'2', config.get_user_option('b'))
1965
def test_set_option(self):
1966
client = FakeClient()
1967
client.add_expected_call(
1968
b'Branch.get_stacked_on_url', (b'memory:///',),
1969
b'error', (b'NotStacked',),)
1970
client.add_expected_call(
1971
b'Branch.lock_write', (b'memory:///', b'', b''),
1972
b'success', (b'ok', b'branch token', b'repo token'))
1973
client.add_expected_call(
1974
b'Branch.set_config_option', (b'memory:///', b'branch token',
1975
b'repo token', b'foo', b'bar', b''),
1977
client.add_expected_call(
1978
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
1979
b'success', (b'ok',))
1980
transport = MemoryTransport()
1981
branch = self.make_remote_branch(transport, client)
1983
config = branch._get_config()
1984
config.set_option('foo', 'bar')
1986
self.assertFinished(client)
1988
def test_set_option_with_dict(self):
1989
client = FakeClient()
1990
client.add_expected_call(
1991
b'Branch.get_stacked_on_url', (b'memory:///',),
1992
b'error', (b'NotStacked',),)
1993
client.add_expected_call(
1994
b'Branch.lock_write', (b'memory:///', b'', b''),
1995
b'success', (b'ok', b'branch token', b'repo token'))
1996
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1997
client.add_expected_call(
1998
b'Branch.set_config_option_dict', (b'memory:///', b'branch token',
1999
b'repo token', encoded_dict_value, b'foo', b''),
2001
client.add_expected_call(
2002
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2003
b'success', (b'ok',))
2004
transport = MemoryTransport()
2005
branch = self.make_remote_branch(transport, client)
2007
config = branch._get_config()
2009
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2012
self.assertFinished(client)
2014
def test_backwards_compat_set_option(self):
2015
self.setup_smart_server_with_call_log()
2016
branch = self.make_branch('.')
2017
verb = b'Branch.set_config_option'
2018
self.disable_verb(verb)
2020
self.addCleanup(branch.unlock)
2021
self.reset_smart_call_log()
2022
branch._get_config().set_option('value', 'name')
2023
self.assertLength(11, self.hpss_calls)
2024
self.assertEqual('value', branch._get_config().get_option('name'))
2026
def test_backwards_compat_set_option_with_dict(self):
2027
self.setup_smart_server_with_call_log()
2028
branch = self.make_branch('.')
2029
verb = b'Branch.set_config_option_dict'
2030
self.disable_verb(verb)
2032
self.addCleanup(branch.unlock)
2033
self.reset_smart_call_log()
2034
config = branch._get_config()
2035
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2036
config.set_option(value_dict, 'name')
2037
self.assertLength(11, self.hpss_calls)
2038
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2041
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2043
def test_get_branch_conf(self):
2044
# in an empty branch we decode the response properly
2045
client = FakeClient()
2046
client.add_expected_call(
2047
b'Branch.get_stacked_on_url', (b'memory:///',),
2048
b'error', (b'NotStacked',),)
2049
client.add_success_response_with_body(b'# config file body', b'ok')
2050
transport = MemoryTransport()
2051
branch = self.make_remote_branch(transport, client)
2052
config = branch.get_config_stack()
2054
config.get("log_format")
2056
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2057
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
2060
def test_set_branch_conf(self):
2061
client = FakeClient()
2062
client.add_expected_call(
2063
b'Branch.get_stacked_on_url', (b'memory:///',),
2064
b'error', (b'NotStacked',),)
2065
client.add_expected_call(
2066
b'Branch.lock_write', (b'memory:///', b'', b''),
2067
b'success', (b'ok', b'branch token', b'repo token'))
2068
client.add_expected_call(
2069
b'Branch.get_config_file', (b'memory:///', ),
2070
b'success', (b'ok', ), b"# line 1\n")
2071
client.add_expected_call(
2072
b'Branch.get_config_file', (b'memory:///', ),
2073
b'success', (b'ok', ), b"# line 1\n")
2074
client.add_expected_call(
2075
b'Branch.put_config_file', (b'memory:///', b'branch token',
2077
b'success', (b'ok',))
2078
client.add_expected_call(
2079
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2080
b'success', (b'ok',))
2081
transport = MemoryTransport()
2082
branch = self.make_remote_branch(transport, client)
2084
config = branch.get_config_stack()
2085
config.set('email', 'The Dude <lebowski@example.com>')
2087
self.assertFinished(client)
2089
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2090
('call', b'Branch.lock_write', (b'memory:///', b'', b'')),
2091
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',)),
2092
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',)),
2093
('call_with_body_bytes_expecting_body', b'Branch.put_config_file',
2094
(b'memory:///', b'branch token', b'repo token'),
2095
b'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2096
('call', b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'))],
2100
class TestBranchLockWrite(RemoteBranchTestCase):
2102
def test_lock_write_unlockable(self):
2103
transport = MemoryTransport()
2104
client = FakeClient(transport.base)
2105
client.add_expected_call(
2106
b'Branch.get_stacked_on_url', (b'quack/',),
2107
b'error', (b'NotStacked',),)
2108
client.add_expected_call(
2109
b'Branch.lock_write', (b'quack/', b'', b''),
2110
b'error', (b'UnlockableTransport',))
2111
transport.mkdir('quack')
2112
transport = transport.clone('quack')
2113
branch = self.make_remote_branch(transport, client)
2114
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2115
self.assertFinished(client)
2118
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2120
def test_simple(self):
2121
transport = MemoryTransport()
2122
client = FakeClient(transport.base)
2123
client.add_expected_call(
2124
b'Branch.get_stacked_on_url', (b'quack/',),
2125
b'error', (b'NotStacked',),)
2126
client.add_expected_call(
2127
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2128
b'success', (b'ok', b'0',),)
2129
client.add_expected_call(
2130
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2131
b'error', (b'NoSuchRevision', b'unknown',),)
2132
transport.mkdir('quack')
2133
transport = transport.clone('quack')
2134
branch = self.make_remote_branch(transport, client)
2135
self.assertEqual(0, branch.revision_id_to_revno(b'null:'))
2136
self.assertRaises(errors.NoSuchRevision,
2137
branch.revision_id_to_revno, b'unknown')
2138
self.assertFinished(client)
2140
def test_dotted(self):
2141
transport = MemoryTransport()
2142
client = FakeClient(transport.base)
2143
client.add_expected_call(
2144
b'Branch.get_stacked_on_url', (b'quack/',),
2145
b'error', (b'NotStacked',),)
2146
client.add_expected_call(
2147
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2148
b'success', (b'ok', b'0',),)
2149
client.add_expected_call(
2150
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2151
b'error', (b'NoSuchRevision', b'unknown',),)
2152
transport.mkdir('quack')
2153
transport = transport.clone('quack')
2154
branch = self.make_remote_branch(transport, client)
2155
self.assertEqual((0, ), branch.revision_id_to_dotted_revno(b'null:'))
2156
self.assertRaises(errors.NoSuchRevision,
2157
branch.revision_id_to_dotted_revno, b'unknown')
2158
self.assertFinished(client)
2160
def test_dotted_no_smart_verb(self):
2161
self.setup_smart_server_with_call_log()
2162
branch = self.make_branch('.')
2163
self.disable_verb(b'Branch.revision_id_to_revno')
2164
self.reset_smart_call_log()
2165
self.assertEqual((0, ),
2166
branch.revision_id_to_dotted_revno(b'null:'))
2167
self.assertLength(8, self.hpss_calls)
2170
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2172
def test__get_config(self):
2173
client = FakeClient()
2174
client.add_success_response_with_body(b'default_stack_on = /\n', b'ok')
2175
transport = MemoryTransport()
2176
bzrdir = self.make_remote_bzrdir(transport, client)
2177
config = bzrdir.get_config()
2178
self.assertEqual('/', config.get_default_stack_on())
2180
[('call_expecting_body', b'BzrDir.get_config_file', (b'memory:///',))],
2183
def test_set_option_uses_vfs(self):
2184
self.setup_smart_server_with_call_log()
2185
bzrdir = self.make_controldir('.')
2186
self.reset_smart_call_log()
2187
config = bzrdir.get_config()
2188
config.set_default_stack_on('/')
2189
self.assertLength(4, self.hpss_calls)
2191
def test_backwards_compat_get_option(self):
2192
self.setup_smart_server_with_call_log()
2193
bzrdir = self.make_controldir('.')
2194
verb = b'BzrDir.get_config_file'
2195
self.disable_verb(verb)
2196
self.reset_smart_call_log()
2197
self.assertEqual(None,
2198
bzrdir._get_config().get_option('default_stack_on'))
2199
self.assertLength(4, self.hpss_calls)
2202
class TestTransportIsReadonly(tests.TestCase):
2204
def test_true(self):
2205
client = FakeClient()
2206
client.add_success_response(b'yes')
2207
transport = RemoteTransport('bzr://example.com/', medium=False,
2209
self.assertEqual(True, transport.is_readonly())
2211
[('call', b'Transport.is_readonly', ())],
2214
def test_false(self):
2215
client = FakeClient()
2216
client.add_success_response(b'no')
2217
transport = RemoteTransport('bzr://example.com/', medium=False,
2219
self.assertEqual(False, transport.is_readonly())
2221
[('call', b'Transport.is_readonly', ())],
2224
def test_error_from_old_server(self):
2225
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2227
Clients should treat it as a "no" response, because is_readonly is only
2228
advisory anyway (a transport could be read-write, but then the
2229
underlying filesystem could be readonly anyway).
2231
client = FakeClient()
2232
client.add_unknown_method_response(b'Transport.is_readonly')
2233
transport = RemoteTransport('bzr://example.com/', medium=False,
2235
self.assertEqual(False, transport.is_readonly())
2237
[('call', b'Transport.is_readonly', ())],
2241
class TestTransportMkdir(tests.TestCase):
2243
def test_permissiondenied(self):
2244
client = FakeClient()
2245
client.add_error_response(b'PermissionDenied', b'remote path', b'extra')
2246
transport = RemoteTransport('bzr://example.com/', medium=False,
2248
exc = self.assertRaises(
2249
errors.PermissionDenied, transport.mkdir, 'client path')
2250
expected_error = errors.PermissionDenied('/client path', 'extra')
2251
self.assertEqual(expected_error, exc)
2254
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2256
def test_defaults_to_none(self):
2257
t = RemoteSSHTransport('bzr+ssh://example.com')
2258
self.assertIs(None, t._get_credentials()[0])
2260
def test_uses_authentication_config(self):
2261
conf = config.AuthenticationConfig()
2262
conf._get_config().update(
2263
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2266
t = RemoteSSHTransport('bzr+ssh://example.com')
2267
self.assertEqual('bar', t._get_credentials()[0])
2270
class TestRemoteRepository(TestRemote):
2271
"""Base for testing RemoteRepository protocol usage.
2273
These tests contain frozen requests and responses. We want any changes to
2274
what is sent or expected to be require a thoughtful update to these tests
2275
because they might break compatibility with different-versioned servers.
2278
def setup_fake_client_and_repository(self, transport_path):
2279
"""Create the fake client and repository for testing with.
2281
There's no real server here; we just have canned responses sent
2284
:param transport_path: Path below the root of the MemoryTransport
2285
where the repository will be created.
2287
transport = MemoryTransport()
2288
transport.mkdir(transport_path)
2289
client = FakeClient(transport.base)
2290
transport = transport.clone(transport_path)
2291
# we do not want bzrdir to make any remote calls
2292
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2294
repo = RemoteRepository(bzrdir, None, _client=client)
2298
def remoted_description(format):
2299
return 'Remote: ' + format.get_format_description()
2302
class TestBranchFormat(tests.TestCase):
2304
def test_get_format_description(self):
2305
remote_format = RemoteBranchFormat()
2306
real_format = branch.format_registry.get_default()
2307
remote_format._network_name = real_format.network_name()
2308
self.assertEqual(remoted_description(real_format),
2309
remote_format.get_format_description())
2312
class TestRepositoryFormat(TestRemoteRepository):
2314
def test_fast_delta(self):
2315
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2316
true_format = RemoteRepositoryFormat()
2317
true_format._network_name = true_name
2318
self.assertEqual(True, true_format.fast_deltas)
2319
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2320
false_format = RemoteRepositoryFormat()
2321
false_format._network_name = false_name
2322
self.assertEqual(False, false_format.fast_deltas)
2324
def test_get_format_description(self):
2325
remote_repo_format = RemoteRepositoryFormat()
2326
real_format = repository.format_registry.get_default()
2327
remote_repo_format._network_name = real_format.network_name()
2328
self.assertEqual(remoted_description(real_format),
2329
remote_repo_format.get_format_description())
2332
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2334
def test_empty(self):
2335
transport_path = 'quack'
2336
repo, client = self.setup_fake_client_and_repository(transport_path)
2337
client.add_success_response_with_body(b'', b'ok')
2338
self.assertEqual([], repo.all_revision_ids())
2340
[('call_expecting_body', b'Repository.all_revision_ids',
2344
def test_with_some_content(self):
2345
transport_path = 'quack'
2346
repo, client = self.setup_fake_client_and_repository(transport_path)
2347
client.add_success_response_with_body(
2348
b'rev1\nrev2\nanotherrev\n', b'ok')
2349
self.assertEqual([b"rev1", b"rev2", b"anotherrev"],
2350
repo.all_revision_ids())
2352
[('call_expecting_body', b'Repository.all_revision_ids',
2357
class TestRepositoryGatherStats(TestRemoteRepository):
2359
def test_revid_none(self):
2360
# ('ok',), body with revisions and size
2361
transport_path = 'quack'
2362
repo, client = self.setup_fake_client_and_repository(transport_path)
2363
client.add_success_response_with_body(
2364
b'revisions: 2\nsize: 18\n', b'ok')
2365
result = repo.gather_stats(None)
2367
[('call_expecting_body', b'Repository.gather_stats',
2368
(b'quack/', b'', b'no'))],
2370
self.assertEqual({'revisions': 2, 'size': 18}, result)
2372
def test_revid_no_committers(self):
2373
# ('ok',), body without committers
2374
body = (b'firstrev: 123456.300 3600\n'
2375
b'latestrev: 654231.400 0\n'
2378
transport_path = 'quick'
2379
revid = u'\xc8'.encode('utf8')
2380
repo, client = self.setup_fake_client_and_repository(transport_path)
2381
client.add_success_response_with_body(body, b'ok')
2382
result = repo.gather_stats(revid)
2384
[('call_expecting_body', b'Repository.gather_stats',
2385
(b'quick/', revid, b'no'))],
2387
self.assertEqual({'revisions': 2, 'size': 18,
2388
'firstrev': (123456.300, 3600),
2389
'latestrev': (654231.400, 0),},
2392
def test_revid_with_committers(self):
2393
# ('ok',), body with committers
2394
body = (b'committers: 128\n'
2395
b'firstrev: 123456.300 3600\n'
2396
b'latestrev: 654231.400 0\n'
2399
transport_path = 'buick'
2400
revid = u'\xc8'.encode('utf8')
2401
repo, client = self.setup_fake_client_and_repository(transport_path)
2402
client.add_success_response_with_body(body, b'ok')
2403
result = repo.gather_stats(revid, True)
2405
[('call_expecting_body', b'Repository.gather_stats',
2406
(b'buick/', revid, b'yes'))],
2408
self.assertEqual({'revisions': 2, 'size': 18,
2410
'firstrev': (123456.300, 3600),
2411
'latestrev': (654231.400, 0),},
2415
class TestRepositoryBreakLock(TestRemoteRepository):
2417
def test_break_lock(self):
2418
transport_path = 'quack'
2419
repo, client = self.setup_fake_client_and_repository(transport_path)
2420
client.add_success_response(b'ok')
2423
[('call', b'Repository.break_lock', (b'quack/',))],
2427
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2429
def test_get_serializer_format(self):
2430
transport_path = 'hill'
2431
repo, client = self.setup_fake_client_and_repository(transport_path)
2432
client.add_success_response(b'ok', b'7')
2433
self.assertEqual(b'7', repo.get_serializer_format())
2435
[('call', b'VersionedFileRepository.get_serializer_format',
2440
class TestRepositoryReconcile(TestRemoteRepository):
2442
def test_reconcile(self):
2443
transport_path = 'hill'
2444
repo, client = self.setup_fake_client_and_repository(transport_path)
2445
body = (b"garbage_inventories: 2\n"
2446
b"inconsistent_parents: 3\n")
2447
client.add_expected_call(
2448
b'Repository.lock_write', (b'hill/', b''),
2449
b'success', (b'ok', b'a token'))
2450
client.add_success_response_with_body(body, b'ok')
2451
reconciler = repo.reconcile()
2453
[('call', b'Repository.lock_write', (b'hill/', b'')),
2454
('call_expecting_body', b'Repository.reconcile',
2455
(b'hill/', b'a token'))],
2457
self.assertEqual(2, reconciler.garbage_inventories)
2458
self.assertEqual(3, reconciler.inconsistent_parents)
2461
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2463
def test_text(self):
2464
# ('ok',), body with signature text
2465
transport_path = 'quack'
2466
repo, client = self.setup_fake_client_and_repository(transport_path)
2467
client.add_success_response_with_body(
2469
self.assertEqual(b"THETEXT", repo.get_signature_text(b"revid"))
2471
[('call_expecting_body', b'Repository.get_revision_signature_text',
2472
(b'quack/', b'revid'))],
2475
def test_no_signature(self):
2476
transport_path = 'quick'
2477
repo, client = self.setup_fake_client_and_repository(transport_path)
2478
client.add_error_response(b'nosuchrevision', b'unknown')
2479
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2482
[('call_expecting_body', b'Repository.get_revision_signature_text',
2483
(b'quick/', b'unknown'))],
2487
class TestRepositoryGetGraph(TestRemoteRepository):
2489
def test_get_graph(self):
2490
# get_graph returns a graph with a custom parents provider.
2491
transport_path = 'quack'
2492
repo, client = self.setup_fake_client_and_repository(transport_path)
2493
graph = repo.get_graph()
2494
self.assertNotEqual(graph._parents_provider, repo)
2497
class TestRepositoryAddSignatureText(TestRemoteRepository):
2499
def test_add_signature_text(self):
2500
transport_path = 'quack'
2501
repo, client = self.setup_fake_client_and_repository(transport_path)
2502
client.add_expected_call(
2503
b'Repository.lock_write', (b'quack/', b''),
2504
b'success', (b'ok', b'a token'))
2505
client.add_expected_call(
2506
b'Repository.start_write_group', (b'quack/', b'a token'),
2507
b'success', (b'ok', (b'token1', )))
2508
client.add_expected_call(
2509
b'Repository.add_signature_text', (b'quack/', b'a token', b'rev1',
2511
b'success', (b'ok', ), None)
2513
repo.start_write_group()
2515
repo.add_signature_text(b"rev1", b"every bloody emperor"))
2517
('call_with_body_bytes_expecting_body',
2518
b'Repository.add_signature_text',
2519
(b'quack/', b'a token', b'rev1', b'token1'),
2520
b'every bloody emperor'),
2524
class TestRepositoryGetParentMap(TestRemoteRepository):
2526
def test_get_parent_map_caching(self):
2527
# get_parent_map returns from cache until unlock()
2528
# setup a reponse with two revisions
2529
r1 = u'\u0e33'.encode('utf8')
2530
r2 = u'\u0dab'.encode('utf8')
2531
lines = [b' '.join([r2, r1]), r1]
2532
encoded_body = bz2.compress(b'\n'.join(lines))
2534
transport_path = 'quack'
2535
repo, client = self.setup_fake_client_and_repository(transport_path)
2536
client.add_success_response_with_body(encoded_body, b'ok')
2537
client.add_success_response_with_body(encoded_body, b'ok')
2539
graph = repo.get_graph()
2540
parents = graph.get_parent_map([r2])
2541
self.assertEqual({r2: (r1,)}, parents)
2542
# locking and unlocking deeper should not reset
2545
parents = graph.get_parent_map([r1])
2546
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2548
[('call_with_body_bytes_expecting_body',
2549
b'Repository.get_parent_map', (b'quack/', b'include-missing:', r2),
2553
# now we call again, and it should use the second response.
2555
graph = repo.get_graph()
2556
parents = graph.get_parent_map([r1])
2557
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2559
[('call_with_body_bytes_expecting_body',
2560
b'Repository.get_parent_map', (b'quack/', b'include-missing:', r2),
2562
('call_with_body_bytes_expecting_body',
2563
b'Repository.get_parent_map', (b'quack/', b'include-missing:', r1),
2569
def test_get_parent_map_reconnects_if_unknown_method(self):
2570
transport_path = 'quack'
2571
rev_id = b'revision-id'
2572
repo, client = self.setup_fake_client_and_repository(transport_path)
2573
client.add_unknown_method_response(b'Repository.get_parent_map')
2574
client.add_success_response_with_body(rev_id, b'ok')
2575
self.assertFalse(client._medium._is_remote_before((1, 2)))
2576
parents = repo.get_parent_map([rev_id])
2578
[('call_with_body_bytes_expecting_body',
2579
b'Repository.get_parent_map',
2580
(b'quack/', b'include-missing:', rev_id), b'\n\n0'),
2581
('disconnect medium',),
2582
('call_expecting_body', b'Repository.get_revision_graph',
2585
# The medium is now marked as being connected to an older server
2586
self.assertTrue(client._medium._is_remote_before((1, 2)))
2587
self.assertEqual({rev_id: (b'null:',)}, parents)
2589
def test_get_parent_map_fallback_parentless_node(self):
2590
"""get_parent_map falls back to get_revision_graph on old servers. The
2591
results from get_revision_graph are tweaked to match the get_parent_map
2594
Specifically, a {key: ()} result from get_revision_graph means "no
2595
parents" for that key, which in get_parent_map results should be
2596
represented as {key: ('null:',)}.
2598
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2600
rev_id = b'revision-id'
2601
transport_path = 'quack'
2602
repo, client = self.setup_fake_client_and_repository(transport_path)
2603
client.add_success_response_with_body(rev_id, b'ok')
2604
client._medium._remember_remote_is_before((1, 2))
2605
parents = repo.get_parent_map([rev_id])
2607
[('call_expecting_body', b'Repository.get_revision_graph',
2610
self.assertEqual({rev_id: (b'null:',)}, parents)
2612
def test_get_parent_map_unexpected_response(self):
2613
repo, client = self.setup_fake_client_and_repository('path')
2614
client.add_success_response(b'something unexpected!')
2616
errors.UnexpectedSmartServerResponse,
2617
repo.get_parent_map, [b'a-revision-id'])
2619
def test_get_parent_map_negative_caches_missing_keys(self):
2620
self.setup_smart_server_with_call_log()
2621
repo = self.make_repository('foo')
2622
self.assertIsInstance(repo, RemoteRepository)
2624
self.addCleanup(repo.unlock)
2625
self.reset_smart_call_log()
2626
graph = repo.get_graph()
2627
self.assertEqual({},
2628
graph.get_parent_map([b'some-missing', b'other-missing']))
2629
self.assertLength(1, self.hpss_calls)
2630
# No call if we repeat this
2631
self.reset_smart_call_log()
2632
graph = repo.get_graph()
2633
self.assertEqual({},
2634
graph.get_parent_map([b'some-missing', b'other-missing']))
2635
self.assertLength(0, self.hpss_calls)
2636
# Asking for more unknown keys makes a request.
2637
self.reset_smart_call_log()
2638
graph = repo.get_graph()
2639
self.assertEqual({},
2640
graph.get_parent_map([b'some-missing', b'other-missing',
2642
self.assertLength(1, self.hpss_calls)
2644
def disableExtraResults(self):
2645
self.overrideAttr(SmartServerRepositoryGetParentMap,
2646
'no_extra_results', True)
2648
def test_null_cached_missing_and_stop_key(self):
2649
self.setup_smart_server_with_call_log()
2650
# Make a branch with a single revision.
2651
builder = self.make_branch_builder('foo')
2652
builder.start_series()
2653
builder.build_snapshot(None, [
2654
('add', ('', b'root-id', 'directory', ''))],
2655
revision_id=b'first')
2656
builder.finish_series()
2657
branch = builder.get_branch()
2658
repo = branch.repository
2659
self.assertIsInstance(repo, RemoteRepository)
2660
# Stop the server from sending extra results.
2661
self.disableExtraResults()
2663
self.addCleanup(repo.unlock)
2664
self.reset_smart_call_log()
2665
graph = repo.get_graph()
2666
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2667
# 'first' it will be a candidate for the stop_keys of subsequent
2668
# requests, and because 'null:' was queried but not returned it will be
2669
# cached as missing.
2670
self.assertEqual({b'first': (b'null:',)},
2671
graph.get_parent_map([b'first', b'null:']))
2672
# Now query for another key. This request will pass along a recipe of
2673
# start and stop keys describing the already cached results, and this
2674
# recipe's revision count must be correct (or else it will trigger an
2675
# error from the server).
2676
self.assertEqual({}, graph.get_parent_map([b'another-key']))
2677
# This assertion guards against disableExtraResults silently failing to
2678
# work, thus invalidating the test.
2679
self.assertLength(2, self.hpss_calls)
2681
def test_get_parent_map_gets_ghosts_from_result(self):
2682
# asking for a revision should negatively cache close ghosts in its
2684
self.setup_smart_server_with_call_log()
2685
tree = self.make_branch_and_memory_tree('foo')
2686
with tree.lock_write():
2687
builder = treebuilder.TreeBuilder()
2688
builder.start_tree(tree)
2690
builder.finish_tree()
2691
tree.set_parent_ids([b'non-existant'], allow_leftmost_as_ghost=True)
2692
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([b'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 = [b' '.join([r2, r1]), r1]
2711
encoded_body = bz2.compress(b'\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, b'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
b'Repository.get_parent_map', (b'quack/', b'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([b'rev1']))
2745
tree.commit('message', rev_id=b'rev1')
2746
graph = tree.branch.repository.get_graph()
2747
self.assertEqual({b'rev1': (b'null:',)}, graph.get_parent_map([b'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
[b'somerev1', b'anotherrev2'])
2760
[('call_with_body_bytes_expecting_body', b'Repository.iter_revisions',
2761
(b'quack/', ), b"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(b"somerev1")
2768
somerev1.committer = "Joe Committer <joe@example.com>"
2769
somerev1.timestamp = 1321828927
2770
somerev1.timezone = -60
2771
somerev1.inventory_sha1 = b"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:]], b'ok', b'10')
2779
revs = repo.get_revisions([b'somerev1'])
2780
self.assertEqual(revs, [somerev1])
2782
[('call_with_body_bytes_expecting_body', b'Repository.iter_revisions',
2783
(b'quack/', ), b"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(b'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 = [b' '.join([r2, r1]), r1]
2806
encoded_body = b'\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, b'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', b'Repository.get_revision_graph',
2816
(b'sinhala/', b''))],
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 = [b' '.join([r2, r11, r12]), r11, r12]
2827
encoded_body = b'\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, b'ok')
2832
result = repo._get_revision_graph(r2)
2834
[('call_expecting_body', b'Repository.get_revision_graph',
2835
(b'sinhala/', r2))],
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(b'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', b'Repository.get_revision_graph',
2849
(b'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(b'AnUnexpectedError')
2857
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2858
repo._get_revision_graph, revid)
2859
self.assertEqual((b'AnUnexpectedError',), e.error_tuple)
2862
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2865
repo, client = self.setup_fake_client_and_repository('quack')
2866
client.add_expected_call(
2867
b'Repository.get_rev_id_for_revno', (b'quack/', 5, (42, b'rev-foo')),
2868
b'success', (b'ok', b'rev-five'))
2869
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2870
self.assertEqual((True, b'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
b'Repository.get_rev_id_for_revno', (b'quack/', 5, (42, b'rev-foo')),
2877
b'success', (b'history-incomplete', 10, b'rev-ten'))
2878
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2879
self.assertEqual((False, (10, b'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
(b'yes', b'no', b'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
b'Repository.get_rev_id_for_revno', (b'quack/', 1, (42, b'rev-foo')),
2899
b'success', (b'history-incomplete', 2, b'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
b'Repository.get_rev_id_for_revno', (b'fallback/', 1, (2, b'rev-two')),
2904
b'success', (b'ok', b'rev-one'))
2905
result = repo.get_rev_id_for_revno(1, (42, b'rev-foo'))
2906
self.assertEqual((True, b'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
b'Repository.get_rev_id_for_revno', (b'quack/', 5, (42, b'rev-foo')),
2915
b'error', (b'nosuchrevision', b'rev-foo'))
2917
errors.NoSuchRevision,
2918
repo.get_rev_id_for_revno, 5, (42, b'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 = b'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(b'yes')
2950
result = repo.has_signature_for_revision_id(b'A')
2952
[('call', b'Repository.has_signature_for_revision_id',
2953
(b'quack/', b'A'))],
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(b'no')
2962
result = repo.has_signature_for_revision_id(b'A')
2964
[('call', b'Repository.has_signature_for_revision_id',
2965
(b'qwack/', b'A'))],
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(b'yes')
2976
result = repo.get_physical_lock_status()
2978
[('call', b'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(b'no')
2987
result = repo.get_physical_lock_status()
2989
[('call', b'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(b'yes')
3002
result = repo.is_shared()
3004
[('call', b'Repository.is_shared', (b'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(b'no')
3013
result = repo.is_shared()
3015
[('call', b'Repository.is_shared', (b'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(b'yes')
3027
result = repo.make_working_trees()
3029
[('call', b'Repository.make_working_trees', (b'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(b'no')
3038
result = repo.make_working_trees()
3040
[('call', b'Repository.make_working_trees', (b'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(b'ok', b'a token')
3051
token = repo.lock_write().repository_token
3053
[('call', b'Repository.lock_write', (b'quack/', b''))],
3055
self.assertEqual(b'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(b'LockContention')
3061
self.assertRaises(errors.LockContention, repo.lock_write)
3063
[('call', b'Repository.lock_write', (b'quack/', b''))],
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(b'UnlockableTransport')
3070
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3072
[('call', b'Repository.lock_write', (b'quack/', b''))],
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
b'Repository.lock_write', (b'quack/', b''),
3083
b'success', (b'ok', b'a token'))
3084
client.add_expected_call(
3085
b'Repository.start_write_group', (b'quack/', b'a token'),
3086
b'success', (b'ok', (b'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
b'Repository.lock_write', (b'quack/', b''),
3101
b'success', (b'ok', b'a token'))
3102
client.add_expected_call(
3103
b'Repository.start_write_group', (b'quack/', b'a token'),
3104
b'error', (b'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
b'Repository.lock_write', (b'quack/', b''),
3116
b'success', (b'ok', b'a token'))
3117
client.add_expected_call(
3118
b'Repository.start_write_group', (b'quack/', b'a token'),
3119
b'success', (b'ok', [b'token1']))
3120
client.add_expected_call(
3121
b'Repository.commit_write_group', (b'quack/', b'a token', [b'token1']),
3122
b'success', (b'ok',))
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
b'Repository.lock_write', (b'quack/', b''),
3132
b'success', (b'ok', b'a token'))
3133
client.add_expected_call(
3134
b'Repository.start_write_group', (b'quack/', b'a token'),
3135
b'success', (b'ok', [b'token1']))
3136
client.add_expected_call(
3137
b'Repository.abort_write_group', (b'quack/', b'a token', [b'token1']),
3138
b'success', (b'ok',))
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
b'Repository.lock_write', (b'quack/', b''),
3153
b'success', (b'ok', b'a token'))
3154
client.add_expected_call(
3155
b'Repository.check_write_group', (b'quack/', b'a token', [b'token1']),
3156
b'success', (b'ok',))
3158
repo.resume_write_group([b'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 = b'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
b'Repository.set_make_working_trees', (b'quack/', b'True'),
3179
b'success', (b'ok',))
3180
client.add_expected_call(
3181
b'Repository.set_make_working_trees', (b'quack/', b'False'),
3182
b'success', (b'ok',))
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(b'ok', b'a token')
3193
client.add_success_response(b'ok')
3197
[('call', b'Repository.lock_write', (b'quack/', b'')),
3198
('call', b'Repository.unlock', (b'quack/', b'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(b'ok', b'a token')
3206
client.add_error_response(b'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
b'Repository.iter_files_bytes', (b'quack/', ),
3233
b'success', (b'ok',), iter([b"ok\x000", b"\n", zlib.compress(b"mydata" * 10)]))
3234
for (identifier, byte_stream) in repo.iter_files_bytes([(b"somefile",
3235
b"somerev", b"myid")]):
3236
self.assertEqual(b"myid", identifier)
3237
self.assertEqual(b"".join(byte_stream), b"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
b'Repository.iter_files_bytes',
3245
b'error', (b'RevisionNotPresent', b'somefile', b'somerev'),
3246
iter([b"absent\0somefile\0somerev\n"]))
3247
self.assertRaises(errors.RevisionNotPresent, list,
3248
repo.iter_files_bytes(
3249
[(b"somefile", b"somerev", b"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(b'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
b'Repository.insert_stream_1.19', (b'quack/', b''),
3287
b'unknown', (b'Repository.insert_stream_1.19',))
3288
client.add_expected_call(
3289
b'Repository.insert_stream', (b'quack/', b''),
3290
b'success', (b'ok',))
3291
client.add_expected_call(
3292
b'Repository.insert_stream', (b'quack/', b''),
3293
b'success', (b'ok',))
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
b'Repository.lock_write', (b'quack/', b''),
3301
b'success', (b'ok', b''))
3302
client.add_expected_call(
3303
b'Repository.insert_stream_1.19', (b'quack/', b''),
3304
b'unknown', (b'Repository.insert_stream_1.19',))
3305
client.add_expected_call(
3306
b'Repository.insert_stream', (b'quack/', b''),
3307
b'success', (b'ok',))
3308
client.add_expected_call(
3309
b'Repository.insert_stream', (b'quack/', b''),
3310
b'success', (b'ok',))
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
b'Repository.lock_write', (b'quack/', b''),
3319
b'success', (b'ok', b'a token'))
3320
client.add_expected_call(
3321
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3322
b'unknown', (b'Repository.insert_stream_1.19',))
3323
client.add_expected_call(
3324
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3325
b'success', (b'ok',))
3326
client.add_expected_call(
3327
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3328
b'success', (b'ok',))
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
b'Repository.insert_stream_1.19', (b'quack/', b''),
3342
b'unknown', (b'Repository.insert_stream_1.19',))
3343
client.add_expected_call(
3344
b'Repository.insert_stream', (b'quack/', b''),
3345
b'success', (b'ok',))
3346
client.add_expected_call(
3347
b'Repository.insert_stream', (b'quack/', b''),
3348
b'success', (b'ok',))
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 [b'fake tokens'], [b'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', [(b'rev2',), (b'rev3',)]),
3377
('texts', [(b'some-rev', b'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([b'fake tokens'], resume_tokens)
3382
self.assertEqual([b'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=b'rev1')
3397
inv.root.revision = b'rev1'
3398
def stream_with_inv_delta():
3399
yield ('inventories', inventories_substream())
3400
yield ('inventory-deltas', inventory_delta_substream())
3402
versionedfile.FulltextContentFactory(
3403
(b'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
(b'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, b'newdir-id')
3414
entry.revision = 'ghost'
3415
delta = [(None, 'newdir', b'newdir-id', entry)]
3416
serializer = inventory_delta.InventoryDeltaSerializer(
3417
versioned_root=True, tree_references=False)
3418
lines = serializer.delta_to_lines(b'rev1', b'rev2', delta)
3419
yield versionedfile.ChunkedContentFactory(
3420
(b'rev2',), ((b'rev1',)), None, lines)
3422
lines = serializer.delta_to_lines(b'rev1', b'rev3', delta)
3423
yield versionedfile.ChunkedContentFactory(
3424
(b'rev3',), ((b'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
b'Repository.insert_stream_1.19', (b'quack/', b''),
3435
b'success', (b'ok',))
3436
client.add_expected_call(
3437
b'Repository.insert_stream_1.19', (b'quack/', b''),
3438
b'success', (b'ok',))
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
b'Repository.lock_write', (b'quack/', b''),
3446
b'success', (b'ok', b''))
3447
client.add_expected_call(
3448
b'Repository.insert_stream_1.19', (b'quack/', b''),
3449
b'success', (b'ok',))
3450
client.add_expected_call(
3451
b'Repository.insert_stream_1.19', (b'quack/', b''),
3452
b'success', (b'ok',))
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
b'Repository.lock_write', (b'quack/', b''),
3461
b'success', (b'ok', b'a token'))
3462
client.add_expected_call(
3463
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3464
b'success', (b'ok',))
3465
client.add_expected_call(
3466
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3467
b'success', (b'ok',))
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', b'Repository.tarball',
3494
(b'repo/', b'bz2',),),
3496
repo, client = self.setup_fake_client_and_repository(transport_path)
3497
client.add_success_response_with_body(self.tarball_content, b'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
b'PackRepository.autopack', (b'quack/',), b'success', (b'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
b'PackRepository.autopack', (b'quack/',),
3573
b'success', (b'ok',))
3574
repo._real_repository = _StubRealPackRepository(client._calls)
3577
[('call', b'PackRepository.autopack', (b'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(b'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', b'PackRepository.autopack', (b'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
b'PackRepository.autopack', (b'quack/',),
3605
b'error', (b'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
(b'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
(b'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((b'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
(b'nobranch', b'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((b'norepository',),
3691
expected_error = errors.NoRepositoryPresent(bzrdir)
3692
self.assertEqual(expected_error, translated_error)
3694
def test_LockContention(self):
3695
translated_error = self.translateTuple((b'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
(b'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((b'LockFailed', lock.encode('ascii'), why.encode('ascii')))
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((b'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
(b'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((b'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((b'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((b'ReadError', path.encode('utf-8')))
3742
expected_error = errors.ReadError(path)
3743
self.assertEqual(expected_error, translated_error)
3745
def test_IncompatibleRepositories(self):
3746
translated_error = self.translateTuple((b'IncompatibleRepositories',
3747
b"repo1", b"repo2", b"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((b'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((b'PermissionDenied', path.encode('utf-8')))
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
(b'PermissionDenied', remote_path.encode('utf-8')), 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
(b'PermissionDenied', path.encode('utf-8'), extra.encode('utf-8')))
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((b'ReadError', b"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((b'ReadError', remote_path.encode('utf-8')))
3796
expected_error = errors.ReadError(remote_path)
3797
self.assertEqual(expected_error, translated_error)
3799
def test_ReadOnlyError(self):
3800
translated_error = self.translateTuple((b'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((b'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((b'error', b"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((b'error', b'KeyError', b"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 = (b'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 = (b'NoSuchRevision', b'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 = (b'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(None,
3890
[('add', ('', None, 'directory', None))],
3891
'message', revision_id=b'rev-id')
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=b'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([b'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([b'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(b'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([b'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(b'\n', 1)[0])
4115
if search_bytes == b'everything':
4117
request.FailedSmartServerResponse((b'BadSearch',)))
4118
return super(OldGetStreamVerb,
4119
self).recreate_search(repository, search_bytes,
4120
discard_excess=discard_excess)
4121
self.override_verb(b'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(b"OutOfCoffee",
4192
lambda err: OutOfCoffee(err.error_args[0]))
4193
transport = MemoryTransport()
4194
client = FakeClient(transport.base)
4195
client.add_expected_call(
4196
b'Branch.get_stacked_on_url', (b'quack/',),
4197
b'error', (b'NotStacked',))
4198
client.add_expected_call(
4199
b'Branch.last_revision_info',
4201
b'error', (b'OutOfCoffee', b'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(b"OutOfTea",
4214
lambda err, find, path: OutOfTea(
4215
err.error_args[0].decode('utf-8'),
4217
transport = MemoryTransport()
4218
client = FakeClient(transport.base)
4219
client.add_expected_call(
4220
b'Branch.get_stacked_on_url', (b'quack/',),
4221
b'error', (b'NotStacked',))
4222
client.add_expected_call(
4223
b'Branch.last_revision_info',
4225
b'error', (b'OutOfTea', b'low'))
4226
transport.mkdir('quack')
4227
transport = transport.clone('quack')
4228
branch = self.make_remote_branch(transport, client)
4229
self.assertRaises(OutOfTea, branch.last_revision_info)
4230
self.assertFinished(client)
4233
class TestRepositoryPack(TestRemoteRepository):
4235
def test_pack(self):
4236
transport_path = 'quack'
4237
repo, client = self.setup_fake_client_and_repository(transport_path)
4238
client.add_expected_call(
4239
b'Repository.lock_write', (b'quack/', b''),
4240
b'success', (b'ok', b'token'))
4241
client.add_expected_call(
4242
b'Repository.pack', (b'quack/', b'token', b'False'),
4243
b'success', (b'ok',), )
4244
client.add_expected_call(
4245
b'Repository.unlock', (b'quack/', b'token'),
4246
b'success', (b'ok', ))
4249
def test_pack_with_hint(self):
4250
transport_path = 'quack'
4251
repo, client = self.setup_fake_client_and_repository(transport_path)
4252
client.add_expected_call(
4253
b'Repository.lock_write', (b'quack/', b''),
4254
b'success', (b'ok', b'token'))
4255
client.add_expected_call(
4256
b'Repository.pack', (b'quack/', b'token', b'False'),
4257
b'success', (b'ok',), )
4258
client.add_expected_call(
4259
b'Repository.unlock', (b'quack/', b'token', b'False'),
4260
b'success', (b'ok', ))
4261
repo.pack([b'hinta', b'hintb'])
4264
class TestRepositoryIterInventories(TestRemoteRepository):
4265
"""Test Repository.iter_inventories."""
4267
def _serialize_inv_delta(self, old_name, new_name, delta):
4268
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4269
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4271
def test_single_empty(self):
4272
transport_path = 'quack'
4273
repo, client = self.setup_fake_client_and_repository(transport_path)
4274
fmt = controldir.format_registry.get('2a')().repository_format
4276
stream = [('inventory-deltas', [
4277
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4278
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4279
client.add_expected_call(
4280
b'VersionedFileRepository.get_inventories', (b'quack/', b'unordered'),
4281
b'success', (b'ok', ),
4282
_stream_to_byte_stream(stream, fmt))
4283
ret = list(repo.iter_inventories([b"somerevid"]))
4284
self.assertLength(1, ret)
4286
self.assertEqual(b"somerevid", inv.revision_id)
4288
def test_empty(self):
4289
transport_path = 'quack'
4290
repo, client = self.setup_fake_client_and_repository(transport_path)
4291
ret = list(repo.iter_inventories([]))
4292
self.assertEqual(ret, [])
4294
def test_missing(self):
4295
transport_path = 'quack'
4296
repo, client = self.setup_fake_client_and_repository(transport_path)
4297
client.add_expected_call(
4298
b'VersionedFileRepository.get_inventories', (b'quack/', b'unordered'),
4299
b'success', (b'ok', ), iter([]))
4300
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(