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.
28
from io import BytesIO
43
from ..branch import Branch
52
from ..bzr.bzrdir import (
59
from ..bzr.chk_serializer import chk_bencode_serializer
60
from ..bzr.remote import (
66
RemoteRepositoryFormat,
68
from ..bzr import groupcompress_repo, knitpack_repo
69
from ..revision import (
73
from ..bzr.smart import medium, request
74
from ..bzr.smart.client import _SmartClient
75
from ..bzr.smart.repository import (
76
SmartServerRepositoryGetParentMap,
77
SmartServerRepositoryGetStream_1_19,
78
_stream_to_byte_stream,
83
from .scenarios import load_tests_apply_scenarios
84
from ..transport.memory import MemoryTransport
85
from ..transport.remote import (
92
load_tests = load_tests_apply_scenarios
95
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
100
test_server.SmartTCPServer_for_testing_v2_only}),
102
{'transport_server': test_server.SmartTCPServer_for_testing})]
105
super(BasicRemoteObjectTests, self).setUp()
106
self.transport = self.get_transport()
107
# make a branch that can be opened over the smart transport
108
self.local_wt = BzrDir.create_standalone_workingtree('.')
109
self.addCleanup(self.transport.disconnect)
111
def test_create_remote_bzrdir(self):
112
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
113
self.assertIsInstance(b, BzrDir)
115
def test_open_remote_branch(self):
116
# open a standalone branch in the working directory
117
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
118
branch = b.open_branch()
119
self.assertIsInstance(branch, Branch)
121
def test_remote_repository(self):
122
b = BzrDir.open_from_transport(self.transport)
123
repo = b.open_repository()
124
revid = u'\xc823123123'.encode('utf8')
125
self.assertFalse(repo.has_revision(revid))
126
self.local_wt.commit(message='test commit', rev_id=revid)
127
self.assertTrue(repo.has_revision(revid))
129
def test_find_correct_format(self):
130
"""Should open a RemoteBzrDir over a RemoteTransport"""
131
fmt = BzrDirFormat.find_format(self.transport)
132
self.assertIn(RemoteBzrProber, controldir.ControlDirFormat._probers)
133
self.assertIsInstance(fmt, RemoteBzrDirFormat)
135
def test_open_detected_smart_format(self):
136
fmt = BzrDirFormat.find_format(self.transport)
137
d = fmt.open(self.transport)
138
self.assertIsInstance(d, BzrDir)
140
def test_remote_branch_repr(self):
141
b = BzrDir.open_from_transport(self.transport).open_branch()
142
self.assertStartsWith(str(b), 'RemoteBranch(')
144
def test_remote_bzrdir_repr(self):
145
b = BzrDir.open_from_transport(self.transport)
146
self.assertStartsWith(str(b), 'RemoteBzrDir(')
148
def test_remote_branch_format_supports_stacking(self):
150
self.make_branch('unstackable', format='pack-0.92')
151
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
152
self.assertFalse(b._format.supports_stacking())
153
self.make_branch('stackable', format='1.9')
154
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
155
self.assertTrue(b._format.supports_stacking())
157
def test_remote_repo_format_supports_external_references(self):
159
bd = self.make_controldir('unstackable', format='pack-0.92')
160
r = bd.create_repository()
161
self.assertFalse(r._format.supports_external_lookups)
162
r = BzrDir.open_from_transport(
163
t.clone('unstackable')).open_repository()
164
self.assertFalse(r._format.supports_external_lookups)
165
bd = self.make_controldir('stackable', format='1.9')
166
r = bd.create_repository()
167
self.assertTrue(r._format.supports_external_lookups)
168
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
169
self.assertTrue(r._format.supports_external_lookups)
171
def test_remote_branch_set_append_revisions_only(self):
172
# Make a format 1.9 branch, which supports append_revisions_only
173
branch = self.make_branch('branch', format='1.9')
174
branch.set_append_revisions_only(True)
175
config = branch.get_config_stack()
177
True, config.get('append_revisions_only'))
178
branch.set_append_revisions_only(False)
179
config = branch.get_config_stack()
181
False, config.get('append_revisions_only'))
183
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
184
branch = self.make_branch('branch', format='knit')
186
errors.UpgradeRequired, branch.set_append_revisions_only, True)
189
class FakeProtocol(object):
190
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
192
def __init__(self, body, fake_client):
194
self._body_buffer = None
195
self._fake_client = fake_client
197
def read_body_bytes(self, count=-1):
198
if self._body_buffer is None:
199
self._body_buffer = BytesIO(self.body)
200
bytes = self._body_buffer.read(count)
201
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
202
self._fake_client.expecting_body = False
205
def cancel_read_body(self):
206
self._fake_client.expecting_body = False
208
def read_streamed_body(self):
212
class FakeClient(_SmartClient):
213
"""Lookalike for _SmartClient allowing testing."""
215
def __init__(self, fake_medium_base='fake base'):
216
"""Create a FakeClient."""
219
self.expecting_body = False
220
# if non-None, this is the list of expected calls, with only the
221
# method name and arguments included. the body might be hard to
222
# compute so is not included. If a call is None, that call can
224
self._expected_calls = None
225
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
227
def add_expected_call(self, call_name, call_args, response_type,
228
response_args, response_body=None):
229
if self._expected_calls is None:
230
self._expected_calls = []
231
self._expected_calls.append((call_name, call_args))
232
self.responses.append((response_type, response_args, response_body))
234
def add_success_response(self, *args):
235
self.responses.append((b'success', args, None))
237
def add_success_response_with_body(self, body, *args):
238
self.responses.append((b'success', args, body))
239
if self._expected_calls is not None:
240
self._expected_calls.append(None)
242
def add_error_response(self, *args):
243
self.responses.append((b'error', args))
245
def add_unknown_method_response(self, verb):
246
self.responses.append((b'unknown', verb))
248
def finished_test(self):
249
if self._expected_calls:
250
raise AssertionError("%r finished but was still expecting %r"
251
% (self, self._expected_calls[0]))
253
def _get_next_response(self):
255
response_tuple = self.responses.pop(0)
257
raise AssertionError("%r didn't expect any more calls" % (self,))
258
if response_tuple[0] == b'unknown':
259
raise errors.UnknownSmartMethod(response_tuple[1])
260
elif response_tuple[0] == b'error':
261
raise errors.ErrorFromSmartServer(response_tuple[1])
262
return response_tuple
264
def _check_call(self, method, args):
265
if self._expected_calls is None:
266
# the test should be updated to say what it expects
269
next_call = self._expected_calls.pop(0)
271
raise AssertionError("%r didn't expect any more calls "
273
% (self, method, args,))
274
if next_call is None:
276
if method != next_call[0] or args != next_call[1]:
277
raise AssertionError(
278
"%r expected %r%r but got %r%r" %
279
(self, next_call[0], next_call[1], method, args,))
281
def call(self, method, *args):
282
self._check_call(method, args)
283
self._calls.append(('call', method, args))
284
return self._get_next_response()[1]
286
def call_expecting_body(self, method, *args):
287
self._check_call(method, args)
288
self._calls.append(('call_expecting_body', method, args))
289
result = self._get_next_response()
290
self.expecting_body = True
291
return result[1], FakeProtocol(result[2], self)
293
def call_with_body_bytes(self, method, args, body):
294
self._check_call(method, args)
295
self._calls.append(('call_with_body_bytes', method, args, body))
296
result = self._get_next_response()
297
return result[1], FakeProtocol(result[2], self)
299
def call_with_body_bytes_expecting_body(self, method, args, body):
300
self._check_call(method, args)
301
self._calls.append(('call_with_body_bytes_expecting_body', method,
303
result = self._get_next_response()
304
self.expecting_body = True
305
return result[1], FakeProtocol(result[2], self)
307
def call_with_body_stream(self, args, stream):
308
# Explicitly consume the stream before checking for an error, because
309
# that's what happens a real medium.
310
stream = list(stream)
311
self._check_call(args[0], args[1:])
313
('call_with_body_stream', args[0], args[1:], stream))
314
result = self._get_next_response()
315
# The second value returned from call_with_body_stream is supposed to
316
# be a response_handler object, but so far no tests depend on that.
317
response_handler = None
318
return result[1], response_handler
321
class FakeMedium(medium.SmartClientMedium):
323
def __init__(self, client_calls, base):
324
medium.SmartClientMedium.__init__(self, base)
325
self._client_calls = client_calls
327
def disconnect(self):
328
self._client_calls.append(('disconnect medium',))
331
class TestVfsHas(tests.TestCase):
333
def test_unicode_path(self):
334
client = FakeClient('/')
335
client.add_success_response(b'yes',)
336
transport = RemoteTransport('bzr://localhost/', _client=client)
337
filename = u'/hell\u00d8'
338
result = transport.has(filename)
340
[('call', b'has', (filename.encode('utf-8'),))],
342
self.assertTrue(result)
345
class TestRemote(tests.TestCaseWithMemoryTransport):
347
def get_branch_format(self):
348
reference_bzrdir_format = controldir.format_registry.get('default')()
349
return reference_bzrdir_format.get_branch_format()
351
def get_repo_format(self):
352
reference_bzrdir_format = controldir.format_registry.get('default')()
353
return reference_bzrdir_format.repository_format
355
def assertFinished(self, fake_client):
356
"""Assert that all of a FakeClient's expected calls have occurred."""
357
fake_client.finished_test()
360
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
361
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
363
def assertRemotePath(self, expected, client_base, transport_base):
364
"""Assert that the result of
365
SmartClientMedium.remote_path_from_transport is the expected value for
366
a given client_base and transport_base.
368
client_medium = medium.SmartClientMedium(client_base)
369
t = transport.get_transport(transport_base)
370
result = client_medium.remote_path_from_transport(t)
371
self.assertEqual(expected, result)
373
def test_remote_path_from_transport(self):
374
"""SmartClientMedium.remote_path_from_transport calculates a URL for
375
the given transport relative to the root of the client base URL.
377
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
378
self.assertRemotePath(
379
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
381
def assertRemotePathHTTP(self, expected, transport_base, relpath):
382
"""Assert that the result of
383
HttpTransportBase.remote_path_from_transport is the expected value for
384
a given transport_base and relpath of that transport. (Note that
385
HttpTransportBase is a subclass of SmartClientMedium)
387
base_transport = transport.get_transport(transport_base)
388
client_medium = base_transport.get_smart_medium()
389
cloned_transport = base_transport.clone(relpath)
390
result = client_medium.remote_path_from_transport(cloned_transport)
391
self.assertEqual(expected, result)
393
def test_remote_path_from_transport_http(self):
394
"""Remote paths for HTTP transports are calculated differently to other
395
transports. They are just relative to the client base, not the root
396
directory of the host.
398
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
399
self.assertRemotePathHTTP(
400
'../xyz/', scheme + '//host/path', '../xyz/')
401
self.assertRemotePathHTTP(
402
'xyz/', scheme + '//host/path', 'xyz/')
405
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
406
"""Tests for the behaviour of client_medium.remote_is_at_least."""
408
def test_initially_unlimited(self):
409
"""A fresh medium assumes that the remote side supports all
412
client_medium = medium.SmartClientMedium('dummy base')
413
self.assertFalse(client_medium._is_remote_before((99, 99)))
415
def test__remember_remote_is_before(self):
416
"""Calling _remember_remote_is_before ratchets down the known remote
419
client_medium = medium.SmartClientMedium('dummy base')
420
# Mark the remote side as being less than 1.6. The remote side may
422
client_medium._remember_remote_is_before((1, 6))
423
self.assertTrue(client_medium._is_remote_before((1, 6)))
424
self.assertFalse(client_medium._is_remote_before((1, 5)))
425
# Calling _remember_remote_is_before again with a lower value works.
426
client_medium._remember_remote_is_before((1, 5))
427
self.assertTrue(client_medium._is_remote_before((1, 5)))
428
# If you call _remember_remote_is_before with a higher value it logs a
429
# warning, and continues to remember the lower value.
430
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
431
client_medium._remember_remote_is_before((1, 9))
432
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
433
self.assertTrue(client_medium._is_remote_before((1, 5)))
436
class TestBzrDirCloningMetaDir(TestRemote):
438
def test_backwards_compat(self):
439
self.setup_smart_server_with_call_log()
440
a_dir = self.make_controldir('.')
441
self.reset_smart_call_log()
442
verb = b'BzrDir.cloning_metadir'
443
self.disable_verb(verb)
444
a_dir.cloning_metadir()
445
call_count = len([call for call in self.hpss_calls if
446
call.call.method == verb])
447
self.assertEqual(1, call_count)
449
def test_branch_reference(self):
450
transport = self.get_transport('quack')
451
referenced = self.make_branch('referenced')
452
expected = referenced.controldir.cloning_metadir()
453
client = FakeClient(transport.base)
454
client.add_expected_call(
455
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
456
b'error', (b'BranchReference',)),
457
client.add_expected_call(
458
b'BzrDir.open_branchV3', (b'quack/',),
459
b'success', (b'ref', self.get_url('referenced').encode('utf-8'))),
460
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
462
result = a_controldir.cloning_metadir()
463
# We should have got a control dir matching the referenced branch.
464
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
465
self.assertEqual(expected._repository_format,
466
result._repository_format)
467
self.assertEqual(expected._branch_format, result._branch_format)
468
self.assertFinished(client)
470
def test_current_server(self):
471
transport = self.get_transport('.')
472
transport = transport.clone('quack')
473
self.make_controldir('quack')
474
client = FakeClient(transport.base)
475
reference_bzrdir_format = controldir.format_registry.get('default')()
476
control_name = reference_bzrdir_format.network_name()
477
client.add_expected_call(
478
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
479
b'success', (control_name, b'', (b'branch', b''))),
480
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
482
result = a_controldir.cloning_metadir()
483
# We should have got a reference control dir with default branch and
484
# repository formats.
485
# This pokes a little, just to be sure.
486
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
487
self.assertEqual(None, result._repository_format)
488
self.assertEqual(None, result._branch_format)
489
self.assertFinished(client)
491
def test_unknown(self):
492
transport = self.get_transport('quack')
493
referenced = self.make_branch('referenced')
494
referenced.controldir.cloning_metadir()
495
client = FakeClient(transport.base)
496
client.add_expected_call(
497
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
498
b'success', (b'unknown', b'unknown', (b'branch', b''))),
499
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
501
self.assertRaises(errors.UnknownFormatError,
502
a_controldir.cloning_metadir)
505
class TestBzrDirCheckoutMetaDir(TestRemote):
507
def test__get_checkout_format(self):
508
transport = MemoryTransport()
509
client = FakeClient(transport.base)
510
reference_bzrdir_format = controldir.format_registry.get('default')()
511
control_name = reference_bzrdir_format.network_name()
512
client.add_expected_call(
513
b'BzrDir.checkout_metadir', (b'quack/', ),
514
b'success', (control_name, b'', b''))
515
transport.mkdir('quack')
516
transport = transport.clone('quack')
517
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
519
result = a_controldir.checkout_metadir()
520
# We should have got a reference control dir with default branch and
521
# repository formats.
522
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
523
self.assertEqual(None, result._repository_format)
524
self.assertEqual(None, result._branch_format)
525
self.assertFinished(client)
527
def test_unknown_format(self):
528
transport = MemoryTransport()
529
client = FakeClient(transport.base)
530
client.add_expected_call(
531
b'BzrDir.checkout_metadir', (b'quack/',),
532
b'success', (b'dontknow', b'', b''))
533
transport.mkdir('quack')
534
transport = transport.clone('quack')
535
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
537
self.assertRaises(errors.UnknownFormatError,
538
a_controldir.checkout_metadir)
539
self.assertFinished(client)
542
class TestBzrDirGetBranches(TestRemote):
544
def test_get_branches(self):
545
transport = MemoryTransport()
546
client = FakeClient(transport.base)
547
reference_bzrdir_format = controldir.format_registry.get('default')()
548
branch_name = reference_bzrdir_format.get_branch_format().network_name()
549
client.add_success_response_with_body(
551
b"foo": (b"branch", branch_name),
552
b"": (b"branch", branch_name)}), b"success")
553
client.add_success_response(
554
b'ok', b'', b'no', b'no', b'no',
555
reference_bzrdir_format.repository_format.network_name())
556
client.add_error_response(b'NotStacked')
557
client.add_success_response(
558
b'ok', b'', b'no', b'no', b'no',
559
reference_bzrdir_format.repository_format.network_name())
560
client.add_error_response(b'NotStacked')
561
transport.mkdir('quack')
562
transport = transport.clone('quack')
563
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
565
result = a_controldir.get_branches()
566
self.assertEqual({"", "foo"}, set(result.keys()))
568
[('call_expecting_body', b'BzrDir.get_branches', (b'quack/',)),
569
('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
570
('call', b'Branch.get_stacked_on_url', (b'quack/', )),
571
('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
572
('call', b'Branch.get_stacked_on_url', (b'quack/', ))],
576
class TestBzrDirDestroyBranch(TestRemote):
578
def test_destroy_default(self):
579
transport = self.get_transport('quack')
580
self.make_branch('referenced')
581
client = FakeClient(transport.base)
582
client.add_expected_call(
583
b'BzrDir.destroy_branch', (b'quack/', ),
584
b'success', (b'ok',)),
585
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
587
a_controldir.destroy_branch()
588
self.assertFinished(client)
591
class TestBzrDirHasWorkingTree(TestRemote):
593
def test_has_workingtree(self):
594
transport = self.get_transport('quack')
595
client = FakeClient(transport.base)
596
client.add_expected_call(
597
b'BzrDir.has_workingtree', (b'quack/',),
598
b'success', (b'yes',)),
599
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
601
self.assertTrue(a_controldir.has_workingtree())
602
self.assertFinished(client)
604
def test_no_workingtree(self):
605
transport = self.get_transport('quack')
606
client = FakeClient(transport.base)
607
client.add_expected_call(
608
b'BzrDir.has_workingtree', (b'quack/',),
609
b'success', (b'no',)),
610
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
612
self.assertFalse(a_controldir.has_workingtree())
613
self.assertFinished(client)
616
class TestBzrDirDestroyRepository(TestRemote):
618
def test_destroy_repository(self):
619
transport = self.get_transport('quack')
620
client = FakeClient(transport.base)
621
client.add_expected_call(
622
b'BzrDir.destroy_repository', (b'quack/',),
623
b'success', (b'ok',)),
624
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
626
a_controldir.destroy_repository()
627
self.assertFinished(client)
630
class TestBzrDirOpen(TestRemote):
632
def make_fake_client_and_transport(self, path='quack'):
633
transport = MemoryTransport()
634
transport.mkdir(path)
635
transport = transport.clone(path)
636
client = FakeClient(transport.base)
637
return client, transport
639
def test_absent(self):
640
client, transport = self.make_fake_client_and_transport()
641
client.add_expected_call(
642
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'no',))
643
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
644
RemoteBzrDirFormat(), _client=client,
646
self.assertFinished(client)
648
def test_present_without_workingtree(self):
649
client, transport = self.make_fake_client_and_transport()
650
client.add_expected_call(
651
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'no'))
652
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
653
_client=client, _force_probe=True)
654
self.assertIsInstance(bd, RemoteBzrDir)
655
self.assertFalse(bd.has_workingtree())
656
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
657
self.assertFinished(client)
659
def test_present_with_workingtree(self):
660
client, transport = self.make_fake_client_and_transport()
661
client.add_expected_call(
662
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'yes'))
663
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
664
_client=client, _force_probe=True)
665
self.assertIsInstance(bd, RemoteBzrDir)
666
self.assertTrue(bd.has_workingtree())
667
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
668
self.assertFinished(client)
670
def test_backwards_compat(self):
671
client, transport = self.make_fake_client_and_transport()
672
client.add_expected_call(
673
b'BzrDir.open_2.1', (b'quack/',), b'unknown',
674
(b'BzrDir.open_2.1',))
675
client.add_expected_call(
676
b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
677
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
678
_client=client, _force_probe=True)
679
self.assertIsInstance(bd, RemoteBzrDir)
680
self.assertFinished(client)
682
def test_backwards_compat_hpss_v2(self):
683
client, transport = self.make_fake_client_and_transport()
684
# Monkey-patch fake client to simulate real-world behaviour with v2
685
# server: upon first RPC call detect the protocol version, and because
686
# the version is 2 also do _remember_remote_is_before((1, 6)) before
687
# continuing with the RPC.
688
orig_check_call = client._check_call
690
def check_call(method, args):
691
client._medium._protocol_version = 2
692
client._medium._remember_remote_is_before((1, 6))
693
client._check_call = orig_check_call
694
client._check_call(method, args)
695
client._check_call = check_call
696
client.add_expected_call(
697
b'BzrDir.open_2.1', (b'quack/',), b'unknown',
698
(b'BzrDir.open_2.1',))
699
client.add_expected_call(
700
b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
701
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
702
_client=client, _force_probe=True)
703
self.assertIsInstance(bd, RemoteBzrDir)
704
self.assertFinished(client)
707
class TestBzrDirOpenBranch(TestRemote):
709
def test_backwards_compat(self):
710
self.setup_smart_server_with_call_log()
711
self.make_branch('.')
712
a_dir = BzrDir.open(self.get_url('.'))
713
self.reset_smart_call_log()
714
verb = b'BzrDir.open_branchV3'
715
self.disable_verb(verb)
717
call_count = len([call for call in self.hpss_calls if
718
call.call.method == verb])
719
self.assertEqual(1, call_count)
721
def test_branch_present(self):
722
reference_format = self.get_repo_format()
723
network_name = reference_format.network_name()
724
branch_network_name = self.get_branch_format().network_name()
725
transport = MemoryTransport()
726
transport.mkdir('quack')
727
transport = transport.clone('quack')
728
client = FakeClient(transport.base)
729
client.add_expected_call(
730
b'BzrDir.open_branchV3', (b'quack/',),
731
b'success', (b'branch', branch_network_name))
732
client.add_expected_call(
733
b'BzrDir.find_repositoryV3', (b'quack/',),
734
b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
735
client.add_expected_call(
736
b'Branch.get_stacked_on_url', (b'quack/',),
737
b'error', (b'NotStacked',))
738
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
740
result = bzrdir.open_branch()
741
self.assertIsInstance(result, RemoteBranch)
742
self.assertEqual(bzrdir, result.controldir)
743
self.assertFinished(client)
745
def test_branch_missing(self):
746
transport = MemoryTransport()
747
transport.mkdir('quack')
748
transport = transport.clone('quack')
749
client = FakeClient(transport.base)
750
client.add_error_response(b'nobranch')
751
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
753
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
755
[('call', b'BzrDir.open_branchV3', (b'quack/',))],
758
def test__get_tree_branch(self):
759
# _get_tree_branch is a form of open_branch, but it should only ask for
760
# branch opening, not any other network requests.
763
def open_branch(name=None, possible_transports=None):
764
calls.append("Called")
766
transport = MemoryTransport()
767
# no requests on the network - catches other api calls being made.
768
client = FakeClient(transport.base)
769
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
771
# patch the open_branch call to record that it was called.
772
bzrdir.open_branch = open_branch
773
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
774
self.assertEqual(["Called"], calls)
775
self.assertEqual([], client._calls)
777
def test_url_quoting_of_path(self):
778
# Relpaths on the wire should not be URL-escaped. So "~" should be
779
# transmitted as "~", not "%7E".
780
transport = RemoteTCPTransport('bzr://localhost/~hello/')
781
client = FakeClient(transport.base)
782
reference_format = self.get_repo_format()
783
network_name = reference_format.network_name()
784
branch_network_name = self.get_branch_format().network_name()
785
client.add_expected_call(
786
b'BzrDir.open_branchV3', (b'~hello/',),
787
b'success', (b'branch', branch_network_name))
788
client.add_expected_call(
789
b'BzrDir.find_repositoryV3', (b'~hello/',),
790
b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
791
client.add_expected_call(
792
b'Branch.get_stacked_on_url', (b'~hello/',),
793
b'error', (b'NotStacked',))
794
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
797
self.assertFinished(client)
799
def check_open_repository(self, rich_root, subtrees,
800
external_lookup=b'no'):
801
reference_format = self.get_repo_format()
802
network_name = reference_format.network_name()
803
transport = MemoryTransport()
804
transport.mkdir('quack')
805
transport = transport.clone('quack')
807
rich_response = b'yes'
809
rich_response = b'no'
811
subtree_response = b'yes'
813
subtree_response = b'no'
814
client = FakeClient(transport.base)
815
client.add_success_response(
816
b'ok', b'', rich_response, subtree_response, external_lookup,
818
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
820
result = bzrdir.open_repository()
822
[('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
824
self.assertIsInstance(result, RemoteRepository)
825
self.assertEqual(bzrdir, result.controldir)
826
self.assertEqual(rich_root, result._format.rich_root_data)
827
self.assertEqual(subtrees, result._format.supports_tree_reference)
829
def test_open_repository_sets_format_attributes(self):
830
self.check_open_repository(True, True)
831
self.check_open_repository(False, True)
832
self.check_open_repository(True, False)
833
self.check_open_repository(False, False)
834
self.check_open_repository(False, False, b'yes')
836
def test_old_server(self):
837
"""RemoteBzrDirFormat should fail to probe if the server version is too
841
errors.NotBranchError,
842
RemoteBzrProber.probe_transport, OldServerTransport())
845
class TestBzrDirCreateBranch(TestRemote):
847
def test_backwards_compat(self):
848
self.setup_smart_server_with_call_log()
849
repo = self.make_repository('.')
850
self.reset_smart_call_log()
851
self.disable_verb(b'BzrDir.create_branch')
852
repo.controldir.create_branch()
853
create_branch_call_count = len(
854
[call for call in self.hpss_calls
855
if call.call.method == b'BzrDir.create_branch'])
856
self.assertEqual(1, create_branch_call_count)
858
def test_current_server(self):
859
transport = self.get_transport('.')
860
transport = transport.clone('quack')
861
self.make_repository('quack')
862
client = FakeClient(transport.base)
863
reference_bzrdir_format = controldir.format_registry.get('default')()
864
reference_format = reference_bzrdir_format.get_branch_format()
865
network_name = reference_format.network_name()
866
reference_repo_fmt = reference_bzrdir_format.repository_format
867
reference_repo_name = reference_repo_fmt.network_name()
868
client.add_expected_call(
869
b'BzrDir.create_branch', (b'quack/', network_name),
870
b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
871
reference_repo_name))
872
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
874
branch = a_controldir.create_branch()
875
# We should have got a remote branch
876
self.assertIsInstance(branch, remote.RemoteBranch)
877
# its format should have the settings from the response
878
format = branch._format
879
self.assertEqual(network_name, format.network_name())
881
def test_already_open_repo_and_reused_medium(self):
882
"""Bug 726584: create_branch(..., repository=repo) should work
883
regardless of what the smart medium's base URL is.
885
self.transport_server = test_server.SmartTCPServer_for_testing
886
transport = self.get_transport('.')
887
repo = self.make_repository('quack')
888
# Client's medium rooted a transport root (not at the bzrdir)
889
client = FakeClient(transport.base)
890
transport = transport.clone('quack')
891
reference_bzrdir_format = controldir.format_registry.get('default')()
892
reference_format = reference_bzrdir_format.get_branch_format()
893
network_name = reference_format.network_name()
894
reference_repo_fmt = reference_bzrdir_format.repository_format
895
reference_repo_name = reference_repo_fmt.network_name()
896
client.add_expected_call(
897
b'BzrDir.create_branch', (b'extra/quack/', network_name),
898
b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
899
reference_repo_name))
900
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
902
branch = a_controldir.create_branch(repository=repo)
903
# We should have got a remote branch
904
self.assertIsInstance(branch, remote.RemoteBranch)
905
# its format should have the settings from the response
906
format = branch._format
907
self.assertEqual(network_name, format.network_name())
910
class TestBzrDirCreateRepository(TestRemote):
912
def test_backwards_compat(self):
913
self.setup_smart_server_with_call_log()
914
bzrdir = self.make_controldir('.')
915
self.reset_smart_call_log()
916
self.disable_verb(b'BzrDir.create_repository')
917
bzrdir.create_repository()
918
create_repo_call_count = len([call for call in self.hpss_calls if
919
call.call.method == b'BzrDir.create_repository'])
920
self.assertEqual(1, create_repo_call_count)
922
def test_current_server(self):
923
transport = self.get_transport('.')
924
transport = transport.clone('quack')
925
self.make_controldir('quack')
926
client = FakeClient(transport.base)
927
reference_bzrdir_format = controldir.format_registry.get('default')()
928
reference_format = reference_bzrdir_format.repository_format
929
network_name = reference_format.network_name()
930
client.add_expected_call(
931
b'BzrDir.create_repository', (b'quack/',
932
b'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
934
b'success', (b'ok', b'yes', b'yes', b'yes', network_name))
935
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
937
repo = a_controldir.create_repository()
938
# We should have got a remote repository
939
self.assertIsInstance(repo, remote.RemoteRepository)
940
# its format should have the settings from the response
941
format = repo._format
942
self.assertTrue(format.rich_root_data)
943
self.assertTrue(format.supports_tree_reference)
944
self.assertTrue(format.supports_external_lookups)
945
self.assertEqual(network_name, format.network_name())
948
class TestBzrDirOpenRepository(TestRemote):
950
def test_backwards_compat_1_2_3(self):
951
# fallback all the way to the first version.
952
reference_format = self.get_repo_format()
953
network_name = reference_format.network_name()
954
server_url = 'bzr://example.com/'
955
self.permit_url(server_url)
956
client = FakeClient(server_url)
957
client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
958
client.add_unknown_method_response(b'BzrDir.find_repositoryV2')
959
client.add_success_response(b'ok', b'', b'no', b'no')
960
# A real repository instance will be created to determine the network
962
client.add_success_response_with_body(
963
b"Bazaar-NG meta directory, format 1\n", b'ok')
964
client.add_success_response(b'stat', b'0', b'65535')
965
client.add_success_response_with_body(
966
reference_format.get_format_string(), b'ok')
967
# PackRepository wants to do a stat
968
client.add_success_response(b'stat', b'0', b'65535')
969
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
971
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
973
repo = bzrdir.open_repository()
975
[('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
976
('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
977
('call', b'BzrDir.find_repository', (b'quack/',)),
978
('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
979
('call', b'stat', (b'/quack/.bzr',)),
980
('call_expecting_body', b'get', (b'/quack/.bzr/repository/format',)),
981
('call', b'stat', (b'/quack/.bzr/repository',)),
984
self.assertEqual(network_name, repo._format.network_name())
986
def test_backwards_compat_2(self):
987
# fallback to find_repositoryV2
988
reference_format = self.get_repo_format()
989
network_name = reference_format.network_name()
990
server_url = 'bzr://example.com/'
991
self.permit_url(server_url)
992
client = FakeClient(server_url)
993
client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
994
client.add_success_response(b'ok', b'', b'no', b'no', b'no')
995
# A real repository instance will be created to determine the network
997
client.add_success_response_with_body(
998
b"Bazaar-NG meta directory, format 1\n", b'ok')
999
client.add_success_response(b'stat', b'0', b'65535')
1000
client.add_success_response_with_body(
1001
reference_format.get_format_string(), b'ok')
1002
# PackRepository wants to do a stat
1003
client.add_success_response(b'stat', b'0', b'65535')
1004
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
1006
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
1008
repo = bzrdir.open_repository()
1010
[('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
1011
('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
1012
('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
1013
('call', b'stat', (b'/quack/.bzr',)),
1014
('call_expecting_body', b'get',
1015
(b'/quack/.bzr/repository/format',)),
1016
('call', b'stat', (b'/quack/.bzr/repository',)),
1019
self.assertEqual(network_name, repo._format.network_name())
1021
def test_current_server(self):
1022
reference_format = self.get_repo_format()
1023
network_name = reference_format.network_name()
1024
transport = MemoryTransport()
1025
transport.mkdir('quack')
1026
transport = transport.clone('quack')
1027
client = FakeClient(transport.base)
1028
client.add_success_response(
1029
b'ok', b'', b'no', b'no', b'no', network_name)
1030
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1032
repo = bzrdir.open_repository()
1034
[('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
1036
self.assertEqual(network_name, repo._format.network_name())
1039
class TestBzrDirFormatInitializeEx(TestRemote):
1041
def test_success(self):
1042
"""Simple test for typical successful call."""
1043
fmt = RemoteBzrDirFormat()
1044
default_format_name = BzrDirFormat.get_default_format().network_name()
1045
transport = self.get_transport()
1046
client = FakeClient(transport.base)
1047
client.add_expected_call(
1048
b'BzrDirFormat.initialize_ex_1.16',
1049
(default_format_name, b'path', b'False', b'False', b'False', b'',
1050
b'', b'', b'', b'False'),
1052
(b'.', b'no', b'no', b'yes', b'repo fmt', b'repo bzrdir fmt',
1053
b'bzrdir fmt', b'False', b'', b'', b'repo lock token'))
1054
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1055
# it's currently hard to test that without supplying a real remote
1056
# transport connected to a real server.
1057
fmt._initialize_on_transport_ex_rpc(
1058
client, b'path', transport, False, False, False, None, None, None,
1060
self.assertFinished(client)
1062
def test_error(self):
1063
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1064
corresponding error from the client.
1066
fmt = RemoteBzrDirFormat()
1067
default_format_name = BzrDirFormat.get_default_format().network_name()
1068
transport = self.get_transport()
1069
client = FakeClient(transport.base)
1070
client.add_expected_call(
1071
b'BzrDirFormat.initialize_ex_1.16',
1072
(default_format_name, b'path', b'False', b'False', b'False', b'',
1073
b'', b'', b'', b'False'),
1075
(b'PermissionDenied', b'path', b'extra info'))
1076
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1077
# it's currently hard to test that without supplying a real remote
1078
# transport connected to a real server.
1079
err = self.assertRaises(
1080
errors.PermissionDenied,
1081
fmt._initialize_on_transport_ex_rpc, client, b'path', transport,
1082
False, False, False, None, None, None, None, False)
1083
self.assertEqual('path', err.path)
1084
self.assertEqual(': extra info', err.extra)
1085
self.assertFinished(client)
1087
def test_error_from_real_server(self):
1088
"""Integration test for error translation."""
1089
transport = self.make_smart_server('foo')
1090
transport = transport.clone('no-such-path')
1091
fmt = RemoteBzrDirFormat()
1093
errors.NoSuchFile, fmt.initialize_on_transport_ex, transport,
1094
create_prefix=False)
1097
class OldSmartClient(object):
1098
"""A fake smart client for test_old_version that just returns a version one
1099
response to the 'hello' (query version) command.
1102
def get_request(self):
1103
input_file = BytesIO(b'ok\x011\n')
1104
output_file = BytesIO()
1105
client_medium = medium.SmartSimplePipesClientMedium(
1106
input_file, output_file)
1107
return medium.SmartClientStreamMediumRequest(client_medium)
1109
def protocol_version(self):
1113
class OldServerTransport(object):
1114
"""A fake transport for test_old_server that reports it's smart server
1115
protocol version as version one.
1121
def get_smart_client(self):
1122
return OldSmartClient()
1125
class RemoteBzrDirTestCase(TestRemote):
1127
def make_remote_bzrdir(self, transport, client):
1128
"""Make a RemotebzrDir using 'client' as the _client."""
1129
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1133
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1135
def lock_remote_branch(self, branch):
1136
"""Trick a RemoteBranch into thinking it is locked."""
1137
branch._lock_mode = 'w'
1138
branch._lock_count = 2
1139
branch._lock_token = b'branch token'
1140
branch._repo_lock_token = b'repo token'
1141
branch.repository._lock_mode = 'w'
1142
branch.repository._lock_count = 2
1143
branch.repository._lock_token = b'repo token'
1145
def make_remote_branch(self, transport, client):
1146
"""Make a RemoteBranch using 'client' as its _SmartClient.
1148
A RemoteBzrDir and RemoteRepository will also be created to fill out
1149
the RemoteBranch, albeit with stub values for some of their attributes.
1151
# we do not want bzrdir to make any remote calls, so use False as its
1152
# _client. If it tries to make a remote call, this will fail
1154
bzrdir = self.make_remote_bzrdir(transport, False)
1155
repo = RemoteRepository(bzrdir, None, _client=client)
1156
branch_format = self.get_branch_format()
1157
format = RemoteBranchFormat(network_name=branch_format.network_name())
1158
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1161
class TestBranchBreakLock(RemoteBranchTestCase):
1163
def test_break_lock(self):
1164
transport = MemoryTransport()
1165
client = FakeClient(transport.base)
1166
client.add_expected_call(
1167
b'Branch.get_stacked_on_url', (b'quack/',),
1168
b'error', (b'NotStacked',))
1169
client.add_expected_call(
1170
b'Branch.break_lock', (b'quack/',),
1171
b'success', (b'ok',))
1172
transport.mkdir('quack')
1173
transport = transport.clone('quack')
1174
branch = self.make_remote_branch(transport, client)
1176
self.assertFinished(client)
1179
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1181
def test_get_physical_lock_status_yes(self):
1182
transport = MemoryTransport()
1183
client = FakeClient(transport.base)
1184
client.add_expected_call(
1185
b'Branch.get_stacked_on_url', (b'quack/',),
1186
b'error', (b'NotStacked',))
1187
client.add_expected_call(
1188
b'Branch.get_physical_lock_status', (b'quack/',),
1189
b'success', (b'yes',))
1190
transport.mkdir('quack')
1191
transport = transport.clone('quack')
1192
branch = self.make_remote_branch(transport, client)
1193
result = branch.get_physical_lock_status()
1194
self.assertFinished(client)
1195
self.assertEqual(True, result)
1197
def test_get_physical_lock_status_no(self):
1198
transport = MemoryTransport()
1199
client = FakeClient(transport.base)
1200
client.add_expected_call(
1201
b'Branch.get_stacked_on_url', (b'quack/',),
1202
b'error', (b'NotStacked',))
1203
client.add_expected_call(
1204
b'Branch.get_physical_lock_status', (b'quack/',),
1205
b'success', (b'no',))
1206
transport.mkdir('quack')
1207
transport = transport.clone('quack')
1208
branch = self.make_remote_branch(transport, client)
1209
result = branch.get_physical_lock_status()
1210
self.assertFinished(client)
1211
self.assertEqual(False, result)
1214
class TestBranchGetParent(RemoteBranchTestCase):
1216
def test_no_parent(self):
1217
# in an empty branch we decode the response properly
1218
transport = MemoryTransport()
1219
client = FakeClient(transport.base)
1220
client.add_expected_call(
1221
b'Branch.get_stacked_on_url', (b'quack/',),
1222
b'error', (b'NotStacked',))
1223
client.add_expected_call(
1224
b'Branch.get_parent', (b'quack/',),
1226
transport.mkdir('quack')
1227
transport = transport.clone('quack')
1228
branch = self.make_remote_branch(transport, client)
1229
result = branch.get_parent()
1230
self.assertFinished(client)
1231
self.assertEqual(None, result)
1233
def test_parent_relative(self):
1234
transport = MemoryTransport()
1235
client = FakeClient(transport.base)
1236
client.add_expected_call(
1237
b'Branch.get_stacked_on_url', (b'kwaak/',),
1238
b'error', (b'NotStacked',))
1239
client.add_expected_call(
1240
b'Branch.get_parent', (b'kwaak/',),
1241
b'success', (b'../foo/',))
1242
transport.mkdir('kwaak')
1243
transport = transport.clone('kwaak')
1244
branch = self.make_remote_branch(transport, client)
1245
result = branch.get_parent()
1246
self.assertEqual(transport.clone('../foo').base, result)
1248
def test_parent_absolute(self):
1249
transport = MemoryTransport()
1250
client = FakeClient(transport.base)
1251
client.add_expected_call(
1252
b'Branch.get_stacked_on_url', (b'kwaak/',),
1253
b'error', (b'NotStacked',))
1254
client.add_expected_call(
1255
b'Branch.get_parent', (b'kwaak/',),
1256
b'success', (b'http://foo/',))
1257
transport.mkdir('kwaak')
1258
transport = transport.clone('kwaak')
1259
branch = self.make_remote_branch(transport, client)
1260
result = branch.get_parent()
1261
self.assertEqual('http://foo/', result)
1262
self.assertFinished(client)
1265
class TestBranchSetParentLocation(RemoteBranchTestCase):
1267
def test_no_parent(self):
1268
# We call the verb when setting parent to None
1269
transport = MemoryTransport()
1270
client = FakeClient(transport.base)
1271
client.add_expected_call(
1272
b'Branch.get_stacked_on_url', (b'quack/',),
1273
b'error', (b'NotStacked',))
1274
client.add_expected_call(
1275
b'Branch.set_parent_location', (b'quack/', b'b', b'r', b''),
1277
transport.mkdir('quack')
1278
transport = transport.clone('quack')
1279
branch = self.make_remote_branch(transport, client)
1280
branch._lock_token = b'b'
1281
branch._repo_lock_token = b'r'
1282
branch._set_parent_location(None)
1283
self.assertFinished(client)
1285
def test_parent(self):
1286
transport = MemoryTransport()
1287
client = FakeClient(transport.base)
1288
client.add_expected_call(
1289
b'Branch.get_stacked_on_url', (b'kwaak/',),
1290
b'error', (b'NotStacked',))
1291
client.add_expected_call(
1292
b'Branch.set_parent_location', (b'kwaak/', b'b', b'r', b'foo'),
1294
transport.mkdir('kwaak')
1295
transport = transport.clone('kwaak')
1296
branch = self.make_remote_branch(transport, client)
1297
branch._lock_token = b'b'
1298
branch._repo_lock_token = b'r'
1299
branch._set_parent_location('foo')
1300
self.assertFinished(client)
1302
def test_backwards_compat(self):
1303
self.setup_smart_server_with_call_log()
1304
branch = self.make_branch('.')
1305
self.reset_smart_call_log()
1306
verb = b'Branch.set_parent_location'
1307
self.disable_verb(verb)
1308
branch.set_parent('http://foo/')
1309
self.assertLength(14, self.hpss_calls)
1312
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1314
def test_backwards_compat(self):
1315
self.setup_smart_server_with_call_log()
1316
branch = self.make_branch('.')
1317
self.reset_smart_call_log()
1318
verb = b'Branch.get_tags_bytes'
1319
self.disable_verb(verb)
1320
branch.tags.get_tag_dict()
1321
call_count = len([call for call in self.hpss_calls if
1322
call.call.method == verb])
1323
self.assertEqual(1, call_count)
1325
def test_trivial(self):
1326
transport = MemoryTransport()
1327
client = FakeClient(transport.base)
1328
client.add_expected_call(
1329
b'Branch.get_stacked_on_url', (b'quack/',),
1330
b'error', (b'NotStacked',))
1331
client.add_expected_call(
1332
b'Branch.get_tags_bytes', (b'quack/',),
1334
transport.mkdir('quack')
1335
transport = transport.clone('quack')
1336
branch = self.make_remote_branch(transport, client)
1337
result = branch.tags.get_tag_dict()
1338
self.assertFinished(client)
1339
self.assertEqual({}, result)
1342
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1344
def test_trivial(self):
1345
transport = MemoryTransport()
1346
client = FakeClient(transport.base)
1347
client.add_expected_call(
1348
b'Branch.get_stacked_on_url', (b'quack/',),
1349
b'error', (b'NotStacked',))
1350
client.add_expected_call(
1351
b'Branch.set_tags_bytes', (b'quack/',
1352
b'branch token', b'repo token'),
1354
transport.mkdir('quack')
1355
transport = transport.clone('quack')
1356
branch = self.make_remote_branch(transport, client)
1357
self.lock_remote_branch(branch)
1358
branch._set_tags_bytes(b'tags bytes')
1359
self.assertFinished(client)
1360
self.assertEqual(b'tags bytes', client._calls[-1][-1])
1362
def test_backwards_compatible(self):
1363
transport = MemoryTransport()
1364
client = FakeClient(transport.base)
1365
client.add_expected_call(
1366
b'Branch.get_stacked_on_url', (b'quack/',),
1367
b'error', (b'NotStacked',))
1368
client.add_expected_call(
1369
b'Branch.set_tags_bytes', (b'quack/',
1370
b'branch token', b'repo token'),
1371
b'unknown', (b'Branch.set_tags_bytes',))
1372
transport.mkdir('quack')
1373
transport = transport.clone('quack')
1374
branch = self.make_remote_branch(transport, client)
1375
self.lock_remote_branch(branch)
1377
class StubRealBranch(object):
1381
def _set_tags_bytes(self, bytes):
1382
self.calls.append(('set_tags_bytes', bytes))
1383
real_branch = StubRealBranch()
1384
branch._real_branch = real_branch
1385
branch._set_tags_bytes(b'tags bytes')
1386
# Call a second time, to exercise the 'remote version already inferred'
1388
branch._set_tags_bytes(b'tags bytes')
1389
self.assertFinished(client)
1391
[('set_tags_bytes', b'tags bytes')] * 2, real_branch.calls)
1394
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1396
def test_uses_last_revision_info_and_tags_by_default(self):
1397
transport = MemoryTransport()
1398
client = FakeClient(transport.base)
1399
client.add_expected_call(
1400
b'Branch.get_stacked_on_url', (b'quack/',),
1401
b'error', (b'NotStacked',))
1402
client.add_expected_call(
1403
b'Branch.last_revision_info', (b'quack/',),
1404
b'success', (b'ok', b'1', b'rev-tip'))
1405
client.add_expected_call(
1406
b'Branch.get_config_file', (b'quack/',),
1407
b'success', (b'ok',), b'')
1408
transport.mkdir('quack')
1409
transport = transport.clone('quack')
1410
branch = self.make_remote_branch(transport, client)
1411
result = branch.heads_to_fetch()
1412
self.assertFinished(client)
1413
self.assertEqual(({b'rev-tip'}, set()), result)
1415
def test_uses_last_revision_info_and_tags_when_set(self):
1416
transport = MemoryTransport()
1417
client = FakeClient(transport.base)
1418
client.add_expected_call(
1419
b'Branch.get_stacked_on_url', (b'quack/',),
1420
b'error', (b'NotStacked',))
1421
client.add_expected_call(
1422
b'Branch.last_revision_info', (b'quack/',),
1423
b'success', (b'ok', b'1', b'rev-tip'))
1424
client.add_expected_call(
1425
b'Branch.get_config_file', (b'quack/',),
1426
b'success', (b'ok',), b'branch.fetch_tags = True')
1427
# XXX: this will break if the default format's serialization of tags
1428
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1429
client.add_expected_call(
1430
b'Branch.get_tags_bytes', (b'quack/',),
1431
b'success', (b'd5:tag-17:rev-foo5:tag-27:rev-bare',))
1432
transport.mkdir('quack')
1433
transport = transport.clone('quack')
1434
branch = self.make_remote_branch(transport, client)
1435
result = branch.heads_to_fetch()
1436
self.assertFinished(client)
1438
({b'rev-tip'}, {b'rev-foo', b'rev-bar'}), result)
1440
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1441
transport = MemoryTransport()
1442
client = FakeClient(transport.base)
1443
client.add_expected_call(
1444
b'Branch.get_stacked_on_url', (b'quack/',),
1445
b'error', (b'NotStacked',))
1446
client.add_expected_call(
1447
b'Branch.heads_to_fetch', (b'quack/',),
1448
b'success', ([b'tip'], [b'tagged-1', b'tagged-2']))
1449
transport.mkdir('quack')
1450
transport = transport.clone('quack')
1451
branch = self.make_remote_branch(transport, client)
1452
branch._format._use_default_local_heads_to_fetch = lambda: False
1453
result = branch.heads_to_fetch()
1454
self.assertFinished(client)
1455
self.assertEqual(({b'tip'}, {b'tagged-1', b'tagged-2'}), result)
1457
def make_branch_with_tags(self):
1458
self.setup_smart_server_with_call_log()
1459
# Make a branch with a single revision.
1460
builder = self.make_branch_builder('foo')
1461
builder.start_series()
1462
builder.build_snapshot(None, [
1463
('add', ('', b'root-id', 'directory', ''))],
1465
builder.finish_series()
1466
branch = builder.get_branch()
1467
# Add two tags to that branch
1468
branch.tags.set_tag('tag-1', b'rev-1')
1469
branch.tags.set_tag('tag-2', b'rev-2')
1472
def test_backwards_compatible(self):
1473
br = self.make_branch_with_tags()
1474
br.get_config_stack().set('branch.fetch_tags', True)
1475
self.addCleanup(br.lock_read().unlock)
1476
# Disable the heads_to_fetch verb
1477
verb = b'Branch.heads_to_fetch'
1478
self.disable_verb(verb)
1479
self.reset_smart_call_log()
1480
result = br.heads_to_fetch()
1481
self.assertEqual(({b'tip'}, {b'rev-1', b'rev-2'}), result)
1483
[b'Branch.last_revision_info', b'Branch.get_tags_bytes'],
1484
[call.call.method for call in self.hpss_calls])
1486
def test_backwards_compatible_no_tags(self):
1487
br = self.make_branch_with_tags()
1488
br.get_config_stack().set('branch.fetch_tags', False)
1489
self.addCleanup(br.lock_read().unlock)
1490
# Disable the heads_to_fetch verb
1491
verb = b'Branch.heads_to_fetch'
1492
self.disable_verb(verb)
1493
self.reset_smart_call_log()
1494
result = br.heads_to_fetch()
1495
self.assertEqual(({b'tip'}, set()), result)
1497
[b'Branch.last_revision_info'],
1498
[call.call.method for call in self.hpss_calls])
1501
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1503
def test_empty_branch(self):
1504
# in an empty branch we decode the response properly
1505
transport = MemoryTransport()
1506
client = FakeClient(transport.base)
1507
client.add_expected_call(
1508
b'Branch.get_stacked_on_url', (b'quack/',),
1509
b'error', (b'NotStacked',))
1510
client.add_expected_call(
1511
b'Branch.last_revision_info', (b'quack/',),
1512
b'success', (b'ok', b'0', b'null:'))
1513
transport.mkdir('quack')
1514
transport = transport.clone('quack')
1515
branch = self.make_remote_branch(transport, client)
1516
result = branch.last_revision_info()
1517
self.assertFinished(client)
1518
self.assertEqual((0, NULL_REVISION), result)
1520
def test_non_empty_branch(self):
1521
# in a non-empty branch we also decode the response properly
1522
revid = u'\xc8'.encode('utf8')
1523
transport = MemoryTransport()
1524
client = FakeClient(transport.base)
1525
client.add_expected_call(
1526
b'Branch.get_stacked_on_url', (b'kwaak/',),
1527
b'error', (b'NotStacked',))
1528
client.add_expected_call(
1529
b'Branch.last_revision_info', (b'kwaak/',),
1530
b'success', (b'ok', b'2', revid))
1531
transport.mkdir('kwaak')
1532
transport = transport.clone('kwaak')
1533
branch = self.make_remote_branch(transport, client)
1534
result = branch.last_revision_info()
1535
self.assertEqual((2, revid), result)
1538
class TestBranch_get_stacked_on_url(TestRemote):
1539
"""Test Branch._get_stacked_on_url rpc"""
1541
def test_get_stacked_on_invalid_url(self):
1542
# test that asking for a stacked on url the server can't access works.
1543
# This isn't perfect, but then as we're in the same process there
1544
# really isn't anything we can do to be 100% sure that the server
1545
# doesn't just open in - this test probably needs to be rewritten using
1546
# a spawn()ed server.
1547
stacked_branch = self.make_branch('stacked', format='1.9')
1548
self.make_branch('base', format='1.9')
1549
vfs_url = self.get_vfs_only_url('base')
1550
stacked_branch.set_stacked_on_url(vfs_url)
1551
transport = stacked_branch.controldir.root_transport
1552
client = FakeClient(transport.base)
1553
client.add_expected_call(
1554
b'Branch.get_stacked_on_url', (b'stacked/',),
1555
b'success', (b'ok', vfs_url.encode('utf-8')))
1556
# XXX: Multiple calls are bad, this second call documents what is
1558
client.add_expected_call(
1559
b'Branch.get_stacked_on_url', (b'stacked/',),
1560
b'success', (b'ok', vfs_url.encode('utf-8')))
1561
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1563
repo_fmt = remote.RemoteRepositoryFormat()
1564
repo_fmt._custom_format = stacked_branch.repository._format
1565
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1567
result = branch.get_stacked_on_url()
1568
self.assertEqual(vfs_url, result)
1570
def test_backwards_compatible(self):
1571
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1572
self.make_branch('base', format='1.6')
1573
stacked_branch = self.make_branch('stacked', format='1.6')
1574
stacked_branch.set_stacked_on_url('../base')
1575
client = FakeClient(self.get_url())
1576
branch_network_name = self.get_branch_format().network_name()
1577
client.add_expected_call(
1578
b'BzrDir.open_branchV3', (b'stacked/',),
1579
b'success', (b'branch', branch_network_name))
1580
client.add_expected_call(
1581
b'BzrDir.find_repositoryV3', (b'stacked/',),
1582
b'success', (b'ok', b'', b'no', b'no', b'yes',
1583
stacked_branch.repository._format.network_name()))
1584
# called twice, once from constructor and then again by us
1585
client.add_expected_call(
1586
b'Branch.get_stacked_on_url', (b'stacked/',),
1587
b'unknown', (b'Branch.get_stacked_on_url',))
1588
client.add_expected_call(
1589
b'Branch.get_stacked_on_url', (b'stacked/',),
1590
b'unknown', (b'Branch.get_stacked_on_url',))
1591
# this will also do vfs access, but that goes direct to the transport
1592
# and isn't seen by the FakeClient.
1593
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1594
RemoteBzrDirFormat(), _client=client)
1595
branch = bzrdir.open_branch()
1596
result = branch.get_stacked_on_url()
1597
self.assertEqual('../base', result)
1598
self.assertFinished(client)
1599
# it's in the fallback list both for the RemoteRepository and its vfs
1601
self.assertEqual(1, len(branch.repository._fallback_repositories))
1603
len(branch.repository._real_repository._fallback_repositories))
1605
def test_get_stacked_on_real_branch(self):
1606
self.make_branch('base')
1607
stacked_branch = self.make_branch('stacked')
1608
stacked_branch.set_stacked_on_url('../base')
1609
reference_format = self.get_repo_format()
1610
network_name = reference_format.network_name()
1611
client = FakeClient(self.get_url())
1612
branch_network_name = self.get_branch_format().network_name()
1613
client.add_expected_call(
1614
b'BzrDir.open_branchV3', (b'stacked/',),
1615
b'success', (b'branch', branch_network_name))
1616
client.add_expected_call(
1617
b'BzrDir.find_repositoryV3', (b'stacked/',),
1618
b'success', (b'ok', b'', b'yes', b'no', b'yes', network_name))
1619
# called twice, once from constructor and then again by us
1620
client.add_expected_call(
1621
b'Branch.get_stacked_on_url', (b'stacked/',),
1622
b'success', (b'ok', b'../base'))
1623
client.add_expected_call(
1624
b'Branch.get_stacked_on_url', (b'stacked/',),
1625
b'success', (b'ok', b'../base'))
1626
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1627
RemoteBzrDirFormat(), _client=client)
1628
branch = bzrdir.open_branch()
1629
result = branch.get_stacked_on_url()
1630
self.assertEqual('../base', result)
1631
self.assertFinished(client)
1632
# it's in the fallback list both for the RemoteRepository.
1633
self.assertEqual(1, len(branch.repository._fallback_repositories))
1634
# And we haven't had to construct a real repository.
1635
self.assertEqual(None, branch.repository._real_repository)
1638
class TestBranchSetLastRevision(RemoteBranchTestCase):
1640
def test_set_empty(self):
1641
# _set_last_revision_info('null:') is translated to calling
1642
# Branch.set_last_revision(path, '') on the wire.
1643
transport = MemoryTransport()
1644
transport.mkdir('branch')
1645
transport = transport.clone('branch')
1647
client = FakeClient(transport.base)
1648
client.add_expected_call(
1649
b'Branch.get_stacked_on_url', (b'branch/',),
1650
b'error', (b'NotStacked',))
1651
client.add_expected_call(
1652
b'Branch.lock_write', (b'branch/', b'', b''),
1653
b'success', (b'ok', b'branch token', b'repo token'))
1654
client.add_expected_call(
1655
b'Branch.last_revision_info',
1657
b'success', (b'ok', b'0', b'null:'))
1658
client.add_expected_call(
1659
b'Branch.set_last_revision', (b'branch/',
1660
b'branch token', b'repo token', b'null:',),
1661
b'success', (b'ok',))
1662
client.add_expected_call(
1663
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1664
b'success', (b'ok',))
1665
branch = self.make_remote_branch(transport, client)
1667
result = branch._set_last_revision(NULL_REVISION)
1669
self.assertEqual(None, result)
1670
self.assertFinished(client)
1672
def test_set_nonempty(self):
1673
# set_last_revision_info(N, rev-idN) is translated to calling
1674
# Branch.set_last_revision(path, rev-idN) on the wire.
1675
transport = MemoryTransport()
1676
transport.mkdir('branch')
1677
transport = transport.clone('branch')
1679
client = FakeClient(transport.base)
1680
client.add_expected_call(
1681
b'Branch.get_stacked_on_url', (b'branch/',),
1682
b'error', (b'NotStacked',))
1683
client.add_expected_call(
1684
b'Branch.lock_write', (b'branch/', b'', b''),
1685
b'success', (b'ok', b'branch token', b'repo token'))
1686
client.add_expected_call(
1687
b'Branch.last_revision_info',
1689
b'success', (b'ok', b'0', b'null:'))
1690
lines = [b'rev-id2']
1691
encoded_body = bz2.compress(b'\n'.join(lines))
1692
client.add_success_response_with_body(encoded_body, b'ok')
1693
client.add_expected_call(
1694
b'Branch.set_last_revision', (b'branch/',
1695
b'branch token', b'repo token', b'rev-id2',),
1696
b'success', (b'ok',))
1697
client.add_expected_call(
1698
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1699
b'success', (b'ok',))
1700
branch = self.make_remote_branch(transport, client)
1701
# Lock the branch, reset the record of remote calls.
1703
result = branch._set_last_revision(b'rev-id2')
1705
self.assertEqual(None, result)
1706
self.assertFinished(client)
1708
def test_no_such_revision(self):
1709
transport = MemoryTransport()
1710
transport.mkdir('branch')
1711
transport = transport.clone('branch')
1712
# A response of 'NoSuchRevision' is translated into an exception.
1713
client = FakeClient(transport.base)
1714
client.add_expected_call(
1715
b'Branch.get_stacked_on_url', (b'branch/',),
1716
b'error', (b'NotStacked',))
1717
client.add_expected_call(
1718
b'Branch.lock_write', (b'branch/', b'', b''),
1719
b'success', (b'ok', b'branch token', b'repo token'))
1720
client.add_expected_call(
1721
b'Branch.last_revision_info',
1723
b'success', (b'ok', b'0', b'null:'))
1724
# get_graph calls to construct the revision history, for the set_rh
1727
encoded_body = bz2.compress(b'\n'.join(lines))
1728
client.add_success_response_with_body(encoded_body, b'ok')
1729
client.add_expected_call(
1730
b'Branch.set_last_revision', (b'branch/',
1731
b'branch token', b'repo token', b'rev-id',),
1732
b'error', (b'NoSuchRevision', b'rev-id'))
1733
client.add_expected_call(
1734
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1735
b'success', (b'ok',))
1737
branch = self.make_remote_branch(transport, client)
1740
errors.NoSuchRevision, branch._set_last_revision, b'rev-id')
1742
self.assertFinished(client)
1744
def test_tip_change_rejected(self):
1745
"""TipChangeRejected responses cause a TipChangeRejected exception to
1748
transport = MemoryTransport()
1749
transport.mkdir('branch')
1750
transport = transport.clone('branch')
1751
client = FakeClient(transport.base)
1752
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1753
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1754
client.add_expected_call(
1755
b'Branch.get_stacked_on_url', (b'branch/',),
1756
b'error', (b'NotStacked',))
1757
client.add_expected_call(
1758
b'Branch.lock_write', (b'branch/', b'', b''),
1759
b'success', (b'ok', b'branch token', b'repo token'))
1760
client.add_expected_call(
1761
b'Branch.last_revision_info',
1763
b'success', (b'ok', b'0', b'null:'))
1765
encoded_body = bz2.compress(b'\n'.join(lines))
1766
client.add_success_response_with_body(encoded_body, b'ok')
1767
client.add_expected_call(
1768
b'Branch.set_last_revision', (b'branch/',
1769
b'branch token', b'repo token', b'rev-id',),
1770
b'error', (b'TipChangeRejected', rejection_msg_utf8))
1771
client.add_expected_call(
1772
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1773
b'success', (b'ok',))
1774
branch = self.make_remote_branch(transport, client)
1776
# The 'TipChangeRejected' error response triggered by calling
1777
# set_last_revision_info causes a TipChangeRejected exception.
1778
err = self.assertRaises(
1779
errors.TipChangeRejected,
1780
branch._set_last_revision, b'rev-id')
1781
# The UTF-8 message from the response has been decoded into a unicode
1783
self.assertIsInstance(err.msg, str)
1784
self.assertEqual(rejection_msg_unicode, err.msg)
1786
self.assertFinished(client)
1789
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1791
def test_set_last_revision_info(self):
1792
# set_last_revision_info(num, b'rev-id') is translated to calling
1793
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1794
transport = MemoryTransport()
1795
transport.mkdir('branch')
1796
transport = transport.clone('branch')
1797
client = FakeClient(transport.base)
1798
# get_stacked_on_url
1799
client.add_error_response(b'NotStacked')
1801
client.add_success_response(b'ok', b'branch token', b'repo token')
1802
# query the current revision
1803
client.add_success_response(b'ok', b'0', b'null:')
1805
client.add_success_response(b'ok')
1807
client.add_success_response(b'ok')
1809
branch = self.make_remote_branch(transport, client)
1810
# Lock the branch, reset the record of remote calls.
1813
result = branch.set_last_revision_info(1234, b'a-revision-id')
1815
[('call', b'Branch.last_revision_info', (b'branch/',)),
1816
('call', b'Branch.set_last_revision_info',
1817
(b'branch/', b'branch token', b'repo token',
1818
b'1234', b'a-revision-id'))],
1820
self.assertEqual(None, result)
1822
def test_no_such_revision(self):
1823
# A response of 'NoSuchRevision' is translated into an exception.
1824
transport = MemoryTransport()
1825
transport.mkdir('branch')
1826
transport = transport.clone('branch')
1827
client = FakeClient(transport.base)
1828
# get_stacked_on_url
1829
client.add_error_response(b'NotStacked')
1831
client.add_success_response(b'ok', b'branch token', b'repo token')
1833
client.add_error_response(b'NoSuchRevision', b'revid')
1835
client.add_success_response(b'ok')
1837
branch = self.make_remote_branch(transport, client)
1838
# Lock the branch, reset the record of remote calls.
1843
errors.NoSuchRevision, branch.set_last_revision_info, 123, b'revid')
1846
def test_backwards_compatibility(self):
1847
"""If the server does not support the Branch.set_last_revision_info
1848
verb (which is new in 1.4), then the client falls back to VFS methods.
1850
# This test is a little messy. Unlike most tests in this file, it
1851
# doesn't purely test what a Remote* object sends over the wire, and
1852
# how it reacts to responses from the wire. It instead relies partly
1853
# on asserting that the RemoteBranch will call
1854
# self._real_branch.set_last_revision_info(...).
1856
# First, set up our RemoteBranch with a FakeClient that raises
1857
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1858
transport = MemoryTransport()
1859
transport.mkdir('branch')
1860
transport = transport.clone('branch')
1861
client = FakeClient(transport.base)
1862
client.add_expected_call(
1863
b'Branch.get_stacked_on_url', (b'branch/',),
1864
b'error', (b'NotStacked',))
1865
client.add_expected_call(
1866
b'Branch.last_revision_info',
1868
b'success', (b'ok', b'0', b'null:'))
1869
client.add_expected_call(
1870
b'Branch.set_last_revision_info',
1871
(b'branch/', b'branch token', b'repo token', b'1234', b'a-revision-id',),
1872
b'unknown', b'Branch.set_last_revision_info')
1874
branch = self.make_remote_branch(transport, client)
1876
class StubRealBranch(object):
1880
def set_last_revision_info(self, revno, revision_id):
1882
('set_last_revision_info', revno, revision_id))
1884
def _clear_cached_state(self):
1886
real_branch = StubRealBranch()
1887
branch._real_branch = real_branch
1888
self.lock_remote_branch(branch)
1890
# Call set_last_revision_info, and verify it behaved as expected.
1891
branch.set_last_revision_info(1234, b'a-revision-id')
1893
[('set_last_revision_info', 1234, b'a-revision-id')],
1895
self.assertFinished(client)
1897
def test_unexpected_error(self):
1898
# If the server sends an error the client doesn't understand, it gets
1899
# turned into an UnknownErrorFromSmartServer, which is presented as a
1900
# non-internal error to the user.
1901
transport = MemoryTransport()
1902
transport.mkdir('branch')
1903
transport = transport.clone('branch')
1904
client = FakeClient(transport.base)
1905
# get_stacked_on_url
1906
client.add_error_response(b'NotStacked')
1908
client.add_success_response(b'ok', b'branch token', b'repo token')
1910
client.add_error_response(b'UnexpectedError')
1912
client.add_success_response(b'ok')
1914
branch = self.make_remote_branch(transport, client)
1915
# Lock the branch, reset the record of remote calls.
1919
err = self.assertRaises(
1920
errors.UnknownErrorFromSmartServer,
1921
branch.set_last_revision_info, 123, b'revid')
1922
self.assertEqual((b'UnexpectedError',), err.error_tuple)
1925
def test_tip_change_rejected(self):
1926
"""TipChangeRejected responses cause a TipChangeRejected exception to
1929
transport = MemoryTransport()
1930
transport.mkdir('branch')
1931
transport = transport.clone('branch')
1932
client = FakeClient(transport.base)
1933
# get_stacked_on_url
1934
client.add_error_response(b'NotStacked')
1936
client.add_success_response(b'ok', b'branch token', b'repo token')
1938
client.add_error_response(b'TipChangeRejected', b'rejection message')
1940
client.add_success_response(b'ok')
1942
branch = self.make_remote_branch(transport, client)
1943
# Lock the branch, reset the record of remote calls.
1945
self.addCleanup(branch.unlock)
1948
# The 'TipChangeRejected' error response triggered by calling
1949
# set_last_revision_info causes a TipChangeRejected exception.
1950
err = self.assertRaises(
1951
errors.TipChangeRejected,
1952
branch.set_last_revision_info, 123, b'revid')
1953
self.assertEqual('rejection message', err.msg)
1956
class TestBranchGetSetConfig(RemoteBranchTestCase):
1958
def test_get_branch_conf(self):
1959
# in an empty branch we decode the response properly
1960
client = FakeClient()
1961
client.add_expected_call(
1962
b'Branch.get_stacked_on_url', (b'memory:///',),
1963
b'error', (b'NotStacked',),)
1964
client.add_success_response_with_body(b'# config file body', b'ok')
1965
transport = MemoryTransport()
1966
branch = self.make_remote_branch(transport, client)
1967
config = branch.get_config()
1968
config.has_explicit_nickname()
1970
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
1971
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
1974
def test_get_multi_line_branch_conf(self):
1975
# Make sure that multiple-line branch.conf files are supported
1977
# https://bugs.launchpad.net/bzr/+bug/354075
1978
client = FakeClient()
1979
client.add_expected_call(
1980
b'Branch.get_stacked_on_url', (b'memory:///',),
1981
b'error', (b'NotStacked',),)
1982
client.add_success_response_with_body(b'a = 1\nb = 2\nc = 3\n', b'ok')
1983
transport = MemoryTransport()
1984
branch = self.make_remote_branch(transport, client)
1985
config = branch.get_config()
1986
self.assertEqual(u'2', config.get_user_option('b'))
1988
def test_set_option(self):
1989
client = FakeClient()
1990
client.add_expected_call(
1991
b'Branch.get_stacked_on_url', (b'memory:///',),
1992
b'error', (b'NotStacked',),)
1993
client.add_expected_call(
1994
b'Branch.lock_write', (b'memory:///', b'', b''),
1995
b'success', (b'ok', b'branch token', b'repo token'))
1996
client.add_expected_call(
1997
b'Branch.set_config_option', (b'memory:///', b'branch token',
1998
b'repo token', b'foo', b'bar', b''),
2000
client.add_expected_call(
2001
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2002
b'success', (b'ok',))
2003
transport = MemoryTransport()
2004
branch = self.make_remote_branch(transport, client)
2006
config = branch._get_config()
2007
config.set_option('foo', 'bar')
2009
self.assertFinished(client)
2011
def test_set_option_with_dict(self):
2012
client = FakeClient()
2013
client.add_expected_call(
2014
b'Branch.get_stacked_on_url', (b'memory:///',),
2015
b'error', (b'NotStacked',),)
2016
client.add_expected_call(
2017
b'Branch.lock_write', (b'memory:///', b'', b''),
2018
b'success', (b'ok', b'branch token', b'repo token'))
2019
encoded_dict_value = b'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
2020
client.add_expected_call(
2021
b'Branch.set_config_option_dict', (b'memory:///', b'branch token',
2022
b'repo token', encoded_dict_value, b'foo', b''),
2024
client.add_expected_call(
2025
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2026
b'success', (b'ok',))
2027
transport = MemoryTransport()
2028
branch = self.make_remote_branch(transport, client)
2030
config = branch._get_config()
2032
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2035
self.assertFinished(client)
2037
def test_set_option_with_bool(self):
2038
client = FakeClient()
2039
client.add_expected_call(
2040
b'Branch.get_stacked_on_url', (b'memory:///',),
2041
b'error', (b'NotStacked',),)
2042
client.add_expected_call(
2043
b'Branch.lock_write', (b'memory:///', b'', b''),
2044
b'success', (b'ok', b'branch token', b'repo token'))
2045
client.add_expected_call(
2046
b'Branch.set_config_option', (b'memory:///', b'branch token',
2047
b'repo token', b'True', b'foo', b''),
2049
client.add_expected_call(
2050
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2051
b'success', (b'ok',))
2052
transport = MemoryTransport()
2053
branch = self.make_remote_branch(transport, client)
2055
config = branch._get_config()
2056
config.set_option(True, 'foo')
2058
self.assertFinished(client)
2060
def test_backwards_compat_set_option(self):
2061
self.setup_smart_server_with_call_log()
2062
branch = self.make_branch('.')
2063
verb = b'Branch.set_config_option'
2064
self.disable_verb(verb)
2066
self.addCleanup(branch.unlock)
2067
self.reset_smart_call_log()
2068
branch._get_config().set_option('value', 'name')
2069
self.assertLength(11, self.hpss_calls)
2070
self.assertEqual('value', branch._get_config().get_option('name'))
2072
def test_backwards_compat_set_option_with_dict(self):
2073
self.setup_smart_server_with_call_log()
2074
branch = self.make_branch('.')
2075
verb = b'Branch.set_config_option_dict'
2076
self.disable_verb(verb)
2078
self.addCleanup(branch.unlock)
2079
self.reset_smart_call_log()
2080
config = branch._get_config()
2081
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2082
config.set_option(value_dict, 'name')
2083
self.assertLength(11, self.hpss_calls)
2084
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2087
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2089
def test_get_branch_conf(self):
2090
# in an empty branch we decode the response properly
2091
client = FakeClient()
2092
client.add_expected_call(
2093
b'Branch.get_stacked_on_url', (b'memory:///',),
2094
b'error', (b'NotStacked',),)
2095
client.add_success_response_with_body(b'# config file body', b'ok')
2096
transport = MemoryTransport()
2097
branch = self.make_remote_branch(transport, client)
2098
config = branch.get_config_stack()
2100
config.get("log_format")
2102
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2103
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
2106
def test_set_branch_conf(self):
2107
client = FakeClient()
2108
client.add_expected_call(
2109
b'Branch.get_stacked_on_url', (b'memory:///',),
2110
b'error', (b'NotStacked',),)
2111
client.add_expected_call(
2112
b'Branch.lock_write', (b'memory:///', b'', b''),
2113
b'success', (b'ok', b'branch token', b'repo token'))
2114
client.add_expected_call(
2115
b'Branch.get_config_file', (b'memory:///', ),
2116
b'success', (b'ok', ), b"# line 1\n")
2117
client.add_expected_call(
2118
b'Branch.get_config_file', (b'memory:///', ),
2119
b'success', (b'ok', ), b"# line 1\n")
2120
client.add_expected_call(
2121
b'Branch.put_config_file', (b'memory:///', b'branch token',
2123
b'success', (b'ok',))
2124
client.add_expected_call(
2125
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2126
b'success', (b'ok',))
2127
transport = MemoryTransport()
2128
branch = self.make_remote_branch(transport, client)
2130
config = branch.get_config_stack()
2131
config.set('email', 'The Dude <lebowski@example.com>')
2133
self.assertFinished(client)
2135
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2136
('call', b'Branch.lock_write', (b'memory:///', b'', b'')),
2137
('call_expecting_body', b'Branch.get_config_file',
2139
('call_expecting_body', b'Branch.get_config_file',
2141
('call_with_body_bytes_expecting_body', b'Branch.put_config_file',
2142
(b'memory:///', b'branch token', b'repo token'),
2143
b'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2144
('call', b'Branch.unlock',
2145
(b'memory:///', b'branch token', b'repo token'))],
2149
class TestBranchLockWrite(RemoteBranchTestCase):
2151
def test_lock_write_unlockable(self):
2152
transport = MemoryTransport()
2153
client = FakeClient(transport.base)
2154
client.add_expected_call(
2155
b'Branch.get_stacked_on_url', (b'quack/',),
2156
b'error', (b'NotStacked',),)
2157
client.add_expected_call(
2158
b'Branch.lock_write', (b'quack/', b'', b''),
2159
b'error', (b'UnlockableTransport',))
2160
transport.mkdir('quack')
2161
transport = transport.clone('quack')
2162
branch = self.make_remote_branch(transport, client)
2163
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2164
self.assertFinished(client)
2167
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2169
def test_simple(self):
2170
transport = MemoryTransport()
2171
client = FakeClient(transport.base)
2172
client.add_expected_call(
2173
b'Branch.get_stacked_on_url', (b'quack/',),
2174
b'error', (b'NotStacked',),)
2175
client.add_expected_call(
2176
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2177
b'success', (b'ok', b'0',),)
2178
client.add_expected_call(
2179
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2180
b'error', (b'NoSuchRevision', b'unknown',),)
2181
transport.mkdir('quack')
2182
transport = transport.clone('quack')
2183
branch = self.make_remote_branch(transport, client)
2184
self.assertEqual(0, branch.revision_id_to_revno(b'null:'))
2185
self.assertRaises(errors.NoSuchRevision,
2186
branch.revision_id_to_revno, b'unknown')
2187
self.assertFinished(client)
2189
def test_dotted(self):
2190
transport = MemoryTransport()
2191
client = FakeClient(transport.base)
2192
client.add_expected_call(
2193
b'Branch.get_stacked_on_url', (b'quack/',),
2194
b'error', (b'NotStacked',),)
2195
client.add_expected_call(
2196
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2197
b'success', (b'ok', b'0',),)
2198
client.add_expected_call(
2199
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2200
b'error', (b'NoSuchRevision', b'unknown',),)
2201
transport.mkdir('quack')
2202
transport = transport.clone('quack')
2203
branch = self.make_remote_branch(transport, client)
2204
self.assertEqual((0, ), branch.revision_id_to_dotted_revno(b'null:'))
2205
self.assertRaises(errors.NoSuchRevision,
2206
branch.revision_id_to_dotted_revno, b'unknown')
2207
self.assertFinished(client)
2209
def test_ghost_revid(self):
2210
transport = MemoryTransport()
2211
client = FakeClient(transport.base)
2212
client.add_expected_call(
2213
b'Branch.get_stacked_on_url', (b'quack/',),
2214
b'error', (b'NotStacked',),)
2215
# Some older versions of bzr/brz didn't explicitly return
2216
# GhostRevisionsHaveNoRevno
2217
client.add_expected_call(
2218
b'Branch.revision_id_to_revno', (b'quack/', b'revid'),
2219
b'error', (b'error', b'GhostRevisionsHaveNoRevno',
2220
b'The reivison {revid} was not found because there was '
2221
b'a ghost at {ghost-revid}'))
2222
client.add_expected_call(
2223
b'Branch.revision_id_to_revno', (b'quack/', b'revid'),
2224
b'error', (b'GhostRevisionsHaveNoRevno', b'revid', b'ghost-revid',))
2225
transport.mkdir('quack')
2226
transport = transport.clone('quack')
2227
branch = self.make_remote_branch(transport, client)
2228
self.assertRaises(errors.GhostRevisionsHaveNoRevno,
2229
branch.revision_id_to_dotted_revno, b'revid')
2230
self.assertRaises(errors.GhostRevisionsHaveNoRevno,
2231
branch.revision_id_to_dotted_revno, b'revid')
2232
self.assertFinished(client)
2234
def test_dotted_no_smart_verb(self):
2235
self.setup_smart_server_with_call_log()
2236
branch = self.make_branch('.')
2237
self.disable_verb(b'Branch.revision_id_to_revno')
2238
self.reset_smart_call_log()
2239
self.assertEqual((0, ),
2240
branch.revision_id_to_dotted_revno(b'null:'))
2241
self.assertLength(8, self.hpss_calls)
2244
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2246
def test__get_config(self):
2247
client = FakeClient()
2248
client.add_success_response_with_body(b'default_stack_on = /\n', b'ok')
2249
transport = MemoryTransport()
2250
bzrdir = self.make_remote_bzrdir(transport, client)
2251
config = bzrdir.get_config()
2252
self.assertEqual('/', config.get_default_stack_on())
2254
[('call_expecting_body', b'BzrDir.get_config_file',
2258
def test_set_option_uses_vfs(self):
2259
self.setup_smart_server_with_call_log()
2260
bzrdir = self.make_controldir('.')
2261
self.reset_smart_call_log()
2262
config = bzrdir.get_config()
2263
config.set_default_stack_on('/')
2264
self.assertLength(4, self.hpss_calls)
2266
def test_backwards_compat_get_option(self):
2267
self.setup_smart_server_with_call_log()
2268
bzrdir = self.make_controldir('.')
2269
verb = b'BzrDir.get_config_file'
2270
self.disable_verb(verb)
2271
self.reset_smart_call_log()
2272
self.assertEqual(None,
2273
bzrdir._get_config().get_option('default_stack_on'))
2274
self.assertLength(4, self.hpss_calls)
2277
class TestTransportIsReadonly(tests.TestCase):
2279
def test_true(self):
2280
client = FakeClient()
2281
client.add_success_response(b'yes')
2282
transport = RemoteTransport('bzr://example.com/', medium=False,
2284
self.assertEqual(True, transport.is_readonly())
2286
[('call', b'Transport.is_readonly', ())],
2289
def test_false(self):
2290
client = FakeClient()
2291
client.add_success_response(b'no')
2292
transport = RemoteTransport('bzr://example.com/', medium=False,
2294
self.assertEqual(False, transport.is_readonly())
2296
[('call', b'Transport.is_readonly', ())],
2299
def test_error_from_old_server(self):
2300
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2302
Clients should treat it as a "no" response, because is_readonly is only
2303
advisory anyway (a transport could be read-write, but then the
2304
underlying filesystem could be readonly anyway).
2306
client = FakeClient()
2307
client.add_unknown_method_response(b'Transport.is_readonly')
2308
transport = RemoteTransport('bzr://example.com/', medium=False,
2310
self.assertEqual(False, transport.is_readonly())
2312
[('call', b'Transport.is_readonly', ())],
2316
class TestTransportMkdir(tests.TestCase):
2318
def test_permissiondenied(self):
2319
client = FakeClient()
2320
client.add_error_response(
2321
b'PermissionDenied', b'remote path', b'extra')
2322
transport = RemoteTransport('bzr://example.com/', medium=False,
2324
exc = self.assertRaises(
2325
errors.PermissionDenied, transport.mkdir, 'client path')
2326
expected_error = errors.PermissionDenied('/client path', 'extra')
2327
self.assertEqual(expected_error, exc)
2330
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2332
def test_defaults_to_none(self):
2333
t = RemoteSSHTransport('bzr+ssh://example.com')
2334
self.assertIs(None, t._get_credentials()[0])
2336
def test_uses_authentication_config(self):
2337
conf = config.AuthenticationConfig()
2338
conf._get_config().update(
2339
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2342
t = RemoteSSHTransport('bzr+ssh://example.com')
2343
self.assertEqual('bar', t._get_credentials()[0])
2346
class TestRemoteRepository(TestRemote):
2347
"""Base for testing RemoteRepository protocol usage.
2349
These tests contain frozen requests and responses. We want any changes to
2350
what is sent or expected to be require a thoughtful update to these tests
2351
because they might break compatibility with different-versioned servers.
2354
def setup_fake_client_and_repository(self, transport_path):
2355
"""Create the fake client and repository for testing with.
2357
There's no real server here; we just have canned responses sent
2360
:param transport_path: Path below the root of the MemoryTransport
2361
where the repository will be created.
2363
transport = MemoryTransport()
2364
transport.mkdir(transport_path)
2365
client = FakeClient(transport.base)
2366
transport = transport.clone(transport_path)
2367
# we do not want bzrdir to make any remote calls
2368
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2370
repo = RemoteRepository(bzrdir, None, _client=client)
2374
def remoted_description(format):
2375
return 'Remote: ' + format.get_format_description()
2378
class TestBranchFormat(tests.TestCase):
2380
def test_get_format_description(self):
2381
remote_format = RemoteBranchFormat()
2382
real_format = branch.format_registry.get_default()
2383
remote_format._network_name = real_format.network_name()
2384
self.assertEqual(remoted_description(real_format),
2385
remote_format.get_format_description())
2388
class TestRepositoryFormat(TestRemoteRepository):
2390
def test_fast_delta(self):
2391
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2392
true_format = RemoteRepositoryFormat()
2393
true_format._network_name = true_name
2394
self.assertEqual(True, true_format.fast_deltas)
2395
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2396
false_format = RemoteRepositoryFormat()
2397
false_format._network_name = false_name
2398
self.assertEqual(False, false_format.fast_deltas)
2400
def test_get_format_description(self):
2401
remote_repo_format = RemoteRepositoryFormat()
2402
real_format = repository.format_registry.get_default()
2403
remote_repo_format._network_name = real_format.network_name()
2404
self.assertEqual(remoted_description(real_format),
2405
remote_repo_format.get_format_description())
2408
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2410
def test_empty(self):
2411
transport_path = 'quack'
2412
repo, client = self.setup_fake_client_and_repository(transport_path)
2413
client.add_success_response_with_body(b'', b'ok')
2414
self.assertEqual([], repo.all_revision_ids())
2416
[('call_expecting_body', b'Repository.all_revision_ids',
2420
def test_with_some_content(self):
2421
transport_path = 'quack'
2422
repo, client = self.setup_fake_client_and_repository(transport_path)
2423
client.add_success_response_with_body(
2424
b'rev1\nrev2\nanotherrev\n', b'ok')
2426
set([b"rev1", b"rev2", b"anotherrev"]),
2427
set(repo.all_revision_ids()))
2429
[('call_expecting_body', b'Repository.all_revision_ids',
2434
class TestRepositoryGatherStats(TestRemoteRepository):
2436
def test_revid_none(self):
2437
# ('ok',), body with revisions and size
2438
transport_path = 'quack'
2439
repo, client = self.setup_fake_client_and_repository(transport_path)
2440
client.add_success_response_with_body(
2441
b'revisions: 2\nsize: 18\n', b'ok')
2442
result = repo.gather_stats(None)
2444
[('call_expecting_body', b'Repository.gather_stats',
2445
(b'quack/', b'', b'no'))],
2447
self.assertEqual({'revisions': 2, 'size': 18}, result)
2449
def test_revid_no_committers(self):
2450
# ('ok',), body without committers
2451
body = (b'firstrev: 123456.300 3600\n'
2452
b'latestrev: 654231.400 0\n'
2455
transport_path = 'quick'
2456
revid = u'\xc8'.encode('utf8')
2457
repo, client = self.setup_fake_client_and_repository(transport_path)
2458
client.add_success_response_with_body(body, b'ok')
2459
result = repo.gather_stats(revid)
2461
[('call_expecting_body', b'Repository.gather_stats',
2462
(b'quick/', revid, b'no'))],
2464
self.assertEqual({'revisions': 2, 'size': 18,
2465
'firstrev': (123456.300, 3600),
2466
'latestrev': (654231.400, 0), },
2469
def test_revid_with_committers(self):
2470
# ('ok',), body with committers
2471
body = (b'committers: 128\n'
2472
b'firstrev: 123456.300 3600\n'
2473
b'latestrev: 654231.400 0\n'
2476
transport_path = 'buick'
2477
revid = u'\xc8'.encode('utf8')
2478
repo, client = self.setup_fake_client_and_repository(transport_path)
2479
client.add_success_response_with_body(body, b'ok')
2480
result = repo.gather_stats(revid, True)
2482
[('call_expecting_body', b'Repository.gather_stats',
2483
(b'buick/', revid, b'yes'))],
2485
self.assertEqual({'revisions': 2, 'size': 18,
2487
'firstrev': (123456.300, 3600),
2488
'latestrev': (654231.400, 0), },
2492
class TestRepositoryBreakLock(TestRemoteRepository):
2494
def test_break_lock(self):
2495
transport_path = 'quack'
2496
repo, client = self.setup_fake_client_and_repository(transport_path)
2497
client.add_success_response(b'ok')
2500
[('call', b'Repository.break_lock', (b'quack/',))],
2504
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2506
def test_get_serializer_format(self):
2507
transport_path = 'hill'
2508
repo, client = self.setup_fake_client_and_repository(transport_path)
2509
client.add_success_response(b'ok', b'7')
2510
self.assertEqual(b'7', repo.get_serializer_format())
2512
[('call', b'VersionedFileRepository.get_serializer_format',
2517
class TestRepositoryReconcile(TestRemoteRepository):
2519
def test_reconcile(self):
2520
transport_path = 'hill'
2521
repo, client = self.setup_fake_client_and_repository(transport_path)
2522
body = (b"garbage_inventories: 2\n"
2523
b"inconsistent_parents: 3\n")
2524
client.add_expected_call(
2525
b'Repository.lock_write', (b'hill/', b''),
2526
b'success', (b'ok', b'a token'))
2527
client.add_success_response_with_body(body, b'ok')
2528
reconciler = repo.reconcile()
2530
[('call', b'Repository.lock_write', (b'hill/', b'')),
2531
('call_expecting_body', b'Repository.reconcile',
2532
(b'hill/', b'a token'))],
2534
self.assertEqual(2, reconciler.garbage_inventories)
2535
self.assertEqual(3, reconciler.inconsistent_parents)
2538
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2540
def test_text(self):
2541
# ('ok',), body with signature text
2542
transport_path = 'quack'
2543
repo, client = self.setup_fake_client_and_repository(transport_path)
2544
client.add_success_response_with_body(
2546
self.assertEqual(b"THETEXT", repo.get_signature_text(b"revid"))
2548
[('call_expecting_body', b'Repository.get_revision_signature_text',
2549
(b'quack/', b'revid'))],
2552
def test_no_signature(self):
2553
transport_path = 'quick'
2554
repo, client = self.setup_fake_client_and_repository(transport_path)
2555
client.add_error_response(b'nosuchrevision', b'unknown')
2556
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2559
[('call_expecting_body', b'Repository.get_revision_signature_text',
2560
(b'quick/', b'unknown'))],
2564
class TestRepositoryGetGraph(TestRemoteRepository):
2566
def test_get_graph(self):
2567
# get_graph returns a graph with a custom parents provider.
2568
transport_path = 'quack'
2569
repo, client = self.setup_fake_client_and_repository(transport_path)
2570
graph = repo.get_graph()
2571
self.assertNotEqual(graph._parents_provider, repo)
2574
class TestRepositoryAddSignatureText(TestRemoteRepository):
2576
def test_add_signature_text(self):
2577
transport_path = 'quack'
2578
repo, client = self.setup_fake_client_and_repository(transport_path)
2579
client.add_expected_call(
2580
b'Repository.lock_write', (b'quack/', b''),
2581
b'success', (b'ok', b'a token'))
2582
client.add_expected_call(
2583
b'Repository.start_write_group', (b'quack/', b'a token'),
2584
b'success', (b'ok', (b'token1', )))
2585
client.add_expected_call(
2586
b'Repository.add_signature_text', (b'quack/', b'a token', b'rev1',
2588
b'success', (b'ok', ), None)
2590
repo.start_write_group()
2592
None, repo.add_signature_text(b"rev1", b"every bloody emperor"))
2594
('call_with_body_bytes_expecting_body',
2595
b'Repository.add_signature_text',
2596
(b'quack/', b'a token', b'rev1', b'token1'),
2597
b'every bloody emperor'),
2601
class TestRepositoryGetParentMap(TestRemoteRepository):
2603
def test_get_parent_map_caching(self):
2604
# get_parent_map returns from cache until unlock()
2605
# setup a reponse with two revisions
2606
r1 = u'\u0e33'.encode('utf8')
2607
r2 = u'\u0dab'.encode('utf8')
2608
lines = [b' '.join([r2, r1]), r1]
2609
encoded_body = bz2.compress(b'\n'.join(lines))
2611
transport_path = 'quack'
2612
repo, client = self.setup_fake_client_and_repository(transport_path)
2613
client.add_success_response_with_body(encoded_body, b'ok')
2614
client.add_success_response_with_body(encoded_body, b'ok')
2616
graph = repo.get_graph()
2617
parents = graph.get_parent_map([r2])
2618
self.assertEqual({r2: (r1,)}, parents)
2619
# locking and unlocking deeper should not reset
2622
parents = graph.get_parent_map([r1])
2623
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2625
[('call_with_body_bytes_expecting_body',
2626
b'Repository.get_parent_map', (b'quack/',
2627
b'include-missing:', r2),
2631
# now we call again, and it should use the second response.
2633
graph = repo.get_graph()
2634
parents = graph.get_parent_map([r1])
2635
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2637
[('call_with_body_bytes_expecting_body',
2638
b'Repository.get_parent_map', (b'quack/',
2639
b'include-missing:', r2),
2641
('call_with_body_bytes_expecting_body',
2642
b'Repository.get_parent_map', (b'quack/',
2643
b'include-missing:', r1),
2649
def test_get_parent_map_reconnects_if_unknown_method(self):
2650
transport_path = 'quack'
2651
rev_id = b'revision-id'
2652
repo, client = self.setup_fake_client_and_repository(transport_path)
2653
client.add_unknown_method_response(b'Repository.get_parent_map')
2654
client.add_success_response_with_body(rev_id, b'ok')
2655
self.assertFalse(client._medium._is_remote_before((1, 2)))
2656
parents = repo.get_parent_map([rev_id])
2658
[('call_with_body_bytes_expecting_body',
2659
b'Repository.get_parent_map',
2660
(b'quack/', b'include-missing:', rev_id), b'\n\n0'),
2661
('disconnect medium',),
2662
('call_expecting_body', b'Repository.get_revision_graph',
2665
# The medium is now marked as being connected to an older server
2666
self.assertTrue(client._medium._is_remote_before((1, 2)))
2667
self.assertEqual({rev_id: (b'null:',)}, parents)
2669
def test_get_parent_map_fallback_parentless_node(self):
2670
"""get_parent_map falls back to get_revision_graph on old servers. The
2671
results from get_revision_graph are tweaked to match the get_parent_map
2674
Specifically, a {key: ()} result from get_revision_graph means "no
2675
parents" for that key, which in get_parent_map results should be
2676
represented as {key: ('null:',)}.
2678
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2680
rev_id = b'revision-id'
2681
transport_path = 'quack'
2682
repo, client = self.setup_fake_client_and_repository(transport_path)
2683
client.add_success_response_with_body(rev_id, b'ok')
2684
client._medium._remember_remote_is_before((1, 2))
2685
parents = repo.get_parent_map([rev_id])
2687
[('call_expecting_body', b'Repository.get_revision_graph',
2690
self.assertEqual({rev_id: (b'null:',)}, parents)
2692
def test_get_parent_map_unexpected_response(self):
2693
repo, client = self.setup_fake_client_and_repository('path')
2694
client.add_success_response(b'something unexpected!')
2696
errors.UnexpectedSmartServerResponse,
2697
repo.get_parent_map, [b'a-revision-id'])
2699
def test_get_parent_map_negative_caches_missing_keys(self):
2700
self.setup_smart_server_with_call_log()
2701
repo = self.make_repository('foo')
2702
self.assertIsInstance(repo, RemoteRepository)
2704
self.addCleanup(repo.unlock)
2705
self.reset_smart_call_log()
2706
graph = repo.get_graph()
2708
{}, graph.get_parent_map([b'some-missing', b'other-missing']))
2709
self.assertLength(1, self.hpss_calls)
2710
# No call if we repeat this
2711
self.reset_smart_call_log()
2712
graph = repo.get_graph()
2714
{}, graph.get_parent_map([b'some-missing', b'other-missing']))
2715
self.assertLength(0, self.hpss_calls)
2716
# Asking for more unknown keys makes a request.
2717
self.reset_smart_call_log()
2718
graph = repo.get_graph()
2720
{}, graph.get_parent_map([b'some-missing', b'other-missing',
2722
self.assertLength(1, self.hpss_calls)
2724
def disableExtraResults(self):
2725
self.overrideAttr(SmartServerRepositoryGetParentMap,
2726
'no_extra_results', True)
2728
def test_null_cached_missing_and_stop_key(self):
2729
self.setup_smart_server_with_call_log()
2730
# Make a branch with a single revision.
2731
builder = self.make_branch_builder('foo')
2732
builder.start_series()
2733
builder.build_snapshot(None, [
2734
('add', ('', b'root-id', 'directory', ''))],
2735
revision_id=b'first')
2736
builder.finish_series()
2737
branch = builder.get_branch()
2738
repo = branch.repository
2739
self.assertIsInstance(repo, RemoteRepository)
2740
# Stop the server from sending extra results.
2741
self.disableExtraResults()
2743
self.addCleanup(repo.unlock)
2744
self.reset_smart_call_log()
2745
graph = repo.get_graph()
2746
# Query for b'first' and b'null:'. Because b'null:' is a parent of
2747
# 'first' it will be a candidate for the stop_keys of subsequent
2748
# requests, and because b'null:' was queried but not returned it will
2749
# be cached as missing.
2750
self.assertEqual({b'first': (b'null:',)},
2751
graph.get_parent_map([b'first', b'null:']))
2752
# Now query for another key. This request will pass along a recipe of
2753
# start and stop keys describing the already cached results, and this
2754
# recipe's revision count must be correct (or else it will trigger an
2755
# error from the server).
2756
self.assertEqual({}, graph.get_parent_map([b'another-key']))
2757
# This assertion guards against disableExtraResults silently failing to
2758
# work, thus invalidating the test.
2759
self.assertLength(2, self.hpss_calls)
2761
def test_get_parent_map_gets_ghosts_from_result(self):
2762
# asking for a revision should negatively cache close ghosts in its
2764
self.setup_smart_server_with_call_log()
2765
tree = self.make_branch_and_memory_tree('foo')
2766
with tree.lock_write():
2767
builder = treebuilder.TreeBuilder()
2768
builder.start_tree(tree)
2770
builder.finish_tree()
2771
tree.set_parent_ids([b'non-existant'],
2772
allow_leftmost_as_ghost=True)
2773
rev_id = tree.commit('')
2775
self.addCleanup(tree.unlock)
2776
repo = tree.branch.repository
2777
self.assertIsInstance(repo, RemoteRepository)
2779
repo.get_parent_map([rev_id])
2780
self.reset_smart_call_log()
2781
# Now asking for rev_id's ghost parent should not make calls
2782
self.assertEqual({}, repo.get_parent_map([b'non-existant']))
2783
self.assertLength(0, self.hpss_calls)
2785
def test_exposes_get_cached_parent_map(self):
2786
"""RemoteRepository exposes get_cached_parent_map from
2789
r1 = u'\u0e33'.encode('utf8')
2790
r2 = u'\u0dab'.encode('utf8')
2791
lines = [b' '.join([r2, r1]), r1]
2792
encoded_body = bz2.compress(b'\n'.join(lines))
2794
transport_path = 'quack'
2795
repo, client = self.setup_fake_client_and_repository(transport_path)
2796
client.add_success_response_with_body(encoded_body, b'ok')
2798
# get_cached_parent_map should *not* trigger an RPC
2799
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2800
self.assertEqual([], client._calls)
2801
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2802
self.assertEqual({r1: (NULL_REVISION,)},
2803
repo.get_cached_parent_map([r1]))
2805
[('call_with_body_bytes_expecting_body',
2806
b'Repository.get_parent_map', (b'quack/',
2807
b'include-missing:', r2),
2813
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2815
def test_allows_new_revisions(self):
2816
"""get_parent_map's results can be updated by commit."""
2817
smart_server = test_server.SmartTCPServer_for_testing()
2818
self.start_server(smart_server)
2819
self.make_branch('branch')
2820
branch = Branch.open(smart_server.get_url() + '/branch')
2821
tree = branch.create_checkout('tree', lightweight=True)
2823
self.addCleanup(tree.unlock)
2824
graph = tree.branch.repository.get_graph()
2825
# This provides an opportunity for the missing rev-id to be cached.
2826
self.assertEqual({}, graph.get_parent_map([b'rev1']))
2827
tree.commit('message', rev_id=b'rev1')
2828
graph = tree.branch.repository.get_graph()
2829
self.assertEqual({b'rev1': (b'null:',)},
2830
graph.get_parent_map([b'rev1']))
2833
class TestRepositoryGetRevisions(TestRemoteRepository):
2835
def test_hpss_missing_revision(self):
2836
transport_path = 'quack'
2837
repo, client = self.setup_fake_client_and_repository(transport_path)
2838
client.add_success_response_with_body(
2840
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2841
[b'somerev1', b'anotherrev2'])
2843
[('call_with_body_bytes_expecting_body',
2844
b'Repository.iter_revisions', (b'quack/', ),
2845
b"somerev1\nanotherrev2")],
2848
def test_hpss_get_single_revision(self):
2849
transport_path = 'quack'
2850
repo, client = self.setup_fake_client_and_repository(transport_path)
2851
somerev1 = Revision(b"somerev1")
2852
somerev1.committer = "Joe Committer <joe@example.com>"
2853
somerev1.timestamp = 1321828927
2854
somerev1.timezone = -60
2855
somerev1.inventory_sha1 = b"691b39be74c67b1212a75fcb19c433aaed903c2b"
2856
somerev1.message = "Message"
2857
body = zlib.compress(b''.join(chk_bencode_serializer.write_revision_to_lines(
2859
# Split up body into two bits to make sure the zlib compression object
2860
# gets data fed twice.
2861
client.add_success_response_with_body(
2862
[body[:10], body[10:]], b'ok', b'10')
2863
revs = repo.get_revisions([b'somerev1'])
2864
self.assertEqual(revs, [somerev1])
2866
[('call_with_body_bytes_expecting_body',
2867
b'Repository.iter_revisions',
2868
(b'quack/', ), b"somerev1")],
2872
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2874
def test_null_revision(self):
2875
# a null revision has the predictable result {}, we should have no wire
2876
# traffic when calling it with this argument
2877
transport_path = 'empty'
2878
repo, client = self.setup_fake_client_and_repository(transport_path)
2879
client.add_success_response(b'notused')
2880
# actual RemoteRepository.get_revision_graph is gone, but there's an
2881
# equivalent private method for testing
2882
result = repo._get_revision_graph(NULL_REVISION)
2883
self.assertEqual([], client._calls)
2884
self.assertEqual({}, result)
2886
def test_none_revision(self):
2887
# with none we want the entire graph
2888
r1 = u'\u0e33'.encode('utf8')
2889
r2 = u'\u0dab'.encode('utf8')
2890
lines = [b' '.join([r2, r1]), r1]
2891
encoded_body = b'\n'.join(lines)
2893
transport_path = 'sinhala'
2894
repo, client = self.setup_fake_client_and_repository(transport_path)
2895
client.add_success_response_with_body(encoded_body, b'ok')
2896
# actual RemoteRepository.get_revision_graph is gone, but there's an
2897
# equivalent private method for testing
2898
result = repo._get_revision_graph(None)
2900
[('call_expecting_body', b'Repository.get_revision_graph',
2901
(b'sinhala/', b''))],
2903
self.assertEqual({r1: (), r2: (r1, )}, result)
2905
def test_specific_revision(self):
2906
# with a specific revision we want the graph for that
2907
# with none we want the entire graph
2908
r11 = u'\u0e33'.encode('utf8')
2909
r12 = u'\xc9'.encode('utf8')
2910
r2 = u'\u0dab'.encode('utf8')
2911
lines = [b' '.join([r2, r11, r12]), r11, r12]
2912
encoded_body = b'\n'.join(lines)
2914
transport_path = 'sinhala'
2915
repo, client = self.setup_fake_client_and_repository(transport_path)
2916
client.add_success_response_with_body(encoded_body, b'ok')
2917
result = repo._get_revision_graph(r2)
2919
[('call_expecting_body', b'Repository.get_revision_graph',
2920
(b'sinhala/', r2))],
2922
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2924
def test_no_such_revision(self):
2926
transport_path = 'sinhala'
2927
repo, client = self.setup_fake_client_and_repository(transport_path)
2928
client.add_error_response(b'nosuchrevision', revid)
2929
# also check that the right revision is reported in the error
2930
self.assertRaises(errors.NoSuchRevision,
2931
repo._get_revision_graph, revid)
2933
[('call_expecting_body', b'Repository.get_revision_graph',
2934
(b'sinhala/', revid))],
2937
def test_unexpected_error(self):
2939
transport_path = 'sinhala'
2940
repo, client = self.setup_fake_client_and_repository(transport_path)
2941
client.add_error_response(b'AnUnexpectedError')
2942
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2943
repo._get_revision_graph, revid)
2944
self.assertEqual((b'AnUnexpectedError',), e.error_tuple)
2947
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2950
repo, client = self.setup_fake_client_and_repository('quack')
2951
client.add_expected_call(
2952
b'Repository.get_rev_id_for_revno', (b'quack/',
2953
5, (42, b'rev-foo')),
2954
b'success', (b'ok', b'rev-five'))
2955
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2956
self.assertEqual((True, b'rev-five'), result)
2957
self.assertFinished(client)
2959
def test_history_incomplete(self):
2960
repo, client = self.setup_fake_client_and_repository('quack')
2961
client.add_expected_call(
2962
b'Repository.get_rev_id_for_revno', (b'quack/',
2963
5, (42, b'rev-foo')),
2964
b'success', (b'history-incomplete', 10, b'rev-ten'))
2965
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2966
self.assertEqual((False, (10, b'rev-ten')), result)
2967
self.assertFinished(client)
2969
def test_history_incomplete_with_fallback(self):
2970
"""A 'history-incomplete' response causes the fallback repository to be
2971
queried too, if one is set.
2973
# Make a repo with a fallback repo, both using a FakeClient.
2974
format = remote.response_tuple_to_repo_format(
2975
(b'yes', b'no', b'yes', self.get_repo_format().network_name()))
2976
repo, client = self.setup_fake_client_and_repository('quack')
2977
repo._format = format
2978
fallback_repo, ignored = self.setup_fake_client_and_repository(
2980
fallback_repo._client = client
2981
fallback_repo._format = format
2982
repo.add_fallback_repository(fallback_repo)
2983
# First the client should ask the primary repo
2984
client.add_expected_call(
2985
b'Repository.get_rev_id_for_revno', (b'quack/',
2986
1, (42, b'rev-foo')),
2987
b'success', (b'history-incomplete', 2, b'rev-two'))
2988
# Then it should ask the fallback, using revno/revid from the
2989
# history-incomplete response as the known revno/revid.
2990
client.add_expected_call(
2991
b'Repository.get_rev_id_for_revno', (
2992
b'fallback/', 1, (2, b'rev-two')),
2993
b'success', (b'ok', b'rev-one'))
2994
result = repo.get_rev_id_for_revno(1, (42, b'rev-foo'))
2995
self.assertEqual((True, b'rev-one'), result)
2996
self.assertFinished(client)
2998
def test_nosuchrevision(self):
2999
# 'nosuchrevision' is returned when the known-revid is not found in the
3000
# remote repo. The client translates that response to NoSuchRevision.
3001
repo, client = self.setup_fake_client_and_repository('quack')
3002
client.add_expected_call(
3003
b'Repository.get_rev_id_for_revno', (b'quack/',
3004
5, (42, b'rev-foo')),
3005
b'error', (b'nosuchrevision', b'rev-foo'))
3007
errors.NoSuchRevision,
3008
repo.get_rev_id_for_revno, 5, (42, b'rev-foo'))
3009
self.assertFinished(client)
3011
def test_outofbounds(self):
3012
repo, client = self.setup_fake_client_and_repository('quack')
3013
client.add_expected_call(
3014
b'Repository.get_rev_id_for_revno', (b'quack/',
3015
43, (42, b'rev-foo')),
3016
b'error', (b'revno-outofbounds', 43, 0, 42))
3018
errors.RevnoOutOfBounds,
3019
repo.get_rev_id_for_revno, 43, (42, b'rev-foo'))
3020
self.assertFinished(client)
3022
def test_outofbounds_old(self):
3023
# Older versions of bzr didn't support RevnoOutOfBounds
3024
repo, client = self.setup_fake_client_and_repository('quack')
3025
client.add_expected_call(
3026
b'Repository.get_rev_id_for_revno', (b'quack/',
3027
43, (42, b'rev-foo')),
3029
b'error', b'ValueError',
3030
b'requested revno (43) is later than given known revno (42)'))
3032
errors.RevnoOutOfBounds,
3033
repo.get_rev_id_for_revno, 43, (42, b'rev-foo'))
3034
self.assertFinished(client)
3036
def test_branch_fallback_locking(self):
3037
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
3038
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
3039
will be invoked, which will fail if the repo is unlocked.
3041
self.setup_smart_server_with_call_log()
3042
tree = self.make_branch_and_memory_tree('.')
3045
rev1 = tree.commit('First')
3046
tree.commit('Second')
3048
branch = tree.branch
3049
self.assertFalse(branch.is_locked())
3050
self.reset_smart_call_log()
3051
verb = b'Repository.get_rev_id_for_revno'
3052
self.disable_verb(verb)
3053
self.assertEqual(rev1, branch.get_rev_id(1))
3054
self.assertLength(1, [call for call in self.hpss_calls if
3055
call.call.method == verb])
3058
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
3060
def test_has_signature_for_revision_id(self):
3061
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
3062
transport_path = 'quack'
3063
repo, client = self.setup_fake_client_and_repository(transport_path)
3064
client.add_success_response(b'yes')
3065
result = repo.has_signature_for_revision_id(b'A')
3067
[('call', b'Repository.has_signature_for_revision_id',
3068
(b'quack/', b'A'))],
3070
self.assertEqual(True, result)
3072
def test_is_not_shared(self):
3073
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
3074
transport_path = 'qwack'
3075
repo, client = self.setup_fake_client_and_repository(transport_path)
3076
client.add_success_response(b'no')
3077
result = repo.has_signature_for_revision_id(b'A')
3079
[('call', b'Repository.has_signature_for_revision_id',
3080
(b'qwack/', b'A'))],
3082
self.assertEqual(False, result)
3085
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
3087
def test_get_physical_lock_status_yes(self):
3088
transport_path = 'qwack'
3089
repo, client = self.setup_fake_client_and_repository(transport_path)
3090
client.add_success_response(b'yes')
3091
result = repo.get_physical_lock_status()
3093
[('call', b'Repository.get_physical_lock_status',
3096
self.assertEqual(True, result)
3098
def test_get_physical_lock_status_no(self):
3099
transport_path = 'qwack'
3100
repo, client = self.setup_fake_client_and_repository(transport_path)
3101
client.add_success_response(b'no')
3102
result = repo.get_physical_lock_status()
3104
[('call', b'Repository.get_physical_lock_status',
3107
self.assertEqual(False, result)
3110
class TestRepositoryIsShared(TestRemoteRepository):
3112
def test_is_shared(self):
3113
# ('yes', ) for Repository.is_shared -> 'True'.
3114
transport_path = 'quack'
3115
repo, client = self.setup_fake_client_and_repository(transport_path)
3116
client.add_success_response(b'yes')
3117
result = repo.is_shared()
3119
[('call', b'Repository.is_shared', (b'quack/',))],
3121
self.assertEqual(True, result)
3123
def test_is_not_shared(self):
3124
# ('no', ) for Repository.is_shared -> 'False'.
3125
transport_path = 'qwack'
3126
repo, client = self.setup_fake_client_and_repository(transport_path)
3127
client.add_success_response(b'no')
3128
result = repo.is_shared()
3130
[('call', b'Repository.is_shared', (b'qwack/',))],
3132
self.assertEqual(False, result)
3135
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3137
def test_make_working_trees(self):
3138
# ('yes', ) for Repository.make_working_trees -> 'True'.
3139
transport_path = 'quack'
3140
repo, client = self.setup_fake_client_and_repository(transport_path)
3141
client.add_success_response(b'yes')
3142
result = repo.make_working_trees()
3144
[('call', b'Repository.make_working_trees', (b'quack/',))],
3146
self.assertEqual(True, result)
3148
def test_no_working_trees(self):
3149
# ('no', ) for Repository.make_working_trees -> 'False'.
3150
transport_path = 'qwack'
3151
repo, client = self.setup_fake_client_and_repository(transport_path)
3152
client.add_success_response(b'no')
3153
result = repo.make_working_trees()
3155
[('call', b'Repository.make_working_trees', (b'qwack/',))],
3157
self.assertEqual(False, result)
3160
class TestRepositoryLockWrite(TestRemoteRepository):
3162
def test_lock_write(self):
3163
transport_path = 'quack'
3164
repo, client = self.setup_fake_client_and_repository(transport_path)
3165
client.add_success_response(b'ok', b'a token')
3166
token = repo.lock_write().repository_token
3168
[('call', b'Repository.lock_write', (b'quack/', b''))],
3170
self.assertEqual(b'a token', token)
3172
def test_lock_write_already_locked(self):
3173
transport_path = 'quack'
3174
repo, client = self.setup_fake_client_and_repository(transport_path)
3175
client.add_error_response(b'LockContention')
3176
self.assertRaises(errors.LockContention, repo.lock_write)
3178
[('call', b'Repository.lock_write', (b'quack/', b''))],
3181
def test_lock_write_unlockable(self):
3182
transport_path = 'quack'
3183
repo, client = self.setup_fake_client_and_repository(transport_path)
3184
client.add_error_response(b'UnlockableTransport')
3185
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3187
[('call', b'Repository.lock_write', (b'quack/', b''))],
3191
class TestRepositoryWriteGroups(TestRemoteRepository):
3193
def test_start_write_group(self):
3194
transport_path = 'quack'
3195
repo, client = self.setup_fake_client_and_repository(transport_path)
3196
client.add_expected_call(
3197
b'Repository.lock_write', (b'quack/', b''),
3198
b'success', (b'ok', b'a token'))
3199
client.add_expected_call(
3200
b'Repository.start_write_group', (b'quack/', b'a token'),
3201
b'success', (b'ok', (b'token1', )))
3203
repo.start_write_group()
3205
def test_start_write_group_unsuspendable(self):
3206
# Some repositories do not support suspending write
3207
# groups. For those, fall back to the "real" repository.
3208
transport_path = 'quack'
3209
repo, client = self.setup_fake_client_and_repository(transport_path)
3211
def stub_ensure_real():
3212
client._calls.append(('_ensure_real',))
3213
repo._real_repository = _StubRealPackRepository(client._calls)
3214
repo._ensure_real = stub_ensure_real
3215
client.add_expected_call(
3216
b'Repository.lock_write', (b'quack/', b''),
3217
b'success', (b'ok', b'a token'))
3218
client.add_expected_call(
3219
b'Repository.start_write_group', (b'quack/', b'a token'),
3220
b'error', (b'UnsuspendableWriteGroup',))
3222
repo.start_write_group()
3223
self.assertEqual(client._calls[-2:], [
3225
('start_write_group',)])
3227
def test_commit_write_group(self):
3228
transport_path = 'quack'
3229
repo, client = self.setup_fake_client_and_repository(transport_path)
3230
client.add_expected_call(
3231
b'Repository.lock_write', (b'quack/', b''),
3232
b'success', (b'ok', b'a token'))
3233
client.add_expected_call(
3234
b'Repository.start_write_group', (b'quack/', b'a token'),
3235
b'success', (b'ok', [b'token1']))
3236
client.add_expected_call(
3237
b'Repository.commit_write_group', (b'quack/',
3238
b'a token', [b'token1']),
3239
b'success', (b'ok',))
3241
repo.start_write_group()
3242
repo.commit_write_group()
3244
def test_abort_write_group(self):
3245
transport_path = 'quack'
3246
repo, client = self.setup_fake_client_and_repository(transport_path)
3247
client.add_expected_call(
3248
b'Repository.lock_write', (b'quack/', b''),
3249
b'success', (b'ok', b'a token'))
3250
client.add_expected_call(
3251
b'Repository.start_write_group', (b'quack/', b'a token'),
3252
b'success', (b'ok', [b'token1']))
3253
client.add_expected_call(
3254
b'Repository.abort_write_group', (b'quack/',
3255
b'a token', [b'token1']),
3256
b'success', (b'ok',))
3258
repo.start_write_group()
3259
repo.abort_write_group(False)
3261
def test_suspend_write_group(self):
3262
transport_path = 'quack'
3263
repo, client = self.setup_fake_client_and_repository(transport_path)
3264
self.assertEqual([], repo.suspend_write_group())
3266
def test_resume_write_group(self):
3267
transport_path = 'quack'
3268
repo, client = self.setup_fake_client_and_repository(transport_path)
3269
client.add_expected_call(
3270
b'Repository.lock_write', (b'quack/', b''),
3271
b'success', (b'ok', b'a token'))
3272
client.add_expected_call(
3273
b'Repository.check_write_group', (b'quack/',
3274
b'a token', [b'token1']),
3275
b'success', (b'ok',))
3277
repo.resume_write_group(['token1'])
3280
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3282
def test_backwards_compat(self):
3283
self.setup_smart_server_with_call_log()
3284
repo = self.make_repository('.')
3285
self.reset_smart_call_log()
3286
verb = b'Repository.set_make_working_trees'
3287
self.disable_verb(verb)
3288
repo.set_make_working_trees(True)
3289
call_count = len([call for call in self.hpss_calls if
3290
call.call.method == verb])
3291
self.assertEqual(1, call_count)
3293
def test_current(self):
3294
transport_path = 'quack'
3295
repo, client = self.setup_fake_client_and_repository(transport_path)
3296
client.add_expected_call(
3297
b'Repository.set_make_working_trees', (b'quack/', b'True'),
3298
b'success', (b'ok',))
3299
client.add_expected_call(
3300
b'Repository.set_make_working_trees', (b'quack/', b'False'),
3301
b'success', (b'ok',))
3302
repo.set_make_working_trees(True)
3303
repo.set_make_working_trees(False)
3306
class TestRepositoryUnlock(TestRemoteRepository):
3308
def test_unlock(self):
3309
transport_path = 'quack'
3310
repo, client = self.setup_fake_client_and_repository(transport_path)
3311
client.add_success_response(b'ok', b'a token')
3312
client.add_success_response(b'ok')
3316
[('call', b'Repository.lock_write', (b'quack/', b'')),
3317
('call', b'Repository.unlock', (b'quack/', b'a token'))],
3320
def test_unlock_wrong_token(self):
3321
# If somehow the token is wrong, unlock will raise TokenMismatch.
3322
transport_path = 'quack'
3323
repo, client = self.setup_fake_client_and_repository(transport_path)
3324
client.add_success_response(b'ok', b'a token')
3325
client.add_error_response(b'TokenMismatch')
3327
self.assertRaises(errors.TokenMismatch, repo.unlock)
3330
class TestRepositoryHasRevision(TestRemoteRepository):
3332
def test_none(self):
3333
# repo.has_revision(None) should not cause any traffic.
3334
transport_path = 'quack'
3335
repo, client = self.setup_fake_client_and_repository(transport_path)
3337
# The null revision is always there, so has_revision(None) == True.
3338
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3340
# The remote repo shouldn't be accessed.
3341
self.assertEqual([], client._calls)
3344
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3345
"""Test Repository.iter_file_bytes."""
3347
def test_single(self):
3348
transport_path = 'quack'
3349
repo, client = self.setup_fake_client_and_repository(transport_path)
3350
client.add_expected_call(
3351
b'Repository.iter_files_bytes', (b'quack/', ),
3352
b'success', (b'ok',), iter([b"ok\x000", b"\n", zlib.compress(b"mydata" * 10)]))
3353
for (identifier, byte_stream) in repo.iter_files_bytes([(b"somefile",
3354
b"somerev", b"myid")]):
3355
self.assertEqual(b"myid", identifier)
3356
self.assertEqual(b"".join(byte_stream), b"mydata" * 10)
3358
def test_missing(self):
3359
transport_path = 'quack'
3360
repo, client = self.setup_fake_client_and_repository(transport_path)
3361
client.add_expected_call(
3362
b'Repository.iter_files_bytes',
3364
b'error', (b'RevisionNotPresent', b'somefile', b'somerev'),
3365
iter([b"absent\0somefile\0somerev\n"]))
3366
self.assertRaises(errors.RevisionNotPresent, list,
3367
repo.iter_files_bytes(
3368
[(b"somefile", b"somerev", b"myid")]))
3371
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3372
"""Base class for Repository.insert_stream and .insert_stream_1.19
3376
def checkInsertEmptyStream(self, repo, client):
3377
"""Insert an empty stream, checking the result.
3379
This checks that there are no resume_tokens or missing_keys, and that
3380
the client is finished.
3382
sink = repo._get_sink()
3383
fmt = repository.format_registry.get_default()
3384
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3385
self.assertEqual([], resume_tokens)
3386
self.assertEqual(set(), missing_keys)
3387
self.assertFinished(client)
3390
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3391
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3394
This test case is very similar to TestRepositoryInsertStream_1_19.
3398
super(TestRepositoryInsertStream, self).setUp()
3399
self.disable_verb(b'Repository.insert_stream_1.19')
3401
def test_unlocked_repo(self):
3402
transport_path = 'quack'
3403
repo, client = self.setup_fake_client_and_repository(transport_path)
3404
client.add_expected_call(
3405
b'Repository.insert_stream_1.19', (b'quack/', b''),
3406
b'unknown', (b'Repository.insert_stream_1.19',))
3407
client.add_expected_call(
3408
b'Repository.insert_stream', (b'quack/', b''),
3409
b'success', (b'ok',))
3410
client.add_expected_call(
3411
b'Repository.insert_stream', (b'quack/', b''),
3412
b'success', (b'ok',))
3413
self.checkInsertEmptyStream(repo, client)
3415
def test_locked_repo_with_no_lock_token(self):
3416
transport_path = 'quack'
3417
repo, client = self.setup_fake_client_and_repository(transport_path)
3418
client.add_expected_call(
3419
b'Repository.lock_write', (b'quack/', b''),
3420
b'success', (b'ok', b''))
3421
client.add_expected_call(
3422
b'Repository.insert_stream_1.19', (b'quack/', b''),
3423
b'unknown', (b'Repository.insert_stream_1.19',))
3424
client.add_expected_call(
3425
b'Repository.insert_stream', (b'quack/', b''),
3426
b'success', (b'ok',))
3427
client.add_expected_call(
3428
b'Repository.insert_stream', (b'quack/', b''),
3429
b'success', (b'ok',))
3431
self.checkInsertEmptyStream(repo, client)
3433
def test_locked_repo_with_lock_token(self):
3434
transport_path = 'quack'
3435
repo, client = self.setup_fake_client_and_repository(transport_path)
3436
client.add_expected_call(
3437
b'Repository.lock_write', (b'quack/', b''),
3438
b'success', (b'ok', b'a token'))
3439
client.add_expected_call(
3440
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3441
b'unknown', (b'Repository.insert_stream_1.19',))
3442
client.add_expected_call(
3443
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3444
b'success', (b'ok',))
3445
client.add_expected_call(
3446
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3447
b'success', (b'ok',))
3449
self.checkInsertEmptyStream(repo, client)
3451
def test_stream_with_inventory_deltas(self):
3452
"""'inventory-deltas' substreams cannot be sent to the
3453
Repository.insert_stream verb, because not all servers that implement
3454
that verb will accept them. So when one is encountered the RemoteSink
3455
immediately stops using that verb and falls back to VFS insert_stream.
3457
transport_path = 'quack'
3458
repo, client = self.setup_fake_client_and_repository(transport_path)
3459
client.add_expected_call(
3460
b'Repository.insert_stream_1.19', (b'quack/', b''),
3461
b'unknown', (b'Repository.insert_stream_1.19',))
3462
client.add_expected_call(
3463
b'Repository.insert_stream', (b'quack/', b''),
3464
b'success', (b'ok',))
3465
client.add_expected_call(
3466
b'Repository.insert_stream', (b'quack/', b''),
3467
b'success', (b'ok',))
3468
# Create a fake real repository for insert_stream to fall back on, so
3469
# that we can directly see the records the RemoteSink passes to the
3476
def insert_stream(self, stream, src_format, resume_tokens):
3477
for substream_kind, substream in stream:
3478
self.records.append(
3479
(substream_kind, [record.key for record in substream]))
3480
return [b'fake tokens'], [b'fake missing keys']
3481
fake_real_sink = FakeRealSink()
3483
class FakeRealRepository:
3484
def _get_sink(self):
3485
return fake_real_sink
3487
def is_in_write_group(self):
3490
def refresh_data(self):
3492
repo._real_repository = FakeRealRepository()
3493
sink = repo._get_sink()
3494
fmt = repository.format_registry.get_default()
3495
stream = self.make_stream_with_inv_deltas(fmt)
3496
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3497
# Every record from the first inventory delta should have been sent to
3499
expected_records = [
3500
('inventory-deltas', [(b'rev2',), (b'rev3',)]),
3501
('texts', [(b'some-rev', b'some-file')])]
3502
self.assertEqual(expected_records, fake_real_sink.records)
3503
# The return values from the real sink's insert_stream are propagated
3504
# back to the original caller.
3505
self.assertEqual([b'fake tokens'], resume_tokens)
3506
self.assertEqual([b'fake missing keys'], missing_keys)
3507
self.assertFinished(client)
3509
def make_stream_with_inv_deltas(self, fmt):
3510
"""Make a simple stream with an inventory delta followed by more
3511
records and more substreams to test that all records and substreams
3512
from that point on are used.
3514
This sends, in order:
3515
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3517
* texts substream: (some-rev, some-file)
3519
# Define a stream using generators so that it isn't rewindable.
3520
inv = inventory.Inventory(revision_id=b'rev1')
3521
inv.root.revision = b'rev1'
3523
def stream_with_inv_delta():
3524
yield ('inventories', inventories_substream())
3525
yield ('inventory-deltas', inventory_delta_substream())
3527
versionedfile.FulltextContentFactory(
3528
(b'some-rev', b'some-file'), (), None, b'content')])
3530
def inventories_substream():
3531
# An empty inventory fulltext. This will be streamed normally.
3532
chunks = fmt._serializer.write_inventory_to_lines(inv)
3533
yield versionedfile.ChunkedContentFactory(
3534
(b'rev1',), (), None, chunks, chunks_are_lines=True)
3536
def inventory_delta_substream():
3537
# An inventory delta. This can't be streamed via this verb, so it
3538
# will trigger a fallback to VFS insert_stream.
3539
entry = inv.make_entry(
3540
'directory', 'newdir', inv.root.file_id, b'newdir-id')
3541
entry.revision = b'ghost'
3542
delta = [(None, 'newdir', b'newdir-id', entry)]
3543
serializer = inventory_delta.InventoryDeltaSerializer(
3544
versioned_root=True, tree_references=False)
3545
lines = serializer.delta_to_lines(b'rev1', b'rev2', delta)
3546
yield versionedfile.ChunkedContentFactory(
3547
(b'rev2',), ((b'rev1',)), None, lines)
3549
lines = serializer.delta_to_lines(b'rev1', b'rev3', delta)
3550
yield versionedfile.ChunkedContentFactory(
3551
(b'rev3',), ((b'rev1',)), None, lines)
3552
return stream_with_inv_delta()
3555
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3557
def test_unlocked_repo(self):
3558
transport_path = 'quack'
3559
repo, client = self.setup_fake_client_and_repository(transport_path)
3560
client.add_expected_call(
3561
b'Repository.insert_stream_1.19', (b'quack/', b''),
3562
b'success', (b'ok',))
3563
client.add_expected_call(
3564
b'Repository.insert_stream_1.19', (b'quack/', b''),
3565
b'success', (b'ok',))
3566
self.checkInsertEmptyStream(repo, client)
3568
def test_locked_repo_with_no_lock_token(self):
3569
transport_path = 'quack'
3570
repo, client = self.setup_fake_client_and_repository(transport_path)
3571
client.add_expected_call(
3572
b'Repository.lock_write', (b'quack/', b''),
3573
b'success', (b'ok', b''))
3574
client.add_expected_call(
3575
b'Repository.insert_stream_1.19', (b'quack/', b''),
3576
b'success', (b'ok',))
3577
client.add_expected_call(
3578
b'Repository.insert_stream_1.19', (b'quack/', b''),
3579
b'success', (b'ok',))
3581
self.checkInsertEmptyStream(repo, client)
3583
def test_locked_repo_with_lock_token(self):
3584
transport_path = 'quack'
3585
repo, client = self.setup_fake_client_and_repository(transport_path)
3586
client.add_expected_call(
3587
b'Repository.lock_write', (b'quack/', b''),
3588
b'success', (b'ok', b'a token'))
3589
client.add_expected_call(
3590
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3591
b'success', (b'ok',))
3592
client.add_expected_call(
3593
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3594
b'success', (b'ok',))
3596
self.checkInsertEmptyStream(repo, client)
3599
class TestRepositoryTarball(TestRemoteRepository):
3601
# This is a canned tarball reponse we can validate against
3602
tarball_content = base64.b64decode(
3603
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3604
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3605
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3606
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3607
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3608
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3609
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3610
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3611
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3612
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3613
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3614
'nWQ7QH/F3JFOFCQ0aSPfA='
3617
def test_repository_tarball(self):
3618
# Test that Repository.tarball generates the right operations
3619
transport_path = 'repo'
3620
expected_calls = [('call_expecting_body', b'Repository.tarball',
3621
(b'repo/', b'bz2',),),
3623
repo, client = self.setup_fake_client_and_repository(transport_path)
3624
client.add_success_response_with_body(self.tarball_content, b'ok')
3625
# Now actually ask for the tarball
3626
tarball_file = repo._get_tarball('bz2')
3628
self.assertEqual(expected_calls, client._calls)
3629
self.assertEqual(self.tarball_content, tarball_file.read())
3631
tarball_file.close()
3634
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3635
"""RemoteRepository.copy_content_into optimizations"""
3637
def test_copy_content_remote_to_local(self):
3638
self.transport_server = test_server.SmartTCPServer_for_testing
3639
src_repo = self.make_repository('repo1')
3640
src_repo = repository.Repository.open(self.get_url('repo1'))
3641
# At the moment the tarball-based copy_content_into can't write back
3642
# into a smart server. It would be good if it could upload the
3643
# tarball; once that works we'd have to create repositories of
3644
# different formats. -- mbp 20070410
3645
dest_url = self.get_vfs_only_url('repo2')
3646
dest_bzrdir = BzrDir.create(dest_url)
3647
dest_repo = dest_bzrdir.create_repository()
3648
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3649
self.assertTrue(isinstance(src_repo, RemoteRepository))
3650
src_repo.copy_content_into(dest_repo)
3653
class _StubRealPackRepository(object):
3655
def __init__(self, calls):
3657
self._pack_collection = _StubPackCollection(calls)
3659
def start_write_group(self):
3660
self.calls.append(('start_write_group',))
3662
def is_in_write_group(self):
3665
def refresh_data(self):
3666
self.calls.append(('pack collection reload_pack_names',))
3669
class _StubPackCollection(object):
3671
def __init__(self, calls):
3675
self.calls.append(('pack collection autopack',))
3678
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3679
"""Tests for RemoteRepository.autopack implementation."""
3682
"""When the server returns 'ok' and there's no _real_repository, then
3683
nothing else happens: the autopack method is done.
3685
transport_path = 'quack'
3686
repo, client = self.setup_fake_client_and_repository(transport_path)
3687
client.add_expected_call(
3688
b'PackRepository.autopack', (b'quack/',), b'success', (b'ok',))
3690
self.assertFinished(client)
3692
def test_ok_with_real_repo(self):
3693
"""When the server returns 'ok' and there is a _real_repository, then
3694
the _real_repository's reload_pack_name's method will be called.
3696
transport_path = 'quack'
3697
repo, client = self.setup_fake_client_and_repository(transport_path)
3698
client.add_expected_call(
3699
b'PackRepository.autopack', (b'quack/',),
3700
b'success', (b'ok',))
3701
repo._real_repository = _StubRealPackRepository(client._calls)
3704
[('call', b'PackRepository.autopack', (b'quack/',)),
3705
('pack collection reload_pack_names',)],
3708
def test_backwards_compatibility(self):
3709
"""If the server does not recognise the PackRepository.autopack verb,
3710
fallback to the real_repository's implementation.
3712
transport_path = 'quack'
3713
repo, client = self.setup_fake_client_and_repository(transport_path)
3714
client.add_unknown_method_response(b'PackRepository.autopack')
3716
def stub_ensure_real():
3717
client._calls.append(('_ensure_real',))
3718
repo._real_repository = _StubRealPackRepository(client._calls)
3719
repo._ensure_real = stub_ensure_real
3722
[('call', b'PackRepository.autopack', (b'quack/',)),
3724
('pack collection autopack',)],
3727
def test_oom_error_reporting(self):
3728
"""An out-of-memory condition on the server is reported clearly"""
3729
transport_path = 'quack'
3730
repo, client = self.setup_fake_client_and_repository(transport_path)
3731
client.add_expected_call(
3732
b'PackRepository.autopack', (b'quack/',),
3733
b'error', (b'MemoryError',))
3734
err = self.assertRaises(errors.BzrError, repo.autopack)
3735
self.assertContainsRe(str(err), "^remote server out of mem")
3738
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3739
"""Base class for unit tests for breezy.bzr.remote._translate_error."""
3741
def translateTuple(self, error_tuple, **context):
3742
"""Call _translate_error with an ErrorFromSmartServer built from the
3745
:param error_tuple: A tuple of a smart server response, as would be
3746
passed to an ErrorFromSmartServer.
3747
:kwargs context: context items to call _translate_error with.
3749
:returns: The error raised by _translate_error.
3751
# Raise the ErrorFromSmartServer before passing it as an argument,
3752
# because _translate_error may need to re-raise it with a bare 'raise'
3754
server_error = errors.ErrorFromSmartServer(error_tuple)
3755
translated_error = self.translateErrorFromSmartServer(
3756
server_error, **context)
3757
return translated_error
3759
def translateErrorFromSmartServer(self, error_object, **context):
3760
"""Like translateTuple, but takes an already constructed
3761
ErrorFromSmartServer rather than a tuple.
3765
except errors.ErrorFromSmartServer as server_error:
3766
translated_error = self.assertRaises(
3767
errors.BzrError, remote._translate_error, server_error,
3769
return translated_error
3772
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3773
"""Unit tests for breezy.bzr.remote._translate_error.
3775
Given an ErrorFromSmartServer (which has an error tuple from a smart
3776
server) and some context, _translate_error raises more specific errors from
3779
This test case covers the cases where _translate_error succeeds in
3780
translating an ErrorFromSmartServer to something better. See
3781
TestErrorTranslationRobustness for other cases.
3784
def test_NoSuchRevision(self):
3785
branch = self.make_branch('')
3787
translated_error = self.translateTuple(
3788
(b'NoSuchRevision', revid), branch=branch)
3789
expected_error = errors.NoSuchRevision(branch, revid)
3790
self.assertEqual(expected_error, translated_error)
3792
def test_nosuchrevision(self):
3793
repository = self.make_repository('')
3795
translated_error = self.translateTuple(
3796
(b'nosuchrevision', revid), repository=repository)
3797
expected_error = errors.NoSuchRevision(repository, revid)
3798
self.assertEqual(expected_error, translated_error)
3800
def test_nobranch(self):
3801
bzrdir = self.make_controldir('')
3802
translated_error = self.translateTuple((b'nobranch',), bzrdir=bzrdir)
3803
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3804
self.assertEqual(expected_error, translated_error)
3806
def test_nobranch_one_arg(self):
3807
bzrdir = self.make_controldir('')
3808
translated_error = self.translateTuple(
3809
(b'nobranch', b'extra detail'), bzrdir=bzrdir)
3810
expected_error = errors.NotBranchError(
3811
path=bzrdir.root_transport.base,
3812
detail='extra detail')
3813
self.assertEqual(expected_error, translated_error)
3815
def test_norepository(self):
3816
bzrdir = self.make_controldir('')
3817
translated_error = self.translateTuple((b'norepository',),
3819
expected_error = errors.NoRepositoryPresent(bzrdir)
3820
self.assertEqual(expected_error, translated_error)
3822
def test_LockContention(self):
3823
translated_error = self.translateTuple((b'LockContention',))
3824
expected_error = errors.LockContention('(remote lock)')
3825
self.assertEqual(expected_error, translated_error)
3827
def test_UnlockableTransport(self):
3828
bzrdir = self.make_controldir('')
3829
translated_error = self.translateTuple(
3830
(b'UnlockableTransport',), bzrdir=bzrdir)
3831
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3832
self.assertEqual(expected_error, translated_error)
3834
def test_LockFailed(self):
3835
lock = 'str() of a server lock'
3836
why = 'str() of why'
3837
translated_error = self.translateTuple(
3838
(b'LockFailed', lock.encode('ascii'), why.encode('ascii')))
3839
expected_error = errors.LockFailed(lock, why)
3840
self.assertEqual(expected_error, translated_error)
3842
def test_TokenMismatch(self):
3843
token = 'a lock token'
3844
translated_error = self.translateTuple(
3845
(b'TokenMismatch',), token=token)
3846
expected_error = errors.TokenMismatch(token, '(remote token)')
3847
self.assertEqual(expected_error, translated_error)
3849
def test_Diverged(self):
3850
branch = self.make_branch('a')
3851
other_branch = self.make_branch('b')
3852
translated_error = self.translateTuple(
3853
(b'Diverged',), branch=branch, other_branch=other_branch)
3854
expected_error = errors.DivergedBranches(branch, other_branch)
3855
self.assertEqual(expected_error, translated_error)
3857
def test_NotStacked(self):
3858
branch = self.make_branch('')
3859
translated_error = self.translateTuple((b'NotStacked',), branch=branch)
3860
expected_error = errors.NotStacked(branch)
3861
self.assertEqual(expected_error, translated_error)
3863
def test_ReadError_no_args(self):
3865
translated_error = self.translateTuple((b'ReadError',), path=path)
3866
expected_error = errors.ReadError(path)
3867
self.assertEqual(expected_error, translated_error)
3869
def test_ReadError(self):
3871
translated_error = self.translateTuple(
3872
(b'ReadError', path.encode('utf-8')))
3873
expected_error = errors.ReadError(path)
3874
self.assertEqual(expected_error, translated_error)
3876
def test_IncompatibleRepositories(self):
3877
translated_error = self.translateTuple((b'IncompatibleRepositories',
3878
b"repo1", b"repo2", b"details here"))
3879
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3881
self.assertEqual(expected_error, translated_error)
3883
def test_GhostRevisionsHaveNoRevno(self):
3884
translated_error = self.translateTuple((b'GhostRevisionsHaveNoRevno',
3885
b"revid1", b"revid2"))
3886
expected_error = errors.GhostRevisionsHaveNoRevno(b"revid1", b"revid2")
3887
self.assertEqual(expected_error, translated_error)
3889
def test_PermissionDenied_no_args(self):
3891
translated_error = self.translateTuple((b'PermissionDenied',),
3893
expected_error = errors.PermissionDenied(path)
3894
self.assertEqual(expected_error, translated_error)
3896
def test_PermissionDenied_one_arg(self):
3898
translated_error = self.translateTuple(
3899
(b'PermissionDenied', path.encode('utf-8')))
3900
expected_error = errors.PermissionDenied(path)
3901
self.assertEqual(expected_error, translated_error)
3903
def test_PermissionDenied_one_arg_and_context(self):
3904
"""Given a choice between a path from the local context and a path on
3905
the wire, _translate_error prefers the path from the local context.
3907
local_path = 'local path'
3908
remote_path = 'remote path'
3909
translated_error = self.translateTuple(
3910
(b'PermissionDenied', remote_path.encode('utf-8')), path=local_path)
3911
expected_error = errors.PermissionDenied(local_path)
3912
self.assertEqual(expected_error, translated_error)
3914
def test_PermissionDenied_two_args(self):
3916
extra = 'a string with extra info'
3917
translated_error = self.translateTuple(
3918
(b'PermissionDenied', path.encode('utf-8'), extra.encode('utf-8')))
3919
expected_error = errors.PermissionDenied(path, extra)
3920
self.assertEqual(expected_error, translated_error)
3922
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3924
def test_NoSuchFile_context_path(self):
3925
local_path = "local path"
3926
translated_error = self.translateTuple((b'ReadError', b"remote path"),
3928
expected_error = errors.ReadError(local_path)
3929
self.assertEqual(expected_error, translated_error)
3931
def test_NoSuchFile_without_context(self):
3932
remote_path = "remote path"
3933
translated_error = self.translateTuple(
3934
(b'ReadError', remote_path.encode('utf-8')))
3935
expected_error = errors.ReadError(remote_path)
3936
self.assertEqual(expected_error, translated_error)
3938
def test_ReadOnlyError(self):
3939
translated_error = self.translateTuple((b'ReadOnlyError',))
3940
expected_error = errors.TransportNotPossible("readonly transport")
3941
self.assertEqual(expected_error, translated_error)
3943
def test_MemoryError(self):
3944
translated_error = self.translateTuple((b'MemoryError',))
3945
self.assertStartsWith(str(translated_error),
3946
"remote server out of memory")
3948
def test_generic_IndexError_no_classname(self):
3949
err = errors.ErrorFromSmartServer(
3950
(b'error', b"list index out of range"))
3951
translated_error = self.translateErrorFromSmartServer(err)
3952
expected_error = errors.UnknownErrorFromSmartServer(err)
3953
self.assertEqual(expected_error, translated_error)
3955
# GZ 2011-03-02: TODO test generic non-ascii error string
3957
def test_generic_KeyError(self):
3958
err = errors.ErrorFromSmartServer((b'error', b'KeyError', b"1"))
3959
translated_error = self.translateErrorFromSmartServer(err)
3960
expected_error = errors.UnknownErrorFromSmartServer(err)
3961
self.assertEqual(expected_error, translated_error)
3963
def test_RevnoOutOfBounds(self):
3964
translated_error = self.translateTuple(
3965
((b'revno-outofbounds', 5, 0, 3)), path=b'path')
3966
expected_error = errors.RevnoOutOfBounds(5, (0, 3))
3967
self.assertEqual(expected_error, translated_error)
3970
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3971
"""Unit tests for breezy.bzr.remote._translate_error's robustness.
3973
TestErrorTranslationSuccess is for cases where _translate_error can
3974
translate successfully. This class about how _translate_err behaves when
3975
it fails to translate: it re-raises the original error.
3978
def test_unrecognised_server_error(self):
3979
"""If the error code from the server is not recognised, the original
3980
ErrorFromSmartServer is propagated unmodified.
3982
error_tuple = (b'An unknown error tuple',)
3983
server_error = errors.ErrorFromSmartServer(error_tuple)
3984
translated_error = self.translateErrorFromSmartServer(server_error)
3985
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3986
self.assertEqual(expected_error, translated_error)
3988
def test_context_missing_a_key(self):
3989
"""In case of a bug in the client, or perhaps an unexpected response
3990
from a server, _translate_error returns the original error tuple from
3991
the server and mutters a warning.
3993
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3994
# in the context dict. So let's give it an empty context dict instead
3995
# to exercise its error recovery.
3996
error_tuple = (b'NoSuchRevision', b'revid')
3997
server_error = errors.ErrorFromSmartServer(error_tuple)
3998
translated_error = self.translateErrorFromSmartServer(server_error)
3999
self.assertEqual(server_error, translated_error)
4000
# In addition to re-raising ErrorFromSmartServer, some debug info has
4001
# been muttered to the log file for developer to look at.
4002
self.assertContainsRe(
4004
"Missing key 'branch' in context")
4006
def test_path_missing(self):
4007
"""Some translations (PermissionDenied, ReadError) can determine the
4008
'path' variable from either the wire or the local context. If neither
4009
has it, then an error is raised.
4011
error_tuple = (b'ReadError',)
4012
server_error = errors.ErrorFromSmartServer(error_tuple)
4013
translated_error = self.translateErrorFromSmartServer(server_error)
4014
self.assertEqual(server_error, translated_error)
4015
# In addition to re-raising ErrorFromSmartServer, some debug info has
4016
# been muttered to the log file for developer to look at.
4017
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
4020
class TestStacking(tests.TestCaseWithTransport):
4021
"""Tests for operations on stacked remote repositories.
4023
The underlying format type must support stacking.
4026
def test_access_stacked_remote(self):
4027
# based on <http://launchpad.net/bugs/261315>
4028
# make a branch stacked on another repository containing an empty
4029
# revision, then open it over hpss - we should be able to see that
4031
base_builder = self.make_branch_builder('base', format='1.9')
4032
base_builder.start_series()
4033
base_revid = base_builder.build_snapshot(None,
4034
[('add', ('', None, 'directory', None))],
4035
'message', revision_id=b'rev-id')
4036
base_builder.finish_series()
4037
stacked_branch = self.make_branch('stacked', format='1.9')
4038
stacked_branch.set_stacked_on_url('../base')
4039
# start a server looking at this
4040
smart_server = test_server.SmartTCPServer_for_testing()
4041
self.start_server(smart_server)
4042
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
4043
# can get its branch and repository
4044
remote_branch = remote_bzrdir.open_branch()
4045
remote_repo = remote_branch.repository
4046
remote_repo.lock_read()
4048
# it should have an appropriate fallback repository, which should also
4049
# be a RemoteRepository
4050
self.assertLength(1, remote_repo._fallback_repositories)
4051
self.assertIsInstance(remote_repo._fallback_repositories[0],
4053
# and it has the revision committed to the underlying repository;
4054
# these have varying implementations so we try several of them
4055
self.assertTrue(remote_repo.has_revisions([base_revid]))
4056
self.assertTrue(remote_repo.has_revision(base_revid))
4057
self.assertEqual(remote_repo.get_revision(base_revid).message,
4060
remote_repo.unlock()
4062
def prepare_stacked_remote_branch(self):
4063
"""Get stacked_upon and stacked branches with content in each."""
4064
self.setup_smart_server_with_call_log()
4065
tree1 = self.make_branch_and_tree('tree1', format='1.9')
4066
tree1.commit('rev1', rev_id=b'rev1')
4067
tree2 = tree1.branch.controldir.sprout('tree2', stacked=True
4068
).open_workingtree()
4069
local_tree = tree2.branch.create_checkout('local')
4070
local_tree.commit('local changes make me feel good.')
4071
branch2 = Branch.open(self.get_url('tree2'))
4073
self.addCleanup(branch2.unlock)
4074
return tree1.branch, branch2
4076
def test_stacked_get_parent_map(self):
4077
# the public implementation of get_parent_map obeys stacking
4078
_, branch = self.prepare_stacked_remote_branch()
4079
repo = branch.repository
4080
self.assertEqual({b'rev1'}, set(repo.get_parent_map([b'rev1'])))
4082
def test_unstacked_get_parent_map(self):
4083
# _unstacked_provider.get_parent_map ignores stacking
4084
_, branch = self.prepare_stacked_remote_branch()
4085
provider = branch.repository._unstacked_provider
4086
self.assertEqual(set(), set(provider.get_parent_map([b'rev1'])))
4088
def fetch_stream_to_rev_order(self, stream):
4090
for kind, substream in stream:
4091
if not kind == 'revisions':
4094
for content in substream:
4095
result.append(content.key[-1])
4098
def get_ordered_revs(self, format, order, branch_factory=None):
4099
"""Get a list of the revisions in a stream to format format.
4101
:param format: The format of the target.
4102
:param order: the order that target should have requested.
4103
:param branch_factory: A callable to create a trunk and stacked branch
4104
to fetch from. If none, self.prepare_stacked_remote_branch is used.
4105
:result: The revision ids in the stream, in the order seen,
4106
the topological order of revisions in the source.
4108
unordered_format = controldir.format_registry.get(format)()
4109
target_repository_format = unordered_format.repository_format
4111
self.assertEqual(order, target_repository_format._fetch_order)
4112
if branch_factory is None:
4113
branch_factory = self.prepare_stacked_remote_branch
4114
_, stacked = branch_factory()
4115
source = stacked.repository._get_source(target_repository_format)
4116
tip = stacked.last_revision()
4117
stacked.repository._ensure_real()
4118
graph = stacked.repository.get_graph()
4119
revs = [r for (r, ps) in graph.iter_ancestry([tip])
4120
if r != NULL_REVISION]
4122
search = vf_search.PendingAncestryResult([tip], stacked.repository)
4123
self.reset_smart_call_log()
4124
stream = source.get_stream(search)
4125
# We trust that if a revision is in the stream the rest of the new
4126
# content for it is too, as per our main fetch tests; here we are
4127
# checking that the revisions are actually included at all, and their
4129
return self.fetch_stream_to_rev_order(stream), revs
4131
def test_stacked_get_stream_unordered(self):
4132
# Repository._get_source.get_stream() from a stacked repository with
4133
# unordered yields the full data from both stacked and stacked upon
4135
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
4136
self.assertEqual(set(expected_revs), set(rev_ord))
4137
# Getting unordered results should have made a streaming data request
4138
# from the server, then one from the backing branch.
4139
self.assertLength(2, self.hpss_calls)
4141
def test_stacked_on_stacked_get_stream_unordered(self):
4142
# Repository._get_source.get_stream() from a stacked repository which
4143
# is itself stacked yields the full data from all three sources.
4144
def make_stacked_stacked():
4145
_, stacked = self.prepare_stacked_remote_branch()
4146
tree = stacked.controldir.sprout('tree3', stacked=True
4147
).open_workingtree()
4148
local_tree = tree.branch.create_checkout('local-tree3')
4149
local_tree.commit('more local changes are better')
4150
branch = Branch.open(self.get_url('tree3'))
4152
self.addCleanup(branch.unlock)
4154
rev_ord, expected_revs = self.get_ordered_revs(
4155
'1.9', 'unordered', branch_factory=make_stacked_stacked)
4156
self.assertEqual(set(expected_revs), set(rev_ord))
4157
# Getting unordered results should have made a streaming data request
4158
# from the server, and one from each backing repo
4159
self.assertLength(3, self.hpss_calls)
4161
def test_stacked_get_stream_topological(self):
4162
# Repository._get_source.get_stream() from a stacked repository with
4163
# topological sorting yields the full data from both stacked and
4164
# stacked upon sources in topological order.
4165
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4166
self.assertEqual(expected_revs, rev_ord)
4167
# Getting topological sort requires VFS calls still - one of which is
4168
# pushing up from the bound branch.
4169
self.assertLength(14, self.hpss_calls)
4171
def test_stacked_get_stream_groupcompress(self):
4172
# Repository._get_source.get_stream() from a stacked repository with
4173
# groupcompress sorting yields the full data from both stacked and
4174
# stacked upon sources in groupcompress order.
4175
raise tests.TestSkipped('No groupcompress ordered format available')
4176
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4177
self.assertEqual(expected_revs, reversed(rev_ord))
4178
# Getting unordered results should have made a streaming data request
4179
# from the backing branch, and one from the stacked on branch.
4180
self.assertLength(2, self.hpss_calls)
4182
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4183
# When pulling some fixed amount of content that is more than the
4184
# source has (because some is coming from a fallback branch, no error
4185
# should be received. This was reported as bug 360791.
4186
# Need three branches: a trunk, a stacked branch, and a preexisting
4187
# branch pulling content from stacked and trunk.
4188
self.setup_smart_server_with_call_log()
4189
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4190
trunk.commit('start')
4191
stacked_branch = trunk.branch.create_clone_on_transport(
4192
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4193
local = self.make_branch('local', format='1.9-rich-root')
4194
local.repository.fetch(stacked_branch.repository,
4195
stacked_branch.last_revision())
4198
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4201
super(TestRemoteBranchEffort, self).setUp()
4202
# Create a smart server that publishes whatever the backing VFS server
4204
self.smart_server = test_server.SmartTCPServer_for_testing()
4205
self.start_server(self.smart_server, self.get_server())
4206
# Log all HPSS calls into self.hpss_calls.
4207
_SmartClient.hooks.install_named_hook(
4208
'call', self.capture_hpss_call, None)
4209
self.hpss_calls = []
4211
def capture_hpss_call(self, params):
4212
self.hpss_calls.append(params.method)
4214
def test_copy_content_into_avoids_revision_history(self):
4215
local = self.make_branch('local')
4216
builder = self.make_branch_builder('remote')
4217
builder.build_commit(message="Commit.")
4218
remote_branch_url = self.smart_server.get_url() + 'remote'
4219
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4220
local.repository.fetch(remote_branch.repository)
4221
self.hpss_calls = []
4222
remote_branch.copy_content_into(local)
4223
self.assertFalse(b'Branch.revision_history' in self.hpss_calls)
4225
def test_fetch_everything_needs_just_one_call(self):
4226
local = self.make_branch('local')
4227
builder = self.make_branch_builder('remote')
4228
builder.build_commit(message="Commit.")
4229
remote_branch_url = self.smart_server.get_url() + 'remote'
4230
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4231
self.hpss_calls = []
4232
local.repository.fetch(
4233
remote_branch.repository,
4234
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4235
self.assertEqual([b'Repository.get_stream_1.19'], self.hpss_calls)
4237
def override_verb(self, verb_name, verb):
4238
request_handlers = request.request_handlers
4239
orig_verb = request_handlers.get(verb_name)
4240
orig_info = request_handlers.get_info(verb_name)
4241
request_handlers.register(verb_name, verb, override_existing=True)
4242
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4243
override_existing=True, info=orig_info)
4245
def test_fetch_everything_backwards_compat(self):
4246
"""Can fetch with EverythingResult even with pre 2.4 servers.
4248
Pre-2.4 do not support 'everything' searches with the
4249
Repository.get_stream_1.19 verb.
4253
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4254
"""A version of the Repository.get_stream_1.19 verb patched to
4255
reject 'everything' searches the way 2.3 and earlier do.
4258
def recreate_search(self, repository, search_bytes,
4259
discard_excess=False):
4260
verb_log.append(search_bytes.split(b'\n', 1)[0])
4261
if search_bytes == b'everything':
4263
request.FailedSmartServerResponse((b'BadSearch',)))
4264
return super(OldGetStreamVerb,
4265
self).recreate_search(repository, search_bytes,
4266
discard_excess=discard_excess)
4267
self.override_verb(b'Repository.get_stream_1.19', OldGetStreamVerb)
4268
local = self.make_branch('local')
4269
builder = self.make_branch_builder('remote')
4270
builder.build_commit(message="Commit.")
4271
remote_branch_url = self.smart_server.get_url() + 'remote'
4272
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4273
self.hpss_calls = []
4274
local.repository.fetch(
4275
remote_branch.repository,
4276
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4277
# make sure the overridden verb was used
4278
self.assertLength(1, verb_log)
4279
# more than one HPSS call is needed, but because it's a VFS callback
4280
# its hard to predict exactly how many.
4281
self.assertTrue(len(self.hpss_calls) > 1)
4284
class TestUpdateBoundBranchWithModifiedBoundLocation(
4285
tests.TestCaseWithTransport):
4286
"""Ensure correct handling of bound_location modifications.
4288
This is tested against a smart server as http://pad.lv/786980 was about a
4289
ReadOnlyError (write attempt during a read-only transaction) which can only
4290
happen in this context.
4294
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4295
self.transport_server = test_server.SmartTCPServer_for_testing
4297
def make_master_and_checkout(self, master_name, checkout_name):
4298
# Create the master branch and its associated checkout
4299
self.master = self.make_branch_and_tree(master_name)
4300
self.checkout = self.master.branch.create_checkout(checkout_name)
4301
# Modify the master branch so there is something to update
4302
self.master.commit('add stuff')
4303
self.last_revid = self.master.commit('even more stuff')
4304
self.bound_location = self.checkout.branch.get_bound_location()
4306
def assertUpdateSucceeds(self, new_location):
4307
self.checkout.branch.set_bound_location(new_location)
4308
self.checkout.update()
4309
self.assertEqual(self.last_revid, self.checkout.last_revision())
4311
def test_without_final_slash(self):
4312
self.make_master_and_checkout('master', 'checkout')
4313
# For unclear reasons some users have a bound_location without a final
4314
# '/', simulate that by forcing such a value
4315
self.assertEndsWith(self.bound_location, '/')
4316
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4318
def test_plus_sign(self):
4319
self.make_master_and_checkout('+master', 'checkout')
4320
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4322
def test_tilda(self):
4323
# Embed ~ in the middle of the path just to avoid any $HOME
4325
self.make_master_and_checkout('mas~ter', 'checkout')
4326
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4329
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4331
def test_no_context(self):
4332
class OutOfCoffee(errors.BzrError):
4333
"""A dummy exception for testing."""
4335
def __init__(self, urgency):
4336
self.urgency = urgency
4337
remote.no_context_error_translators.register(b"OutOfCoffee",
4338
lambda err: OutOfCoffee(err.error_args[0]))
4339
transport = MemoryTransport()
4340
client = FakeClient(transport.base)
4341
client.add_expected_call(
4342
b'Branch.get_stacked_on_url', (b'quack/',),
4343
b'error', (b'NotStacked',))
4344
client.add_expected_call(
4345
b'Branch.last_revision_info',
4347
b'error', (b'OutOfCoffee', b'low'))
4348
transport.mkdir('quack')
4349
transport = transport.clone('quack')
4350
branch = self.make_remote_branch(transport, client)
4351
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4352
self.assertFinished(client)
4354
def test_with_context(self):
4355
class OutOfTea(errors.BzrError):
4356
def __init__(self, branch, urgency):
4357
self.branch = branch
4358
self.urgency = urgency
4359
remote.error_translators.register(b"OutOfTea",
4360
lambda err, find, path: OutOfTea(
4361
err.error_args[0].decode(
4364
transport = MemoryTransport()
4365
client = FakeClient(transport.base)
4366
client.add_expected_call(
4367
b'Branch.get_stacked_on_url', (b'quack/',),
4368
b'error', (b'NotStacked',))
4369
client.add_expected_call(
4370
b'Branch.last_revision_info',
4372
b'error', (b'OutOfTea', b'low'))
4373
transport.mkdir('quack')
4374
transport = transport.clone('quack')
4375
branch = self.make_remote_branch(transport, client)
4376
self.assertRaises(OutOfTea, branch.last_revision_info)
4377
self.assertFinished(client)
4380
class TestRepositoryPack(TestRemoteRepository):
4382
def test_pack(self):
4383
transport_path = 'quack'
4384
repo, client = self.setup_fake_client_and_repository(transport_path)
4385
client.add_expected_call(
4386
b'Repository.lock_write', (b'quack/', b''),
4387
b'success', (b'ok', b'token'))
4388
client.add_expected_call(
4389
b'Repository.pack', (b'quack/', b'token', b'False'),
4390
b'success', (b'ok',), )
4391
client.add_expected_call(
4392
b'Repository.unlock', (b'quack/', b'token'),
4393
b'success', (b'ok', ))
4396
def test_pack_with_hint(self):
4397
transport_path = 'quack'
4398
repo, client = self.setup_fake_client_and_repository(transport_path)
4399
client.add_expected_call(
4400
b'Repository.lock_write', (b'quack/', b''),
4401
b'success', (b'ok', b'token'))
4402
client.add_expected_call(
4403
b'Repository.pack', (b'quack/', b'token', b'False'),
4404
b'success', (b'ok',), )
4405
client.add_expected_call(
4406
b'Repository.unlock', (b'quack/', b'token', b'False'),
4407
b'success', (b'ok', ))
4408
repo.pack(['hinta', 'hintb'])
4411
class TestRepositoryIterInventories(TestRemoteRepository):
4412
"""Test Repository.iter_inventories."""
4414
def _serialize_inv_delta(self, old_name, new_name, delta):
4415
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4416
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4418
def test_single_empty(self):
4419
transport_path = 'quack'
4420
repo, client = self.setup_fake_client_and_repository(transport_path)
4421
fmt = controldir.format_registry.get('2a')().repository_format
4423
stream = [('inventory-deltas', [
4424
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4425
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4426
client.add_expected_call(
4427
b'VersionedFileRepository.get_inventories', (
4428
b'quack/', b'unordered'),
4429
b'success', (b'ok', ),
4430
_stream_to_byte_stream(stream, fmt))
4431
ret = list(repo.iter_inventories([b"somerevid"]))
4432
self.assertLength(1, ret)
4434
self.assertEqual(b"somerevid", inv.revision_id)
4436
def test_empty(self):
4437
transport_path = 'quack'
4438
repo, client = self.setup_fake_client_and_repository(transport_path)
4439
ret = list(repo.iter_inventories([]))
4440
self.assertEqual(ret, [])
4442
def test_missing(self):
4443
transport_path = 'quack'
4444
repo, client = self.setup_fake_client_and_repository(transport_path)
4445
client.add_expected_call(
4446
b'VersionedFileRepository.get_inventories', (
4447
b'quack/', b'unordered'),
4448
b'success', (b'ok', ), iter([]))
4449
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(
4453
class TestRepositoryRevisionTreeArchive(TestRemoteRepository):
4454
"""Test Repository.iter_inventories."""
4456
def _serialize_inv_delta(self, old_name, new_name, delta):
4457
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4458
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4460
def test_simple(self):
4461
transport_path = 'quack'
4462
repo, client = self.setup_fake_client_and_repository(transport_path)
4463
fmt = controldir.format_registry.get('2a')().repository_format
4465
stream = [('inventory-deltas', [
4466
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4467
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4468
client.add_expected_call(
4469
b'VersionedFileRepository.get_inventories', (
4470
b'quack/', b'unordered'),
4471
b'success', (b'ok', ),
4472
_stream_to_byte_stream(stream, fmt))
4474
with tarfile.open(mode='w', fileobj=f) as tf:
4475
info = tarfile.TarInfo('somefile')
4477
contents = b'some data'
4478
info.type = tarfile.REGTYPE
4480
info.size = len(contents)
4481
tf.addfile(info, BytesIO(contents))
4482
client.add_expected_call(
4483
b'Repository.revision_archive', (b'quack/',
4484
b'somerevid', b'tar', b'foo.tar', b'', b'', None),
4485
b'success', (b'ok', ),
4487
tree = repo.revision_tree(b'somerevid')
4488
self.assertEqual(f.getvalue(), b''.join(
4489
tree.archive('tar', 'foo.tar')))
4492
class TestRepositoryAnnotate(TestRemoteRepository):
4493
"""Test RemoteRevisionTree.annotate.."""
4495
def _serialize_inv_delta(self, old_name, new_name, delta):
4496
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4497
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4499
def test_simple(self):
4500
transport_path = 'quack'
4501
repo, client = self.setup_fake_client_and_repository(transport_path)
4502
fmt = controldir.format_registry.get('2a')().repository_format
4505
('inventory-deltas', [
4506
versionedfile.FulltextContentFactory(
4507
b'somerevid', None, None,
4508
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4509
client.add_expected_call(
4510
b'VersionedFileRepository.get_inventories', (
4511
b'quack/', b'unordered'),
4512
b'success', (b'ok', ),
4513
_stream_to_byte_stream(stream, fmt))
4514
client.add_expected_call(
4515
b'Repository.annotate_file_revision',
4516
(b'quack/', b'somerevid', b'filename', b'', b'current:'),
4517
b'success', (b'ok', ),
4518
bencode.bencode([[b'baserevid', b'line 1\n'],
4519
[b'somerevid', b'line2\n']]))
4520
tree = repo.revision_tree(b'somerevid')
4522
(b'baserevid', b'line 1\n'),
4523
(b'somerevid', b'line2\n')],
4524
list(tree.annotate_iter('filename')))
4527
class TestBranchGetAllReferenceInfo(RemoteBranchTestCase):
4529
def test_get_all_reference_info(self):
4530
transport = MemoryTransport()
4531
client = FakeClient(transport.base)
4532
client.add_expected_call(
4533
b'Branch.get_stacked_on_url', (b'quack/',),
4534
b'error', (b'NotStacked',))
4535
client.add_expected_call(
4536
b'Branch.get_all_reference_info', (b'quack/',),
4537
b'success', (b'ok',), bencode.bencode([
4538
(b'file-id', b'https://www.example.com/', b'')]))
4539
transport.mkdir('quack')
4540
transport = transport.clone('quack')
4541
branch = self.make_remote_branch(transport, client)
4542
result = branch._get_all_reference_info()
4543
self.assertFinished(client)
4544
self.assertEqual({b'file-id': ('https://www.example.com/', None)}, result)