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.
42
from ..branch import Branch
51
from ..bzr.bzrdir import (
58
from ..bzr.chk_serializer import chk_bencode_serializer
59
from ..bzr.remote import (
65
RemoteRepositoryFormat,
67
from ..bzr import groupcompress_repo, knitpack_repo
68
from ..revision import (
72
from ..sixish import (
77
from ..bzr.smart import medium, request
78
from ..bzr.smart.client import _SmartClient
79
from ..bzr.smart.repository import (
80
SmartServerRepositoryGetParentMap,
81
SmartServerRepositoryGetStream_1_19,
82
_stream_to_byte_stream,
87
from .scenarios import load_tests_apply_scenarios
88
from ..transport.memory import MemoryTransport
89
from ..transport.remote import (
96
load_tests = load_tests_apply_scenarios
99
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
104
test_server.SmartTCPServer_for_testing_v2_only}),
106
{'transport_server': test_server.SmartTCPServer_for_testing})]
109
super(BasicRemoteObjectTests, self).setUp()
110
self.transport = self.get_transport()
111
# make a branch that can be opened over the smart transport
112
self.local_wt = BzrDir.create_standalone_workingtree('.')
113
self.addCleanup(self.transport.disconnect)
115
def test_create_remote_bzrdir(self):
116
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
117
self.assertIsInstance(b, BzrDir)
119
def test_open_remote_branch(self):
120
# open a standalone branch in the working directory
121
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
122
branch = b.open_branch()
123
self.assertIsInstance(branch, Branch)
125
def test_remote_repository(self):
126
b = BzrDir.open_from_transport(self.transport)
127
repo = b.open_repository()
128
revid = u'\xc823123123'.encode('utf8')
129
self.assertFalse(repo.has_revision(revid))
130
self.local_wt.commit(message='test commit', rev_id=revid)
131
self.assertTrue(repo.has_revision(revid))
133
def test_find_correct_format(self):
134
"""Should open a RemoteBzrDir over a RemoteTransport"""
135
fmt = BzrDirFormat.find_format(self.transport)
136
self.assertIn(RemoteBzrProber, controldir.ControlDirFormat._probers)
137
self.assertIsInstance(fmt, RemoteBzrDirFormat)
139
def test_open_detected_smart_format(self):
140
fmt = BzrDirFormat.find_format(self.transport)
141
d = fmt.open(self.transport)
142
self.assertIsInstance(d, BzrDir)
144
def test_remote_branch_repr(self):
145
b = BzrDir.open_from_transport(self.transport).open_branch()
146
self.assertStartsWith(str(b), 'RemoteBranch(')
148
def test_remote_bzrdir_repr(self):
149
b = BzrDir.open_from_transport(self.transport)
150
self.assertStartsWith(str(b), 'RemoteBzrDir(')
152
def test_remote_branch_format_supports_stacking(self):
154
self.make_branch('unstackable', format='pack-0.92')
155
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
156
self.assertFalse(b._format.supports_stacking())
157
self.make_branch('stackable', format='1.9')
158
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
159
self.assertTrue(b._format.supports_stacking())
161
def test_remote_repo_format_supports_external_references(self):
163
bd = self.make_controldir('unstackable', format='pack-0.92')
164
r = bd.create_repository()
165
self.assertFalse(r._format.supports_external_lookups)
166
r = BzrDir.open_from_transport(
167
t.clone('unstackable')).open_repository()
168
self.assertFalse(r._format.supports_external_lookups)
169
bd = self.make_controldir('stackable', format='1.9')
170
r = bd.create_repository()
171
self.assertTrue(r._format.supports_external_lookups)
172
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
173
self.assertTrue(r._format.supports_external_lookups)
175
def test_remote_branch_set_append_revisions_only(self):
176
# Make a format 1.9 branch, which supports append_revisions_only
177
branch = self.make_branch('branch', format='1.9')
178
branch.set_append_revisions_only(True)
179
config = branch.get_config_stack()
181
True, config.get('append_revisions_only'))
182
branch.set_append_revisions_only(False)
183
config = branch.get_config_stack()
185
False, config.get('append_revisions_only'))
187
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
188
branch = self.make_branch('branch', format='knit')
190
errors.UpgradeRequired, branch.set_append_revisions_only, True)
193
class FakeProtocol(object):
194
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
196
def __init__(self, body, fake_client):
198
self._body_buffer = None
199
self._fake_client = fake_client
201
def read_body_bytes(self, count=-1):
202
if self._body_buffer is None:
203
self._body_buffer = BytesIO(self.body)
204
bytes = self._body_buffer.read(count)
205
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
206
self._fake_client.expecting_body = False
209
def cancel_read_body(self):
210
self._fake_client.expecting_body = False
212
def read_streamed_body(self):
216
class FakeClient(_SmartClient):
217
"""Lookalike for _SmartClient allowing testing."""
219
def __init__(self, fake_medium_base='fake base'):
220
"""Create a FakeClient."""
223
self.expecting_body = False
224
# if non-None, this is the list of expected calls, with only the
225
# method name and arguments included. the body might be hard to
226
# compute so is not included. If a call is None, that call can
228
self._expected_calls = None
229
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
231
def add_expected_call(self, call_name, call_args, response_type,
232
response_args, response_body=None):
233
if self._expected_calls is None:
234
self._expected_calls = []
235
self._expected_calls.append((call_name, call_args))
236
self.responses.append((response_type, response_args, response_body))
238
def add_success_response(self, *args):
239
self.responses.append((b'success', args, None))
241
def add_success_response_with_body(self, body, *args):
242
self.responses.append((b'success', args, body))
243
if self._expected_calls is not None:
244
self._expected_calls.append(None)
246
def add_error_response(self, *args):
247
self.responses.append((b'error', args))
249
def add_unknown_method_response(self, verb):
250
self.responses.append((b'unknown', verb))
252
def finished_test(self):
253
if self._expected_calls:
254
raise AssertionError("%r finished but was still expecting %r"
255
% (self, self._expected_calls[0]))
257
def _get_next_response(self):
259
response_tuple = self.responses.pop(0)
261
raise AssertionError("%r didn't expect any more calls" % (self,))
262
if response_tuple[0] == b'unknown':
263
raise errors.UnknownSmartMethod(response_tuple[1])
264
elif response_tuple[0] == b'error':
265
raise errors.ErrorFromSmartServer(response_tuple[1])
266
return response_tuple
268
def _check_call(self, method, args):
269
if self._expected_calls is None:
270
# the test should be updated to say what it expects
273
next_call = self._expected_calls.pop(0)
275
raise AssertionError("%r didn't expect any more calls "
277
% (self, method, args,))
278
if next_call is None:
280
if method != next_call[0] or args != next_call[1]:
281
raise AssertionError(
282
"%r expected %r%r but got %r%r" %
283
(self, next_call[0], next_call[1], method, args,))
285
def call(self, method, *args):
286
self._check_call(method, args)
287
self._calls.append(('call', method, args))
288
return self._get_next_response()[1]
290
def call_expecting_body(self, method, *args):
291
self._check_call(method, args)
292
self._calls.append(('call_expecting_body', method, args))
293
result = self._get_next_response()
294
self.expecting_body = True
295
return result[1], FakeProtocol(result[2], self)
297
def call_with_body_bytes(self, method, args, body):
298
self._check_call(method, args)
299
self._calls.append(('call_with_body_bytes', method, args, body))
300
result = self._get_next_response()
301
return result[1], FakeProtocol(result[2], self)
303
def call_with_body_bytes_expecting_body(self, method, args, body):
304
self._check_call(method, args)
305
self._calls.append(('call_with_body_bytes_expecting_body', method,
307
result = self._get_next_response()
308
self.expecting_body = True
309
return result[1], FakeProtocol(result[2], self)
311
def call_with_body_stream(self, args, stream):
312
# Explicitly consume the stream before checking for an error, because
313
# that's what happens a real medium.
314
stream = list(stream)
315
self._check_call(args[0], args[1:])
317
('call_with_body_stream', args[0], args[1:], stream))
318
result = self._get_next_response()
319
# The second value returned from call_with_body_stream is supposed to
320
# be a response_handler object, but so far no tests depend on that.
321
response_handler = None
322
return result[1], response_handler
325
class FakeMedium(medium.SmartClientMedium):
327
def __init__(self, client_calls, base):
328
medium.SmartClientMedium.__init__(self, base)
329
self._client_calls = client_calls
331
def disconnect(self):
332
self._client_calls.append(('disconnect medium',))
335
class TestVfsHas(tests.TestCase):
337
def test_unicode_path(self):
338
client = FakeClient('/')
339
client.add_success_response(b'yes',)
340
transport = RemoteTransport('bzr://localhost/', _client=client)
341
filename = u'/hell\u00d8'
343
result = transport.has(filename)
345
result = transport.has(filename.encode('utf-8'))
347
[('call', b'has', (filename.encode('utf-8'),))],
349
self.assertTrue(result)
352
class TestRemote(tests.TestCaseWithMemoryTransport):
354
def get_branch_format(self):
355
reference_bzrdir_format = controldir.format_registry.get('default')()
356
return reference_bzrdir_format.get_branch_format()
358
def get_repo_format(self):
359
reference_bzrdir_format = controldir.format_registry.get('default')()
360
return reference_bzrdir_format.repository_format
362
def assertFinished(self, fake_client):
363
"""Assert that all of a FakeClient's expected calls have occurred."""
364
fake_client.finished_test()
367
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
368
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
370
def assertRemotePath(self, expected, client_base, transport_base):
371
"""Assert that the result of
372
SmartClientMedium.remote_path_from_transport is the expected value for
373
a given client_base and transport_base.
375
client_medium = medium.SmartClientMedium(client_base)
376
t = transport.get_transport(transport_base)
377
result = client_medium.remote_path_from_transport(t)
378
self.assertEqual(expected, result)
380
def test_remote_path_from_transport(self):
381
"""SmartClientMedium.remote_path_from_transport calculates a URL for
382
the given transport relative to the root of the client base URL.
384
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
385
self.assertRemotePath(
386
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
388
def assertRemotePathHTTP(self, expected, transport_base, relpath):
389
"""Assert that the result of
390
HttpTransportBase.remote_path_from_transport is the expected value for
391
a given transport_base and relpath of that transport. (Note that
392
HttpTransportBase is a subclass of SmartClientMedium)
394
base_transport = transport.get_transport(transport_base)
395
client_medium = base_transport.get_smart_medium()
396
cloned_transport = base_transport.clone(relpath)
397
result = client_medium.remote_path_from_transport(cloned_transport)
398
self.assertEqual(expected, result)
400
def test_remote_path_from_transport_http(self):
401
"""Remote paths for HTTP transports are calculated differently to other
402
transports. They are just relative to the client base, not the root
403
directory of the host.
405
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
406
self.assertRemotePathHTTP(
407
'../xyz/', scheme + '//host/path', '../xyz/')
408
self.assertRemotePathHTTP(
409
'xyz/', scheme + '//host/path', 'xyz/')
412
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
413
"""Tests for the behaviour of client_medium.remote_is_at_least."""
415
def test_initially_unlimited(self):
416
"""A fresh medium assumes that the remote side supports all
419
client_medium = medium.SmartClientMedium('dummy base')
420
self.assertFalse(client_medium._is_remote_before((99, 99)))
422
def test__remember_remote_is_before(self):
423
"""Calling _remember_remote_is_before ratchets down the known remote
426
client_medium = medium.SmartClientMedium('dummy base')
427
# Mark the remote side as being less than 1.6. The remote side may
429
client_medium._remember_remote_is_before((1, 6))
430
self.assertTrue(client_medium._is_remote_before((1, 6)))
431
self.assertFalse(client_medium._is_remote_before((1, 5)))
432
# Calling _remember_remote_is_before again with a lower value works.
433
client_medium._remember_remote_is_before((1, 5))
434
self.assertTrue(client_medium._is_remote_before((1, 5)))
435
# If you call _remember_remote_is_before with a higher value it logs a
436
# warning, and continues to remember the lower value.
437
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
438
client_medium._remember_remote_is_before((1, 9))
439
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
440
self.assertTrue(client_medium._is_remote_before((1, 5)))
443
class TestBzrDirCloningMetaDir(TestRemote):
445
def test_backwards_compat(self):
446
self.setup_smart_server_with_call_log()
447
a_dir = self.make_controldir('.')
448
self.reset_smart_call_log()
449
verb = b'BzrDir.cloning_metadir'
450
self.disable_verb(verb)
451
a_dir.cloning_metadir()
452
call_count = len([call for call in self.hpss_calls if
453
call.call.method == verb])
454
self.assertEqual(1, call_count)
456
def test_branch_reference(self):
457
transport = self.get_transport('quack')
458
referenced = self.make_branch('referenced')
459
expected = referenced.controldir.cloning_metadir()
460
client = FakeClient(transport.base)
461
client.add_expected_call(
462
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
463
b'error', (b'BranchReference',)),
464
client.add_expected_call(
465
b'BzrDir.open_branchV3', (b'quack/',),
466
b'success', (b'ref', self.get_url('referenced').encode('utf-8'))),
467
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
469
result = a_controldir.cloning_metadir()
470
# We should have got a control dir matching the referenced branch.
471
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
472
self.assertEqual(expected._repository_format,
473
result._repository_format)
474
self.assertEqual(expected._branch_format, result._branch_format)
475
self.assertFinished(client)
477
def test_current_server(self):
478
transport = self.get_transport('.')
479
transport = transport.clone('quack')
480
self.make_controldir('quack')
481
client = FakeClient(transport.base)
482
reference_bzrdir_format = controldir.format_registry.get('default')()
483
control_name = reference_bzrdir_format.network_name()
484
client.add_expected_call(
485
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
486
b'success', (control_name, b'', (b'branch', b''))),
487
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
489
result = a_controldir.cloning_metadir()
490
# We should have got a reference control dir with default branch and
491
# repository formats.
492
# This pokes a little, just to be sure.
493
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
494
self.assertEqual(None, result._repository_format)
495
self.assertEqual(None, result._branch_format)
496
self.assertFinished(client)
498
def test_unknown(self):
499
transport = self.get_transport('quack')
500
referenced = self.make_branch('referenced')
501
referenced.controldir.cloning_metadir()
502
client = FakeClient(transport.base)
503
client.add_expected_call(
504
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
505
b'success', (b'unknown', b'unknown', (b'branch', b''))),
506
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
508
self.assertRaises(errors.UnknownFormatError,
509
a_controldir.cloning_metadir)
512
class TestBzrDirCheckoutMetaDir(TestRemote):
514
def test__get_checkout_format(self):
515
transport = MemoryTransport()
516
client = FakeClient(transport.base)
517
reference_bzrdir_format = controldir.format_registry.get('default')()
518
control_name = reference_bzrdir_format.network_name()
519
client.add_expected_call(
520
b'BzrDir.checkout_metadir', (b'quack/', ),
521
b'success', (control_name, b'', b''))
522
transport.mkdir('quack')
523
transport = transport.clone('quack')
524
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
526
result = a_controldir.checkout_metadir()
527
# We should have got a reference control dir with default branch and
528
# repository formats.
529
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
530
self.assertEqual(None, result._repository_format)
531
self.assertEqual(None, result._branch_format)
532
self.assertFinished(client)
534
def test_unknown_format(self):
535
transport = MemoryTransport()
536
client = FakeClient(transport.base)
537
client.add_expected_call(
538
b'BzrDir.checkout_metadir', (b'quack/',),
539
b'success', (b'dontknow', b'', b''))
540
transport.mkdir('quack')
541
transport = transport.clone('quack')
542
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
544
self.assertRaises(errors.UnknownFormatError,
545
a_controldir.checkout_metadir)
546
self.assertFinished(client)
549
class TestBzrDirGetBranches(TestRemote):
551
def test_get_branches(self):
552
transport = MemoryTransport()
553
client = FakeClient(transport.base)
554
reference_bzrdir_format = controldir.format_registry.get('default')()
555
branch_name = reference_bzrdir_format.get_branch_format().network_name()
556
client.add_success_response_with_body(
558
b"foo": (b"branch", branch_name),
559
b"": (b"branch", branch_name)}), b"success")
560
client.add_success_response(
561
b'ok', b'', b'no', b'no', b'no',
562
reference_bzrdir_format.repository_format.network_name())
563
client.add_error_response(b'NotStacked')
564
client.add_success_response(
565
b'ok', b'', b'no', b'no', b'no',
566
reference_bzrdir_format.repository_format.network_name())
567
client.add_error_response(b'NotStacked')
568
transport.mkdir('quack')
569
transport = transport.clone('quack')
570
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
572
result = a_controldir.get_branches()
573
self.assertEqual({"", "foo"}, set(result.keys()))
575
[('call_expecting_body', b'BzrDir.get_branches', (b'quack/',)),
576
('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
577
('call', b'Branch.get_stacked_on_url', (b'quack/', )),
578
('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
579
('call', b'Branch.get_stacked_on_url', (b'quack/', ))],
583
class TestBzrDirDestroyBranch(TestRemote):
585
def test_destroy_default(self):
586
transport = self.get_transport('quack')
587
self.make_branch('referenced')
588
client = FakeClient(transport.base)
589
client.add_expected_call(
590
b'BzrDir.destroy_branch', (b'quack/', ),
591
b'success', (b'ok',)),
592
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
594
a_controldir.destroy_branch()
595
self.assertFinished(client)
598
class TestBzrDirHasWorkingTree(TestRemote):
600
def test_has_workingtree(self):
601
transport = self.get_transport('quack')
602
client = FakeClient(transport.base)
603
client.add_expected_call(
604
b'BzrDir.has_workingtree', (b'quack/',),
605
b'success', (b'yes',)),
606
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
608
self.assertTrue(a_controldir.has_workingtree())
609
self.assertFinished(client)
611
def test_no_workingtree(self):
612
transport = self.get_transport('quack')
613
client = FakeClient(transport.base)
614
client.add_expected_call(
615
b'BzrDir.has_workingtree', (b'quack/',),
616
b'success', (b'no',)),
617
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
619
self.assertFalse(a_controldir.has_workingtree())
620
self.assertFinished(client)
623
class TestBzrDirDestroyRepository(TestRemote):
625
def test_destroy_repository(self):
626
transport = self.get_transport('quack')
627
client = FakeClient(transport.base)
628
client.add_expected_call(
629
b'BzrDir.destroy_repository', (b'quack/',),
630
b'success', (b'ok',)),
631
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
633
a_controldir.destroy_repository()
634
self.assertFinished(client)
637
class TestBzrDirOpen(TestRemote):
639
def make_fake_client_and_transport(self, path='quack'):
640
transport = MemoryTransport()
641
transport.mkdir(path)
642
transport = transport.clone(path)
643
client = FakeClient(transport.base)
644
return client, transport
646
def test_absent(self):
647
client, transport = self.make_fake_client_and_transport()
648
client.add_expected_call(
649
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'no',))
650
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
651
RemoteBzrDirFormat(), _client=client,
653
self.assertFinished(client)
655
def test_present_without_workingtree(self):
656
client, transport = self.make_fake_client_and_transport()
657
client.add_expected_call(
658
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'no'))
659
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
660
_client=client, _force_probe=True)
661
self.assertIsInstance(bd, RemoteBzrDir)
662
self.assertFalse(bd.has_workingtree())
663
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
664
self.assertFinished(client)
666
def test_present_with_workingtree(self):
667
client, transport = self.make_fake_client_and_transport()
668
client.add_expected_call(
669
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'yes'))
670
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
671
_client=client, _force_probe=True)
672
self.assertIsInstance(bd, RemoteBzrDir)
673
self.assertTrue(bd.has_workingtree())
674
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
675
self.assertFinished(client)
677
def test_backwards_compat(self):
678
client, transport = self.make_fake_client_and_transport()
679
client.add_expected_call(
680
b'BzrDir.open_2.1', (b'quack/',), b'unknown',
681
(b'BzrDir.open_2.1',))
682
client.add_expected_call(
683
b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
684
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
685
_client=client, _force_probe=True)
686
self.assertIsInstance(bd, RemoteBzrDir)
687
self.assertFinished(client)
689
def test_backwards_compat_hpss_v2(self):
690
client, transport = self.make_fake_client_and_transport()
691
# Monkey-patch fake client to simulate real-world behaviour with v2
692
# server: upon first RPC call detect the protocol version, and because
693
# the version is 2 also do _remember_remote_is_before((1, 6)) before
694
# continuing with the RPC.
695
orig_check_call = client._check_call
697
def check_call(method, args):
698
client._medium._protocol_version = 2
699
client._medium._remember_remote_is_before((1, 6))
700
client._check_call = orig_check_call
701
client._check_call(method, args)
702
client._check_call = check_call
703
client.add_expected_call(
704
b'BzrDir.open_2.1', (b'quack/',), b'unknown',
705
(b'BzrDir.open_2.1',))
706
client.add_expected_call(
707
b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
708
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
709
_client=client, _force_probe=True)
710
self.assertIsInstance(bd, RemoteBzrDir)
711
self.assertFinished(client)
714
class TestBzrDirOpenBranch(TestRemote):
716
def test_backwards_compat(self):
717
self.setup_smart_server_with_call_log()
718
self.make_branch('.')
719
a_dir = BzrDir.open(self.get_url('.'))
720
self.reset_smart_call_log()
721
verb = b'BzrDir.open_branchV3'
722
self.disable_verb(verb)
724
call_count = len([call for call in self.hpss_calls if
725
call.call.method == verb])
726
self.assertEqual(1, call_count)
728
def test_branch_present(self):
729
reference_format = self.get_repo_format()
730
network_name = reference_format.network_name()
731
branch_network_name = self.get_branch_format().network_name()
732
transport = MemoryTransport()
733
transport.mkdir('quack')
734
transport = transport.clone('quack')
735
client = FakeClient(transport.base)
736
client.add_expected_call(
737
b'BzrDir.open_branchV3', (b'quack/',),
738
b'success', (b'branch', branch_network_name))
739
client.add_expected_call(
740
b'BzrDir.find_repositoryV3', (b'quack/',),
741
b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
742
client.add_expected_call(
743
b'Branch.get_stacked_on_url', (b'quack/',),
744
b'error', (b'NotStacked',))
745
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
747
result = bzrdir.open_branch()
748
self.assertIsInstance(result, RemoteBranch)
749
self.assertEqual(bzrdir, result.controldir)
750
self.assertFinished(client)
752
def test_branch_missing(self):
753
transport = MemoryTransport()
754
transport.mkdir('quack')
755
transport = transport.clone('quack')
756
client = FakeClient(transport.base)
757
client.add_error_response(b'nobranch')
758
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
760
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
762
[('call', b'BzrDir.open_branchV3', (b'quack/',))],
765
def test__get_tree_branch(self):
766
# _get_tree_branch is a form of open_branch, but it should only ask for
767
# branch opening, not any other network requests.
770
def open_branch(name=None, possible_transports=None):
771
calls.append("Called")
773
transport = MemoryTransport()
774
# no requests on the network - catches other api calls being made.
775
client = FakeClient(transport.base)
776
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
778
# patch the open_branch call to record that it was called.
779
bzrdir.open_branch = open_branch
780
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
781
self.assertEqual(["Called"], calls)
782
self.assertEqual([], client._calls)
784
def test_url_quoting_of_path(self):
785
# Relpaths on the wire should not be URL-escaped. So "~" should be
786
# transmitted as "~", not "%7E".
787
transport = RemoteTCPTransport('bzr://localhost/~hello/')
788
client = FakeClient(transport.base)
789
reference_format = self.get_repo_format()
790
network_name = reference_format.network_name()
791
branch_network_name = self.get_branch_format().network_name()
792
client.add_expected_call(
793
b'BzrDir.open_branchV3', (b'~hello/',),
794
b'success', (b'branch', branch_network_name))
795
client.add_expected_call(
796
b'BzrDir.find_repositoryV3', (b'~hello/',),
797
b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
798
client.add_expected_call(
799
b'Branch.get_stacked_on_url', (b'~hello/',),
800
b'error', (b'NotStacked',))
801
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
804
self.assertFinished(client)
806
def check_open_repository(self, rich_root, subtrees,
807
external_lookup=b'no'):
808
reference_format = self.get_repo_format()
809
network_name = reference_format.network_name()
810
transport = MemoryTransport()
811
transport.mkdir('quack')
812
transport = transport.clone('quack')
814
rich_response = b'yes'
816
rich_response = b'no'
818
subtree_response = b'yes'
820
subtree_response = b'no'
821
client = FakeClient(transport.base)
822
client.add_success_response(
823
b'ok', b'', rich_response, subtree_response, external_lookup,
825
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
827
result = bzrdir.open_repository()
829
[('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
831
self.assertIsInstance(result, RemoteRepository)
832
self.assertEqual(bzrdir, result.controldir)
833
self.assertEqual(rich_root, result._format.rich_root_data)
834
self.assertEqual(subtrees, result._format.supports_tree_reference)
836
def test_open_repository_sets_format_attributes(self):
837
self.check_open_repository(True, True)
838
self.check_open_repository(False, True)
839
self.check_open_repository(True, False)
840
self.check_open_repository(False, False)
841
self.check_open_repository(False, False, b'yes')
843
def test_old_server(self):
844
"""RemoteBzrDirFormat should fail to probe if the server version is too
848
errors.NotBranchError,
849
RemoteBzrProber.probe_transport, OldServerTransport())
852
class TestBzrDirCreateBranch(TestRemote):
854
def test_backwards_compat(self):
855
self.setup_smart_server_with_call_log()
856
repo = self.make_repository('.')
857
self.reset_smart_call_log()
858
self.disable_verb(b'BzrDir.create_branch')
859
repo.controldir.create_branch()
860
create_branch_call_count = len(
861
[call for call in self.hpss_calls
862
if call.call.method == b'BzrDir.create_branch'])
863
self.assertEqual(1, create_branch_call_count)
865
def test_current_server(self):
866
transport = self.get_transport('.')
867
transport = transport.clone('quack')
868
self.make_repository('quack')
869
client = FakeClient(transport.base)
870
reference_bzrdir_format = controldir.format_registry.get('default')()
871
reference_format = reference_bzrdir_format.get_branch_format()
872
network_name = reference_format.network_name()
873
reference_repo_fmt = reference_bzrdir_format.repository_format
874
reference_repo_name = reference_repo_fmt.network_name()
875
client.add_expected_call(
876
b'BzrDir.create_branch', (b'quack/', network_name),
877
b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
878
reference_repo_name))
879
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
881
branch = a_controldir.create_branch()
882
# We should have got a remote branch
883
self.assertIsInstance(branch, remote.RemoteBranch)
884
# its format should have the settings from the response
885
format = branch._format
886
self.assertEqual(network_name, format.network_name())
888
def test_already_open_repo_and_reused_medium(self):
889
"""Bug 726584: create_branch(..., repository=repo) should work
890
regardless of what the smart medium's base URL is.
892
self.transport_server = test_server.SmartTCPServer_for_testing
893
transport = self.get_transport('.')
894
repo = self.make_repository('quack')
895
# Client's medium rooted a transport root (not at the bzrdir)
896
client = FakeClient(transport.base)
897
transport = transport.clone('quack')
898
reference_bzrdir_format = controldir.format_registry.get('default')()
899
reference_format = reference_bzrdir_format.get_branch_format()
900
network_name = reference_format.network_name()
901
reference_repo_fmt = reference_bzrdir_format.repository_format
902
reference_repo_name = reference_repo_fmt.network_name()
903
client.add_expected_call(
904
b'BzrDir.create_branch', (b'extra/quack/', network_name),
905
b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
906
reference_repo_name))
907
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
909
branch = a_controldir.create_branch(repository=repo)
910
# We should have got a remote branch
911
self.assertIsInstance(branch, remote.RemoteBranch)
912
# its format should have the settings from the response
913
format = branch._format
914
self.assertEqual(network_name, format.network_name())
917
class TestBzrDirCreateRepository(TestRemote):
919
def test_backwards_compat(self):
920
self.setup_smart_server_with_call_log()
921
bzrdir = self.make_controldir('.')
922
self.reset_smart_call_log()
923
self.disable_verb(b'BzrDir.create_repository')
924
bzrdir.create_repository()
925
create_repo_call_count = len([call for call in self.hpss_calls if
926
call.call.method == b'BzrDir.create_repository'])
927
self.assertEqual(1, create_repo_call_count)
929
def test_current_server(self):
930
transport = self.get_transport('.')
931
transport = transport.clone('quack')
932
self.make_controldir('quack')
933
client = FakeClient(transport.base)
934
reference_bzrdir_format = controldir.format_registry.get('default')()
935
reference_format = reference_bzrdir_format.repository_format
936
network_name = reference_format.network_name()
937
client.add_expected_call(
938
b'BzrDir.create_repository', (b'quack/',
939
b'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
941
b'success', (b'ok', b'yes', b'yes', b'yes', network_name))
942
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
944
repo = a_controldir.create_repository()
945
# We should have got a remote repository
946
self.assertIsInstance(repo, remote.RemoteRepository)
947
# its format should have the settings from the response
948
format = repo._format
949
self.assertTrue(format.rich_root_data)
950
self.assertTrue(format.supports_tree_reference)
951
self.assertTrue(format.supports_external_lookups)
952
self.assertEqual(network_name, format.network_name())
955
class TestBzrDirOpenRepository(TestRemote):
957
def test_backwards_compat_1_2_3(self):
958
# fallback all the way to the first version.
959
reference_format = self.get_repo_format()
960
network_name = reference_format.network_name()
961
server_url = 'bzr://example.com/'
962
self.permit_url(server_url)
963
client = FakeClient(server_url)
964
client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
965
client.add_unknown_method_response(b'BzrDir.find_repositoryV2')
966
client.add_success_response(b'ok', b'', b'no', b'no')
967
# A real repository instance will be created to determine the network
969
client.add_success_response_with_body(
970
b"Bazaar-NG meta directory, format 1\n", b'ok')
971
client.add_success_response(b'stat', b'0', b'65535')
972
client.add_success_response_with_body(
973
reference_format.get_format_string(), b'ok')
974
# PackRepository wants to do a stat
975
client.add_success_response(b'stat', b'0', b'65535')
976
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
978
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
980
repo = bzrdir.open_repository()
982
[('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
983
('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
984
('call', b'BzrDir.find_repository', (b'quack/',)),
985
('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
986
('call', b'stat', (b'/quack/.bzr',)),
987
('call_expecting_body', b'get', (b'/quack/.bzr/repository/format',)),
988
('call', b'stat', (b'/quack/.bzr/repository',)),
991
self.assertEqual(network_name, repo._format.network_name())
993
def test_backwards_compat_2(self):
994
# fallback to find_repositoryV2
995
reference_format = self.get_repo_format()
996
network_name = reference_format.network_name()
997
server_url = 'bzr://example.com/'
998
self.permit_url(server_url)
999
client = FakeClient(server_url)
1000
client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
1001
client.add_success_response(b'ok', b'', b'no', b'no', b'no')
1002
# A real repository instance will be created to determine the network
1004
client.add_success_response_with_body(
1005
b"Bazaar-NG meta directory, format 1\n", b'ok')
1006
client.add_success_response(b'stat', b'0', b'65535')
1007
client.add_success_response_with_body(
1008
reference_format.get_format_string(), b'ok')
1009
# PackRepository wants to do a stat
1010
client.add_success_response(b'stat', b'0', b'65535')
1011
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
1013
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
1015
repo = bzrdir.open_repository()
1017
[('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
1018
('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
1019
('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
1020
('call', b'stat', (b'/quack/.bzr',)),
1021
('call_expecting_body', b'get',
1022
(b'/quack/.bzr/repository/format',)),
1023
('call', b'stat', (b'/quack/.bzr/repository',)),
1026
self.assertEqual(network_name, repo._format.network_name())
1028
def test_current_server(self):
1029
reference_format = self.get_repo_format()
1030
network_name = reference_format.network_name()
1031
transport = MemoryTransport()
1032
transport.mkdir('quack')
1033
transport = transport.clone('quack')
1034
client = FakeClient(transport.base)
1035
client.add_success_response(
1036
b'ok', b'', b'no', b'no', b'no', network_name)
1037
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1039
repo = bzrdir.open_repository()
1041
[('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
1043
self.assertEqual(network_name, repo._format.network_name())
1046
class TestBzrDirFormatInitializeEx(TestRemote):
1048
def test_success(self):
1049
"""Simple test for typical successful call."""
1050
fmt = RemoteBzrDirFormat()
1051
default_format_name = BzrDirFormat.get_default_format().network_name()
1052
transport = self.get_transport()
1053
client = FakeClient(transport.base)
1054
client.add_expected_call(
1055
b'BzrDirFormat.initialize_ex_1.16',
1056
(default_format_name, b'path', b'False', b'False', b'False', b'',
1057
b'', b'', b'', b'False'),
1059
(b'.', b'no', b'no', b'yes', b'repo fmt', b'repo bzrdir fmt',
1060
b'bzrdir fmt', b'False', b'', b'', b'repo lock token'))
1061
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1062
# it's currently hard to test that without supplying a real remote
1063
# transport connected to a real server.
1064
fmt._initialize_on_transport_ex_rpc(
1065
client, b'path', transport, False, False, False, None, None, None,
1067
self.assertFinished(client)
1069
def test_error(self):
1070
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1071
corresponding error from the client.
1073
fmt = RemoteBzrDirFormat()
1074
default_format_name = BzrDirFormat.get_default_format().network_name()
1075
transport = self.get_transport()
1076
client = FakeClient(transport.base)
1077
client.add_expected_call(
1078
b'BzrDirFormat.initialize_ex_1.16',
1079
(default_format_name, b'path', b'False', b'False', b'False', b'',
1080
b'', b'', b'', b'False'),
1082
(b'PermissionDenied', b'path', b'extra info'))
1083
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1084
# it's currently hard to test that without supplying a real remote
1085
# transport connected to a real server.
1086
err = self.assertRaises(
1087
errors.PermissionDenied,
1088
fmt._initialize_on_transport_ex_rpc, client, b'path', transport,
1089
False, False, False, None, None, None, None, False)
1090
self.assertEqual('path', err.path)
1091
self.assertEqual(': extra info', err.extra)
1092
self.assertFinished(client)
1094
def test_error_from_real_server(self):
1095
"""Integration test for error translation."""
1096
transport = self.make_smart_server('foo')
1097
transport = transport.clone('no-such-path')
1098
fmt = RemoteBzrDirFormat()
1100
errors.NoSuchFile, fmt.initialize_on_transport_ex, transport,
1101
create_prefix=False)
1104
class OldSmartClient(object):
1105
"""A fake smart client for test_old_version that just returns a version one
1106
response to the 'hello' (query version) command.
1109
def get_request(self):
1110
input_file = BytesIO(b'ok\x011\n')
1111
output_file = BytesIO()
1112
client_medium = medium.SmartSimplePipesClientMedium(
1113
input_file, output_file)
1114
return medium.SmartClientStreamMediumRequest(client_medium)
1116
def protocol_version(self):
1120
class OldServerTransport(object):
1121
"""A fake transport for test_old_server that reports it's smart server
1122
protocol version as version one.
1128
def get_smart_client(self):
1129
return OldSmartClient()
1132
class RemoteBzrDirTestCase(TestRemote):
1134
def make_remote_bzrdir(self, transport, client):
1135
"""Make a RemotebzrDir using 'client' as the _client."""
1136
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1140
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1142
def lock_remote_branch(self, branch):
1143
"""Trick a RemoteBranch into thinking it is locked."""
1144
branch._lock_mode = 'w'
1145
branch._lock_count = 2
1146
branch._lock_token = b'branch token'
1147
branch._repo_lock_token = b'repo token'
1148
branch.repository._lock_mode = 'w'
1149
branch.repository._lock_count = 2
1150
branch.repository._lock_token = b'repo token'
1152
def make_remote_branch(self, transport, client):
1153
"""Make a RemoteBranch using 'client' as its _SmartClient.
1155
A RemoteBzrDir and RemoteRepository will also be created to fill out
1156
the RemoteBranch, albeit with stub values for some of their attributes.
1158
# we do not want bzrdir to make any remote calls, so use False as its
1159
# _client. If it tries to make a remote call, this will fail
1161
bzrdir = self.make_remote_bzrdir(transport, False)
1162
repo = RemoteRepository(bzrdir, None, _client=client)
1163
branch_format = self.get_branch_format()
1164
format = RemoteBranchFormat(network_name=branch_format.network_name())
1165
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1168
class TestBranchBreakLock(RemoteBranchTestCase):
1170
def test_break_lock(self):
1171
transport = MemoryTransport()
1172
client = FakeClient(transport.base)
1173
client.add_expected_call(
1174
b'Branch.get_stacked_on_url', (b'quack/',),
1175
b'error', (b'NotStacked',))
1176
client.add_expected_call(
1177
b'Branch.break_lock', (b'quack/',),
1178
b'success', (b'ok',))
1179
transport.mkdir('quack')
1180
transport = transport.clone('quack')
1181
branch = self.make_remote_branch(transport, client)
1183
self.assertFinished(client)
1186
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1188
def test_get_physical_lock_status_yes(self):
1189
transport = MemoryTransport()
1190
client = FakeClient(transport.base)
1191
client.add_expected_call(
1192
b'Branch.get_stacked_on_url', (b'quack/',),
1193
b'error', (b'NotStacked',))
1194
client.add_expected_call(
1195
b'Branch.get_physical_lock_status', (b'quack/',),
1196
b'success', (b'yes',))
1197
transport.mkdir('quack')
1198
transport = transport.clone('quack')
1199
branch = self.make_remote_branch(transport, client)
1200
result = branch.get_physical_lock_status()
1201
self.assertFinished(client)
1202
self.assertEqual(True, result)
1204
def test_get_physical_lock_status_no(self):
1205
transport = MemoryTransport()
1206
client = FakeClient(transport.base)
1207
client.add_expected_call(
1208
b'Branch.get_stacked_on_url', (b'quack/',),
1209
b'error', (b'NotStacked',))
1210
client.add_expected_call(
1211
b'Branch.get_physical_lock_status', (b'quack/',),
1212
b'success', (b'no',))
1213
transport.mkdir('quack')
1214
transport = transport.clone('quack')
1215
branch = self.make_remote_branch(transport, client)
1216
result = branch.get_physical_lock_status()
1217
self.assertFinished(client)
1218
self.assertEqual(False, result)
1221
class TestBranchGetParent(RemoteBranchTestCase):
1223
def test_no_parent(self):
1224
# in an empty branch we decode the response properly
1225
transport = MemoryTransport()
1226
client = FakeClient(transport.base)
1227
client.add_expected_call(
1228
b'Branch.get_stacked_on_url', (b'quack/',),
1229
b'error', (b'NotStacked',))
1230
client.add_expected_call(
1231
b'Branch.get_parent', (b'quack/',),
1233
transport.mkdir('quack')
1234
transport = transport.clone('quack')
1235
branch = self.make_remote_branch(transport, client)
1236
result = branch.get_parent()
1237
self.assertFinished(client)
1238
self.assertEqual(None, result)
1240
def test_parent_relative(self):
1241
transport = MemoryTransport()
1242
client = FakeClient(transport.base)
1243
client.add_expected_call(
1244
b'Branch.get_stacked_on_url', (b'kwaak/',),
1245
b'error', (b'NotStacked',))
1246
client.add_expected_call(
1247
b'Branch.get_parent', (b'kwaak/',),
1248
b'success', (b'../foo/',))
1249
transport.mkdir('kwaak')
1250
transport = transport.clone('kwaak')
1251
branch = self.make_remote_branch(transport, client)
1252
result = branch.get_parent()
1253
self.assertEqual(transport.clone('../foo').base, result)
1255
def test_parent_absolute(self):
1256
transport = MemoryTransport()
1257
client = FakeClient(transport.base)
1258
client.add_expected_call(
1259
b'Branch.get_stacked_on_url', (b'kwaak/',),
1260
b'error', (b'NotStacked',))
1261
client.add_expected_call(
1262
b'Branch.get_parent', (b'kwaak/',),
1263
b'success', (b'http://foo/',))
1264
transport.mkdir('kwaak')
1265
transport = transport.clone('kwaak')
1266
branch = self.make_remote_branch(transport, client)
1267
result = branch.get_parent()
1268
self.assertEqual('http://foo/', result)
1269
self.assertFinished(client)
1272
class TestBranchSetParentLocation(RemoteBranchTestCase):
1274
def test_no_parent(self):
1275
# We call the verb when setting parent to None
1276
transport = MemoryTransport()
1277
client = FakeClient(transport.base)
1278
client.add_expected_call(
1279
b'Branch.get_stacked_on_url', (b'quack/',),
1280
b'error', (b'NotStacked',))
1281
client.add_expected_call(
1282
b'Branch.set_parent_location', (b'quack/', b'b', b'r', b''),
1284
transport.mkdir('quack')
1285
transport = transport.clone('quack')
1286
branch = self.make_remote_branch(transport, client)
1287
branch._lock_token = b'b'
1288
branch._repo_lock_token = b'r'
1289
branch._set_parent_location(None)
1290
self.assertFinished(client)
1292
def test_parent(self):
1293
transport = MemoryTransport()
1294
client = FakeClient(transport.base)
1295
client.add_expected_call(
1296
b'Branch.get_stacked_on_url', (b'kwaak/',),
1297
b'error', (b'NotStacked',))
1298
client.add_expected_call(
1299
b'Branch.set_parent_location', (b'kwaak/', b'b', b'r', b'foo'),
1301
transport.mkdir('kwaak')
1302
transport = transport.clone('kwaak')
1303
branch = self.make_remote_branch(transport, client)
1304
branch._lock_token = b'b'
1305
branch._repo_lock_token = b'r'
1306
branch._set_parent_location('foo')
1307
self.assertFinished(client)
1309
def test_backwards_compat(self):
1310
self.setup_smart_server_with_call_log()
1311
branch = self.make_branch('.')
1312
self.reset_smart_call_log()
1313
verb = b'Branch.set_parent_location'
1314
self.disable_verb(verb)
1315
branch.set_parent('http://foo/')
1316
self.assertLength(14, self.hpss_calls)
1319
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1321
def test_backwards_compat(self):
1322
self.setup_smart_server_with_call_log()
1323
branch = self.make_branch('.')
1324
self.reset_smart_call_log()
1325
verb = b'Branch.get_tags_bytes'
1326
self.disable_verb(verb)
1327
branch.tags.get_tag_dict()
1328
call_count = len([call for call in self.hpss_calls if
1329
call.call.method == verb])
1330
self.assertEqual(1, call_count)
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.get_tags_bytes', (b'quack/',),
1341
transport.mkdir('quack')
1342
transport = transport.clone('quack')
1343
branch = self.make_remote_branch(transport, client)
1344
result = branch.tags.get_tag_dict()
1345
self.assertFinished(client)
1346
self.assertEqual({}, result)
1349
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1351
def test_trivial(self):
1352
transport = MemoryTransport()
1353
client = FakeClient(transport.base)
1354
client.add_expected_call(
1355
b'Branch.get_stacked_on_url', (b'quack/',),
1356
b'error', (b'NotStacked',))
1357
client.add_expected_call(
1358
b'Branch.set_tags_bytes', (b'quack/',
1359
b'branch token', b'repo token'),
1361
transport.mkdir('quack')
1362
transport = transport.clone('quack')
1363
branch = self.make_remote_branch(transport, client)
1364
self.lock_remote_branch(branch)
1365
branch._set_tags_bytes(b'tags bytes')
1366
self.assertFinished(client)
1367
self.assertEqual(b'tags bytes', client._calls[-1][-1])
1369
def test_backwards_compatible(self):
1370
transport = MemoryTransport()
1371
client = FakeClient(transport.base)
1372
client.add_expected_call(
1373
b'Branch.get_stacked_on_url', (b'quack/',),
1374
b'error', (b'NotStacked',))
1375
client.add_expected_call(
1376
b'Branch.set_tags_bytes', (b'quack/',
1377
b'branch token', b'repo token'),
1378
b'unknown', (b'Branch.set_tags_bytes',))
1379
transport.mkdir('quack')
1380
transport = transport.clone('quack')
1381
branch = self.make_remote_branch(transport, client)
1382
self.lock_remote_branch(branch)
1384
class StubRealBranch(object):
1388
def _set_tags_bytes(self, bytes):
1389
self.calls.append(('set_tags_bytes', bytes))
1390
real_branch = StubRealBranch()
1391
branch._real_branch = real_branch
1392
branch._set_tags_bytes(b'tags bytes')
1393
# Call a second time, to exercise the 'remote version already inferred'
1395
branch._set_tags_bytes(b'tags bytes')
1396
self.assertFinished(client)
1398
[('set_tags_bytes', b'tags bytes')] * 2, real_branch.calls)
1401
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1403
def test_uses_last_revision_info_and_tags_by_default(self):
1404
transport = MemoryTransport()
1405
client = FakeClient(transport.base)
1406
client.add_expected_call(
1407
b'Branch.get_stacked_on_url', (b'quack/',),
1408
b'error', (b'NotStacked',))
1409
client.add_expected_call(
1410
b'Branch.last_revision_info', (b'quack/',),
1411
b'success', (b'ok', b'1', b'rev-tip'))
1412
client.add_expected_call(
1413
b'Branch.get_config_file', (b'quack/',),
1414
b'success', (b'ok',), b'')
1415
transport.mkdir('quack')
1416
transport = transport.clone('quack')
1417
branch = self.make_remote_branch(transport, client)
1418
result = branch.heads_to_fetch()
1419
self.assertFinished(client)
1420
self.assertEqual(({b'rev-tip'}, set()), result)
1422
def test_uses_last_revision_info_and_tags_when_set(self):
1423
transport = MemoryTransport()
1424
client = FakeClient(transport.base)
1425
client.add_expected_call(
1426
b'Branch.get_stacked_on_url', (b'quack/',),
1427
b'error', (b'NotStacked',))
1428
client.add_expected_call(
1429
b'Branch.last_revision_info', (b'quack/',),
1430
b'success', (b'ok', b'1', b'rev-tip'))
1431
client.add_expected_call(
1432
b'Branch.get_config_file', (b'quack/',),
1433
b'success', (b'ok',), b'branch.fetch_tags = True')
1434
# XXX: this will break if the default format's serialization of tags
1435
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1436
client.add_expected_call(
1437
b'Branch.get_tags_bytes', (b'quack/',),
1438
b'success', (b'd5:tag-17:rev-foo5:tag-27:rev-bare',))
1439
transport.mkdir('quack')
1440
transport = transport.clone('quack')
1441
branch = self.make_remote_branch(transport, client)
1442
result = branch.heads_to_fetch()
1443
self.assertFinished(client)
1445
({b'rev-tip'}, {b'rev-foo', b'rev-bar'}), result)
1447
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1448
transport = MemoryTransport()
1449
client = FakeClient(transport.base)
1450
client.add_expected_call(
1451
b'Branch.get_stacked_on_url', (b'quack/',),
1452
b'error', (b'NotStacked',))
1453
client.add_expected_call(
1454
b'Branch.heads_to_fetch', (b'quack/',),
1455
b'success', ([b'tip'], [b'tagged-1', b'tagged-2']))
1456
transport.mkdir('quack')
1457
transport = transport.clone('quack')
1458
branch = self.make_remote_branch(transport, client)
1459
branch._format._use_default_local_heads_to_fetch = lambda: False
1460
result = branch.heads_to_fetch()
1461
self.assertFinished(client)
1462
self.assertEqual(({b'tip'}, {b'tagged-1', b'tagged-2'}), result)
1464
def make_branch_with_tags(self):
1465
self.setup_smart_server_with_call_log()
1466
# Make a branch with a single revision.
1467
builder = self.make_branch_builder('foo')
1468
builder.start_series()
1469
builder.build_snapshot(None, [
1470
('add', ('', b'root-id', 'directory', ''))],
1472
builder.finish_series()
1473
branch = builder.get_branch()
1474
# Add two tags to that branch
1475
branch.tags.set_tag('tag-1', b'rev-1')
1476
branch.tags.set_tag('tag-2', b'rev-2')
1479
def test_backwards_compatible(self):
1480
br = self.make_branch_with_tags()
1481
br.get_config_stack().set('branch.fetch_tags', True)
1482
self.addCleanup(br.lock_read().unlock)
1483
# Disable the heads_to_fetch verb
1484
verb = b'Branch.heads_to_fetch'
1485
self.disable_verb(verb)
1486
self.reset_smart_call_log()
1487
result = br.heads_to_fetch()
1488
self.assertEqual(({b'tip'}, {b'rev-1', b'rev-2'}), result)
1490
[b'Branch.last_revision_info', b'Branch.get_tags_bytes'],
1491
[call.call.method for call in self.hpss_calls])
1493
def test_backwards_compatible_no_tags(self):
1494
br = self.make_branch_with_tags()
1495
br.get_config_stack().set('branch.fetch_tags', False)
1496
self.addCleanup(br.lock_read().unlock)
1497
# Disable the heads_to_fetch verb
1498
verb = b'Branch.heads_to_fetch'
1499
self.disable_verb(verb)
1500
self.reset_smart_call_log()
1501
result = br.heads_to_fetch()
1502
self.assertEqual(({b'tip'}, set()), result)
1504
[b'Branch.last_revision_info'],
1505
[call.call.method for call in self.hpss_calls])
1508
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1510
def test_empty_branch(self):
1511
# in an empty branch we decode the response properly
1512
transport = MemoryTransport()
1513
client = FakeClient(transport.base)
1514
client.add_expected_call(
1515
b'Branch.get_stacked_on_url', (b'quack/',),
1516
b'error', (b'NotStacked',))
1517
client.add_expected_call(
1518
b'Branch.last_revision_info', (b'quack/',),
1519
b'success', (b'ok', b'0', b'null:'))
1520
transport.mkdir('quack')
1521
transport = transport.clone('quack')
1522
branch = self.make_remote_branch(transport, client)
1523
result = branch.last_revision_info()
1524
self.assertFinished(client)
1525
self.assertEqual((0, NULL_REVISION), result)
1527
def test_non_empty_branch(self):
1528
# in a non-empty branch we also decode the response properly
1529
revid = u'\xc8'.encode('utf8')
1530
transport = MemoryTransport()
1531
client = FakeClient(transport.base)
1532
client.add_expected_call(
1533
b'Branch.get_stacked_on_url', (b'kwaak/',),
1534
b'error', (b'NotStacked',))
1535
client.add_expected_call(
1536
b'Branch.last_revision_info', (b'kwaak/',),
1537
b'success', (b'ok', b'2', revid))
1538
transport.mkdir('kwaak')
1539
transport = transport.clone('kwaak')
1540
branch = self.make_remote_branch(transport, client)
1541
result = branch.last_revision_info()
1542
self.assertEqual((2, revid), result)
1545
class TestBranch_get_stacked_on_url(TestRemote):
1546
"""Test Branch._get_stacked_on_url rpc"""
1548
def test_get_stacked_on_invalid_url(self):
1549
# test that asking for a stacked on url the server can't access works.
1550
# This isn't perfect, but then as we're in the same process there
1551
# really isn't anything we can do to be 100% sure that the server
1552
# doesn't just open in - this test probably needs to be rewritten using
1553
# a spawn()ed server.
1554
stacked_branch = self.make_branch('stacked', format='1.9')
1555
self.make_branch('base', format='1.9')
1556
vfs_url = self.get_vfs_only_url('base')
1557
stacked_branch.set_stacked_on_url(vfs_url)
1558
transport = stacked_branch.controldir.root_transport
1559
client = FakeClient(transport.base)
1560
client.add_expected_call(
1561
b'Branch.get_stacked_on_url', (b'stacked/',),
1562
b'success', (b'ok', vfs_url.encode('utf-8')))
1563
# XXX: Multiple calls are bad, this second call documents what is
1565
client.add_expected_call(
1566
b'Branch.get_stacked_on_url', (b'stacked/',),
1567
b'success', (b'ok', vfs_url.encode('utf-8')))
1568
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1570
repo_fmt = remote.RemoteRepositoryFormat()
1571
repo_fmt._custom_format = stacked_branch.repository._format
1572
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1574
result = branch.get_stacked_on_url()
1575
self.assertEqual(vfs_url, result)
1577
def test_backwards_compatible(self):
1578
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1579
self.make_branch('base', format='1.6')
1580
stacked_branch = self.make_branch('stacked', format='1.6')
1581
stacked_branch.set_stacked_on_url('../base')
1582
client = FakeClient(self.get_url())
1583
branch_network_name = self.get_branch_format().network_name()
1584
client.add_expected_call(
1585
b'BzrDir.open_branchV3', (b'stacked/',),
1586
b'success', (b'branch', branch_network_name))
1587
client.add_expected_call(
1588
b'BzrDir.find_repositoryV3', (b'stacked/',),
1589
b'success', (b'ok', b'', b'no', b'no', b'yes',
1590
stacked_branch.repository._format.network_name()))
1591
# called twice, once from constructor and then again by us
1592
client.add_expected_call(
1593
b'Branch.get_stacked_on_url', (b'stacked/',),
1594
b'unknown', (b'Branch.get_stacked_on_url',))
1595
client.add_expected_call(
1596
b'Branch.get_stacked_on_url', (b'stacked/',),
1597
b'unknown', (b'Branch.get_stacked_on_url',))
1598
# this will also do vfs access, but that goes direct to the transport
1599
# and isn't seen by the FakeClient.
1600
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1601
RemoteBzrDirFormat(), _client=client)
1602
branch = bzrdir.open_branch()
1603
result = branch.get_stacked_on_url()
1604
self.assertEqual('../base', result)
1605
self.assertFinished(client)
1606
# it's in the fallback list both for the RemoteRepository and its vfs
1608
self.assertEqual(1, len(branch.repository._fallback_repositories))
1610
len(branch.repository._real_repository._fallback_repositories))
1612
def test_get_stacked_on_real_branch(self):
1613
self.make_branch('base')
1614
stacked_branch = self.make_branch('stacked')
1615
stacked_branch.set_stacked_on_url('../base')
1616
reference_format = self.get_repo_format()
1617
network_name = reference_format.network_name()
1618
client = FakeClient(self.get_url())
1619
branch_network_name = self.get_branch_format().network_name()
1620
client.add_expected_call(
1621
b'BzrDir.open_branchV3', (b'stacked/',),
1622
b'success', (b'branch', branch_network_name))
1623
client.add_expected_call(
1624
b'BzrDir.find_repositoryV3', (b'stacked/',),
1625
b'success', (b'ok', b'', b'yes', b'no', b'yes', network_name))
1626
# called twice, once from constructor and then again by us
1627
client.add_expected_call(
1628
b'Branch.get_stacked_on_url', (b'stacked/',),
1629
b'success', (b'ok', b'../base'))
1630
client.add_expected_call(
1631
b'Branch.get_stacked_on_url', (b'stacked/',),
1632
b'success', (b'ok', b'../base'))
1633
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1634
RemoteBzrDirFormat(), _client=client)
1635
branch = bzrdir.open_branch()
1636
result = branch.get_stacked_on_url()
1637
self.assertEqual('../base', result)
1638
self.assertFinished(client)
1639
# it's in the fallback list both for the RemoteRepository.
1640
self.assertEqual(1, len(branch.repository._fallback_repositories))
1641
# And we haven't had to construct a real repository.
1642
self.assertEqual(None, branch.repository._real_repository)
1645
class TestBranchSetLastRevision(RemoteBranchTestCase):
1647
def test_set_empty(self):
1648
# _set_last_revision_info('null:') is translated to calling
1649
# Branch.set_last_revision(path, '') on the wire.
1650
transport = MemoryTransport()
1651
transport.mkdir('branch')
1652
transport = transport.clone('branch')
1654
client = FakeClient(transport.base)
1655
client.add_expected_call(
1656
b'Branch.get_stacked_on_url', (b'branch/',),
1657
b'error', (b'NotStacked',))
1658
client.add_expected_call(
1659
b'Branch.lock_write', (b'branch/', b'', b''),
1660
b'success', (b'ok', b'branch token', b'repo token'))
1661
client.add_expected_call(
1662
b'Branch.last_revision_info',
1664
b'success', (b'ok', b'0', b'null:'))
1665
client.add_expected_call(
1666
b'Branch.set_last_revision', (b'branch/',
1667
b'branch token', b'repo token', b'null:',),
1668
b'success', (b'ok',))
1669
client.add_expected_call(
1670
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1671
b'success', (b'ok',))
1672
branch = self.make_remote_branch(transport, client)
1674
result = branch._set_last_revision(NULL_REVISION)
1676
self.assertEqual(None, result)
1677
self.assertFinished(client)
1679
def test_set_nonempty(self):
1680
# set_last_revision_info(N, rev-idN) is translated to calling
1681
# Branch.set_last_revision(path, rev-idN) on the wire.
1682
transport = MemoryTransport()
1683
transport.mkdir('branch')
1684
transport = transport.clone('branch')
1686
client = FakeClient(transport.base)
1687
client.add_expected_call(
1688
b'Branch.get_stacked_on_url', (b'branch/',),
1689
b'error', (b'NotStacked',))
1690
client.add_expected_call(
1691
b'Branch.lock_write', (b'branch/', b'', b''),
1692
b'success', (b'ok', b'branch token', b'repo token'))
1693
client.add_expected_call(
1694
b'Branch.last_revision_info',
1696
b'success', (b'ok', b'0', b'null:'))
1697
lines = [b'rev-id2']
1698
encoded_body = bz2.compress(b'\n'.join(lines))
1699
client.add_success_response_with_body(encoded_body, b'ok')
1700
client.add_expected_call(
1701
b'Branch.set_last_revision', (b'branch/',
1702
b'branch token', b'repo token', b'rev-id2',),
1703
b'success', (b'ok',))
1704
client.add_expected_call(
1705
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1706
b'success', (b'ok',))
1707
branch = self.make_remote_branch(transport, client)
1708
# Lock the branch, reset the record of remote calls.
1710
result = branch._set_last_revision(b'rev-id2')
1712
self.assertEqual(None, result)
1713
self.assertFinished(client)
1715
def test_no_such_revision(self):
1716
transport = MemoryTransport()
1717
transport.mkdir('branch')
1718
transport = transport.clone('branch')
1719
# A response of 'NoSuchRevision' is translated into an exception.
1720
client = FakeClient(transport.base)
1721
client.add_expected_call(
1722
b'Branch.get_stacked_on_url', (b'branch/',),
1723
b'error', (b'NotStacked',))
1724
client.add_expected_call(
1725
b'Branch.lock_write', (b'branch/', b'', b''),
1726
b'success', (b'ok', b'branch token', b'repo token'))
1727
client.add_expected_call(
1728
b'Branch.last_revision_info',
1730
b'success', (b'ok', b'0', b'null:'))
1731
# get_graph calls to construct the revision history, for the set_rh
1734
encoded_body = bz2.compress(b'\n'.join(lines))
1735
client.add_success_response_with_body(encoded_body, b'ok')
1736
client.add_expected_call(
1737
b'Branch.set_last_revision', (b'branch/',
1738
b'branch token', b'repo token', b'rev-id',),
1739
b'error', (b'NoSuchRevision', b'rev-id'))
1740
client.add_expected_call(
1741
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1742
b'success', (b'ok',))
1744
branch = self.make_remote_branch(transport, client)
1747
errors.NoSuchRevision, branch._set_last_revision, b'rev-id')
1749
self.assertFinished(client)
1751
def test_tip_change_rejected(self):
1752
"""TipChangeRejected responses cause a TipChangeRejected exception to
1755
transport = MemoryTransport()
1756
transport.mkdir('branch')
1757
transport = transport.clone('branch')
1758
client = FakeClient(transport.base)
1759
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1760
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1761
client.add_expected_call(
1762
b'Branch.get_stacked_on_url', (b'branch/',),
1763
b'error', (b'NotStacked',))
1764
client.add_expected_call(
1765
b'Branch.lock_write', (b'branch/', b'', b''),
1766
b'success', (b'ok', b'branch token', b'repo token'))
1767
client.add_expected_call(
1768
b'Branch.last_revision_info',
1770
b'success', (b'ok', b'0', b'null:'))
1772
encoded_body = bz2.compress(b'\n'.join(lines))
1773
client.add_success_response_with_body(encoded_body, b'ok')
1774
client.add_expected_call(
1775
b'Branch.set_last_revision', (b'branch/',
1776
b'branch token', b'repo token', b'rev-id',),
1777
b'error', (b'TipChangeRejected', rejection_msg_utf8))
1778
client.add_expected_call(
1779
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1780
b'success', (b'ok',))
1781
branch = self.make_remote_branch(transport, client)
1783
# The 'TipChangeRejected' error response triggered by calling
1784
# set_last_revision_info causes a TipChangeRejected exception.
1785
err = self.assertRaises(
1786
errors.TipChangeRejected,
1787
branch._set_last_revision, b'rev-id')
1788
# The UTF-8 message from the response has been decoded into a unicode
1790
self.assertIsInstance(err.msg, text_type)
1791
self.assertEqual(rejection_msg_unicode, err.msg)
1793
self.assertFinished(client)
1796
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1798
def test_set_last_revision_info(self):
1799
# set_last_revision_info(num, b'rev-id') is translated to calling
1800
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1801
transport = MemoryTransport()
1802
transport.mkdir('branch')
1803
transport = transport.clone('branch')
1804
client = FakeClient(transport.base)
1805
# get_stacked_on_url
1806
client.add_error_response(b'NotStacked')
1808
client.add_success_response(b'ok', b'branch token', b'repo token')
1809
# query the current revision
1810
client.add_success_response(b'ok', b'0', b'null:')
1812
client.add_success_response(b'ok')
1814
client.add_success_response(b'ok')
1816
branch = self.make_remote_branch(transport, client)
1817
# Lock the branch, reset the record of remote calls.
1820
result = branch.set_last_revision_info(1234, b'a-revision-id')
1822
[('call', b'Branch.last_revision_info', (b'branch/',)),
1823
('call', b'Branch.set_last_revision_info',
1824
(b'branch/', b'branch token', b'repo token',
1825
b'1234', b'a-revision-id'))],
1827
self.assertEqual(None, result)
1829
def test_no_such_revision(self):
1830
# A response of 'NoSuchRevision' is translated into an exception.
1831
transport = MemoryTransport()
1832
transport.mkdir('branch')
1833
transport = transport.clone('branch')
1834
client = FakeClient(transport.base)
1835
# get_stacked_on_url
1836
client.add_error_response(b'NotStacked')
1838
client.add_success_response(b'ok', b'branch token', b'repo token')
1840
client.add_error_response(b'NoSuchRevision', b'revid')
1842
client.add_success_response(b'ok')
1844
branch = self.make_remote_branch(transport, client)
1845
# Lock the branch, reset the record of remote calls.
1850
errors.NoSuchRevision, branch.set_last_revision_info, 123, b'revid')
1853
def test_backwards_compatibility(self):
1854
"""If the server does not support the Branch.set_last_revision_info
1855
verb (which is new in 1.4), then the client falls back to VFS methods.
1857
# This test is a little messy. Unlike most tests in this file, it
1858
# doesn't purely test what a Remote* object sends over the wire, and
1859
# how it reacts to responses from the wire. It instead relies partly
1860
# on asserting that the RemoteBranch will call
1861
# self._real_branch.set_last_revision_info(...).
1863
# First, set up our RemoteBranch with a FakeClient that raises
1864
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1865
transport = MemoryTransport()
1866
transport.mkdir('branch')
1867
transport = transport.clone('branch')
1868
client = FakeClient(transport.base)
1869
client.add_expected_call(
1870
b'Branch.get_stacked_on_url', (b'branch/',),
1871
b'error', (b'NotStacked',))
1872
client.add_expected_call(
1873
b'Branch.last_revision_info',
1875
b'success', (b'ok', b'0', b'null:'))
1876
client.add_expected_call(
1877
b'Branch.set_last_revision_info',
1878
(b'branch/', b'branch token', b'repo token', b'1234', b'a-revision-id',),
1879
b'unknown', b'Branch.set_last_revision_info')
1881
branch = self.make_remote_branch(transport, client)
1883
class StubRealBranch(object):
1887
def set_last_revision_info(self, revno, revision_id):
1889
('set_last_revision_info', revno, revision_id))
1891
def _clear_cached_state(self):
1893
real_branch = StubRealBranch()
1894
branch._real_branch = real_branch
1895
self.lock_remote_branch(branch)
1897
# Call set_last_revision_info, and verify it behaved as expected.
1898
branch.set_last_revision_info(1234, b'a-revision-id')
1900
[('set_last_revision_info', 1234, b'a-revision-id')],
1902
self.assertFinished(client)
1904
def test_unexpected_error(self):
1905
# If the server sends an error the client doesn't understand, it gets
1906
# turned into an UnknownErrorFromSmartServer, which is presented as a
1907
# non-internal error to the user.
1908
transport = MemoryTransport()
1909
transport.mkdir('branch')
1910
transport = transport.clone('branch')
1911
client = FakeClient(transport.base)
1912
# get_stacked_on_url
1913
client.add_error_response(b'NotStacked')
1915
client.add_success_response(b'ok', b'branch token', b'repo token')
1917
client.add_error_response(b'UnexpectedError')
1919
client.add_success_response(b'ok')
1921
branch = self.make_remote_branch(transport, client)
1922
# Lock the branch, reset the record of remote calls.
1926
err = self.assertRaises(
1927
errors.UnknownErrorFromSmartServer,
1928
branch.set_last_revision_info, 123, b'revid')
1929
self.assertEqual((b'UnexpectedError',), err.error_tuple)
1932
def test_tip_change_rejected(self):
1933
"""TipChangeRejected responses cause a TipChangeRejected exception to
1936
transport = MemoryTransport()
1937
transport.mkdir('branch')
1938
transport = transport.clone('branch')
1939
client = FakeClient(transport.base)
1940
# get_stacked_on_url
1941
client.add_error_response(b'NotStacked')
1943
client.add_success_response(b'ok', b'branch token', b'repo token')
1945
client.add_error_response(b'TipChangeRejected', b'rejection message')
1947
client.add_success_response(b'ok')
1949
branch = self.make_remote_branch(transport, client)
1950
# Lock the branch, reset the record of remote calls.
1952
self.addCleanup(branch.unlock)
1955
# The 'TipChangeRejected' error response triggered by calling
1956
# set_last_revision_info causes a TipChangeRejected exception.
1957
err = self.assertRaises(
1958
errors.TipChangeRejected,
1959
branch.set_last_revision_info, 123, b'revid')
1960
self.assertEqual('rejection message', err.msg)
1963
class TestBranchGetSetConfig(RemoteBranchTestCase):
1965
def test_get_branch_conf(self):
1966
# in an empty branch we decode the response properly
1967
client = FakeClient()
1968
client.add_expected_call(
1969
b'Branch.get_stacked_on_url', (b'memory:///',),
1970
b'error', (b'NotStacked',),)
1971
client.add_success_response_with_body(b'# config file body', b'ok')
1972
transport = MemoryTransport()
1973
branch = self.make_remote_branch(transport, client)
1974
config = branch.get_config()
1975
config.has_explicit_nickname()
1977
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
1978
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
1981
def test_get_multi_line_branch_conf(self):
1982
# Make sure that multiple-line branch.conf files are supported
1984
# https://bugs.launchpad.net/bzr/+bug/354075
1985
client = FakeClient()
1986
client.add_expected_call(
1987
b'Branch.get_stacked_on_url', (b'memory:///',),
1988
b'error', (b'NotStacked',),)
1989
client.add_success_response_with_body(b'a = 1\nb = 2\nc = 3\n', b'ok')
1990
transport = MemoryTransport()
1991
branch = self.make_remote_branch(transport, client)
1992
config = branch.get_config()
1993
self.assertEqual(u'2', config.get_user_option('b'))
1995
def test_set_option(self):
1996
client = FakeClient()
1997
client.add_expected_call(
1998
b'Branch.get_stacked_on_url', (b'memory:///',),
1999
b'error', (b'NotStacked',),)
2000
client.add_expected_call(
2001
b'Branch.lock_write', (b'memory:///', b'', b''),
2002
b'success', (b'ok', b'branch token', b'repo token'))
2003
client.add_expected_call(
2004
b'Branch.set_config_option', (b'memory:///', b'branch token',
2005
b'repo token', b'foo', b'bar', b''),
2007
client.add_expected_call(
2008
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2009
b'success', (b'ok',))
2010
transport = MemoryTransport()
2011
branch = self.make_remote_branch(transport, client)
2013
config = branch._get_config()
2014
config.set_option('foo', 'bar')
2016
self.assertFinished(client)
2018
def test_set_option_with_dict(self):
2019
client = FakeClient()
2020
client.add_expected_call(
2021
b'Branch.get_stacked_on_url', (b'memory:///',),
2022
b'error', (b'NotStacked',),)
2023
client.add_expected_call(
2024
b'Branch.lock_write', (b'memory:///', b'', b''),
2025
b'success', (b'ok', b'branch token', b'repo token'))
2026
encoded_dict_value = b'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
2027
client.add_expected_call(
2028
b'Branch.set_config_option_dict', (b'memory:///', b'branch token',
2029
b'repo token', encoded_dict_value, b'foo', b''),
2031
client.add_expected_call(
2032
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2033
b'success', (b'ok',))
2034
transport = MemoryTransport()
2035
branch = self.make_remote_branch(transport, client)
2037
config = branch._get_config()
2039
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2042
self.assertFinished(client)
2044
def test_set_option_with_bool(self):
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_expected_call(
2050
b'Branch.lock_write', (b'memory:///', b'', b''),
2051
b'success', (b'ok', b'branch token', b'repo token'))
2052
client.add_expected_call(
2053
b'Branch.set_config_option', (b'memory:///', b'branch token',
2054
b'repo token', b'True', b'foo', b''),
2056
client.add_expected_call(
2057
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2058
b'success', (b'ok',))
2059
transport = MemoryTransport()
2060
branch = self.make_remote_branch(transport, client)
2062
config = branch._get_config()
2063
config.set_option(True, 'foo')
2065
self.assertFinished(client)
2067
def test_backwards_compat_set_option(self):
2068
self.setup_smart_server_with_call_log()
2069
branch = self.make_branch('.')
2070
verb = b'Branch.set_config_option'
2071
self.disable_verb(verb)
2073
self.addCleanup(branch.unlock)
2074
self.reset_smart_call_log()
2075
branch._get_config().set_option('value', 'name')
2076
self.assertLength(11, self.hpss_calls)
2077
self.assertEqual('value', branch._get_config().get_option('name'))
2079
def test_backwards_compat_set_option_with_dict(self):
2080
self.setup_smart_server_with_call_log()
2081
branch = self.make_branch('.')
2082
verb = b'Branch.set_config_option_dict'
2083
self.disable_verb(verb)
2085
self.addCleanup(branch.unlock)
2086
self.reset_smart_call_log()
2087
config = branch._get_config()
2088
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2089
config.set_option(value_dict, 'name')
2090
self.assertLength(11, self.hpss_calls)
2091
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2094
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2096
def test_get_branch_conf(self):
2097
# in an empty branch we decode the response properly
2098
client = FakeClient()
2099
client.add_expected_call(
2100
b'Branch.get_stacked_on_url', (b'memory:///',),
2101
b'error', (b'NotStacked',),)
2102
client.add_success_response_with_body(b'# config file body', b'ok')
2103
transport = MemoryTransport()
2104
branch = self.make_remote_branch(transport, client)
2105
config = branch.get_config_stack()
2107
config.get("log_format")
2109
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2110
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
2113
def test_set_branch_conf(self):
2114
client = FakeClient()
2115
client.add_expected_call(
2116
b'Branch.get_stacked_on_url', (b'memory:///',),
2117
b'error', (b'NotStacked',),)
2118
client.add_expected_call(
2119
b'Branch.lock_write', (b'memory:///', b'', b''),
2120
b'success', (b'ok', b'branch token', b'repo token'))
2121
client.add_expected_call(
2122
b'Branch.get_config_file', (b'memory:///', ),
2123
b'success', (b'ok', ), b"# line 1\n")
2124
client.add_expected_call(
2125
b'Branch.get_config_file', (b'memory:///', ),
2126
b'success', (b'ok', ), b"# line 1\n")
2127
client.add_expected_call(
2128
b'Branch.put_config_file', (b'memory:///', b'branch token',
2130
b'success', (b'ok',))
2131
client.add_expected_call(
2132
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2133
b'success', (b'ok',))
2134
transport = MemoryTransport()
2135
branch = self.make_remote_branch(transport, client)
2137
config = branch.get_config_stack()
2138
config.set('email', 'The Dude <lebowski@example.com>')
2140
self.assertFinished(client)
2142
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2143
('call', b'Branch.lock_write', (b'memory:///', b'', b'')),
2144
('call_expecting_body', b'Branch.get_config_file',
2146
('call_expecting_body', b'Branch.get_config_file',
2148
('call_with_body_bytes_expecting_body', b'Branch.put_config_file',
2149
(b'memory:///', b'branch token', b'repo token'),
2150
b'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2151
('call', b'Branch.unlock',
2152
(b'memory:///', b'branch token', b'repo token'))],
2156
class TestBranchLockWrite(RemoteBranchTestCase):
2158
def test_lock_write_unlockable(self):
2159
transport = MemoryTransport()
2160
client = FakeClient(transport.base)
2161
client.add_expected_call(
2162
b'Branch.get_stacked_on_url', (b'quack/',),
2163
b'error', (b'NotStacked',),)
2164
client.add_expected_call(
2165
b'Branch.lock_write', (b'quack/', b'', b''),
2166
b'error', (b'UnlockableTransport',))
2167
transport.mkdir('quack')
2168
transport = transport.clone('quack')
2169
branch = self.make_remote_branch(transport, client)
2170
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2171
self.assertFinished(client)
2174
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2176
def test_simple(self):
2177
transport = MemoryTransport()
2178
client = FakeClient(transport.base)
2179
client.add_expected_call(
2180
b'Branch.get_stacked_on_url', (b'quack/',),
2181
b'error', (b'NotStacked',),)
2182
client.add_expected_call(
2183
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2184
b'success', (b'ok', b'0',),)
2185
client.add_expected_call(
2186
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2187
b'error', (b'NoSuchRevision', b'unknown',),)
2188
transport.mkdir('quack')
2189
transport = transport.clone('quack')
2190
branch = self.make_remote_branch(transport, client)
2191
self.assertEqual(0, branch.revision_id_to_revno(b'null:'))
2192
self.assertRaises(errors.NoSuchRevision,
2193
branch.revision_id_to_revno, b'unknown')
2194
self.assertFinished(client)
2196
def test_dotted(self):
2197
transport = MemoryTransport()
2198
client = FakeClient(transport.base)
2199
client.add_expected_call(
2200
b'Branch.get_stacked_on_url', (b'quack/',),
2201
b'error', (b'NotStacked',),)
2202
client.add_expected_call(
2203
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2204
b'success', (b'ok', b'0',),)
2205
client.add_expected_call(
2206
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2207
b'error', (b'NoSuchRevision', b'unknown',),)
2208
transport.mkdir('quack')
2209
transport = transport.clone('quack')
2210
branch = self.make_remote_branch(transport, client)
2211
self.assertEqual((0, ), branch.revision_id_to_dotted_revno(b'null:'))
2212
self.assertRaises(errors.NoSuchRevision,
2213
branch.revision_id_to_dotted_revno, b'unknown')
2214
self.assertFinished(client)
2216
def test_ghost_revid(self):
2217
transport = MemoryTransport()
2218
client = FakeClient(transport.base)
2219
client.add_expected_call(
2220
b'Branch.get_stacked_on_url', (b'quack/',),
2221
b'error', (b'NotStacked',),)
2222
# Some older versions of bzr/brz didn't explicitly return
2223
# GhostRevisionsHaveNoRevno
2224
client.add_expected_call(
2225
b'Branch.revision_id_to_revno', (b'quack/', b'revid'),
2226
b'error', (b'error', b'GhostRevisionsHaveNoRevno',
2227
b'The reivison {revid} was not found because there was '
2228
b'a ghost at {ghost-revid}'))
2229
client.add_expected_call(
2230
b'Branch.revision_id_to_revno', (b'quack/', b'revid'),
2231
b'error', (b'GhostRevisionsHaveNoRevno', b'revid', b'ghost-revid',))
2232
transport.mkdir('quack')
2233
transport = transport.clone('quack')
2234
branch = self.make_remote_branch(transport, client)
2235
self.assertRaises(errors.GhostRevisionsHaveNoRevno,
2236
branch.revision_id_to_dotted_revno, b'revid')
2237
self.assertRaises(errors.GhostRevisionsHaveNoRevno,
2238
branch.revision_id_to_dotted_revno, b'revid')
2239
self.assertFinished(client)
2241
def test_dotted_no_smart_verb(self):
2242
self.setup_smart_server_with_call_log()
2243
branch = self.make_branch('.')
2244
self.disable_verb(b'Branch.revision_id_to_revno')
2245
self.reset_smart_call_log()
2246
self.assertEqual((0, ),
2247
branch.revision_id_to_dotted_revno(b'null:'))
2248
self.assertLength(8, self.hpss_calls)
2251
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2253
def test__get_config(self):
2254
client = FakeClient()
2255
client.add_success_response_with_body(b'default_stack_on = /\n', b'ok')
2256
transport = MemoryTransport()
2257
bzrdir = self.make_remote_bzrdir(transport, client)
2258
config = bzrdir.get_config()
2259
self.assertEqual('/', config.get_default_stack_on())
2261
[('call_expecting_body', b'BzrDir.get_config_file',
2265
def test_set_option_uses_vfs(self):
2266
self.setup_smart_server_with_call_log()
2267
bzrdir = self.make_controldir('.')
2268
self.reset_smart_call_log()
2269
config = bzrdir.get_config()
2270
config.set_default_stack_on('/')
2271
self.assertLength(4, self.hpss_calls)
2273
def test_backwards_compat_get_option(self):
2274
self.setup_smart_server_with_call_log()
2275
bzrdir = self.make_controldir('.')
2276
verb = b'BzrDir.get_config_file'
2277
self.disable_verb(verb)
2278
self.reset_smart_call_log()
2279
self.assertEqual(None,
2280
bzrdir._get_config().get_option('default_stack_on'))
2281
self.assertLength(4, self.hpss_calls)
2284
class TestTransportIsReadonly(tests.TestCase):
2286
def test_true(self):
2287
client = FakeClient()
2288
client.add_success_response(b'yes')
2289
transport = RemoteTransport('bzr://example.com/', medium=False,
2291
self.assertEqual(True, transport.is_readonly())
2293
[('call', b'Transport.is_readonly', ())],
2296
def test_false(self):
2297
client = FakeClient()
2298
client.add_success_response(b'no')
2299
transport = RemoteTransport('bzr://example.com/', medium=False,
2301
self.assertEqual(False, transport.is_readonly())
2303
[('call', b'Transport.is_readonly', ())],
2306
def test_error_from_old_server(self):
2307
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2309
Clients should treat it as a "no" response, because is_readonly is only
2310
advisory anyway (a transport could be read-write, but then the
2311
underlying filesystem could be readonly anyway).
2313
client = FakeClient()
2314
client.add_unknown_method_response(b'Transport.is_readonly')
2315
transport = RemoteTransport('bzr://example.com/', medium=False,
2317
self.assertEqual(False, transport.is_readonly())
2319
[('call', b'Transport.is_readonly', ())],
2323
class TestTransportMkdir(tests.TestCase):
2325
def test_permissiondenied(self):
2326
client = FakeClient()
2327
client.add_error_response(
2328
b'PermissionDenied', b'remote path', b'extra')
2329
transport = RemoteTransport('bzr://example.com/', medium=False,
2331
exc = self.assertRaises(
2332
errors.PermissionDenied, transport.mkdir, 'client path')
2333
expected_error = errors.PermissionDenied('/client path', 'extra')
2334
self.assertEqual(expected_error, exc)
2337
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2339
def test_defaults_to_none(self):
2340
t = RemoteSSHTransport('bzr+ssh://example.com')
2341
self.assertIs(None, t._get_credentials()[0])
2343
def test_uses_authentication_config(self):
2344
conf = config.AuthenticationConfig()
2345
conf._get_config().update(
2346
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2349
t = RemoteSSHTransport('bzr+ssh://example.com')
2350
self.assertEqual('bar', t._get_credentials()[0])
2353
class TestRemoteRepository(TestRemote):
2354
"""Base for testing RemoteRepository protocol usage.
2356
These tests contain frozen requests and responses. We want any changes to
2357
what is sent or expected to be require a thoughtful update to these tests
2358
because they might break compatibility with different-versioned servers.
2361
def setup_fake_client_and_repository(self, transport_path):
2362
"""Create the fake client and repository for testing with.
2364
There's no real server here; we just have canned responses sent
2367
:param transport_path: Path below the root of the MemoryTransport
2368
where the repository will be created.
2370
transport = MemoryTransport()
2371
transport.mkdir(transport_path)
2372
client = FakeClient(transport.base)
2373
transport = transport.clone(transport_path)
2374
# we do not want bzrdir to make any remote calls
2375
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2377
repo = RemoteRepository(bzrdir, None, _client=client)
2381
def remoted_description(format):
2382
return 'Remote: ' + format.get_format_description()
2385
class TestBranchFormat(tests.TestCase):
2387
def test_get_format_description(self):
2388
remote_format = RemoteBranchFormat()
2389
real_format = branch.format_registry.get_default()
2390
remote_format._network_name = real_format.network_name()
2391
self.assertEqual(remoted_description(real_format),
2392
remote_format.get_format_description())
2395
class TestRepositoryFormat(TestRemoteRepository):
2397
def test_fast_delta(self):
2398
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2399
true_format = RemoteRepositoryFormat()
2400
true_format._network_name = true_name
2401
self.assertEqual(True, true_format.fast_deltas)
2402
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2403
false_format = RemoteRepositoryFormat()
2404
false_format._network_name = false_name
2405
self.assertEqual(False, false_format.fast_deltas)
2407
def test_get_format_description(self):
2408
remote_repo_format = RemoteRepositoryFormat()
2409
real_format = repository.format_registry.get_default()
2410
remote_repo_format._network_name = real_format.network_name()
2411
self.assertEqual(remoted_description(real_format),
2412
remote_repo_format.get_format_description())
2415
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2417
def test_empty(self):
2418
transport_path = 'quack'
2419
repo, client = self.setup_fake_client_and_repository(transport_path)
2420
client.add_success_response_with_body(b'', b'ok')
2421
self.assertEqual([], repo.all_revision_ids())
2423
[('call_expecting_body', b'Repository.all_revision_ids',
2427
def test_with_some_content(self):
2428
transport_path = 'quack'
2429
repo, client = self.setup_fake_client_and_repository(transport_path)
2430
client.add_success_response_with_body(
2431
b'rev1\nrev2\nanotherrev\n', b'ok')
2433
set([b"rev1", b"rev2", b"anotherrev"]),
2434
set(repo.all_revision_ids()))
2436
[('call_expecting_body', b'Repository.all_revision_ids',
2441
class TestRepositoryGatherStats(TestRemoteRepository):
2443
def test_revid_none(self):
2444
# ('ok',), body with revisions and size
2445
transport_path = 'quack'
2446
repo, client = self.setup_fake_client_and_repository(transport_path)
2447
client.add_success_response_with_body(
2448
b'revisions: 2\nsize: 18\n', b'ok')
2449
result = repo.gather_stats(None)
2451
[('call_expecting_body', b'Repository.gather_stats',
2452
(b'quack/', b'', b'no'))],
2454
self.assertEqual({'revisions': 2, 'size': 18}, result)
2456
def test_revid_no_committers(self):
2457
# ('ok',), body without committers
2458
body = (b'firstrev: 123456.300 3600\n'
2459
b'latestrev: 654231.400 0\n'
2462
transport_path = 'quick'
2463
revid = u'\xc8'.encode('utf8')
2464
repo, client = self.setup_fake_client_and_repository(transport_path)
2465
client.add_success_response_with_body(body, b'ok')
2466
result = repo.gather_stats(revid)
2468
[('call_expecting_body', b'Repository.gather_stats',
2469
(b'quick/', revid, b'no'))],
2471
self.assertEqual({'revisions': 2, 'size': 18,
2472
'firstrev': (123456.300, 3600),
2473
'latestrev': (654231.400, 0), },
2476
def test_revid_with_committers(self):
2477
# ('ok',), body with committers
2478
body = (b'committers: 128\n'
2479
b'firstrev: 123456.300 3600\n'
2480
b'latestrev: 654231.400 0\n'
2483
transport_path = 'buick'
2484
revid = u'\xc8'.encode('utf8')
2485
repo, client = self.setup_fake_client_and_repository(transport_path)
2486
client.add_success_response_with_body(body, b'ok')
2487
result = repo.gather_stats(revid, True)
2489
[('call_expecting_body', b'Repository.gather_stats',
2490
(b'buick/', revid, b'yes'))],
2492
self.assertEqual({'revisions': 2, 'size': 18,
2494
'firstrev': (123456.300, 3600),
2495
'latestrev': (654231.400, 0), },
2499
class TestRepositoryBreakLock(TestRemoteRepository):
2501
def test_break_lock(self):
2502
transport_path = 'quack'
2503
repo, client = self.setup_fake_client_and_repository(transport_path)
2504
client.add_success_response(b'ok')
2507
[('call', b'Repository.break_lock', (b'quack/',))],
2511
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2513
def test_get_serializer_format(self):
2514
transport_path = 'hill'
2515
repo, client = self.setup_fake_client_and_repository(transport_path)
2516
client.add_success_response(b'ok', b'7')
2517
self.assertEqual(b'7', repo.get_serializer_format())
2519
[('call', b'VersionedFileRepository.get_serializer_format',
2524
class TestRepositoryReconcile(TestRemoteRepository):
2526
def test_reconcile(self):
2527
transport_path = 'hill'
2528
repo, client = self.setup_fake_client_and_repository(transport_path)
2529
body = (b"garbage_inventories: 2\n"
2530
b"inconsistent_parents: 3\n")
2531
client.add_expected_call(
2532
b'Repository.lock_write', (b'hill/', b''),
2533
b'success', (b'ok', b'a token'))
2534
client.add_success_response_with_body(body, b'ok')
2535
reconciler = repo.reconcile()
2537
[('call', b'Repository.lock_write', (b'hill/', b'')),
2538
('call_expecting_body', b'Repository.reconcile',
2539
(b'hill/', b'a token'))],
2541
self.assertEqual(2, reconciler.garbage_inventories)
2542
self.assertEqual(3, reconciler.inconsistent_parents)
2545
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2547
def test_text(self):
2548
# ('ok',), body with signature text
2549
transport_path = 'quack'
2550
repo, client = self.setup_fake_client_and_repository(transport_path)
2551
client.add_success_response_with_body(
2553
self.assertEqual(b"THETEXT", repo.get_signature_text(b"revid"))
2555
[('call_expecting_body', b'Repository.get_revision_signature_text',
2556
(b'quack/', b'revid'))],
2559
def test_no_signature(self):
2560
transport_path = 'quick'
2561
repo, client = self.setup_fake_client_and_repository(transport_path)
2562
client.add_error_response(b'nosuchrevision', b'unknown')
2563
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2566
[('call_expecting_body', b'Repository.get_revision_signature_text',
2567
(b'quick/', b'unknown'))],
2571
class TestRepositoryGetGraph(TestRemoteRepository):
2573
def test_get_graph(self):
2574
# get_graph returns a graph with a custom parents provider.
2575
transport_path = 'quack'
2576
repo, client = self.setup_fake_client_and_repository(transport_path)
2577
graph = repo.get_graph()
2578
self.assertNotEqual(graph._parents_provider, repo)
2581
class TestRepositoryAddSignatureText(TestRemoteRepository):
2583
def test_add_signature_text(self):
2584
transport_path = 'quack'
2585
repo, client = self.setup_fake_client_and_repository(transport_path)
2586
client.add_expected_call(
2587
b'Repository.lock_write', (b'quack/', b''),
2588
b'success', (b'ok', b'a token'))
2589
client.add_expected_call(
2590
b'Repository.start_write_group', (b'quack/', b'a token'),
2591
b'success', (b'ok', (b'token1', )))
2592
client.add_expected_call(
2593
b'Repository.add_signature_text', (b'quack/', b'a token', b'rev1',
2595
b'success', (b'ok', ), None)
2597
repo.start_write_group()
2599
None, repo.add_signature_text(b"rev1", b"every bloody emperor"))
2601
('call_with_body_bytes_expecting_body',
2602
b'Repository.add_signature_text',
2603
(b'quack/', b'a token', b'rev1', b'token1'),
2604
b'every bloody emperor'),
2608
class TestRepositoryGetParentMap(TestRemoteRepository):
2610
def test_get_parent_map_caching(self):
2611
# get_parent_map returns from cache until unlock()
2612
# setup a reponse with two revisions
2613
r1 = u'\u0e33'.encode('utf8')
2614
r2 = u'\u0dab'.encode('utf8')
2615
lines = [b' '.join([r2, r1]), r1]
2616
encoded_body = bz2.compress(b'\n'.join(lines))
2618
transport_path = 'quack'
2619
repo, client = self.setup_fake_client_and_repository(transport_path)
2620
client.add_success_response_with_body(encoded_body, b'ok')
2621
client.add_success_response_with_body(encoded_body, b'ok')
2623
graph = repo.get_graph()
2624
parents = graph.get_parent_map([r2])
2625
self.assertEqual({r2: (r1,)}, parents)
2626
# locking and unlocking deeper should not reset
2629
parents = graph.get_parent_map([r1])
2630
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2632
[('call_with_body_bytes_expecting_body',
2633
b'Repository.get_parent_map', (b'quack/',
2634
b'include-missing:', r2),
2638
# now we call again, and it should use the second response.
2640
graph = repo.get_graph()
2641
parents = graph.get_parent_map([r1])
2642
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2644
[('call_with_body_bytes_expecting_body',
2645
b'Repository.get_parent_map', (b'quack/',
2646
b'include-missing:', r2),
2648
('call_with_body_bytes_expecting_body',
2649
b'Repository.get_parent_map', (b'quack/',
2650
b'include-missing:', r1),
2656
def test_get_parent_map_reconnects_if_unknown_method(self):
2657
transport_path = 'quack'
2658
rev_id = b'revision-id'
2659
repo, client = self.setup_fake_client_and_repository(transport_path)
2660
client.add_unknown_method_response(b'Repository.get_parent_map')
2661
client.add_success_response_with_body(rev_id, b'ok')
2662
self.assertFalse(client._medium._is_remote_before((1, 2)))
2663
parents = repo.get_parent_map([rev_id])
2665
[('call_with_body_bytes_expecting_body',
2666
b'Repository.get_parent_map',
2667
(b'quack/', b'include-missing:', rev_id), b'\n\n0'),
2668
('disconnect medium',),
2669
('call_expecting_body', b'Repository.get_revision_graph',
2672
# The medium is now marked as being connected to an older server
2673
self.assertTrue(client._medium._is_remote_before((1, 2)))
2674
self.assertEqual({rev_id: (b'null:',)}, parents)
2676
def test_get_parent_map_fallback_parentless_node(self):
2677
"""get_parent_map falls back to get_revision_graph on old servers. The
2678
results from get_revision_graph are tweaked to match the get_parent_map
2681
Specifically, a {key: ()} result from get_revision_graph means "no
2682
parents" for that key, which in get_parent_map results should be
2683
represented as {key: ('null:',)}.
2685
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2687
rev_id = b'revision-id'
2688
transport_path = 'quack'
2689
repo, client = self.setup_fake_client_and_repository(transport_path)
2690
client.add_success_response_with_body(rev_id, b'ok')
2691
client._medium._remember_remote_is_before((1, 2))
2692
parents = repo.get_parent_map([rev_id])
2694
[('call_expecting_body', b'Repository.get_revision_graph',
2697
self.assertEqual({rev_id: (b'null:',)}, parents)
2699
def test_get_parent_map_unexpected_response(self):
2700
repo, client = self.setup_fake_client_and_repository('path')
2701
client.add_success_response(b'something unexpected!')
2703
errors.UnexpectedSmartServerResponse,
2704
repo.get_parent_map, [b'a-revision-id'])
2706
def test_get_parent_map_negative_caches_missing_keys(self):
2707
self.setup_smart_server_with_call_log()
2708
repo = self.make_repository('foo')
2709
self.assertIsInstance(repo, RemoteRepository)
2711
self.addCleanup(repo.unlock)
2712
self.reset_smart_call_log()
2713
graph = repo.get_graph()
2715
{}, graph.get_parent_map([b'some-missing', b'other-missing']))
2716
self.assertLength(1, self.hpss_calls)
2717
# No call if we repeat this
2718
self.reset_smart_call_log()
2719
graph = repo.get_graph()
2721
{}, graph.get_parent_map([b'some-missing', b'other-missing']))
2722
self.assertLength(0, self.hpss_calls)
2723
# Asking for more unknown keys makes a request.
2724
self.reset_smart_call_log()
2725
graph = repo.get_graph()
2727
{}, graph.get_parent_map([b'some-missing', b'other-missing',
2729
self.assertLength(1, self.hpss_calls)
2731
def disableExtraResults(self):
2732
self.overrideAttr(SmartServerRepositoryGetParentMap,
2733
'no_extra_results', True)
2735
def test_null_cached_missing_and_stop_key(self):
2736
self.setup_smart_server_with_call_log()
2737
# Make a branch with a single revision.
2738
builder = self.make_branch_builder('foo')
2739
builder.start_series()
2740
builder.build_snapshot(None, [
2741
('add', ('', b'root-id', 'directory', ''))],
2742
revision_id=b'first')
2743
builder.finish_series()
2744
branch = builder.get_branch()
2745
repo = branch.repository
2746
self.assertIsInstance(repo, RemoteRepository)
2747
# Stop the server from sending extra results.
2748
self.disableExtraResults()
2750
self.addCleanup(repo.unlock)
2751
self.reset_smart_call_log()
2752
graph = repo.get_graph()
2753
# Query for b'first' and b'null:'. Because b'null:' is a parent of
2754
# 'first' it will be a candidate for the stop_keys of subsequent
2755
# requests, and because b'null:' was queried but not returned it will
2756
# be cached as missing.
2757
self.assertEqual({b'first': (b'null:',)},
2758
graph.get_parent_map([b'first', b'null:']))
2759
# Now query for another key. This request will pass along a recipe of
2760
# start and stop keys describing the already cached results, and this
2761
# recipe's revision count must be correct (or else it will trigger an
2762
# error from the server).
2763
self.assertEqual({}, graph.get_parent_map([b'another-key']))
2764
# This assertion guards against disableExtraResults silently failing to
2765
# work, thus invalidating the test.
2766
self.assertLength(2, self.hpss_calls)
2768
def test_get_parent_map_gets_ghosts_from_result(self):
2769
# asking for a revision should negatively cache close ghosts in its
2771
self.setup_smart_server_with_call_log()
2772
tree = self.make_branch_and_memory_tree('foo')
2773
with tree.lock_write():
2774
builder = treebuilder.TreeBuilder()
2775
builder.start_tree(tree)
2777
builder.finish_tree()
2778
tree.set_parent_ids([b'non-existant'],
2779
allow_leftmost_as_ghost=True)
2780
rev_id = tree.commit('')
2782
self.addCleanup(tree.unlock)
2783
repo = tree.branch.repository
2784
self.assertIsInstance(repo, RemoteRepository)
2786
repo.get_parent_map([rev_id])
2787
self.reset_smart_call_log()
2788
# Now asking for rev_id's ghost parent should not make calls
2789
self.assertEqual({}, repo.get_parent_map([b'non-existant']))
2790
self.assertLength(0, self.hpss_calls)
2792
def test_exposes_get_cached_parent_map(self):
2793
"""RemoteRepository exposes get_cached_parent_map from
2796
r1 = u'\u0e33'.encode('utf8')
2797
r2 = u'\u0dab'.encode('utf8')
2798
lines = [b' '.join([r2, r1]), r1]
2799
encoded_body = bz2.compress(b'\n'.join(lines))
2801
transport_path = 'quack'
2802
repo, client = self.setup_fake_client_and_repository(transport_path)
2803
client.add_success_response_with_body(encoded_body, b'ok')
2805
# get_cached_parent_map should *not* trigger an RPC
2806
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2807
self.assertEqual([], client._calls)
2808
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2809
self.assertEqual({r1: (NULL_REVISION,)},
2810
repo.get_cached_parent_map([r1]))
2812
[('call_with_body_bytes_expecting_body',
2813
b'Repository.get_parent_map', (b'quack/',
2814
b'include-missing:', r2),
2820
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2822
def test_allows_new_revisions(self):
2823
"""get_parent_map's results can be updated by commit."""
2824
smart_server = test_server.SmartTCPServer_for_testing()
2825
self.start_server(smart_server)
2826
self.make_branch('branch')
2827
branch = Branch.open(smart_server.get_url() + '/branch')
2828
tree = branch.create_checkout('tree', lightweight=True)
2830
self.addCleanup(tree.unlock)
2831
graph = tree.branch.repository.get_graph()
2832
# This provides an opportunity for the missing rev-id to be cached.
2833
self.assertEqual({}, graph.get_parent_map([b'rev1']))
2834
tree.commit('message', rev_id=b'rev1')
2835
graph = tree.branch.repository.get_graph()
2836
self.assertEqual({b'rev1': (b'null:',)},
2837
graph.get_parent_map([b'rev1']))
2840
class TestRepositoryGetRevisions(TestRemoteRepository):
2842
def test_hpss_missing_revision(self):
2843
transport_path = 'quack'
2844
repo, client = self.setup_fake_client_and_repository(transport_path)
2845
client.add_success_response_with_body(
2847
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2848
[b'somerev1', b'anotherrev2'])
2850
[('call_with_body_bytes_expecting_body',
2851
b'Repository.iter_revisions', (b'quack/', ),
2852
b"somerev1\nanotherrev2")],
2855
def test_hpss_get_single_revision(self):
2856
transport_path = 'quack'
2857
repo, client = self.setup_fake_client_and_repository(transport_path)
2858
somerev1 = Revision(b"somerev1")
2859
somerev1.committer = "Joe Committer <joe@example.com>"
2860
somerev1.timestamp = 1321828927
2861
somerev1.timezone = -60
2862
somerev1.inventory_sha1 = b"691b39be74c67b1212a75fcb19c433aaed903c2b"
2863
somerev1.message = "Message"
2864
body = zlib.compress(b''.join(chk_bencode_serializer.write_revision_to_lines(
2866
# Split up body into two bits to make sure the zlib compression object
2867
# gets data fed twice.
2868
client.add_success_response_with_body(
2869
[body[:10], body[10:]], b'ok', b'10')
2870
revs = repo.get_revisions([b'somerev1'])
2871
self.assertEqual(revs, [somerev1])
2873
[('call_with_body_bytes_expecting_body',
2874
b'Repository.iter_revisions',
2875
(b'quack/', ), b"somerev1")],
2879
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2881
def test_null_revision(self):
2882
# a null revision has the predictable result {}, we should have no wire
2883
# traffic when calling it with this argument
2884
transport_path = 'empty'
2885
repo, client = self.setup_fake_client_and_repository(transport_path)
2886
client.add_success_response(b'notused')
2887
# actual RemoteRepository.get_revision_graph is gone, but there's an
2888
# equivalent private method for testing
2889
result = repo._get_revision_graph(NULL_REVISION)
2890
self.assertEqual([], client._calls)
2891
self.assertEqual({}, result)
2893
def test_none_revision(self):
2894
# with none we want the entire graph
2895
r1 = u'\u0e33'.encode('utf8')
2896
r2 = u'\u0dab'.encode('utf8')
2897
lines = [b' '.join([r2, r1]), r1]
2898
encoded_body = b'\n'.join(lines)
2900
transport_path = 'sinhala'
2901
repo, client = self.setup_fake_client_and_repository(transport_path)
2902
client.add_success_response_with_body(encoded_body, b'ok')
2903
# actual RemoteRepository.get_revision_graph is gone, but there's an
2904
# equivalent private method for testing
2905
result = repo._get_revision_graph(None)
2907
[('call_expecting_body', b'Repository.get_revision_graph',
2908
(b'sinhala/', b''))],
2910
self.assertEqual({r1: (), r2: (r1, )}, result)
2912
def test_specific_revision(self):
2913
# with a specific revision we want the graph for that
2914
# with none we want the entire graph
2915
r11 = u'\u0e33'.encode('utf8')
2916
r12 = u'\xc9'.encode('utf8')
2917
r2 = u'\u0dab'.encode('utf8')
2918
lines = [b' '.join([r2, r11, r12]), r11, r12]
2919
encoded_body = b'\n'.join(lines)
2921
transport_path = 'sinhala'
2922
repo, client = self.setup_fake_client_and_repository(transport_path)
2923
client.add_success_response_with_body(encoded_body, b'ok')
2924
result = repo._get_revision_graph(r2)
2926
[('call_expecting_body', b'Repository.get_revision_graph',
2927
(b'sinhala/', r2))],
2929
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2931
def test_no_such_revision(self):
2933
transport_path = 'sinhala'
2934
repo, client = self.setup_fake_client_and_repository(transport_path)
2935
client.add_error_response(b'nosuchrevision', revid)
2936
# also check that the right revision is reported in the error
2937
self.assertRaises(errors.NoSuchRevision,
2938
repo._get_revision_graph, revid)
2940
[('call_expecting_body', b'Repository.get_revision_graph',
2941
(b'sinhala/', revid))],
2944
def test_unexpected_error(self):
2946
transport_path = 'sinhala'
2947
repo, client = self.setup_fake_client_and_repository(transport_path)
2948
client.add_error_response(b'AnUnexpectedError')
2949
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2950
repo._get_revision_graph, revid)
2951
self.assertEqual((b'AnUnexpectedError',), e.error_tuple)
2954
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2957
repo, client = self.setup_fake_client_and_repository('quack')
2958
client.add_expected_call(
2959
b'Repository.get_rev_id_for_revno', (b'quack/',
2960
5, (42, b'rev-foo')),
2961
b'success', (b'ok', b'rev-five'))
2962
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2963
self.assertEqual((True, b'rev-five'), result)
2964
self.assertFinished(client)
2966
def test_history_incomplete(self):
2967
repo, client = self.setup_fake_client_and_repository('quack')
2968
client.add_expected_call(
2969
b'Repository.get_rev_id_for_revno', (b'quack/',
2970
5, (42, b'rev-foo')),
2971
b'success', (b'history-incomplete', 10, b'rev-ten'))
2972
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2973
self.assertEqual((False, (10, b'rev-ten')), result)
2974
self.assertFinished(client)
2976
def test_history_incomplete_with_fallback(self):
2977
"""A 'history-incomplete' response causes the fallback repository to be
2978
queried too, if one is set.
2980
# Make a repo with a fallback repo, both using a FakeClient.
2981
format = remote.response_tuple_to_repo_format(
2982
(b'yes', b'no', b'yes', self.get_repo_format().network_name()))
2983
repo, client = self.setup_fake_client_and_repository('quack')
2984
repo._format = format
2985
fallback_repo, ignored = self.setup_fake_client_and_repository(
2987
fallback_repo._client = client
2988
fallback_repo._format = format
2989
repo.add_fallback_repository(fallback_repo)
2990
# First the client should ask the primary repo
2991
client.add_expected_call(
2992
b'Repository.get_rev_id_for_revno', (b'quack/',
2993
1, (42, b'rev-foo')),
2994
b'success', (b'history-incomplete', 2, b'rev-two'))
2995
# Then it should ask the fallback, using revno/revid from the
2996
# history-incomplete response as the known revno/revid.
2997
client.add_expected_call(
2998
b'Repository.get_rev_id_for_revno', (
2999
b'fallback/', 1, (2, b'rev-two')),
3000
b'success', (b'ok', b'rev-one'))
3001
result = repo.get_rev_id_for_revno(1, (42, b'rev-foo'))
3002
self.assertEqual((True, b'rev-one'), result)
3003
self.assertFinished(client)
3005
def test_nosuchrevision(self):
3006
# 'nosuchrevision' is returned when the known-revid is not found in the
3007
# remote repo. The client translates that response to NoSuchRevision.
3008
repo, client = self.setup_fake_client_and_repository('quack')
3009
client.add_expected_call(
3010
b'Repository.get_rev_id_for_revno', (b'quack/',
3011
5, (42, b'rev-foo')),
3012
b'error', (b'nosuchrevision', b'rev-foo'))
3014
errors.NoSuchRevision,
3015
repo.get_rev_id_for_revno, 5, (42, b'rev-foo'))
3016
self.assertFinished(client)
3018
def test_outofbounds(self):
3019
repo, client = self.setup_fake_client_and_repository('quack')
3020
client.add_expected_call(
3021
b'Repository.get_rev_id_for_revno', (b'quack/',
3022
43, (42, b'rev-foo')),
3023
b'error', (b'revno-outofbounds', 43, 0, 42))
3025
errors.RevnoOutOfBounds,
3026
repo.get_rev_id_for_revno, 43, (42, b'rev-foo'))
3027
self.assertFinished(client)
3029
def test_outofbounds_old(self):
3030
# Older versions of bzr didn't support RevnoOutOfBounds
3031
repo, client = self.setup_fake_client_and_repository('quack')
3032
client.add_expected_call(
3033
b'Repository.get_rev_id_for_revno', (b'quack/',
3034
43, (42, b'rev-foo')),
3036
b'error', b'ValueError',
3037
b'requested revno (43) is later than given known revno (42)'))
3039
errors.RevnoOutOfBounds,
3040
repo.get_rev_id_for_revno, 43, (42, b'rev-foo'))
3041
self.assertFinished(client)
3043
def test_branch_fallback_locking(self):
3044
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
3045
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
3046
will be invoked, which will fail if the repo is unlocked.
3048
self.setup_smart_server_with_call_log()
3049
tree = self.make_branch_and_memory_tree('.')
3052
rev1 = tree.commit('First')
3053
tree.commit('Second')
3055
branch = tree.branch
3056
self.assertFalse(branch.is_locked())
3057
self.reset_smart_call_log()
3058
verb = b'Repository.get_rev_id_for_revno'
3059
self.disable_verb(verb)
3060
self.assertEqual(rev1, branch.get_rev_id(1))
3061
self.assertLength(1, [call for call in self.hpss_calls if
3062
call.call.method == verb])
3065
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
3067
def test_has_signature_for_revision_id(self):
3068
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
3069
transport_path = 'quack'
3070
repo, client = self.setup_fake_client_and_repository(transport_path)
3071
client.add_success_response(b'yes')
3072
result = repo.has_signature_for_revision_id(b'A')
3074
[('call', b'Repository.has_signature_for_revision_id',
3075
(b'quack/', b'A'))],
3077
self.assertEqual(True, result)
3079
def test_is_not_shared(self):
3080
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
3081
transport_path = 'qwack'
3082
repo, client = self.setup_fake_client_and_repository(transport_path)
3083
client.add_success_response(b'no')
3084
result = repo.has_signature_for_revision_id(b'A')
3086
[('call', b'Repository.has_signature_for_revision_id',
3087
(b'qwack/', b'A'))],
3089
self.assertEqual(False, result)
3092
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
3094
def test_get_physical_lock_status_yes(self):
3095
transport_path = 'qwack'
3096
repo, client = self.setup_fake_client_and_repository(transport_path)
3097
client.add_success_response(b'yes')
3098
result = repo.get_physical_lock_status()
3100
[('call', b'Repository.get_physical_lock_status',
3103
self.assertEqual(True, result)
3105
def test_get_physical_lock_status_no(self):
3106
transport_path = 'qwack'
3107
repo, client = self.setup_fake_client_and_repository(transport_path)
3108
client.add_success_response(b'no')
3109
result = repo.get_physical_lock_status()
3111
[('call', b'Repository.get_physical_lock_status',
3114
self.assertEqual(False, result)
3117
class TestRepositoryIsShared(TestRemoteRepository):
3119
def test_is_shared(self):
3120
# ('yes', ) for Repository.is_shared -> 'True'.
3121
transport_path = 'quack'
3122
repo, client = self.setup_fake_client_and_repository(transport_path)
3123
client.add_success_response(b'yes')
3124
result = repo.is_shared()
3126
[('call', b'Repository.is_shared', (b'quack/',))],
3128
self.assertEqual(True, result)
3130
def test_is_not_shared(self):
3131
# ('no', ) for Repository.is_shared -> 'False'.
3132
transport_path = 'qwack'
3133
repo, client = self.setup_fake_client_and_repository(transport_path)
3134
client.add_success_response(b'no')
3135
result = repo.is_shared()
3137
[('call', b'Repository.is_shared', (b'qwack/',))],
3139
self.assertEqual(False, result)
3142
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3144
def test_make_working_trees(self):
3145
# ('yes', ) for Repository.make_working_trees -> 'True'.
3146
transport_path = 'quack'
3147
repo, client = self.setup_fake_client_and_repository(transport_path)
3148
client.add_success_response(b'yes')
3149
result = repo.make_working_trees()
3151
[('call', b'Repository.make_working_trees', (b'quack/',))],
3153
self.assertEqual(True, result)
3155
def test_no_working_trees(self):
3156
# ('no', ) for Repository.make_working_trees -> 'False'.
3157
transport_path = 'qwack'
3158
repo, client = self.setup_fake_client_and_repository(transport_path)
3159
client.add_success_response(b'no')
3160
result = repo.make_working_trees()
3162
[('call', b'Repository.make_working_trees', (b'qwack/',))],
3164
self.assertEqual(False, result)
3167
class TestRepositoryLockWrite(TestRemoteRepository):
3169
def test_lock_write(self):
3170
transport_path = 'quack'
3171
repo, client = self.setup_fake_client_and_repository(transport_path)
3172
client.add_success_response(b'ok', b'a token')
3173
token = repo.lock_write().repository_token
3175
[('call', b'Repository.lock_write', (b'quack/', b''))],
3177
self.assertEqual(b'a token', token)
3179
def test_lock_write_already_locked(self):
3180
transport_path = 'quack'
3181
repo, client = self.setup_fake_client_and_repository(transport_path)
3182
client.add_error_response(b'LockContention')
3183
self.assertRaises(errors.LockContention, repo.lock_write)
3185
[('call', b'Repository.lock_write', (b'quack/', b''))],
3188
def test_lock_write_unlockable(self):
3189
transport_path = 'quack'
3190
repo, client = self.setup_fake_client_and_repository(transport_path)
3191
client.add_error_response(b'UnlockableTransport')
3192
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3194
[('call', b'Repository.lock_write', (b'quack/', b''))],
3198
class TestRepositoryWriteGroups(TestRemoteRepository):
3200
def test_start_write_group(self):
3201
transport_path = 'quack'
3202
repo, client = self.setup_fake_client_and_repository(transport_path)
3203
client.add_expected_call(
3204
b'Repository.lock_write', (b'quack/', b''),
3205
b'success', (b'ok', b'a token'))
3206
client.add_expected_call(
3207
b'Repository.start_write_group', (b'quack/', b'a token'),
3208
b'success', (b'ok', (b'token1', )))
3210
repo.start_write_group()
3212
def test_start_write_group_unsuspendable(self):
3213
# Some repositories do not support suspending write
3214
# groups. For those, fall back to the "real" repository.
3215
transport_path = 'quack'
3216
repo, client = self.setup_fake_client_and_repository(transport_path)
3218
def stub_ensure_real():
3219
client._calls.append(('_ensure_real',))
3220
repo._real_repository = _StubRealPackRepository(client._calls)
3221
repo._ensure_real = stub_ensure_real
3222
client.add_expected_call(
3223
b'Repository.lock_write', (b'quack/', b''),
3224
b'success', (b'ok', b'a token'))
3225
client.add_expected_call(
3226
b'Repository.start_write_group', (b'quack/', b'a token'),
3227
b'error', (b'UnsuspendableWriteGroup',))
3229
repo.start_write_group()
3230
self.assertEqual(client._calls[-2:], [
3232
('start_write_group',)])
3234
def test_commit_write_group(self):
3235
transport_path = 'quack'
3236
repo, client = self.setup_fake_client_and_repository(transport_path)
3237
client.add_expected_call(
3238
b'Repository.lock_write', (b'quack/', b''),
3239
b'success', (b'ok', b'a token'))
3240
client.add_expected_call(
3241
b'Repository.start_write_group', (b'quack/', b'a token'),
3242
b'success', (b'ok', [b'token1']))
3243
client.add_expected_call(
3244
b'Repository.commit_write_group', (b'quack/',
3245
b'a token', [b'token1']),
3246
b'success', (b'ok',))
3248
repo.start_write_group()
3249
repo.commit_write_group()
3251
def test_abort_write_group(self):
3252
transport_path = 'quack'
3253
repo, client = self.setup_fake_client_and_repository(transport_path)
3254
client.add_expected_call(
3255
b'Repository.lock_write', (b'quack/', b''),
3256
b'success', (b'ok', b'a token'))
3257
client.add_expected_call(
3258
b'Repository.start_write_group', (b'quack/', b'a token'),
3259
b'success', (b'ok', [b'token1']))
3260
client.add_expected_call(
3261
b'Repository.abort_write_group', (b'quack/',
3262
b'a token', [b'token1']),
3263
b'success', (b'ok',))
3265
repo.start_write_group()
3266
repo.abort_write_group(False)
3268
def test_suspend_write_group(self):
3269
transport_path = 'quack'
3270
repo, client = self.setup_fake_client_and_repository(transport_path)
3271
self.assertEqual([], repo.suspend_write_group())
3273
def test_resume_write_group(self):
3274
transport_path = 'quack'
3275
repo, client = self.setup_fake_client_and_repository(transport_path)
3276
client.add_expected_call(
3277
b'Repository.lock_write', (b'quack/', b''),
3278
b'success', (b'ok', b'a token'))
3279
client.add_expected_call(
3280
b'Repository.check_write_group', (b'quack/',
3281
b'a token', [b'token1']),
3282
b'success', (b'ok',))
3284
repo.resume_write_group(['token1'])
3287
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3289
def test_backwards_compat(self):
3290
self.setup_smart_server_with_call_log()
3291
repo = self.make_repository('.')
3292
self.reset_smart_call_log()
3293
verb = b'Repository.set_make_working_trees'
3294
self.disable_verb(verb)
3295
repo.set_make_working_trees(True)
3296
call_count = len([call for call in self.hpss_calls if
3297
call.call.method == verb])
3298
self.assertEqual(1, call_count)
3300
def test_current(self):
3301
transport_path = 'quack'
3302
repo, client = self.setup_fake_client_and_repository(transport_path)
3303
client.add_expected_call(
3304
b'Repository.set_make_working_trees', (b'quack/', b'True'),
3305
b'success', (b'ok',))
3306
client.add_expected_call(
3307
b'Repository.set_make_working_trees', (b'quack/', b'False'),
3308
b'success', (b'ok',))
3309
repo.set_make_working_trees(True)
3310
repo.set_make_working_trees(False)
3313
class TestRepositoryUnlock(TestRemoteRepository):
3315
def test_unlock(self):
3316
transport_path = 'quack'
3317
repo, client = self.setup_fake_client_and_repository(transport_path)
3318
client.add_success_response(b'ok', b'a token')
3319
client.add_success_response(b'ok')
3323
[('call', b'Repository.lock_write', (b'quack/', b'')),
3324
('call', b'Repository.unlock', (b'quack/', b'a token'))],
3327
def test_unlock_wrong_token(self):
3328
# If somehow the token is wrong, unlock will raise TokenMismatch.
3329
transport_path = 'quack'
3330
repo, client = self.setup_fake_client_and_repository(transport_path)
3331
client.add_success_response(b'ok', b'a token')
3332
client.add_error_response(b'TokenMismatch')
3334
self.assertRaises(errors.TokenMismatch, repo.unlock)
3337
class TestRepositoryHasRevision(TestRemoteRepository):
3339
def test_none(self):
3340
# repo.has_revision(None) should not cause any traffic.
3341
transport_path = 'quack'
3342
repo, client = self.setup_fake_client_and_repository(transport_path)
3344
# The null revision is always there, so has_revision(None) == True.
3345
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3347
# The remote repo shouldn't be accessed.
3348
self.assertEqual([], client._calls)
3351
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3352
"""Test Repository.iter_file_bytes."""
3354
def test_single(self):
3355
transport_path = 'quack'
3356
repo, client = self.setup_fake_client_and_repository(transport_path)
3357
client.add_expected_call(
3358
b'Repository.iter_files_bytes', (b'quack/', ),
3359
b'success', (b'ok',), iter([b"ok\x000", b"\n", zlib.compress(b"mydata" * 10)]))
3360
for (identifier, byte_stream) in repo.iter_files_bytes([(b"somefile",
3361
b"somerev", b"myid")]):
3362
self.assertEqual(b"myid", identifier)
3363
self.assertEqual(b"".join(byte_stream), b"mydata" * 10)
3365
def test_missing(self):
3366
transport_path = 'quack'
3367
repo, client = self.setup_fake_client_and_repository(transport_path)
3368
client.add_expected_call(
3369
b'Repository.iter_files_bytes',
3371
b'error', (b'RevisionNotPresent', b'somefile', b'somerev'),
3372
iter([b"absent\0somefile\0somerev\n"]))
3373
self.assertRaises(errors.RevisionNotPresent, list,
3374
repo.iter_files_bytes(
3375
[(b"somefile", b"somerev", b"myid")]))
3378
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3379
"""Base class for Repository.insert_stream and .insert_stream_1.19
3383
def checkInsertEmptyStream(self, repo, client):
3384
"""Insert an empty stream, checking the result.
3386
This checks that there are no resume_tokens or missing_keys, and that
3387
the client is finished.
3389
sink = repo._get_sink()
3390
fmt = repository.format_registry.get_default()
3391
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3392
self.assertEqual([], resume_tokens)
3393
self.assertEqual(set(), missing_keys)
3394
self.assertFinished(client)
3397
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3398
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3401
This test case is very similar to TestRepositoryInsertStream_1_19.
3405
super(TestRepositoryInsertStream, self).setUp()
3406
self.disable_verb(b'Repository.insert_stream_1.19')
3408
def test_unlocked_repo(self):
3409
transport_path = 'quack'
3410
repo, client = self.setup_fake_client_and_repository(transport_path)
3411
client.add_expected_call(
3412
b'Repository.insert_stream_1.19', (b'quack/', b''),
3413
b'unknown', (b'Repository.insert_stream_1.19',))
3414
client.add_expected_call(
3415
b'Repository.insert_stream', (b'quack/', b''),
3416
b'success', (b'ok',))
3417
client.add_expected_call(
3418
b'Repository.insert_stream', (b'quack/', b''),
3419
b'success', (b'ok',))
3420
self.checkInsertEmptyStream(repo, client)
3422
def test_locked_repo_with_no_lock_token(self):
3423
transport_path = 'quack'
3424
repo, client = self.setup_fake_client_and_repository(transport_path)
3425
client.add_expected_call(
3426
b'Repository.lock_write', (b'quack/', b''),
3427
b'success', (b'ok', b''))
3428
client.add_expected_call(
3429
b'Repository.insert_stream_1.19', (b'quack/', b''),
3430
b'unknown', (b'Repository.insert_stream_1.19',))
3431
client.add_expected_call(
3432
b'Repository.insert_stream', (b'quack/', b''),
3433
b'success', (b'ok',))
3434
client.add_expected_call(
3435
b'Repository.insert_stream', (b'quack/', b''),
3436
b'success', (b'ok',))
3438
self.checkInsertEmptyStream(repo, client)
3440
def test_locked_repo_with_lock_token(self):
3441
transport_path = 'quack'
3442
repo, client = self.setup_fake_client_and_repository(transport_path)
3443
client.add_expected_call(
3444
b'Repository.lock_write', (b'quack/', b''),
3445
b'success', (b'ok', b'a token'))
3446
client.add_expected_call(
3447
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3448
b'unknown', (b'Repository.insert_stream_1.19',))
3449
client.add_expected_call(
3450
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3451
b'success', (b'ok',))
3452
client.add_expected_call(
3453
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3454
b'success', (b'ok',))
3456
self.checkInsertEmptyStream(repo, client)
3458
def test_stream_with_inventory_deltas(self):
3459
"""'inventory-deltas' substreams cannot be sent to the
3460
Repository.insert_stream verb, because not all servers that implement
3461
that verb will accept them. So when one is encountered the RemoteSink
3462
immediately stops using that verb and falls back to VFS insert_stream.
3464
transport_path = 'quack'
3465
repo, client = self.setup_fake_client_and_repository(transport_path)
3466
client.add_expected_call(
3467
b'Repository.insert_stream_1.19', (b'quack/', b''),
3468
b'unknown', (b'Repository.insert_stream_1.19',))
3469
client.add_expected_call(
3470
b'Repository.insert_stream', (b'quack/', b''),
3471
b'success', (b'ok',))
3472
client.add_expected_call(
3473
b'Repository.insert_stream', (b'quack/', b''),
3474
b'success', (b'ok',))
3475
# Create a fake real repository for insert_stream to fall back on, so
3476
# that we can directly see the records the RemoteSink passes to the
3483
def insert_stream(self, stream, src_format, resume_tokens):
3484
for substream_kind, substream in stream:
3485
self.records.append(
3486
(substream_kind, [record.key for record in substream]))
3487
return [b'fake tokens'], [b'fake missing keys']
3488
fake_real_sink = FakeRealSink()
3490
class FakeRealRepository:
3491
def _get_sink(self):
3492
return fake_real_sink
3494
def is_in_write_group(self):
3497
def refresh_data(self):
3499
repo._real_repository = FakeRealRepository()
3500
sink = repo._get_sink()
3501
fmt = repository.format_registry.get_default()
3502
stream = self.make_stream_with_inv_deltas(fmt)
3503
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3504
# Every record from the first inventory delta should have been sent to
3506
expected_records = [
3507
('inventory-deltas', [(b'rev2',), (b'rev3',)]),
3508
('texts', [(b'some-rev', b'some-file')])]
3509
self.assertEqual(expected_records, fake_real_sink.records)
3510
# The return values from the real sink's insert_stream are propagated
3511
# back to the original caller.
3512
self.assertEqual([b'fake tokens'], resume_tokens)
3513
self.assertEqual([b'fake missing keys'], missing_keys)
3514
self.assertFinished(client)
3516
def make_stream_with_inv_deltas(self, fmt):
3517
"""Make a simple stream with an inventory delta followed by more
3518
records and more substreams to test that all records and substreams
3519
from that point on are used.
3521
This sends, in order:
3522
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3524
* texts substream: (some-rev, some-file)
3526
# Define a stream using generators so that it isn't rewindable.
3527
inv = inventory.Inventory(revision_id=b'rev1')
3528
inv.root.revision = b'rev1'
3530
def stream_with_inv_delta():
3531
yield ('inventories', inventories_substream())
3532
yield ('inventory-deltas', inventory_delta_substream())
3534
versionedfile.FulltextContentFactory(
3535
(b'some-rev', b'some-file'), (), None, b'content')])
3537
def inventories_substream():
3538
# An empty inventory fulltext. This will be streamed normally.
3539
chunks = fmt._serializer.write_inventory_to_lines(inv)
3540
yield versionedfile.ChunkedContentFactory(
3541
(b'rev1',), (), None, chunks, chunks_are_lines=True)
3543
def inventory_delta_substream():
3544
# An inventory delta. This can't be streamed via this verb, so it
3545
# will trigger a fallback to VFS insert_stream.
3546
entry = inv.make_entry(
3547
'directory', 'newdir', inv.root.file_id, b'newdir-id')
3548
entry.revision = b'ghost'
3549
delta = [(None, 'newdir', b'newdir-id', entry)]
3550
serializer = inventory_delta.InventoryDeltaSerializer(
3551
versioned_root=True, tree_references=False)
3552
lines = serializer.delta_to_lines(b'rev1', b'rev2', delta)
3553
yield versionedfile.ChunkedContentFactory(
3554
(b'rev2',), ((b'rev1',)), None, lines)
3556
lines = serializer.delta_to_lines(b'rev1', b'rev3', delta)
3557
yield versionedfile.ChunkedContentFactory(
3558
(b'rev3',), ((b'rev1',)), None, lines)
3559
return stream_with_inv_delta()
3562
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3564
def test_unlocked_repo(self):
3565
transport_path = 'quack'
3566
repo, client = self.setup_fake_client_and_repository(transport_path)
3567
client.add_expected_call(
3568
b'Repository.insert_stream_1.19', (b'quack/', b''),
3569
b'success', (b'ok',))
3570
client.add_expected_call(
3571
b'Repository.insert_stream_1.19', (b'quack/', b''),
3572
b'success', (b'ok',))
3573
self.checkInsertEmptyStream(repo, client)
3575
def test_locked_repo_with_no_lock_token(self):
3576
transport_path = 'quack'
3577
repo, client = self.setup_fake_client_and_repository(transport_path)
3578
client.add_expected_call(
3579
b'Repository.lock_write', (b'quack/', b''),
3580
b'success', (b'ok', b''))
3581
client.add_expected_call(
3582
b'Repository.insert_stream_1.19', (b'quack/', b''),
3583
b'success', (b'ok',))
3584
client.add_expected_call(
3585
b'Repository.insert_stream_1.19', (b'quack/', b''),
3586
b'success', (b'ok',))
3588
self.checkInsertEmptyStream(repo, client)
3590
def test_locked_repo_with_lock_token(self):
3591
transport_path = 'quack'
3592
repo, client = self.setup_fake_client_and_repository(transport_path)
3593
client.add_expected_call(
3594
b'Repository.lock_write', (b'quack/', b''),
3595
b'success', (b'ok', b'a token'))
3596
client.add_expected_call(
3597
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3598
b'success', (b'ok',))
3599
client.add_expected_call(
3600
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3601
b'success', (b'ok',))
3603
self.checkInsertEmptyStream(repo, client)
3606
class TestRepositoryTarball(TestRemoteRepository):
3608
# This is a canned tarball reponse we can validate against
3609
tarball_content = base64.b64decode(
3610
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3611
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3612
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3613
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3614
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3615
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3616
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3617
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3618
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3619
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3620
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3621
'nWQ7QH/F3JFOFCQ0aSPfA='
3624
def test_repository_tarball(self):
3625
# Test that Repository.tarball generates the right operations
3626
transport_path = 'repo'
3627
expected_calls = [('call_expecting_body', b'Repository.tarball',
3628
(b'repo/', b'bz2',),),
3630
repo, client = self.setup_fake_client_and_repository(transport_path)
3631
client.add_success_response_with_body(self.tarball_content, b'ok')
3632
# Now actually ask for the tarball
3633
tarball_file = repo._get_tarball('bz2')
3635
self.assertEqual(expected_calls, client._calls)
3636
self.assertEqual(self.tarball_content, tarball_file.read())
3638
tarball_file.close()
3641
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3642
"""RemoteRepository.copy_content_into optimizations"""
3644
def test_copy_content_remote_to_local(self):
3645
self.transport_server = test_server.SmartTCPServer_for_testing
3646
src_repo = self.make_repository('repo1')
3647
src_repo = repository.Repository.open(self.get_url('repo1'))
3648
# At the moment the tarball-based copy_content_into can't write back
3649
# into a smart server. It would be good if it could upload the
3650
# tarball; once that works we'd have to create repositories of
3651
# different formats. -- mbp 20070410
3652
dest_url = self.get_vfs_only_url('repo2')
3653
dest_bzrdir = BzrDir.create(dest_url)
3654
dest_repo = dest_bzrdir.create_repository()
3655
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3656
self.assertTrue(isinstance(src_repo, RemoteRepository))
3657
src_repo.copy_content_into(dest_repo)
3660
class _StubRealPackRepository(object):
3662
def __init__(self, calls):
3664
self._pack_collection = _StubPackCollection(calls)
3666
def start_write_group(self):
3667
self.calls.append(('start_write_group',))
3669
def is_in_write_group(self):
3672
def refresh_data(self):
3673
self.calls.append(('pack collection reload_pack_names',))
3676
class _StubPackCollection(object):
3678
def __init__(self, calls):
3682
self.calls.append(('pack collection autopack',))
3685
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3686
"""Tests for RemoteRepository.autopack implementation."""
3689
"""When the server returns 'ok' and there's no _real_repository, then
3690
nothing else happens: the autopack method is done.
3692
transport_path = 'quack'
3693
repo, client = self.setup_fake_client_and_repository(transport_path)
3694
client.add_expected_call(
3695
b'PackRepository.autopack', (b'quack/',), b'success', (b'ok',))
3697
self.assertFinished(client)
3699
def test_ok_with_real_repo(self):
3700
"""When the server returns 'ok' and there is a _real_repository, then
3701
the _real_repository's reload_pack_name's method will be called.
3703
transport_path = 'quack'
3704
repo, client = self.setup_fake_client_and_repository(transport_path)
3705
client.add_expected_call(
3706
b'PackRepository.autopack', (b'quack/',),
3707
b'success', (b'ok',))
3708
repo._real_repository = _StubRealPackRepository(client._calls)
3711
[('call', b'PackRepository.autopack', (b'quack/',)),
3712
('pack collection reload_pack_names',)],
3715
def test_backwards_compatibility(self):
3716
"""If the server does not recognise the PackRepository.autopack verb,
3717
fallback to the real_repository's implementation.
3719
transport_path = 'quack'
3720
repo, client = self.setup_fake_client_and_repository(transport_path)
3721
client.add_unknown_method_response(b'PackRepository.autopack')
3723
def stub_ensure_real():
3724
client._calls.append(('_ensure_real',))
3725
repo._real_repository = _StubRealPackRepository(client._calls)
3726
repo._ensure_real = stub_ensure_real
3729
[('call', b'PackRepository.autopack', (b'quack/',)),
3731
('pack collection autopack',)],
3734
def test_oom_error_reporting(self):
3735
"""An out-of-memory condition on the server is reported clearly"""
3736
transport_path = 'quack'
3737
repo, client = self.setup_fake_client_and_repository(transport_path)
3738
client.add_expected_call(
3739
b'PackRepository.autopack', (b'quack/',),
3740
b'error', (b'MemoryError',))
3741
err = self.assertRaises(errors.BzrError, repo.autopack)
3742
self.assertContainsRe(str(err), "^remote server out of mem")
3745
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3746
"""Base class for unit tests for breezy.bzr.remote._translate_error."""
3748
def translateTuple(self, error_tuple, **context):
3749
"""Call _translate_error with an ErrorFromSmartServer built from the
3752
:param error_tuple: A tuple of a smart server response, as would be
3753
passed to an ErrorFromSmartServer.
3754
:kwargs context: context items to call _translate_error with.
3756
:returns: The error raised by _translate_error.
3758
# Raise the ErrorFromSmartServer before passing it as an argument,
3759
# because _translate_error may need to re-raise it with a bare 'raise'
3761
server_error = errors.ErrorFromSmartServer(error_tuple)
3762
translated_error = self.translateErrorFromSmartServer(
3763
server_error, **context)
3764
return translated_error
3766
def translateErrorFromSmartServer(self, error_object, **context):
3767
"""Like translateTuple, but takes an already constructed
3768
ErrorFromSmartServer rather than a tuple.
3772
except errors.ErrorFromSmartServer as server_error:
3773
translated_error = self.assertRaises(
3774
errors.BzrError, remote._translate_error, server_error,
3776
return translated_error
3779
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3780
"""Unit tests for breezy.bzr.remote._translate_error.
3782
Given an ErrorFromSmartServer (which has an error tuple from a smart
3783
server) and some context, _translate_error raises more specific errors from
3786
This test case covers the cases where _translate_error succeeds in
3787
translating an ErrorFromSmartServer to something better. See
3788
TestErrorTranslationRobustness for other cases.
3791
def test_NoSuchRevision(self):
3792
branch = self.make_branch('')
3794
translated_error = self.translateTuple(
3795
(b'NoSuchRevision', revid), branch=branch)
3796
expected_error = errors.NoSuchRevision(branch, revid)
3797
self.assertEqual(expected_error, translated_error)
3799
def test_nosuchrevision(self):
3800
repository = self.make_repository('')
3802
translated_error = self.translateTuple(
3803
(b'nosuchrevision', revid), repository=repository)
3804
expected_error = errors.NoSuchRevision(repository, revid)
3805
self.assertEqual(expected_error, translated_error)
3807
def test_nobranch(self):
3808
bzrdir = self.make_controldir('')
3809
translated_error = self.translateTuple((b'nobranch',), bzrdir=bzrdir)
3810
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3811
self.assertEqual(expected_error, translated_error)
3813
def test_nobranch_one_arg(self):
3814
bzrdir = self.make_controldir('')
3815
translated_error = self.translateTuple(
3816
(b'nobranch', b'extra detail'), bzrdir=bzrdir)
3817
expected_error = errors.NotBranchError(
3818
path=bzrdir.root_transport.base,
3819
detail='extra detail')
3820
self.assertEqual(expected_error, translated_error)
3822
def test_norepository(self):
3823
bzrdir = self.make_controldir('')
3824
translated_error = self.translateTuple((b'norepository',),
3826
expected_error = errors.NoRepositoryPresent(bzrdir)
3827
self.assertEqual(expected_error, translated_error)
3829
def test_LockContention(self):
3830
translated_error = self.translateTuple((b'LockContention',))
3831
expected_error = errors.LockContention('(remote lock)')
3832
self.assertEqual(expected_error, translated_error)
3834
def test_UnlockableTransport(self):
3835
bzrdir = self.make_controldir('')
3836
translated_error = self.translateTuple(
3837
(b'UnlockableTransport',), bzrdir=bzrdir)
3838
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3839
self.assertEqual(expected_error, translated_error)
3841
def test_LockFailed(self):
3842
lock = 'str() of a server lock'
3843
why = 'str() of why'
3844
translated_error = self.translateTuple(
3845
(b'LockFailed', lock.encode('ascii'), why.encode('ascii')))
3846
expected_error = errors.LockFailed(lock, why)
3847
self.assertEqual(expected_error, translated_error)
3849
def test_TokenMismatch(self):
3850
token = 'a lock token'
3851
translated_error = self.translateTuple(
3852
(b'TokenMismatch',), token=token)
3853
expected_error = errors.TokenMismatch(token, '(remote token)')
3854
self.assertEqual(expected_error, translated_error)
3856
def test_Diverged(self):
3857
branch = self.make_branch('a')
3858
other_branch = self.make_branch('b')
3859
translated_error = self.translateTuple(
3860
(b'Diverged',), branch=branch, other_branch=other_branch)
3861
expected_error = errors.DivergedBranches(branch, other_branch)
3862
self.assertEqual(expected_error, translated_error)
3864
def test_NotStacked(self):
3865
branch = self.make_branch('')
3866
translated_error = self.translateTuple((b'NotStacked',), branch=branch)
3867
expected_error = errors.NotStacked(branch)
3868
self.assertEqual(expected_error, translated_error)
3870
def test_ReadError_no_args(self):
3872
translated_error = self.translateTuple((b'ReadError',), path=path)
3873
expected_error = errors.ReadError(path)
3874
self.assertEqual(expected_error, translated_error)
3876
def test_ReadError(self):
3878
translated_error = self.translateTuple(
3879
(b'ReadError', path.encode('utf-8')))
3880
expected_error = errors.ReadError(path)
3881
self.assertEqual(expected_error, translated_error)
3883
def test_IncompatibleRepositories(self):
3884
translated_error = self.translateTuple((b'IncompatibleRepositories',
3885
b"repo1", b"repo2", b"details here"))
3886
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3888
self.assertEqual(expected_error, translated_error)
3890
def test_GhostRevisionsHaveNoRevno(self):
3891
translated_error = self.translateTuple((b'GhostRevisionsHaveNoRevno',
3892
b"revid1", b"revid2"))
3893
expected_error = errors.GhostRevisionsHaveNoRevno(b"revid1", b"revid2")
3894
self.assertEqual(expected_error, translated_error)
3896
def test_PermissionDenied_no_args(self):
3898
translated_error = self.translateTuple((b'PermissionDenied',),
3900
expected_error = errors.PermissionDenied(path)
3901
self.assertEqual(expected_error, translated_error)
3903
def test_PermissionDenied_one_arg(self):
3905
translated_error = self.translateTuple(
3906
(b'PermissionDenied', path.encode('utf-8')))
3907
expected_error = errors.PermissionDenied(path)
3908
self.assertEqual(expected_error, translated_error)
3910
def test_PermissionDenied_one_arg_and_context(self):
3911
"""Given a choice between a path from the local context and a path on
3912
the wire, _translate_error prefers the path from the local context.
3914
local_path = 'local path'
3915
remote_path = 'remote path'
3916
translated_error = self.translateTuple(
3917
(b'PermissionDenied', remote_path.encode('utf-8')), path=local_path)
3918
expected_error = errors.PermissionDenied(local_path)
3919
self.assertEqual(expected_error, translated_error)
3921
def test_PermissionDenied_two_args(self):
3923
extra = 'a string with extra info'
3924
translated_error = self.translateTuple(
3925
(b'PermissionDenied', path.encode('utf-8'), extra.encode('utf-8')))
3926
expected_error = errors.PermissionDenied(path, extra)
3927
self.assertEqual(expected_error, translated_error)
3929
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3931
def test_NoSuchFile_context_path(self):
3932
local_path = "local path"
3933
translated_error = self.translateTuple((b'ReadError', b"remote path"),
3935
expected_error = errors.ReadError(local_path)
3936
self.assertEqual(expected_error, translated_error)
3938
def test_NoSuchFile_without_context(self):
3939
remote_path = "remote path"
3940
translated_error = self.translateTuple(
3941
(b'ReadError', remote_path.encode('utf-8')))
3942
expected_error = errors.ReadError(remote_path)
3943
self.assertEqual(expected_error, translated_error)
3945
def test_ReadOnlyError(self):
3946
translated_error = self.translateTuple((b'ReadOnlyError',))
3947
expected_error = errors.TransportNotPossible("readonly transport")
3948
self.assertEqual(expected_error, translated_error)
3950
def test_MemoryError(self):
3951
translated_error = self.translateTuple((b'MemoryError',))
3952
self.assertStartsWith(str(translated_error),
3953
"remote server out of memory")
3955
def test_generic_IndexError_no_classname(self):
3956
err = errors.ErrorFromSmartServer(
3957
(b'error', b"list index out of range"))
3958
translated_error = self.translateErrorFromSmartServer(err)
3959
expected_error = errors.UnknownErrorFromSmartServer(err)
3960
self.assertEqual(expected_error, translated_error)
3962
# GZ 2011-03-02: TODO test generic non-ascii error string
3964
def test_generic_KeyError(self):
3965
err = errors.ErrorFromSmartServer((b'error', b'KeyError', b"1"))
3966
translated_error = self.translateErrorFromSmartServer(err)
3967
expected_error = errors.UnknownErrorFromSmartServer(err)
3968
self.assertEqual(expected_error, translated_error)
3970
def test_RevnoOutOfBounds(self):
3971
translated_error = self.translateTuple(
3972
((b'revno-outofbounds', 5, 0, 3)), path=b'path')
3973
expected_error = errors.RevnoOutOfBounds(5, (0, 3))
3974
self.assertEqual(expected_error, translated_error)
3977
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3978
"""Unit tests for breezy.bzr.remote._translate_error's robustness.
3980
TestErrorTranslationSuccess is for cases where _translate_error can
3981
translate successfully. This class about how _translate_err behaves when
3982
it fails to translate: it re-raises the original error.
3985
def test_unrecognised_server_error(self):
3986
"""If the error code from the server is not recognised, the original
3987
ErrorFromSmartServer is propagated unmodified.
3989
error_tuple = (b'An unknown error tuple',)
3990
server_error = errors.ErrorFromSmartServer(error_tuple)
3991
translated_error = self.translateErrorFromSmartServer(server_error)
3992
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3993
self.assertEqual(expected_error, translated_error)
3995
def test_context_missing_a_key(self):
3996
"""In case of a bug in the client, or perhaps an unexpected response
3997
from a server, _translate_error returns the original error tuple from
3998
the server and mutters a warning.
4000
# To translate a NoSuchRevision error _translate_error needs a 'branch'
4001
# in the context dict. So let's give it an empty context dict instead
4002
# to exercise its error recovery.
4003
error_tuple = (b'NoSuchRevision', b'revid')
4004
server_error = errors.ErrorFromSmartServer(error_tuple)
4005
translated_error = self.translateErrorFromSmartServer(server_error)
4006
self.assertEqual(server_error, translated_error)
4007
# In addition to re-raising ErrorFromSmartServer, some debug info has
4008
# been muttered to the log file for developer to look at.
4009
self.assertContainsRe(
4011
"Missing key 'branch' in context")
4013
def test_path_missing(self):
4014
"""Some translations (PermissionDenied, ReadError) can determine the
4015
'path' variable from either the wire or the local context. If neither
4016
has it, then an error is raised.
4018
error_tuple = (b'ReadError',)
4019
server_error = errors.ErrorFromSmartServer(error_tuple)
4020
translated_error = self.translateErrorFromSmartServer(server_error)
4021
self.assertEqual(server_error, translated_error)
4022
# In addition to re-raising ErrorFromSmartServer, some debug info has
4023
# been muttered to the log file for developer to look at.
4024
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
4027
class TestStacking(tests.TestCaseWithTransport):
4028
"""Tests for operations on stacked remote repositories.
4030
The underlying format type must support stacking.
4033
def test_access_stacked_remote(self):
4034
# based on <http://launchpad.net/bugs/261315>
4035
# make a branch stacked on another repository containing an empty
4036
# revision, then open it over hpss - we should be able to see that
4038
base_builder = self.make_branch_builder('base', format='1.9')
4039
base_builder.start_series()
4040
base_revid = base_builder.build_snapshot(None,
4041
[('add', ('', None, 'directory', None))],
4042
'message', revision_id=b'rev-id')
4043
base_builder.finish_series()
4044
stacked_branch = self.make_branch('stacked', format='1.9')
4045
stacked_branch.set_stacked_on_url('../base')
4046
# start a server looking at this
4047
smart_server = test_server.SmartTCPServer_for_testing()
4048
self.start_server(smart_server)
4049
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
4050
# can get its branch and repository
4051
remote_branch = remote_bzrdir.open_branch()
4052
remote_repo = remote_branch.repository
4053
remote_repo.lock_read()
4055
# it should have an appropriate fallback repository, which should also
4056
# be a RemoteRepository
4057
self.assertLength(1, remote_repo._fallback_repositories)
4058
self.assertIsInstance(remote_repo._fallback_repositories[0],
4060
# and it has the revision committed to the underlying repository;
4061
# these have varying implementations so we try several of them
4062
self.assertTrue(remote_repo.has_revisions([base_revid]))
4063
self.assertTrue(remote_repo.has_revision(base_revid))
4064
self.assertEqual(remote_repo.get_revision(base_revid).message,
4067
remote_repo.unlock()
4069
def prepare_stacked_remote_branch(self):
4070
"""Get stacked_upon and stacked branches with content in each."""
4071
self.setup_smart_server_with_call_log()
4072
tree1 = self.make_branch_and_tree('tree1', format='1.9')
4073
tree1.commit('rev1', rev_id=b'rev1')
4074
tree2 = tree1.branch.controldir.sprout('tree2', stacked=True
4075
).open_workingtree()
4076
local_tree = tree2.branch.create_checkout('local')
4077
local_tree.commit('local changes make me feel good.')
4078
branch2 = Branch.open(self.get_url('tree2'))
4080
self.addCleanup(branch2.unlock)
4081
return tree1.branch, branch2
4083
def test_stacked_get_parent_map(self):
4084
# the public implementation of get_parent_map obeys stacking
4085
_, branch = self.prepare_stacked_remote_branch()
4086
repo = branch.repository
4087
self.assertEqual({b'rev1'}, set(repo.get_parent_map([b'rev1'])))
4089
def test_unstacked_get_parent_map(self):
4090
# _unstacked_provider.get_parent_map ignores stacking
4091
_, branch = self.prepare_stacked_remote_branch()
4092
provider = branch.repository._unstacked_provider
4093
self.assertEqual(set(), set(provider.get_parent_map([b'rev1'])))
4095
def fetch_stream_to_rev_order(self, stream):
4097
for kind, substream in stream:
4098
if not kind == 'revisions':
4101
for content in substream:
4102
result.append(content.key[-1])
4105
def get_ordered_revs(self, format, order, branch_factory=None):
4106
"""Get a list of the revisions in a stream to format format.
4108
:param format: The format of the target.
4109
:param order: the order that target should have requested.
4110
:param branch_factory: A callable to create a trunk and stacked branch
4111
to fetch from. If none, self.prepare_stacked_remote_branch is used.
4112
:result: The revision ids in the stream, in the order seen,
4113
the topological order of revisions in the source.
4115
unordered_format = controldir.format_registry.get(format)()
4116
target_repository_format = unordered_format.repository_format
4118
self.assertEqual(order, target_repository_format._fetch_order)
4119
if branch_factory is None:
4120
branch_factory = self.prepare_stacked_remote_branch
4121
_, stacked = branch_factory()
4122
source = stacked.repository._get_source(target_repository_format)
4123
tip = stacked.last_revision()
4124
stacked.repository._ensure_real()
4125
graph = stacked.repository.get_graph()
4126
revs = [r for (r, ps) in graph.iter_ancestry([tip])
4127
if r != NULL_REVISION]
4129
search = vf_search.PendingAncestryResult([tip], stacked.repository)
4130
self.reset_smart_call_log()
4131
stream = source.get_stream(search)
4132
# We trust that if a revision is in the stream the rest of the new
4133
# content for it is too, as per our main fetch tests; here we are
4134
# checking that the revisions are actually included at all, and their
4136
return self.fetch_stream_to_rev_order(stream), revs
4138
def test_stacked_get_stream_unordered(self):
4139
# Repository._get_source.get_stream() from a stacked repository with
4140
# unordered yields the full data from both stacked and stacked upon
4142
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
4143
self.assertEqual(set(expected_revs), set(rev_ord))
4144
# Getting unordered results should have made a streaming data request
4145
# from the server, then one from the backing branch.
4146
self.assertLength(2, self.hpss_calls)
4148
def test_stacked_on_stacked_get_stream_unordered(self):
4149
# Repository._get_source.get_stream() from a stacked repository which
4150
# is itself stacked yields the full data from all three sources.
4151
def make_stacked_stacked():
4152
_, stacked = self.prepare_stacked_remote_branch()
4153
tree = stacked.controldir.sprout('tree3', stacked=True
4154
).open_workingtree()
4155
local_tree = tree.branch.create_checkout('local-tree3')
4156
local_tree.commit('more local changes are better')
4157
branch = Branch.open(self.get_url('tree3'))
4159
self.addCleanup(branch.unlock)
4161
rev_ord, expected_revs = self.get_ordered_revs(
4162
'1.9', 'unordered', branch_factory=make_stacked_stacked)
4163
self.assertEqual(set(expected_revs), set(rev_ord))
4164
# Getting unordered results should have made a streaming data request
4165
# from the server, and one from each backing repo
4166
self.assertLength(3, self.hpss_calls)
4168
def test_stacked_get_stream_topological(self):
4169
# Repository._get_source.get_stream() from a stacked repository with
4170
# topological sorting yields the full data from both stacked and
4171
# stacked upon sources in topological order.
4172
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4173
self.assertEqual(expected_revs, rev_ord)
4174
# Getting topological sort requires VFS calls still - one of which is
4175
# pushing up from the bound branch.
4176
self.assertLength(14, self.hpss_calls)
4178
def test_stacked_get_stream_groupcompress(self):
4179
# Repository._get_source.get_stream() from a stacked repository with
4180
# groupcompress sorting yields the full data from both stacked and
4181
# stacked upon sources in groupcompress order.
4182
raise tests.TestSkipped('No groupcompress ordered format available')
4183
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4184
self.assertEqual(expected_revs, reversed(rev_ord))
4185
# Getting unordered results should have made a streaming data request
4186
# from the backing branch, and one from the stacked on branch.
4187
self.assertLength(2, self.hpss_calls)
4189
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4190
# When pulling some fixed amount of content that is more than the
4191
# source has (because some is coming from a fallback branch, no error
4192
# should be received. This was reported as bug 360791.
4193
# Need three branches: a trunk, a stacked branch, and a preexisting
4194
# branch pulling content from stacked and trunk.
4195
self.setup_smart_server_with_call_log()
4196
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4197
trunk.commit('start')
4198
stacked_branch = trunk.branch.create_clone_on_transport(
4199
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4200
local = self.make_branch('local', format='1.9-rich-root')
4201
local.repository.fetch(stacked_branch.repository,
4202
stacked_branch.last_revision())
4205
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4208
super(TestRemoteBranchEffort, self).setUp()
4209
# Create a smart server that publishes whatever the backing VFS server
4211
self.smart_server = test_server.SmartTCPServer_for_testing()
4212
self.start_server(self.smart_server, self.get_server())
4213
# Log all HPSS calls into self.hpss_calls.
4214
_SmartClient.hooks.install_named_hook(
4215
'call', self.capture_hpss_call, None)
4216
self.hpss_calls = []
4218
def capture_hpss_call(self, params):
4219
self.hpss_calls.append(params.method)
4221
def test_copy_content_into_avoids_revision_history(self):
4222
local = self.make_branch('local')
4223
builder = self.make_branch_builder('remote')
4224
builder.build_commit(message="Commit.")
4225
remote_branch_url = self.smart_server.get_url() + 'remote'
4226
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4227
local.repository.fetch(remote_branch.repository)
4228
self.hpss_calls = []
4229
remote_branch.copy_content_into(local)
4230
self.assertFalse(b'Branch.revision_history' in self.hpss_calls)
4232
def test_fetch_everything_needs_just_one_call(self):
4233
local = self.make_branch('local')
4234
builder = self.make_branch_builder('remote')
4235
builder.build_commit(message="Commit.")
4236
remote_branch_url = self.smart_server.get_url() + 'remote'
4237
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4238
self.hpss_calls = []
4239
local.repository.fetch(
4240
remote_branch.repository,
4241
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4242
self.assertEqual([b'Repository.get_stream_1.19'], self.hpss_calls)
4244
def override_verb(self, verb_name, verb):
4245
request_handlers = request.request_handlers
4246
orig_verb = request_handlers.get(verb_name)
4247
orig_info = request_handlers.get_info(verb_name)
4248
request_handlers.register(verb_name, verb, override_existing=True)
4249
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4250
override_existing=True, info=orig_info)
4252
def test_fetch_everything_backwards_compat(self):
4253
"""Can fetch with EverythingResult even with pre 2.4 servers.
4255
Pre-2.4 do not support 'everything' searches with the
4256
Repository.get_stream_1.19 verb.
4260
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4261
"""A version of the Repository.get_stream_1.19 verb patched to
4262
reject 'everything' searches the way 2.3 and earlier do.
4265
def recreate_search(self, repository, search_bytes,
4266
discard_excess=False):
4267
verb_log.append(search_bytes.split(b'\n', 1)[0])
4268
if search_bytes == b'everything':
4270
request.FailedSmartServerResponse((b'BadSearch',)))
4271
return super(OldGetStreamVerb,
4272
self).recreate_search(repository, search_bytes,
4273
discard_excess=discard_excess)
4274
self.override_verb(b'Repository.get_stream_1.19', OldGetStreamVerb)
4275
local = self.make_branch('local')
4276
builder = self.make_branch_builder('remote')
4277
builder.build_commit(message="Commit.")
4278
remote_branch_url = self.smart_server.get_url() + 'remote'
4279
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4280
self.hpss_calls = []
4281
local.repository.fetch(
4282
remote_branch.repository,
4283
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4284
# make sure the overridden verb was used
4285
self.assertLength(1, verb_log)
4286
# more than one HPSS call is needed, but because it's a VFS callback
4287
# its hard to predict exactly how many.
4288
self.assertTrue(len(self.hpss_calls) > 1)
4291
class TestUpdateBoundBranchWithModifiedBoundLocation(
4292
tests.TestCaseWithTransport):
4293
"""Ensure correct handling of bound_location modifications.
4295
This is tested against a smart server as http://pad.lv/786980 was about a
4296
ReadOnlyError (write attempt during a read-only transaction) which can only
4297
happen in this context.
4301
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4302
self.transport_server = test_server.SmartTCPServer_for_testing
4304
def make_master_and_checkout(self, master_name, checkout_name):
4305
# Create the master branch and its associated checkout
4306
self.master = self.make_branch_and_tree(master_name)
4307
self.checkout = self.master.branch.create_checkout(checkout_name)
4308
# Modify the master branch so there is something to update
4309
self.master.commit('add stuff')
4310
self.last_revid = self.master.commit('even more stuff')
4311
self.bound_location = self.checkout.branch.get_bound_location()
4313
def assertUpdateSucceeds(self, new_location):
4314
self.checkout.branch.set_bound_location(new_location)
4315
self.checkout.update()
4316
self.assertEqual(self.last_revid, self.checkout.last_revision())
4318
def test_without_final_slash(self):
4319
self.make_master_and_checkout('master', 'checkout')
4320
# For unclear reasons some users have a bound_location without a final
4321
# '/', simulate that by forcing such a value
4322
self.assertEndsWith(self.bound_location, '/')
4323
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4325
def test_plus_sign(self):
4326
self.make_master_and_checkout('+master', 'checkout')
4327
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4329
def test_tilda(self):
4330
# Embed ~ in the middle of the path just to avoid any $HOME
4332
self.make_master_and_checkout('mas~ter', 'checkout')
4333
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4336
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4338
def test_no_context(self):
4339
class OutOfCoffee(errors.BzrError):
4340
"""A dummy exception for testing."""
4342
def __init__(self, urgency):
4343
self.urgency = urgency
4344
remote.no_context_error_translators.register(b"OutOfCoffee",
4345
lambda err: OutOfCoffee(err.error_args[0]))
4346
transport = MemoryTransport()
4347
client = FakeClient(transport.base)
4348
client.add_expected_call(
4349
b'Branch.get_stacked_on_url', (b'quack/',),
4350
b'error', (b'NotStacked',))
4351
client.add_expected_call(
4352
b'Branch.last_revision_info',
4354
b'error', (b'OutOfCoffee', b'low'))
4355
transport.mkdir('quack')
4356
transport = transport.clone('quack')
4357
branch = self.make_remote_branch(transport, client)
4358
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4359
self.assertFinished(client)
4361
def test_with_context(self):
4362
class OutOfTea(errors.BzrError):
4363
def __init__(self, branch, urgency):
4364
self.branch = branch
4365
self.urgency = urgency
4366
remote.error_translators.register(b"OutOfTea",
4367
lambda err, find, path: OutOfTea(
4368
err.error_args[0].decode(
4371
transport = MemoryTransport()
4372
client = FakeClient(transport.base)
4373
client.add_expected_call(
4374
b'Branch.get_stacked_on_url', (b'quack/',),
4375
b'error', (b'NotStacked',))
4376
client.add_expected_call(
4377
b'Branch.last_revision_info',
4379
b'error', (b'OutOfTea', b'low'))
4380
transport.mkdir('quack')
4381
transport = transport.clone('quack')
4382
branch = self.make_remote_branch(transport, client)
4383
self.assertRaises(OutOfTea, branch.last_revision_info)
4384
self.assertFinished(client)
4387
class TestRepositoryPack(TestRemoteRepository):
4389
def test_pack(self):
4390
transport_path = 'quack'
4391
repo, client = self.setup_fake_client_and_repository(transport_path)
4392
client.add_expected_call(
4393
b'Repository.lock_write', (b'quack/', b''),
4394
b'success', (b'ok', b'token'))
4395
client.add_expected_call(
4396
b'Repository.pack', (b'quack/', b'token', b'False'),
4397
b'success', (b'ok',), )
4398
client.add_expected_call(
4399
b'Repository.unlock', (b'quack/', b'token'),
4400
b'success', (b'ok', ))
4403
def test_pack_with_hint(self):
4404
transport_path = 'quack'
4405
repo, client = self.setup_fake_client_and_repository(transport_path)
4406
client.add_expected_call(
4407
b'Repository.lock_write', (b'quack/', b''),
4408
b'success', (b'ok', b'token'))
4409
client.add_expected_call(
4410
b'Repository.pack', (b'quack/', b'token', b'False'),
4411
b'success', (b'ok',), )
4412
client.add_expected_call(
4413
b'Repository.unlock', (b'quack/', b'token', b'False'),
4414
b'success', (b'ok', ))
4415
repo.pack(['hinta', 'hintb'])
4418
class TestRepositoryIterInventories(TestRemoteRepository):
4419
"""Test Repository.iter_inventories."""
4421
def _serialize_inv_delta(self, old_name, new_name, delta):
4422
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4423
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4425
def test_single_empty(self):
4426
transport_path = 'quack'
4427
repo, client = self.setup_fake_client_and_repository(transport_path)
4428
fmt = controldir.format_registry.get('2a')().repository_format
4430
stream = [('inventory-deltas', [
4431
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4432
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4433
client.add_expected_call(
4434
b'VersionedFileRepository.get_inventories', (
4435
b'quack/', b'unordered'),
4436
b'success', (b'ok', ),
4437
_stream_to_byte_stream(stream, fmt))
4438
ret = list(repo.iter_inventories([b"somerevid"]))
4439
self.assertLength(1, ret)
4441
self.assertEqual(b"somerevid", inv.revision_id)
4443
def test_empty(self):
4444
transport_path = 'quack'
4445
repo, client = self.setup_fake_client_and_repository(transport_path)
4446
ret = list(repo.iter_inventories([]))
4447
self.assertEqual(ret, [])
4449
def test_missing(self):
4450
transport_path = 'quack'
4451
repo, client = self.setup_fake_client_and_repository(transport_path)
4452
client.add_expected_call(
4453
b'VersionedFileRepository.get_inventories', (
4454
b'quack/', b'unordered'),
4455
b'success', (b'ok', ), iter([]))
4456
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(
4460
class TestRepositoryRevisionTreeArchive(TestRemoteRepository):
4461
"""Test Repository.iter_inventories."""
4463
def _serialize_inv_delta(self, old_name, new_name, delta):
4464
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4465
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4467
def test_simple(self):
4468
transport_path = 'quack'
4469
repo, client = self.setup_fake_client_and_repository(transport_path)
4470
fmt = controldir.format_registry.get('2a')().repository_format
4472
stream = [('inventory-deltas', [
4473
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4474
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4475
client.add_expected_call(
4476
b'VersionedFileRepository.get_inventories', (
4477
b'quack/', b'unordered'),
4478
b'success', (b'ok', ),
4479
_stream_to_byte_stream(stream, fmt))
4481
with tarfile.open(mode='w', fileobj=f) as tf:
4482
info = tarfile.TarInfo('somefile')
4484
contents = b'some data'
4485
info.type = tarfile.REGTYPE
4487
info.size = len(contents)
4488
tf.addfile(info, BytesIO(contents))
4489
client.add_expected_call(
4490
b'Repository.revision_archive', (b'quack/',
4491
b'somerevid', b'tar', b'foo.tar', b'', b'', None),
4492
b'success', (b'ok', ),
4494
tree = repo.revision_tree(b'somerevid')
4495
self.assertEqual(f.getvalue(), b''.join(
4496
tree.archive('tar', 'foo.tar')))
4499
class TestRepositoryAnnotate(TestRemoteRepository):
4500
"""Test RemoteRevisionTree.annotate.."""
4502
def _serialize_inv_delta(self, old_name, new_name, delta):
4503
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4504
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4506
def test_simple(self):
4507
transport_path = 'quack'
4508
repo, client = self.setup_fake_client_and_repository(transport_path)
4509
fmt = controldir.format_registry.get('2a')().repository_format
4512
('inventory-deltas', [
4513
versionedfile.FulltextContentFactory(
4514
b'somerevid', None, None,
4515
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4516
client.add_expected_call(
4517
b'VersionedFileRepository.get_inventories', (
4518
b'quack/', b'unordered'),
4519
b'success', (b'ok', ),
4520
_stream_to_byte_stream(stream, fmt))
4521
client.add_expected_call(
4522
b'Repository.annotate_file_revision',
4523
(b'quack/', b'somerevid', b'filename', b'', b'current:'),
4524
b'success', (b'ok', ),
4525
bencode.bencode([[b'baserevid', b'line 1\n'],
4526
[b'somerevid', b'line2\n']]))
4527
tree = repo.revision_tree(b'somerevid')
4529
(b'baserevid', b'line 1\n'),
4530
(b'somerevid', b'line2\n')],
4531
list(tree.annotate_iter('filename')))
4534
class TestBranchGetAllReferenceInfo(RemoteBranchTestCase):
4536
def test_get_all_reference_info(self):
4537
transport = MemoryTransport()
4538
client = FakeClient(transport.base)
4539
client.add_expected_call(
4540
b'Branch.get_stacked_on_url', (b'quack/',),
4541
b'error', (b'NotStacked',))
4542
client.add_expected_call(
4543
b'Branch.get_all_reference_info', (b'quack/',),
4544
b'success', (b'ok',), bencode.bencode([
4545
(b'file-id', b'https://www.example.com/', b'')]))
4546
transport.mkdir('quack')
4547
transport = transport.clone('quack')
4548
branch = self.make_remote_branch(transport, client)
4549
result = branch._get_all_reference_info()
4550
self.assertFinished(client)
4551
self.assertEqual({b'file-id': ('https://www.example.com/', None)}, result)