1
# Copyright (C) 2006-2013, 2016 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for remote bzrdir/branch/repo/etc
19
These are proxy objects which act on remote objects by sending messages
20
through a smart client. The proxies are to be created when attempting to open
21
the object given a transport that supports smartserver rpc operations.
23
These tests correspond to tests.test_smart, which exercises the server side.
42
from ..branch import Branch
51
from ..bzr.bzrdir import (
58
from ..bzr.chk_serializer import chk_bencode_serializer
59
from ..bzr.remote import (
65
RemoteRepositoryFormat,
67
from ..bzr import groupcompress_repo, knitpack_repo
68
from ..revision import (
72
from ..sixish import (
76
from ..bzr.smart import medium, request
77
from ..bzr.smart.client import _SmartClient
78
from ..bzr.smart.repository import (
79
SmartServerRepositoryGetParentMap,
80
SmartServerRepositoryGetStream_1_19,
81
_stream_to_byte_stream,
86
from .scenarios import load_tests_apply_scenarios
87
from ..transport.memory import MemoryTransport
88
from ..transport.remote import (
95
load_tests = load_tests_apply_scenarios
98
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
102
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
104
{'transport_server': test_server.SmartTCPServer_for_testing})]
108
super(BasicRemoteObjectTests, self).setUp()
109
self.transport = self.get_transport()
110
# make a branch that can be opened over the smart transport
111
self.local_wt = BzrDir.create_standalone_workingtree('.')
112
self.addCleanup(self.transport.disconnect)
114
def test_create_remote_bzrdir(self):
115
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
116
self.assertIsInstance(b, BzrDir)
118
def test_open_remote_branch(self):
119
# open a standalone branch in the working directory
120
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
121
branch = b.open_branch()
122
self.assertIsInstance(branch, Branch)
124
def test_remote_repository(self):
125
b = BzrDir.open_from_transport(self.transport)
126
repo = b.open_repository()
127
revid = u'\xc823123123'.encode('utf8')
128
self.assertFalse(repo.has_revision(revid))
129
self.local_wt.commit(message='test commit', rev_id=revid)
130
self.assertTrue(repo.has_revision(revid))
132
def test_find_correct_format(self):
133
"""Should open a RemoteBzrDir over a RemoteTransport"""
134
fmt = BzrDirFormat.find_format(self.transport)
135
self.assertTrue(RemoteBzrProber
136
in controldir.ControlDirFormat._server_probers)
137
self.assertIsInstance(fmt, RemoteBzrDirFormat)
139
def test_open_detected_smart_format(self):
140
fmt = BzrDirFormat.find_format(self.transport)
141
d = fmt.open(self.transport)
142
self.assertIsInstance(d, BzrDir)
144
def test_remote_branch_repr(self):
145
b = BzrDir.open_from_transport(self.transport).open_branch()
146
self.assertStartsWith(str(b), 'RemoteBranch(')
148
def test_remote_bzrdir_repr(self):
149
b = BzrDir.open_from_transport(self.transport)
150
self.assertStartsWith(str(b), 'RemoteBzrDir(')
152
def test_remote_branch_format_supports_stacking(self):
154
self.make_branch('unstackable', format='pack-0.92')
155
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
156
self.assertFalse(b._format.supports_stacking())
157
self.make_branch('stackable', format='1.9')
158
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
159
self.assertTrue(b._format.supports_stacking())
161
def test_remote_repo_format_supports_external_references(self):
163
bd = self.make_controldir('unstackable', format='pack-0.92')
164
r = bd.create_repository()
165
self.assertFalse(r._format.supports_external_lookups)
166
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
167
self.assertFalse(r._format.supports_external_lookups)
168
bd = self.make_controldir('stackable', format='1.9')
169
r = bd.create_repository()
170
self.assertTrue(r._format.supports_external_lookups)
171
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
172
self.assertTrue(r._format.supports_external_lookups)
174
def test_remote_branch_set_append_revisions_only(self):
175
# Make a format 1.9 branch, which supports append_revisions_only
176
branch = self.make_branch('branch', format='1.9')
177
branch.set_append_revisions_only(True)
178
config = branch.get_config_stack()
180
True, config.get('append_revisions_only'))
181
branch.set_append_revisions_only(False)
182
config = branch.get_config_stack()
184
False, config.get('append_revisions_only'))
186
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
187
branch = self.make_branch('branch', format='knit')
189
errors.UpgradeRequired, branch.set_append_revisions_only, True)
192
class FakeProtocol(object):
193
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
195
def __init__(self, body, fake_client):
197
self._body_buffer = None
198
self._fake_client = fake_client
200
def read_body_bytes(self, count=-1):
201
if self._body_buffer is None:
202
self._body_buffer = BytesIO(self.body)
203
bytes = self._body_buffer.read(count)
204
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
205
self._fake_client.expecting_body = False
208
def cancel_read_body(self):
209
self._fake_client.expecting_body = False
211
def read_streamed_body(self):
215
class FakeClient(_SmartClient):
216
"""Lookalike for _SmartClient allowing testing."""
218
def __init__(self, fake_medium_base='fake base'):
219
"""Create a FakeClient."""
222
self.expecting_body = False
223
# if non-None, this is the list of expected calls, with only the
224
# method name and arguments included. the body might be hard to
225
# compute so is not included. If a call is None, that call can
227
self._expected_calls = None
228
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
230
def add_expected_call(self, call_name, call_args, response_type,
231
response_args, response_body=None):
232
if self._expected_calls is None:
233
self._expected_calls = []
234
self._expected_calls.append((call_name, call_args))
235
self.responses.append((response_type, response_args, response_body))
237
def add_success_response(self, *args):
238
self.responses.append((b'success', args, None))
240
def add_success_response_with_body(self, body, *args):
241
self.responses.append((b'success', args, body))
242
if self._expected_calls is not None:
243
self._expected_calls.append(None)
245
def add_error_response(self, *args):
246
self.responses.append((b'error', args))
248
def add_unknown_method_response(self, verb):
249
self.responses.append((b'unknown', verb))
251
def finished_test(self):
252
if self._expected_calls:
253
raise AssertionError("%r finished but was still expecting %r"
254
% (self, self._expected_calls[0]))
256
def _get_next_response(self):
258
response_tuple = self.responses.pop(0)
259
except IndexError as e:
260
raise AssertionError("%r didn't expect any more calls"
262
if response_tuple[0] == b'unknown':
263
raise errors.UnknownSmartMethod(response_tuple[1])
264
elif response_tuple[0] == b'error':
265
raise errors.ErrorFromSmartServer(response_tuple[1])
266
return response_tuple
268
def _check_call(self, method, args):
269
if self._expected_calls is None:
270
# the test should be updated to say what it expects
273
next_call = self._expected_calls.pop(0)
275
raise AssertionError("%r didn't expect any more calls "
277
% (self, method, args,))
278
if next_call is None:
280
if method != next_call[0] or args != next_call[1]:
281
raise AssertionError("%r expected %r%r "
283
% (self, next_call[0], next_call[1], method, args,))
285
def call(self, method, *args):
286
self._check_call(method, args)
287
self._calls.append(('call', method, args))
288
return self._get_next_response()[1]
290
def call_expecting_body(self, method, *args):
291
self._check_call(method, args)
292
self._calls.append(('call_expecting_body', method, args))
293
result = self._get_next_response()
294
self.expecting_body = True
295
return result[1], FakeProtocol(result[2], self)
297
def call_with_body_bytes(self, method, args, body):
298
self._check_call(method, args)
299
self._calls.append(('call_with_body_bytes', method, args, body))
300
result = self._get_next_response()
301
return result[1], FakeProtocol(result[2], self)
303
def call_with_body_bytes_expecting_body(self, method, args, body):
304
self._check_call(method, args)
305
self._calls.append(('call_with_body_bytes_expecting_body', method,
307
result = self._get_next_response()
308
self.expecting_body = True
309
return result[1], FakeProtocol(result[2], self)
311
def call_with_body_stream(self, args, stream):
312
# Explicitly consume the stream before checking for an error, because
313
# that's what happens a real medium.
314
stream = list(stream)
315
self._check_call(args[0], args[1:])
316
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
317
result = self._get_next_response()
318
# The second value returned from call_with_body_stream is supposed to
319
# be a response_handler object, but so far no tests depend on that.
320
response_handler = None
321
return result[1], response_handler
324
class FakeMedium(medium.SmartClientMedium):
326
def __init__(self, client_calls, base):
327
medium.SmartClientMedium.__init__(self, base)
328
self._client_calls = client_calls
330
def disconnect(self):
331
self._client_calls.append(('disconnect medium',))
334
class TestVfsHas(tests.TestCase):
336
def test_unicode_path(self):
337
client = FakeClient('/')
338
client.add_success_response(b'yes',)
339
transport = RemoteTransport('bzr://localhost/', _client=client)
340
filename = u'/hell\u00d8'.encode('utf-8')
341
result = transport.has(filename)
343
[('call', b'has', (filename,))],
345
self.assertTrue(result)
348
class TestRemote(tests.TestCaseWithMemoryTransport):
350
def get_branch_format(self):
351
reference_bzrdir_format = controldir.format_registry.get('default')()
352
return reference_bzrdir_format.get_branch_format()
354
def get_repo_format(self):
355
reference_bzrdir_format = controldir.format_registry.get('default')()
356
return reference_bzrdir_format.repository_format
358
def assertFinished(self, fake_client):
359
"""Assert that all of a FakeClient's expected calls have occurred."""
360
fake_client.finished_test()
363
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
364
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
366
def assertRemotePath(self, expected, client_base, transport_base):
367
"""Assert that the result of
368
SmartClientMedium.remote_path_from_transport is the expected value for
369
a given client_base and transport_base.
371
client_medium = medium.SmartClientMedium(client_base)
372
t = transport.get_transport(transport_base)
373
result = client_medium.remote_path_from_transport(t)
374
self.assertEqual(expected, result)
376
def test_remote_path_from_transport(self):
377
"""SmartClientMedium.remote_path_from_transport calculates a URL for
378
the given transport relative to the root of the client base URL.
380
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
381
self.assertRemotePath(
382
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
384
def assertRemotePathHTTP(self, expected, transport_base, relpath):
385
"""Assert that the result of
386
HttpTransportBase.remote_path_from_transport is the expected value for
387
a given transport_base and relpath of that transport. (Note that
388
HttpTransportBase is a subclass of SmartClientMedium)
390
base_transport = transport.get_transport(transport_base)
391
client_medium = base_transport.get_smart_medium()
392
cloned_transport = base_transport.clone(relpath)
393
result = client_medium.remote_path_from_transport(cloned_transport)
394
self.assertEqual(expected, result)
396
def test_remote_path_from_transport_http(self):
397
"""Remote paths for HTTP transports are calculated differently to other
398
transports. They are just relative to the client base, not the root
399
directory of the host.
401
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
402
self.assertRemotePathHTTP(
403
'../xyz/', scheme + '//host/path', '../xyz/')
404
self.assertRemotePathHTTP(
405
'xyz/', scheme + '//host/path', 'xyz/')
408
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
409
"""Tests for the behaviour of client_medium.remote_is_at_least."""
411
def test_initially_unlimited(self):
412
"""A fresh medium assumes that the remote side supports all
415
client_medium = medium.SmartClientMedium('dummy base')
416
self.assertFalse(client_medium._is_remote_before((99, 99)))
418
def test__remember_remote_is_before(self):
419
"""Calling _remember_remote_is_before ratchets down the known remote
422
client_medium = medium.SmartClientMedium('dummy base')
423
# Mark the remote side as being less than 1.6. The remote side may
425
client_medium._remember_remote_is_before((1, 6))
426
self.assertTrue(client_medium._is_remote_before((1, 6)))
427
self.assertFalse(client_medium._is_remote_before((1, 5)))
428
# Calling _remember_remote_is_before again with a lower value works.
429
client_medium._remember_remote_is_before((1, 5))
430
self.assertTrue(client_medium._is_remote_before((1, 5)))
431
# If you call _remember_remote_is_before with a higher value it logs a
432
# warning, and continues to remember the lower value.
433
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
434
client_medium._remember_remote_is_before((1, 9))
435
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
436
self.assertTrue(client_medium._is_remote_before((1, 5)))
439
class TestBzrDirCloningMetaDir(TestRemote):
441
def test_backwards_compat(self):
442
self.setup_smart_server_with_call_log()
443
a_dir = self.make_controldir('.')
444
self.reset_smart_call_log()
445
verb = b'BzrDir.cloning_metadir'
446
self.disable_verb(verb)
447
format = a_dir.cloning_metadir()
448
call_count = len([call for call in self.hpss_calls if
449
call.call.method == verb])
450
self.assertEqual(1, call_count)
452
def test_branch_reference(self):
453
transport = self.get_transport('quack')
454
referenced = self.make_branch('referenced')
455
expected = referenced.controldir.cloning_metadir()
456
client = FakeClient(transport.base)
457
client.add_expected_call(
458
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
459
b'error', (b'BranchReference',)),
460
client.add_expected_call(
461
b'BzrDir.open_branchV3', (b'quack/',),
462
b'success', (b'ref', self.get_url('referenced'))),
463
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
465
result = a_controldir.cloning_metadir()
466
# We should have got a control dir matching the referenced branch.
467
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
468
self.assertEqual(expected._repository_format, result._repository_format)
469
self.assertEqual(expected._branch_format, result._branch_format)
470
self.assertFinished(client)
472
def test_current_server(self):
473
transport = self.get_transport('.')
474
transport = transport.clone('quack')
475
self.make_controldir('quack')
476
client = FakeClient(transport.base)
477
reference_bzrdir_format = controldir.format_registry.get('default')()
478
control_name = reference_bzrdir_format.network_name()
479
client.add_expected_call(
480
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
481
b'success', (control_name, b'', (b'branch', b''))),
482
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
484
result = a_controldir.cloning_metadir()
485
# We should have got a reference control dir with default branch and
486
# repository formats.
487
# This pokes a little, just to be sure.
488
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
489
self.assertEqual(None, result._repository_format)
490
self.assertEqual(None, result._branch_format)
491
self.assertFinished(client)
493
def test_unknown(self):
494
transport = self.get_transport('quack')
495
referenced = self.make_branch('referenced')
496
expected = referenced.controldir.cloning_metadir()
497
client = FakeClient(transport.base)
498
client.add_expected_call(
499
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
500
b'success', (b'unknown', b'unknown', (b'branch', b''))),
501
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
503
self.assertRaises(errors.UnknownFormatError, a_controldir.cloning_metadir)
506
class TestBzrDirCheckoutMetaDir(TestRemote):
508
def test__get_checkout_format(self):
509
transport = MemoryTransport()
510
client = FakeClient(transport.base)
511
reference_bzrdir_format = controldir.format_registry.get('default')()
512
control_name = reference_bzrdir_format.network_name()
513
client.add_expected_call(
514
b'BzrDir.checkout_metadir', (b'quack/', ),
515
b'success', (control_name, b'', b''))
516
transport.mkdir('quack')
517
transport = transport.clone('quack')
518
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
520
result = a_controldir.checkout_metadir()
521
# We should have got a reference control dir with default branch and
522
# repository formats.
523
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
524
self.assertEqual(None, result._repository_format)
525
self.assertEqual(None, result._branch_format)
526
self.assertFinished(client)
528
def test_unknown_format(self):
529
transport = MemoryTransport()
530
client = FakeClient(transport.base)
531
client.add_expected_call(
532
b'BzrDir.checkout_metadir', (b'quack/',),
533
b'success', (b'dontknow', b'', b''))
534
transport.mkdir('quack')
535
transport = transport.clone('quack')
536
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
538
self.assertRaises(errors.UnknownFormatError,
539
a_controldir.checkout_metadir)
540
self.assertFinished(client)
543
class TestBzrDirGetBranches(TestRemote):
545
def test_get_branches(self):
546
transport = MemoryTransport()
547
client = FakeClient(transport.base)
548
reference_bzrdir_format = controldir.format_registry.get('default')()
549
branch_name = reference_bzrdir_format.get_branch_format().network_name()
550
client.add_success_response_with_body(
552
b"foo": (b"branch", branch_name),
553
b"": (b"branch", branch_name)}), b"success")
554
client.add_success_response(
555
b'ok', b'', b'no', b'no', b'no',
556
reference_bzrdir_format.repository_format.network_name())
557
client.add_error_response(b'NotStacked')
558
client.add_success_response(
559
b'ok', b'', b'no', b'no', b'no',
560
reference_bzrdir_format.repository_format.network_name())
561
client.add_error_response(b'NotStacked')
562
transport.mkdir('quack')
563
transport = transport.clone('quack')
564
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
566
result = a_controldir.get_branches()
567
self.assertEqual({"", "foo"}, set(result.keys()))
569
[('call_expecting_body', b'BzrDir.get_branches', (b'quack/',)),
570
('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
571
('call', b'Branch.get_stacked_on_url', (b'quack/', )),
572
('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
573
('call', b'Branch.get_stacked_on_url', (b'quack/', ))],
577
class TestBzrDirDestroyBranch(TestRemote):
579
def test_destroy_default(self):
580
transport = self.get_transport('quack')
581
referenced = self.make_branch('referenced')
582
client = FakeClient(transport.base)
583
client.add_expected_call(
584
b'BzrDir.destroy_branch', (b'quack/', ),
585
b'success', (b'ok',)),
586
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
588
a_controldir.destroy_branch()
589
self.assertFinished(client)
592
class TestBzrDirHasWorkingTree(TestRemote):
594
def test_has_workingtree(self):
595
transport = self.get_transport('quack')
596
client = FakeClient(transport.base)
597
client.add_expected_call(
598
b'BzrDir.has_workingtree', (b'quack/',),
599
b'success', (b'yes',)),
600
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
602
self.assertTrue(a_controldir.has_workingtree())
603
self.assertFinished(client)
605
def test_no_workingtree(self):
606
transport = self.get_transport('quack')
607
client = FakeClient(transport.base)
608
client.add_expected_call(
609
b'BzrDir.has_workingtree', (b'quack/',),
610
b'success', (b'no',)),
611
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
613
self.assertFalse(a_controldir.has_workingtree())
614
self.assertFinished(client)
617
class TestBzrDirDestroyRepository(TestRemote):
619
def test_destroy_repository(self):
620
transport = self.get_transport('quack')
621
client = FakeClient(transport.base)
622
client.add_expected_call(
623
b'BzrDir.destroy_repository', (b'quack/',),
624
b'success', (b'ok',)),
625
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
627
a_controldir.destroy_repository()
628
self.assertFinished(client)
631
class TestBzrDirOpen(TestRemote):
633
def make_fake_client_and_transport(self, path='quack'):
634
transport = MemoryTransport()
635
transport.mkdir(path)
636
transport = transport.clone(path)
637
client = FakeClient(transport.base)
638
return client, transport
640
def test_absent(self):
641
client, transport = self.make_fake_client_and_transport()
642
client.add_expected_call(
643
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'no',))
644
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
645
RemoteBzrDirFormat(), _client=client, _force_probe=True)
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', (b'BzrDir.open_2.1',))
674
client.add_expected_call(
675
b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
676
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
677
_client=client, _force_probe=True)
678
self.assertIsInstance(bd, RemoteBzrDir)
679
self.assertFinished(client)
681
def test_backwards_compat_hpss_v2(self):
682
client, transport = self.make_fake_client_and_transport()
683
# Monkey-patch fake client to simulate real-world behaviour with v2
684
# server: upon first RPC call detect the protocol version, and because
685
# the version is 2 also do _remember_remote_is_before((1, 6)) before
686
# continuing with the RPC.
687
orig_check_call = client._check_call
688
def check_call(method, args):
689
client._medium._protocol_version = 2
690
client._medium._remember_remote_is_before((1, 6))
691
client._check_call = orig_check_call
692
client._check_call(method, args)
693
client._check_call = check_call
694
client.add_expected_call(
695
b'BzrDir.open_2.1', (b'quack/',), b'unknown', (b'BzrDir.open_2.1',))
696
client.add_expected_call(
697
b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
698
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
699
_client=client, _force_probe=True)
700
self.assertIsInstance(bd, RemoteBzrDir)
701
self.assertFinished(client)
704
class TestBzrDirOpenBranch(TestRemote):
706
def test_backwards_compat(self):
707
self.setup_smart_server_with_call_log()
708
self.make_branch('.')
709
a_dir = BzrDir.open(self.get_url('.'))
710
self.reset_smart_call_log()
711
verb = b'BzrDir.open_branchV3'
712
self.disable_verb(verb)
713
format = a_dir.open_branch()
714
call_count = len([call for call in self.hpss_calls if
715
call.call.method == verb])
716
self.assertEqual(1, call_count)
718
def test_branch_present(self):
719
reference_format = self.get_repo_format()
720
network_name = reference_format.network_name()
721
branch_network_name = self.get_branch_format().network_name()
722
transport = MemoryTransport()
723
transport.mkdir('quack')
724
transport = transport.clone('quack')
725
client = FakeClient(transport.base)
726
client.add_expected_call(
727
b'BzrDir.open_branchV3', (b'quack/',),
728
b'success', (b'branch', branch_network_name))
729
client.add_expected_call(
730
b'BzrDir.find_repositoryV3', (b'quack/',),
731
b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
732
client.add_expected_call(
733
b'Branch.get_stacked_on_url', (b'quack/',),
734
b'error', (b'NotStacked',))
735
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
737
result = bzrdir.open_branch()
738
self.assertIsInstance(result, RemoteBranch)
739
self.assertEqual(bzrdir, result.controldir)
740
self.assertFinished(client)
742
def test_branch_missing(self):
743
transport = MemoryTransport()
744
transport.mkdir('quack')
745
transport = transport.clone('quack')
746
client = FakeClient(transport.base)
747
client.add_error_response(b'nobranch')
748
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
750
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
752
[('call', b'BzrDir.open_branchV3', (b'quack/',))],
755
def test__get_tree_branch(self):
756
# _get_tree_branch is a form of open_branch, but it should only ask for
757
# branch opening, not any other network requests.
759
def open_branch(name=None, possible_transports=None):
760
calls.append("Called")
762
transport = MemoryTransport()
763
# no requests on the network - catches other api calls being made.
764
client = FakeClient(transport.base)
765
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
767
# patch the open_branch call to record that it was called.
768
bzrdir.open_branch = open_branch
769
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
770
self.assertEqual(["Called"], calls)
771
self.assertEqual([], client._calls)
773
def test_url_quoting_of_path(self):
774
# Relpaths on the wire should not be URL-escaped. So "~" should be
775
# transmitted as "~", not "%7E".
776
transport = RemoteTCPTransport('bzr://localhost/~hello/')
777
client = FakeClient(transport.base)
778
reference_format = self.get_repo_format()
779
network_name = reference_format.network_name()
780
branch_network_name = self.get_branch_format().network_name()
781
client.add_expected_call(
782
b'BzrDir.open_branchV3', (b'~hello/',),
783
b'success', (b'branch', branch_network_name))
784
client.add_expected_call(
785
b'BzrDir.find_repositoryV3', (b'~hello/',),
786
b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
787
client.add_expected_call(
788
b'Branch.get_stacked_on_url', (b'~hello/',),
789
b'error', (b'NotStacked',))
790
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
792
result = bzrdir.open_branch()
793
self.assertFinished(client)
795
def check_open_repository(self, rich_root, subtrees, external_lookup=b'no'):
796
reference_format = self.get_repo_format()
797
network_name = reference_format.network_name()
798
transport = MemoryTransport()
799
transport.mkdir('quack')
800
transport = transport.clone('quack')
802
rich_response = b'yes'
804
rich_response = b'no'
806
subtree_response = b'yes'
808
subtree_response = b'no'
809
client = FakeClient(transport.base)
810
client.add_success_response(
811
b'ok', b'', rich_response, subtree_response, external_lookup,
813
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
815
result = bzrdir.open_repository()
817
[('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
819
self.assertIsInstance(result, RemoteRepository)
820
self.assertEqual(bzrdir, result.controldir)
821
self.assertEqual(rich_root, result._format.rich_root_data)
822
self.assertEqual(subtrees, result._format.supports_tree_reference)
824
def test_open_repository_sets_format_attributes(self):
825
self.check_open_repository(True, True)
826
self.check_open_repository(False, True)
827
self.check_open_repository(True, False)
828
self.check_open_repository(False, False)
829
self.check_open_repository(False, False, b'yes')
831
def test_old_server(self):
832
"""RemoteBzrDirFormat should fail to probe if the server version is too
835
self.assertRaises(errors.NotBranchError,
836
RemoteBzrProber.probe_transport, OldServerTransport())
839
class TestBzrDirCreateBranch(TestRemote):
841
def test_backwards_compat(self):
842
self.setup_smart_server_with_call_log()
843
repo = self.make_repository('.')
844
self.reset_smart_call_log()
845
self.disable_verb(b'BzrDir.create_branch')
846
branch = repo.controldir.create_branch()
847
create_branch_call_count = len([call for call in self.hpss_calls if
848
call.call.method == b'BzrDir.create_branch'])
849
self.assertEqual(1, create_branch_call_count)
851
def test_current_server(self):
852
transport = self.get_transport('.')
853
transport = transport.clone('quack')
854
self.make_repository('quack')
855
client = FakeClient(transport.base)
856
reference_bzrdir_format = controldir.format_registry.get('default')()
857
reference_format = reference_bzrdir_format.get_branch_format()
858
network_name = reference_format.network_name()
859
reference_repo_fmt = reference_bzrdir_format.repository_format
860
reference_repo_name = reference_repo_fmt.network_name()
861
client.add_expected_call(
862
b'BzrDir.create_branch', (b'quack/', network_name),
863
b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
864
reference_repo_name))
865
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
867
branch = a_controldir.create_branch()
868
# We should have got a remote branch
869
self.assertIsInstance(branch, remote.RemoteBranch)
870
# its format should have the settings from the response
871
format = branch._format
872
self.assertEqual(network_name, format.network_name())
874
def test_already_open_repo_and_reused_medium(self):
875
"""Bug 726584: create_branch(..., repository=repo) should work
876
regardless of what the smart medium's base URL is.
878
self.transport_server = test_server.SmartTCPServer_for_testing
879
transport = self.get_transport('.')
880
repo = self.make_repository('quack')
881
# Client's medium rooted a transport root (not at the bzrdir)
882
client = FakeClient(transport.base)
883
transport = transport.clone('quack')
884
reference_bzrdir_format = controldir.format_registry.get('default')()
885
reference_format = reference_bzrdir_format.get_branch_format()
886
network_name = reference_format.network_name()
887
reference_repo_fmt = reference_bzrdir_format.repository_format
888
reference_repo_name = reference_repo_fmt.network_name()
889
client.add_expected_call(
890
b'BzrDir.create_branch', (b'extra/quack/', network_name),
891
b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
892
reference_repo_name))
893
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
895
branch = a_controldir.create_branch(repository=repo)
896
# We should have got a remote branch
897
self.assertIsInstance(branch, remote.RemoteBranch)
898
# its format should have the settings from the response
899
format = branch._format
900
self.assertEqual(network_name, format.network_name())
903
class TestBzrDirCreateRepository(TestRemote):
905
def test_backwards_compat(self):
906
self.setup_smart_server_with_call_log()
907
bzrdir = self.make_controldir('.')
908
self.reset_smart_call_log()
909
self.disable_verb(b'BzrDir.create_repository')
910
repo = bzrdir.create_repository()
911
create_repo_call_count = len([call for call in self.hpss_calls if
912
call.call.method == b'BzrDir.create_repository'])
913
self.assertEqual(1, create_repo_call_count)
915
def test_current_server(self):
916
transport = self.get_transport('.')
917
transport = transport.clone('quack')
918
self.make_controldir('quack')
919
client = FakeClient(transport.base)
920
reference_bzrdir_format = controldir.format_registry.get('default')()
921
reference_format = reference_bzrdir_format.repository_format
922
network_name = reference_format.network_name()
923
client.add_expected_call(
924
b'BzrDir.create_repository', (b'quack/',
925
b'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
927
b'success', (b'ok', b'yes', b'yes', b'yes', network_name))
928
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
930
repo = a_controldir.create_repository()
931
# We should have got a remote repository
932
self.assertIsInstance(repo, remote.RemoteRepository)
933
# its format should have the settings from the response
934
format = repo._format
935
self.assertTrue(format.rich_root_data)
936
self.assertTrue(format.supports_tree_reference)
937
self.assertTrue(format.supports_external_lookups)
938
self.assertEqual(network_name, format.network_name())
941
class TestBzrDirOpenRepository(TestRemote):
943
def test_backwards_compat_1_2_3(self):
944
# fallback all the way to the first version.
945
reference_format = self.get_repo_format()
946
network_name = reference_format.network_name()
947
server_url = 'bzr://example.com/'
948
self.permit_url(server_url)
949
client = FakeClient(server_url)
950
client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
951
client.add_unknown_method_response(b'BzrDir.find_repositoryV2')
952
client.add_success_response(b'ok', b'', b'no', b'no')
953
# A real repository instance will be created to determine the network
955
client.add_success_response_with_body(
956
b"Bazaar-NG meta directory, format 1\n", b'ok')
957
client.add_success_response(b'stat', b'0', b'65535')
958
client.add_success_response_with_body(
959
reference_format.get_format_string(), b'ok')
960
# PackRepository wants to do a stat
961
client.add_success_response(b'stat', b'0', b'65535')
962
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
964
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
966
repo = bzrdir.open_repository()
968
[('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
969
('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
970
('call', b'BzrDir.find_repository', (b'quack/',)),
971
('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
972
('call', b'stat', (b'/quack/.bzr',)),
973
('call_expecting_body', b'get', (b'/quack/.bzr/repository/format',)),
974
('call', b'stat', (b'/quack/.bzr/repository',)),
977
self.assertEqual(network_name, repo._format.network_name())
979
def test_backwards_compat_2(self):
980
# fallback to find_repositoryV2
981
reference_format = self.get_repo_format()
982
network_name = reference_format.network_name()
983
server_url = 'bzr://example.com/'
984
self.permit_url(server_url)
985
client = FakeClient(server_url)
986
client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
987
client.add_success_response(b'ok', b'', b'no', b'no', b'no')
988
# A real repository instance will be created to determine the network
990
client.add_success_response_with_body(
991
b"Bazaar-NG meta directory, format 1\n", b'ok')
992
client.add_success_response(b'stat', b'0', b'65535')
993
client.add_success_response_with_body(
994
reference_format.get_format_string(), b'ok')
995
# PackRepository wants to do a stat
996
client.add_success_response(b'stat', b'0', b'65535')
997
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
999
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
1001
repo = bzrdir.open_repository()
1003
[('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
1004
('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
1005
('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
1006
('call', b'stat', (b'/quack/.bzr',)),
1007
('call_expecting_body', b'get', (b'/quack/.bzr/repository/format',)),
1008
('call', b'stat', (b'/quack/.bzr/repository',)),
1011
self.assertEqual(network_name, repo._format.network_name())
1013
def test_current_server(self):
1014
reference_format = self.get_repo_format()
1015
network_name = reference_format.network_name()
1016
transport = MemoryTransport()
1017
transport.mkdir('quack')
1018
transport = transport.clone('quack')
1019
client = FakeClient(transport.base)
1020
client.add_success_response(b'ok', b'', b'no', b'no', b'no', network_name)
1021
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1023
repo = bzrdir.open_repository()
1025
[('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
1027
self.assertEqual(network_name, repo._format.network_name())
1030
class TestBzrDirFormatInitializeEx(TestRemote):
1032
def test_success(self):
1033
"""Simple test for typical successful call."""
1034
fmt = RemoteBzrDirFormat()
1035
default_format_name = BzrDirFormat.get_default_format().network_name()
1036
transport = self.get_transport()
1037
client = FakeClient(transport.base)
1038
client.add_expected_call(
1039
b'BzrDirFormat.initialize_ex_1.16',
1040
(default_format_name, b'path', b'False', b'False', b'False', b'',
1041
b'', b'', b'', b'False'),
1043
(b'.', b'no', b'no', b'yes', b'repo fmt', b'repo bzrdir fmt',
1044
b'bzrdir fmt', b'False', b'', b'', b'repo lock token'))
1045
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1046
# it's currently hard to test that without supplying a real remote
1047
# transport connected to a real server.
1048
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1049
transport, False, False, False, None, None, None, None, False)
1050
self.assertFinished(client)
1052
def test_error(self):
1053
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1054
corresponding error from the client.
1056
fmt = RemoteBzrDirFormat()
1057
default_format_name = BzrDirFormat.get_default_format().network_name()
1058
transport = self.get_transport()
1059
client = FakeClient(transport.base)
1060
client.add_expected_call(
1061
b'BzrDirFormat.initialize_ex_1.16',
1062
(default_format_name, b'path', b'False', b'False', b'False', b'',
1063
b'', b'', b'', b'False'),
1065
(b'PermissionDenied', b'path', b'extra info'))
1066
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1067
# it's currently hard to test that without supplying a real remote
1068
# transport connected to a real server.
1069
err = self.assertRaises(errors.PermissionDenied,
1070
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1071
False, False, False, None, None, None, None, False)
1072
self.assertEqual('path', err.path)
1073
self.assertEqual(': extra info', err.extra)
1074
self.assertFinished(client)
1076
def test_error_from_real_server(self):
1077
"""Integration test for error translation."""
1078
transport = self.make_smart_server('foo')
1079
transport = transport.clone('no-such-path')
1080
fmt = RemoteBzrDirFormat()
1081
err = self.assertRaises(errors.NoSuchFile,
1082
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1085
class OldSmartClient(object):
1086
"""A fake smart client for test_old_version that just returns a version one
1087
response to the 'hello' (query version) command.
1090
def get_request(self):
1091
input_file = BytesIO(b'ok\x011\n')
1092
output_file = BytesIO()
1093
client_medium = medium.SmartSimplePipesClientMedium(
1094
input_file, output_file)
1095
return medium.SmartClientStreamMediumRequest(client_medium)
1097
def protocol_version(self):
1101
class OldServerTransport(object):
1102
"""A fake transport for test_old_server that reports it's smart server
1103
protocol version as version one.
1109
def get_smart_client(self):
1110
return OldSmartClient()
1113
class RemoteBzrDirTestCase(TestRemote):
1115
def make_remote_bzrdir(self, transport, client):
1116
"""Make a RemotebzrDir using 'client' as the _client."""
1117
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1121
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1123
def lock_remote_branch(self, branch):
1124
"""Trick a RemoteBranch into thinking it is locked."""
1125
branch._lock_mode = 'w'
1126
branch._lock_count = 2
1127
branch._lock_token = b'branch token'
1128
branch._repo_lock_token = b'repo token'
1129
branch.repository._lock_mode = 'w'
1130
branch.repository._lock_count = 2
1131
branch.repository._lock_token = b'repo token'
1133
def make_remote_branch(self, transport, client):
1134
"""Make a RemoteBranch using 'client' as its _SmartClient.
1136
A RemoteBzrDir and RemoteRepository will also be created to fill out
1137
the RemoteBranch, albeit with stub values for some of their attributes.
1139
# we do not want bzrdir to make any remote calls, so use False as its
1140
# _client. If it tries to make a remote call, this will fail
1142
bzrdir = self.make_remote_bzrdir(transport, False)
1143
repo = RemoteRepository(bzrdir, None, _client=client)
1144
branch_format = self.get_branch_format()
1145
format = RemoteBranchFormat(network_name=branch_format.network_name())
1146
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1149
class TestBranchBreakLock(RemoteBranchTestCase):
1151
def test_break_lock(self):
1152
transport_path = 'quack'
1153
transport = MemoryTransport()
1154
client = FakeClient(transport.base)
1155
client.add_expected_call(
1156
b'Branch.get_stacked_on_url', (b'quack/',),
1157
b'error', (b'NotStacked',))
1158
client.add_expected_call(
1159
b'Branch.break_lock', (b'quack/',),
1160
b'success', (b'ok',))
1161
transport.mkdir('quack')
1162
transport = transport.clone('quack')
1163
branch = self.make_remote_branch(transport, client)
1165
self.assertFinished(client)
1168
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1170
def test_get_physical_lock_status_yes(self):
1171
transport = MemoryTransport()
1172
client = FakeClient(transport.base)
1173
client.add_expected_call(
1174
b'Branch.get_stacked_on_url', (b'quack/',),
1175
b'error', (b'NotStacked',))
1176
client.add_expected_call(
1177
b'Branch.get_physical_lock_status', (b'quack/',),
1178
b'success', (b'yes',))
1179
transport.mkdir('quack')
1180
transport = transport.clone('quack')
1181
branch = self.make_remote_branch(transport, client)
1182
result = branch.get_physical_lock_status()
1183
self.assertFinished(client)
1184
self.assertEqual(True, result)
1186
def test_get_physical_lock_status_no(self):
1187
transport = MemoryTransport()
1188
client = FakeClient(transport.base)
1189
client.add_expected_call(
1190
b'Branch.get_stacked_on_url', (b'quack/',),
1191
b'error', (b'NotStacked',))
1192
client.add_expected_call(
1193
b'Branch.get_physical_lock_status', (b'quack/',),
1194
b'success', (b'no',))
1195
transport.mkdir('quack')
1196
transport = transport.clone('quack')
1197
branch = self.make_remote_branch(transport, client)
1198
result = branch.get_physical_lock_status()
1199
self.assertFinished(client)
1200
self.assertEqual(False, result)
1203
class TestBranchGetParent(RemoteBranchTestCase):
1205
def test_no_parent(self):
1206
# in an empty branch we decode the response properly
1207
transport = MemoryTransport()
1208
client = FakeClient(transport.base)
1209
client.add_expected_call(
1210
b'Branch.get_stacked_on_url', (b'quack/',),
1211
b'error', (b'NotStacked',))
1212
client.add_expected_call(
1213
b'Branch.get_parent', (b'quack/',),
1215
transport.mkdir('quack')
1216
transport = transport.clone('quack')
1217
branch = self.make_remote_branch(transport, client)
1218
result = branch.get_parent()
1219
self.assertFinished(client)
1220
self.assertEqual(None, result)
1222
def test_parent_relative(self):
1223
transport = MemoryTransport()
1224
client = FakeClient(transport.base)
1225
client.add_expected_call(
1226
b'Branch.get_stacked_on_url', (b'kwaak/',),
1227
b'error', (b'NotStacked',))
1228
client.add_expected_call(
1229
b'Branch.get_parent', (b'kwaak/',),
1230
b'success', (b'../foo/',))
1231
transport.mkdir('kwaak')
1232
transport = transport.clone('kwaak')
1233
branch = self.make_remote_branch(transport, client)
1234
result = branch.get_parent()
1235
self.assertEqual(transport.clone('../foo').base, result)
1237
def test_parent_absolute(self):
1238
transport = MemoryTransport()
1239
client = FakeClient(transport.base)
1240
client.add_expected_call(
1241
b'Branch.get_stacked_on_url', (b'kwaak/',),
1242
b'error', (b'NotStacked',))
1243
client.add_expected_call(
1244
b'Branch.get_parent', (b'kwaak/',),
1245
b'success', (b'http://foo/',))
1246
transport.mkdir('kwaak')
1247
transport = transport.clone('kwaak')
1248
branch = self.make_remote_branch(transport, client)
1249
result = branch.get_parent()
1250
self.assertEqual('http://foo/', result)
1251
self.assertFinished(client)
1254
class TestBranchSetParentLocation(RemoteBranchTestCase):
1256
def test_no_parent(self):
1257
# We call the verb when setting parent to None
1258
transport = MemoryTransport()
1259
client = FakeClient(transport.base)
1260
client.add_expected_call(
1261
b'Branch.get_stacked_on_url', (b'quack/',),
1262
b'error', (b'NotStacked',))
1263
client.add_expected_call(
1264
b'Branch.set_parent_location', (b'quack/', b'b', b'r', b''),
1266
transport.mkdir('quack')
1267
transport = transport.clone('quack')
1268
branch = self.make_remote_branch(transport, client)
1269
branch._lock_token = b'b'
1270
branch._repo_lock_token = b'r'
1271
branch._set_parent_location(None)
1272
self.assertFinished(client)
1274
def test_parent(self):
1275
transport = MemoryTransport()
1276
client = FakeClient(transport.base)
1277
client.add_expected_call(
1278
b'Branch.get_stacked_on_url', (b'kwaak/',),
1279
b'error', (b'NotStacked',))
1280
client.add_expected_call(
1281
b'Branch.set_parent_location', (b'kwaak/', b'b', b'r', b'foo'),
1283
transport.mkdir('kwaak')
1284
transport = transport.clone('kwaak')
1285
branch = self.make_remote_branch(transport, client)
1286
branch._lock_token = b'b'
1287
branch._repo_lock_token = b'r'
1288
branch._set_parent_location('foo')
1289
self.assertFinished(client)
1291
def test_backwards_compat(self):
1292
self.setup_smart_server_with_call_log()
1293
branch = self.make_branch('.')
1294
self.reset_smart_call_log()
1295
verb = b'Branch.set_parent_location'
1296
self.disable_verb(verb)
1297
branch.set_parent('http://foo/')
1298
self.assertLength(14, self.hpss_calls)
1301
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1303
def test_backwards_compat(self):
1304
self.setup_smart_server_with_call_log()
1305
branch = self.make_branch('.')
1306
self.reset_smart_call_log()
1307
verb = b'Branch.get_tags_bytes'
1308
self.disable_verb(verb)
1309
branch.tags.get_tag_dict()
1310
call_count = len([call for call in self.hpss_calls if
1311
call.call.method == verb])
1312
self.assertEqual(1, call_count)
1314
def test_trivial(self):
1315
transport = MemoryTransport()
1316
client = FakeClient(transport.base)
1317
client.add_expected_call(
1318
b'Branch.get_stacked_on_url', (b'quack/',),
1319
b'error', (b'NotStacked',))
1320
client.add_expected_call(
1321
b'Branch.get_tags_bytes', (b'quack/',),
1323
transport.mkdir('quack')
1324
transport = transport.clone('quack')
1325
branch = self.make_remote_branch(transport, client)
1326
result = branch.tags.get_tag_dict()
1327
self.assertFinished(client)
1328
self.assertEqual({}, result)
1331
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1333
def test_trivial(self):
1334
transport = MemoryTransport()
1335
client = FakeClient(transport.base)
1336
client.add_expected_call(
1337
b'Branch.get_stacked_on_url', (b'quack/',),
1338
b'error', (b'NotStacked',))
1339
client.add_expected_call(
1340
b'Branch.set_tags_bytes', (b'quack/', b'branch token', b'repo token'),
1342
transport.mkdir('quack')
1343
transport = transport.clone('quack')
1344
branch = self.make_remote_branch(transport, client)
1345
self.lock_remote_branch(branch)
1346
branch._set_tags_bytes(b'tags bytes')
1347
self.assertFinished(client)
1348
self.assertEqual(b'tags bytes', client._calls[-1][-1])
1350
def test_backwards_compatible(self):
1351
transport = MemoryTransport()
1352
client = FakeClient(transport.base)
1353
client.add_expected_call(
1354
b'Branch.get_stacked_on_url', (b'quack/',),
1355
b'error', (b'NotStacked',))
1356
client.add_expected_call(
1357
b'Branch.set_tags_bytes', (b'quack/', b'branch token', b'repo token'),
1358
b'unknown', (b'Branch.set_tags_bytes',))
1359
transport.mkdir('quack')
1360
transport = transport.clone('quack')
1361
branch = self.make_remote_branch(transport, client)
1362
self.lock_remote_branch(branch)
1363
class StubRealBranch(object):
1366
def _set_tags_bytes(self, bytes):
1367
self.calls.append(('set_tags_bytes', bytes))
1368
real_branch = StubRealBranch()
1369
branch._real_branch = real_branch
1370
branch._set_tags_bytes(b'tags bytes')
1371
# Call a second time, to exercise the 'remote version already inferred'
1373
branch._set_tags_bytes(b'tags bytes')
1374
self.assertFinished(client)
1376
[('set_tags_bytes', b'tags bytes')] * 2, real_branch.calls)
1379
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1381
def test_uses_last_revision_info_and_tags_by_default(self):
1382
transport = MemoryTransport()
1383
client = FakeClient(transport.base)
1384
client.add_expected_call(
1385
b'Branch.get_stacked_on_url', (b'quack/',),
1386
b'error', (b'NotStacked',))
1387
client.add_expected_call(
1388
b'Branch.last_revision_info', (b'quack/',),
1389
b'success', (b'ok', b'1', b'rev-tip'))
1390
client.add_expected_call(
1391
b'Branch.get_config_file', (b'quack/',),
1392
b'success', (b'ok',), b'')
1393
transport.mkdir('quack')
1394
transport = transport.clone('quack')
1395
branch = self.make_remote_branch(transport, client)
1396
result = branch.heads_to_fetch()
1397
self.assertFinished(client)
1398
self.assertEqual(({b'rev-tip'}, set()), result)
1400
def test_uses_last_revision_info_and_tags_when_set(self):
1401
transport = MemoryTransport()
1402
client = FakeClient(transport.base)
1403
client.add_expected_call(
1404
b'Branch.get_stacked_on_url', (b'quack/',),
1405
b'error', (b'NotStacked',))
1406
client.add_expected_call(
1407
b'Branch.last_revision_info', (b'quack/',),
1408
b'success', (b'ok', b'1', b'rev-tip'))
1409
client.add_expected_call(
1410
b'Branch.get_config_file', (b'quack/',),
1411
b'success', (b'ok',), b'branch.fetch_tags = True')
1412
# XXX: this will break if the default format's serialization of tags
1413
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1414
client.add_expected_call(
1415
b'Branch.get_tags_bytes', (b'quack/',),
1416
b'success', (b'd5:tag-17:rev-foo5:tag-27:rev-bare',))
1417
transport.mkdir('quack')
1418
transport = transport.clone('quack')
1419
branch = self.make_remote_branch(transport, client)
1420
result = branch.heads_to_fetch()
1421
self.assertFinished(client)
1423
({b'rev-tip'}, {b'rev-foo', b'rev-bar'}), result)
1425
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1426
transport = MemoryTransport()
1427
client = FakeClient(transport.base)
1428
client.add_expected_call(
1429
b'Branch.get_stacked_on_url', (b'quack/',),
1430
b'error', (b'NotStacked',))
1431
client.add_expected_call(
1432
b'Branch.heads_to_fetch', (b'quack/',),
1433
b'success', ([b'tip'], [b'tagged-1', b'tagged-2']))
1434
transport.mkdir('quack')
1435
transport = transport.clone('quack')
1436
branch = self.make_remote_branch(transport, client)
1437
branch._format._use_default_local_heads_to_fetch = lambda: False
1438
result = branch.heads_to_fetch()
1439
self.assertFinished(client)
1440
self.assertEqual(({b'tip'}, {b'tagged-1', b'tagged-2'}), result)
1442
def make_branch_with_tags(self):
1443
self.setup_smart_server_with_call_log()
1444
# Make a branch with a single revision.
1445
builder = self.make_branch_builder('foo')
1446
builder.start_series()
1447
builder.build_snapshot(None, [
1448
('add', ('', b'root-id', 'directory', ''))],
1450
builder.finish_series()
1451
branch = builder.get_branch()
1452
# Add two tags to that branch
1453
branch.tags.set_tag('tag-1', b'rev-1')
1454
branch.tags.set_tag('tag-2', b'rev-2')
1457
def test_backwards_compatible(self):
1458
br = self.make_branch_with_tags()
1459
br.get_config_stack().set('branch.fetch_tags', True)
1460
self.addCleanup(br.lock_read().unlock)
1461
# Disable the heads_to_fetch verb
1462
verb = b'Branch.heads_to_fetch'
1463
self.disable_verb(verb)
1464
self.reset_smart_call_log()
1465
result = br.heads_to_fetch()
1466
self.assertEqual(({b'tip'}, {b'rev-1', b'rev-2'}), result)
1468
[b'Branch.last_revision_info', b'Branch.get_tags_bytes'],
1469
[call.call.method for call in self.hpss_calls])
1471
def test_backwards_compatible_no_tags(self):
1472
br = self.make_branch_with_tags()
1473
br.get_config_stack().set('branch.fetch_tags', False)
1474
self.addCleanup(br.lock_read().unlock)
1475
# Disable the heads_to_fetch verb
1476
verb = b'Branch.heads_to_fetch'
1477
self.disable_verb(verb)
1478
self.reset_smart_call_log()
1479
result = br.heads_to_fetch()
1480
self.assertEqual(({b'tip'}, set()), result)
1482
[b'Branch.last_revision_info'],
1483
[call.call.method for call in self.hpss_calls])
1486
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1488
def test_empty_branch(self):
1489
# in an empty branch we decode the response properly
1490
transport = MemoryTransport()
1491
client = FakeClient(transport.base)
1492
client.add_expected_call(
1493
b'Branch.get_stacked_on_url', (b'quack/',),
1494
b'error', (b'NotStacked',))
1495
client.add_expected_call(
1496
b'Branch.last_revision_info', (b'quack/',),
1497
b'success', (b'ok', b'0', b'null:'))
1498
transport.mkdir('quack')
1499
transport = transport.clone('quack')
1500
branch = self.make_remote_branch(transport, client)
1501
result = branch.last_revision_info()
1502
self.assertFinished(client)
1503
self.assertEqual((0, NULL_REVISION), result)
1505
def test_non_empty_branch(self):
1506
# in a non-empty branch we also decode the response properly
1507
revid = u'\xc8'.encode('utf8')
1508
transport = MemoryTransport()
1509
client = FakeClient(transport.base)
1510
client.add_expected_call(
1511
b'Branch.get_stacked_on_url', (b'kwaak/',),
1512
b'error', (b'NotStacked',))
1513
client.add_expected_call(
1514
b'Branch.last_revision_info', (b'kwaak/',),
1515
b'success', (b'ok', b'2', revid))
1516
transport.mkdir('kwaak')
1517
transport = transport.clone('kwaak')
1518
branch = self.make_remote_branch(transport, client)
1519
result = branch.last_revision_info()
1520
self.assertEqual((2, revid), result)
1523
class TestBranch_get_stacked_on_url(TestRemote):
1524
"""Test Branch._get_stacked_on_url rpc"""
1526
def test_get_stacked_on_invalid_url(self):
1527
# test that asking for a stacked on url the server can't access works.
1528
# This isn't perfect, but then as we're in the same process there
1529
# really isn't anything we can do to be 100% sure that the server
1530
# doesn't just open in - this test probably needs to be rewritten using
1531
# a spawn()ed server.
1532
stacked_branch = self.make_branch('stacked', format='1.9')
1533
memory_branch = self.make_branch('base', format='1.9')
1534
vfs_url = self.get_vfs_only_url('base')
1535
stacked_branch.set_stacked_on_url(vfs_url)
1536
transport = stacked_branch.controldir.root_transport
1537
client = FakeClient(transport.base)
1538
client.add_expected_call(
1539
b'Branch.get_stacked_on_url', (b'stacked/',),
1540
b'success', (b'ok', vfs_url.encode('utf-8')))
1541
# XXX: Multiple calls are bad, this second call documents what is
1543
client.add_expected_call(
1544
b'Branch.get_stacked_on_url', (b'stacked/',),
1545
b'success', (b'ok', vfs_url.encode('utf-8')))
1546
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1548
repo_fmt = remote.RemoteRepositoryFormat()
1549
repo_fmt._custom_format = stacked_branch.repository._format
1550
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1552
result = branch.get_stacked_on_url()
1553
self.assertEqual(vfs_url, result)
1555
def test_backwards_compatible(self):
1556
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1557
base_branch = self.make_branch('base', format='1.6')
1558
stacked_branch = self.make_branch('stacked', format='1.6')
1559
stacked_branch.set_stacked_on_url('../base')
1560
client = FakeClient(self.get_url())
1561
branch_network_name = self.get_branch_format().network_name()
1562
client.add_expected_call(
1563
b'BzrDir.open_branchV3', (b'stacked/',),
1564
b'success', (b'branch', branch_network_name))
1565
client.add_expected_call(
1566
b'BzrDir.find_repositoryV3', (b'stacked/',),
1567
b'success', (b'ok', b'', b'no', b'no', b'yes',
1568
stacked_branch.repository._format.network_name()))
1569
# called twice, once from constructor and then again by us
1570
client.add_expected_call(
1571
b'Branch.get_stacked_on_url', (b'stacked/',),
1572
b'unknown', (b'Branch.get_stacked_on_url',))
1573
client.add_expected_call(
1574
b'Branch.get_stacked_on_url', (b'stacked/',),
1575
b'unknown', (b'Branch.get_stacked_on_url',))
1576
# this will also do vfs access, but that goes direct to the transport
1577
# and isn't seen by the FakeClient.
1578
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1579
RemoteBzrDirFormat(), _client=client)
1580
branch = bzrdir.open_branch()
1581
result = branch.get_stacked_on_url()
1582
self.assertEqual('../base', result)
1583
self.assertFinished(client)
1584
# it's in the fallback list both for the RemoteRepository and its vfs
1586
self.assertEqual(1, len(branch.repository._fallback_repositories))
1588
len(branch.repository._real_repository._fallback_repositories))
1590
def test_get_stacked_on_real_branch(self):
1591
base_branch = self.make_branch('base')
1592
stacked_branch = self.make_branch('stacked')
1593
stacked_branch.set_stacked_on_url('../base')
1594
reference_format = self.get_repo_format()
1595
network_name = reference_format.network_name()
1596
client = FakeClient(self.get_url())
1597
branch_network_name = self.get_branch_format().network_name()
1598
client.add_expected_call(
1599
b'BzrDir.open_branchV3', ('stacked/',),
1600
b'success', ('branch', branch_network_name))
1601
client.add_expected_call(
1602
b'BzrDir.find_repositoryV3', (b'stacked/',),
1603
b'success', (b'ok', b'', b'yes', b'no', b'yes', network_name))
1604
# called twice, once from constructor and then again by us
1605
client.add_expected_call(
1606
b'Branch.get_stacked_on_url', (b'stacked/',),
1607
b'success', (b'ok', b'../base'))
1608
client.add_expected_call(
1609
b'Branch.get_stacked_on_url', (b'stacked/',),
1610
b'success', (b'ok', b'../base'))
1611
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1612
RemoteBzrDirFormat(), _client=client)
1613
branch = bzrdir.open_branch()
1614
result = branch.get_stacked_on_url()
1615
self.assertEqual('../base', result)
1616
self.assertFinished(client)
1617
# it's in the fallback list both for the RemoteRepository.
1618
self.assertEqual(1, len(branch.repository._fallback_repositories))
1619
# And we haven't had to construct a real repository.
1620
self.assertEqual(None, branch.repository._real_repository)
1623
class TestBranchSetLastRevision(RemoteBranchTestCase):
1625
def test_set_empty(self):
1626
# _set_last_revision_info('null:') is translated to calling
1627
# Branch.set_last_revision(path, '') on the wire.
1628
transport = MemoryTransport()
1629
transport.mkdir('branch')
1630
transport = transport.clone('branch')
1632
client = FakeClient(transport.base)
1633
client.add_expected_call(
1634
b'Branch.get_stacked_on_url', (b'branch/',),
1635
b'error', (b'NotStacked',))
1636
client.add_expected_call(
1637
b'Branch.lock_write', (b'branch/', b'', b''),
1638
b'success', (b'ok', b'branch token', b'repo token'))
1639
client.add_expected_call(
1640
b'Branch.last_revision_info',
1642
b'success', (b'ok', b'0', b'null:'))
1643
client.add_expected_call(
1644
b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'null:',),
1645
b'success', (b'ok',))
1646
client.add_expected_call(
1647
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1648
b'success', (b'ok',))
1649
branch = self.make_remote_branch(transport, client)
1651
result = branch._set_last_revision(NULL_REVISION)
1653
self.assertEqual(None, result)
1654
self.assertFinished(client)
1656
def test_set_nonempty(self):
1657
# set_last_revision_info(N, rev-idN) is translated to calling
1658
# Branch.set_last_revision(path, rev-idN) on the wire.
1659
transport = MemoryTransport()
1660
transport.mkdir('branch')
1661
transport = transport.clone('branch')
1663
client = FakeClient(transport.base)
1664
client.add_expected_call(
1665
b'Branch.get_stacked_on_url', (b'branch/',),
1666
b'error', (b'NotStacked',))
1667
client.add_expected_call(
1668
b'Branch.lock_write', (b'branch/', b'', b''),
1669
b'success', (b'ok', b'branch token', b'repo token'))
1670
client.add_expected_call(
1671
b'Branch.last_revision_info',
1673
b'success', (b'ok', b'0', b'null:'))
1674
lines = [b'rev-id2']
1675
encoded_body = bz2.compress(b'\n'.join(lines))
1676
client.add_success_response_with_body(encoded_body, b'ok')
1677
client.add_expected_call(
1678
b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'rev-id2',),
1679
b'success', (b'ok',))
1680
client.add_expected_call(
1681
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1682
b'success', (b'ok',))
1683
branch = self.make_remote_branch(transport, client)
1684
# Lock the branch, reset the record of remote calls.
1686
result = branch._set_last_revision(b'rev-id2')
1688
self.assertEqual(None, result)
1689
self.assertFinished(client)
1691
def test_no_such_revision(self):
1692
transport = MemoryTransport()
1693
transport.mkdir('branch')
1694
transport = transport.clone('branch')
1695
# A response of 'NoSuchRevision' is translated into an exception.
1696
client = FakeClient(transport.base)
1697
client.add_expected_call(
1698
b'Branch.get_stacked_on_url', (b'branch/',),
1699
b'error', (b'NotStacked',))
1700
client.add_expected_call(
1701
b'Branch.lock_write', (b'branch/', b'', b''),
1702
b'success', (b'ok', b'branch token', b'repo token'))
1703
client.add_expected_call(
1704
b'Branch.last_revision_info',
1706
b'success', (b'ok', b'0', b'null:'))
1707
# get_graph calls to construct the revision history, for the set_rh
1710
encoded_body = bz2.compress(b'\n'.join(lines))
1711
client.add_success_response_with_body(encoded_body, b'ok')
1712
client.add_expected_call(
1713
b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'rev-id',),
1714
b'error', (b'NoSuchRevision', b'rev-id'))
1715
client.add_expected_call(
1716
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1717
b'success', (b'ok',))
1719
branch = self.make_remote_branch(transport, client)
1722
errors.NoSuchRevision, branch._set_last_revision, b'rev-id')
1724
self.assertFinished(client)
1726
def test_tip_change_rejected(self):
1727
"""TipChangeRejected responses cause a TipChangeRejected exception to
1730
transport = MemoryTransport()
1731
transport.mkdir('branch')
1732
transport = transport.clone('branch')
1733
client = FakeClient(transport.base)
1734
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1735
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1736
client.add_expected_call(
1737
b'Branch.get_stacked_on_url', (b'branch/',),
1738
b'error', (b'NotStacked',))
1739
client.add_expected_call(
1740
b'Branch.lock_write', (b'branch/', b'', b''),
1741
b'success', (b'ok', b'branch token', b'repo token'))
1742
client.add_expected_call(
1743
b'Branch.last_revision_info',
1745
b'success', (b'ok', b'0', b'null:'))
1747
encoded_body = bz2.compress(b'\n'.join(lines))
1748
client.add_success_response_with_body(encoded_body, b'ok')
1749
client.add_expected_call(
1750
b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'rev-id',),
1751
b'error', (b'TipChangeRejected', rejection_msg_utf8))
1752
client.add_expected_call(
1753
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1754
b'success', (b'ok',))
1755
branch = self.make_remote_branch(transport, client)
1757
# The 'TipChangeRejected' error response triggered by calling
1758
# set_last_revision_info causes a TipChangeRejected exception.
1759
err = self.assertRaises(
1760
errors.TipChangeRejected,
1761
branch._set_last_revision, b'rev-id')
1762
# The UTF-8 message from the response has been decoded into a unicode
1764
self.assertIsInstance(err.msg, text_type)
1765
self.assertEqual(rejection_msg_unicode, err.msg)
1767
self.assertFinished(client)
1770
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1772
def test_set_last_revision_info(self):
1773
# set_last_revision_info(num, b'rev-id') is translated to calling
1774
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1775
transport = MemoryTransport()
1776
transport.mkdir('branch')
1777
transport = transport.clone('branch')
1778
client = FakeClient(transport.base)
1779
# get_stacked_on_url
1780
client.add_error_response(b'NotStacked')
1782
client.add_success_response(b'ok', b'branch token', b'repo token')
1783
# query the current revision
1784
client.add_success_response(b'ok', b'0', b'null:')
1786
client.add_success_response(b'ok')
1788
client.add_success_response(b'ok')
1790
branch = self.make_remote_branch(transport, client)
1791
# Lock the branch, reset the record of remote calls.
1794
result = branch.set_last_revision_info(1234, b'a-revision-id')
1796
[('call', b'Branch.last_revision_info', (b'branch/',)),
1797
('call', b'Branch.set_last_revision_info',
1798
(b'branch/', b'branch token', b'repo token',
1799
b'1234', b'a-revision-id'))],
1801
self.assertEqual(None, result)
1803
def test_no_such_revision(self):
1804
# A response of 'NoSuchRevision' is translated into an exception.
1805
transport = MemoryTransport()
1806
transport.mkdir('branch')
1807
transport = transport.clone('branch')
1808
client = FakeClient(transport.base)
1809
# get_stacked_on_url
1810
client.add_error_response(b'NotStacked')
1812
client.add_success_response(b'ok', b'branch token', b'repo token')
1814
client.add_error_response(b'NoSuchRevision', b'revid')
1816
client.add_success_response(b'ok')
1818
branch = self.make_remote_branch(transport, client)
1819
# Lock the branch, reset the record of remote calls.
1824
errors.NoSuchRevision, branch.set_last_revision_info, 123, b'revid')
1827
def test_backwards_compatibility(self):
1828
"""If the server does not support the Branch.set_last_revision_info
1829
verb (which is new in 1.4), then the client falls back to VFS methods.
1831
# This test is a little messy. Unlike most tests in this file, it
1832
# doesn't purely test what a Remote* object sends over the wire, and
1833
# how it reacts to responses from the wire. It instead relies partly
1834
# on asserting that the RemoteBranch will call
1835
# self._real_branch.set_last_revision_info(...).
1837
# First, set up our RemoteBranch with a FakeClient that raises
1838
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1839
transport = MemoryTransport()
1840
transport.mkdir('branch')
1841
transport = transport.clone('branch')
1842
client = FakeClient(transport.base)
1843
client.add_expected_call(
1844
b'Branch.get_stacked_on_url', (b'branch/',),
1845
b'error', (b'NotStacked',))
1846
client.add_expected_call(
1847
b'Branch.last_revision_info',
1849
b'success', (b'ok', b'0', b'null:'))
1850
client.add_expected_call(
1851
b'Branch.set_last_revision_info',
1852
(b'branch/', b'branch token', b'repo token', b'1234', b'a-revision-id',),
1853
b'unknown', b'Branch.set_last_revision_info')
1855
branch = self.make_remote_branch(transport, client)
1856
class StubRealBranch(object):
1859
def set_last_revision_info(self, revno, revision_id):
1861
('set_last_revision_info', revno, revision_id))
1862
def _clear_cached_state(self):
1864
real_branch = StubRealBranch()
1865
branch._real_branch = real_branch
1866
self.lock_remote_branch(branch)
1868
# Call set_last_revision_info, and verify it behaved as expected.
1869
result = branch.set_last_revision_info(1234, b'a-revision-id')
1871
[('set_last_revision_info', 1234, b'a-revision-id')],
1873
self.assertFinished(client)
1875
def test_unexpected_error(self):
1876
# If the server sends an error the client doesn't understand, it gets
1877
# turned into an UnknownErrorFromSmartServer, which is presented as a
1878
# non-internal error to the user.
1879
transport = MemoryTransport()
1880
transport.mkdir('branch')
1881
transport = transport.clone('branch')
1882
client = FakeClient(transport.base)
1883
# get_stacked_on_url
1884
client.add_error_response(b'NotStacked')
1886
client.add_success_response(b'ok', b'branch token', b'repo token')
1888
client.add_error_response(b'UnexpectedError')
1890
client.add_success_response(b'ok')
1892
branch = self.make_remote_branch(transport, client)
1893
# Lock the branch, reset the record of remote calls.
1897
err = self.assertRaises(
1898
errors.UnknownErrorFromSmartServer,
1899
branch.set_last_revision_info, 123, b'revid')
1900
self.assertEqual((b'UnexpectedError',), err.error_tuple)
1903
def test_tip_change_rejected(self):
1904
"""TipChangeRejected responses cause a TipChangeRejected exception to
1907
transport = MemoryTransport()
1908
transport.mkdir('branch')
1909
transport = transport.clone('branch')
1910
client = FakeClient(transport.base)
1911
# get_stacked_on_url
1912
client.add_error_response(b'NotStacked')
1914
client.add_success_response(b'ok', b'branch token', b'repo token')
1916
client.add_error_response(b'TipChangeRejected', b'rejection message')
1918
client.add_success_response(b'ok')
1920
branch = self.make_remote_branch(transport, client)
1921
# Lock the branch, reset the record of remote calls.
1923
self.addCleanup(branch.unlock)
1926
# The 'TipChangeRejected' error response triggered by calling
1927
# set_last_revision_info causes a TipChangeRejected exception.
1928
err = self.assertRaises(
1929
errors.TipChangeRejected,
1930
branch.set_last_revision_info, 123, b'revid')
1931
self.assertEqual('rejection message', err.msg)
1934
class TestBranchGetSetConfig(RemoteBranchTestCase):
1936
def test_get_branch_conf(self):
1937
# in an empty branch we decode the response properly
1938
client = FakeClient()
1939
client.add_expected_call(
1940
b'Branch.get_stacked_on_url', (b'memory:///',),
1941
b'error', (b'NotStacked',),)
1942
client.add_success_response_with_body(b'# config file body', b'ok')
1943
transport = MemoryTransport()
1944
branch = self.make_remote_branch(transport, client)
1945
config = branch.get_config()
1946
config.has_explicit_nickname()
1948
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
1949
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
1952
def test_get_multi_line_branch_conf(self):
1953
# Make sure that multiple-line branch.conf files are supported
1955
# https://bugs.launchpad.net/bzr/+bug/354075
1956
client = FakeClient()
1957
client.add_expected_call(
1958
b'Branch.get_stacked_on_url', (b'memory:///',),
1959
b'error', (b'NotStacked',),)
1960
client.add_success_response_with_body(b'a = 1\nb = 2\nc = 3\n', b'ok')
1961
transport = MemoryTransport()
1962
branch = self.make_remote_branch(transport, client)
1963
config = branch.get_config()
1964
self.assertEqual(u'2', config.get_user_option('b'))
1966
def test_set_option(self):
1967
client = FakeClient()
1968
client.add_expected_call(
1969
b'Branch.get_stacked_on_url', (b'memory:///',),
1970
b'error', (b'NotStacked',),)
1971
client.add_expected_call(
1972
b'Branch.lock_write', (b'memory:///', b'', b''),
1973
b'success', (b'ok', b'branch token', b'repo token'))
1974
client.add_expected_call(
1975
b'Branch.set_config_option', (b'memory:///', b'branch token',
1976
b'repo token', b'foo', b'bar', b''),
1978
client.add_expected_call(
1979
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
1980
b'success', (b'ok',))
1981
transport = MemoryTransport()
1982
branch = self.make_remote_branch(transport, client)
1984
config = branch._get_config()
1985
config.set_option('foo', 'bar')
1987
self.assertFinished(client)
1989
def test_set_option_with_dict(self):
1990
client = FakeClient()
1991
client.add_expected_call(
1992
b'Branch.get_stacked_on_url', (b'memory:///',),
1993
b'error', (b'NotStacked',),)
1994
client.add_expected_call(
1995
b'Branch.lock_write', (b'memory:///', b'', b''),
1996
b'success', (b'ok', b'branch token', b'repo token'))
1997
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1998
client.add_expected_call(
1999
b'Branch.set_config_option_dict', (b'memory:///', b'branch token',
2000
b'repo token', encoded_dict_value, b'foo', b''),
2002
client.add_expected_call(
2003
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2004
b'success', (b'ok',))
2005
transport = MemoryTransport()
2006
branch = self.make_remote_branch(transport, client)
2008
config = branch._get_config()
2010
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2013
self.assertFinished(client)
2015
def test_backwards_compat_set_option(self):
2016
self.setup_smart_server_with_call_log()
2017
branch = self.make_branch('.')
2018
verb = b'Branch.set_config_option'
2019
self.disable_verb(verb)
2021
self.addCleanup(branch.unlock)
2022
self.reset_smart_call_log()
2023
branch._get_config().set_option('value', 'name')
2024
self.assertLength(11, self.hpss_calls)
2025
self.assertEqual('value', branch._get_config().get_option('name'))
2027
def test_backwards_compat_set_option_with_dict(self):
2028
self.setup_smart_server_with_call_log()
2029
branch = self.make_branch('.')
2030
verb = b'Branch.set_config_option_dict'
2031
self.disable_verb(verb)
2033
self.addCleanup(branch.unlock)
2034
self.reset_smart_call_log()
2035
config = branch._get_config()
2036
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2037
config.set_option(value_dict, 'name')
2038
self.assertLength(11, self.hpss_calls)
2039
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2042
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2044
def test_get_branch_conf(self):
2045
# in an empty branch we decode the response properly
2046
client = FakeClient()
2047
client.add_expected_call(
2048
b'Branch.get_stacked_on_url', (b'memory:///',),
2049
b'error', (b'NotStacked',),)
2050
client.add_success_response_with_body(b'# config file body', b'ok')
2051
transport = MemoryTransport()
2052
branch = self.make_remote_branch(transport, client)
2053
config = branch.get_config_stack()
2055
config.get("log_format")
2057
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2058
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
2061
def test_set_branch_conf(self):
2062
client = FakeClient()
2063
client.add_expected_call(
2064
b'Branch.get_stacked_on_url', (b'memory:///',),
2065
b'error', (b'NotStacked',),)
2066
client.add_expected_call(
2067
b'Branch.lock_write', (b'memory:///', b'', b''),
2068
b'success', (b'ok', b'branch token', b'repo token'))
2069
client.add_expected_call(
2070
b'Branch.get_config_file', (b'memory:///', ),
2071
b'success', (b'ok', ), b"# line 1\n")
2072
client.add_expected_call(
2073
b'Branch.get_config_file', (b'memory:///', ),
2074
b'success', (b'ok', ), b"# line 1\n")
2075
client.add_expected_call(
2076
b'Branch.put_config_file', (b'memory:///', b'branch token',
2078
b'success', (b'ok',))
2079
client.add_expected_call(
2080
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2081
b'success', (b'ok',))
2082
transport = MemoryTransport()
2083
branch = self.make_remote_branch(transport, client)
2085
config = branch.get_config_stack()
2086
config.set('email', 'The Dude <lebowski@example.com>')
2088
self.assertFinished(client)
2090
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2091
('call', b'Branch.lock_write', (b'memory:///', b'', b'')),
2092
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',)),
2093
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',)),
2094
('call_with_body_bytes_expecting_body', b'Branch.put_config_file',
2095
(b'memory:///', b'branch token', b'repo token'),
2096
b'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2097
('call', b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'))],
2101
class TestBranchLockWrite(RemoteBranchTestCase):
2103
def test_lock_write_unlockable(self):
2104
transport = MemoryTransport()
2105
client = FakeClient(transport.base)
2106
client.add_expected_call(
2107
b'Branch.get_stacked_on_url', (b'quack/',),
2108
b'error', (b'NotStacked',),)
2109
client.add_expected_call(
2110
b'Branch.lock_write', (b'quack/', b'', b''),
2111
b'error', (b'UnlockableTransport',))
2112
transport.mkdir('quack')
2113
transport = transport.clone('quack')
2114
branch = self.make_remote_branch(transport, client)
2115
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2116
self.assertFinished(client)
2119
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2121
def test_simple(self):
2122
transport = MemoryTransport()
2123
client = FakeClient(transport.base)
2124
client.add_expected_call(
2125
b'Branch.get_stacked_on_url', (b'quack/',),
2126
b'error', (b'NotStacked',),)
2127
client.add_expected_call(
2128
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2129
b'success', (b'ok', b'0',),)
2130
client.add_expected_call(
2131
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2132
b'error', (b'NoSuchRevision', b'unknown',),)
2133
transport.mkdir('quack')
2134
transport = transport.clone('quack')
2135
branch = self.make_remote_branch(transport, client)
2136
self.assertEqual(0, branch.revision_id_to_revno(b'null:'))
2137
self.assertRaises(errors.NoSuchRevision,
2138
branch.revision_id_to_revno, b'unknown')
2139
self.assertFinished(client)
2141
def test_dotted(self):
2142
transport = MemoryTransport()
2143
client = FakeClient(transport.base)
2144
client.add_expected_call(
2145
b'Branch.get_stacked_on_url', (b'quack/',),
2146
b'error', (b'NotStacked',),)
2147
client.add_expected_call(
2148
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2149
b'success', (b'ok', b'0',),)
2150
client.add_expected_call(
2151
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2152
b'error', (b'NoSuchRevision', b'unknown',),)
2153
transport.mkdir('quack')
2154
transport = transport.clone('quack')
2155
branch = self.make_remote_branch(transport, client)
2156
self.assertEqual((0, ), branch.revision_id_to_dotted_revno(b'null:'))
2157
self.assertRaises(errors.NoSuchRevision,
2158
branch.revision_id_to_dotted_revno, b'unknown')
2159
self.assertFinished(client)
2161
def test_dotted_no_smart_verb(self):
2162
self.setup_smart_server_with_call_log()
2163
branch = self.make_branch('.')
2164
self.disable_verb(b'Branch.revision_id_to_revno')
2165
self.reset_smart_call_log()
2166
self.assertEqual((0, ),
2167
branch.revision_id_to_dotted_revno(b'null:'))
2168
self.assertLength(8, self.hpss_calls)
2171
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2173
def test__get_config(self):
2174
client = FakeClient()
2175
client.add_success_response_with_body(b'default_stack_on = /\n', b'ok')
2176
transport = MemoryTransport()
2177
bzrdir = self.make_remote_bzrdir(transport, client)
2178
config = bzrdir.get_config()
2179
self.assertEqual('/', config.get_default_stack_on())
2181
[('call_expecting_body', b'BzrDir.get_config_file', (b'memory:///',))],
2184
def test_set_option_uses_vfs(self):
2185
self.setup_smart_server_with_call_log()
2186
bzrdir = self.make_controldir('.')
2187
self.reset_smart_call_log()
2188
config = bzrdir.get_config()
2189
config.set_default_stack_on('/')
2190
self.assertLength(4, self.hpss_calls)
2192
def test_backwards_compat_get_option(self):
2193
self.setup_smart_server_with_call_log()
2194
bzrdir = self.make_controldir('.')
2195
verb = b'BzrDir.get_config_file'
2196
self.disable_verb(verb)
2197
self.reset_smart_call_log()
2198
self.assertEqual(None,
2199
bzrdir._get_config().get_option('default_stack_on'))
2200
self.assertLength(4, self.hpss_calls)
2203
class TestTransportIsReadonly(tests.TestCase):
2205
def test_true(self):
2206
client = FakeClient()
2207
client.add_success_response(b'yes')
2208
transport = RemoteTransport('bzr://example.com/', medium=False,
2210
self.assertEqual(True, transport.is_readonly())
2212
[('call', b'Transport.is_readonly', ())],
2215
def test_false(self):
2216
client = FakeClient()
2217
client.add_success_response(b'no')
2218
transport = RemoteTransport('bzr://example.com/', medium=False,
2220
self.assertEqual(False, transport.is_readonly())
2222
[('call', b'Transport.is_readonly', ())],
2225
def test_error_from_old_server(self):
2226
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2228
Clients should treat it as a "no" response, because is_readonly is only
2229
advisory anyway (a transport could be read-write, but then the
2230
underlying filesystem could be readonly anyway).
2232
client = FakeClient()
2233
client.add_unknown_method_response(b'Transport.is_readonly')
2234
transport = RemoteTransport('bzr://example.com/', medium=False,
2236
self.assertEqual(False, transport.is_readonly())
2238
[('call', b'Transport.is_readonly', ())],
2242
class TestTransportMkdir(tests.TestCase):
2244
def test_permissiondenied(self):
2245
client = FakeClient()
2246
client.add_error_response(b'PermissionDenied', b'remote path', b'extra')
2247
transport = RemoteTransport('bzr://example.com/', medium=False,
2249
exc = self.assertRaises(
2250
errors.PermissionDenied, transport.mkdir, 'client path')
2251
expected_error = errors.PermissionDenied('/client path', 'extra')
2252
self.assertEqual(expected_error, exc)
2255
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2257
def test_defaults_to_none(self):
2258
t = RemoteSSHTransport('bzr+ssh://example.com')
2259
self.assertIs(None, t._get_credentials()[0])
2261
def test_uses_authentication_config(self):
2262
conf = config.AuthenticationConfig()
2263
conf._get_config().update(
2264
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2267
t = RemoteSSHTransport('bzr+ssh://example.com')
2268
self.assertEqual('bar', t._get_credentials()[0])
2271
class TestRemoteRepository(TestRemote):
2272
"""Base for testing RemoteRepository protocol usage.
2274
These tests contain frozen requests and responses. We want any changes to
2275
what is sent or expected to be require a thoughtful update to these tests
2276
because they might break compatibility with different-versioned servers.
2279
def setup_fake_client_and_repository(self, transport_path):
2280
"""Create the fake client and repository for testing with.
2282
There's no real server here; we just have canned responses sent
2285
:param transport_path: Path below the root of the MemoryTransport
2286
where the repository will be created.
2288
transport = MemoryTransport()
2289
transport.mkdir(transport_path)
2290
client = FakeClient(transport.base)
2291
transport = transport.clone(transport_path)
2292
# we do not want bzrdir to make any remote calls
2293
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2295
repo = RemoteRepository(bzrdir, None, _client=client)
2299
def remoted_description(format):
2300
return 'Remote: ' + format.get_format_description()
2303
class TestBranchFormat(tests.TestCase):
2305
def test_get_format_description(self):
2306
remote_format = RemoteBranchFormat()
2307
real_format = branch.format_registry.get_default()
2308
remote_format._network_name = real_format.network_name()
2309
self.assertEqual(remoted_description(real_format),
2310
remote_format.get_format_description())
2313
class TestRepositoryFormat(TestRemoteRepository):
2315
def test_fast_delta(self):
2316
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2317
true_format = RemoteRepositoryFormat()
2318
true_format._network_name = true_name
2319
self.assertEqual(True, true_format.fast_deltas)
2320
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2321
false_format = RemoteRepositoryFormat()
2322
false_format._network_name = false_name
2323
self.assertEqual(False, false_format.fast_deltas)
2325
def test_get_format_description(self):
2326
remote_repo_format = RemoteRepositoryFormat()
2327
real_format = repository.format_registry.get_default()
2328
remote_repo_format._network_name = real_format.network_name()
2329
self.assertEqual(remoted_description(real_format),
2330
remote_repo_format.get_format_description())
2333
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2335
def test_empty(self):
2336
transport_path = 'quack'
2337
repo, client = self.setup_fake_client_and_repository(transport_path)
2338
client.add_success_response_with_body(b'', b'ok')
2339
self.assertEqual([], repo.all_revision_ids())
2341
[('call_expecting_body', b'Repository.all_revision_ids',
2345
def test_with_some_content(self):
2346
transport_path = 'quack'
2347
repo, client = self.setup_fake_client_and_repository(transport_path)
2348
client.add_success_response_with_body(
2349
b'rev1\nrev2\nanotherrev\n', b'ok')
2351
set([b"rev1", b"rev2", b"anotherrev"]),
2352
set(repo.all_revision_ids()))
2354
[('call_expecting_body', b'Repository.all_revision_ids',
2359
class TestRepositoryGatherStats(TestRemoteRepository):
2361
def test_revid_none(self):
2362
# ('ok',), body with revisions and size
2363
transport_path = 'quack'
2364
repo, client = self.setup_fake_client_and_repository(transport_path)
2365
client.add_success_response_with_body(
2366
b'revisions: 2\nsize: 18\n', b'ok')
2367
result = repo.gather_stats(None)
2369
[('call_expecting_body', b'Repository.gather_stats',
2370
(b'quack/', b'', b'no'))],
2372
self.assertEqual({'revisions': 2, 'size': 18}, result)
2374
def test_revid_no_committers(self):
2375
# ('ok',), body without committers
2376
body = (b'firstrev: 123456.300 3600\n'
2377
b'latestrev: 654231.400 0\n'
2380
transport_path = 'quick'
2381
revid = u'\xc8'.encode('utf8')
2382
repo, client = self.setup_fake_client_and_repository(transport_path)
2383
client.add_success_response_with_body(body, b'ok')
2384
result = repo.gather_stats(revid)
2386
[('call_expecting_body', b'Repository.gather_stats',
2387
(b'quick/', revid, b'no'))],
2389
self.assertEqual({'revisions': 2, 'size': 18,
2390
'firstrev': (123456.300, 3600),
2391
'latestrev': (654231.400, 0),},
2394
def test_revid_with_committers(self):
2395
# ('ok',), body with committers
2396
body = (b'committers: 128\n'
2397
b'firstrev: 123456.300 3600\n'
2398
b'latestrev: 654231.400 0\n'
2401
transport_path = 'buick'
2402
revid = u'\xc8'.encode('utf8')
2403
repo, client = self.setup_fake_client_and_repository(transport_path)
2404
client.add_success_response_with_body(body, b'ok')
2405
result = repo.gather_stats(revid, True)
2407
[('call_expecting_body', b'Repository.gather_stats',
2408
(b'buick/', revid, b'yes'))],
2410
self.assertEqual({'revisions': 2, 'size': 18,
2412
'firstrev': (123456.300, 3600),
2413
'latestrev': (654231.400, 0),},
2417
class TestRepositoryBreakLock(TestRemoteRepository):
2419
def test_break_lock(self):
2420
transport_path = 'quack'
2421
repo, client = self.setup_fake_client_and_repository(transport_path)
2422
client.add_success_response(b'ok')
2425
[('call', b'Repository.break_lock', (b'quack/',))],
2429
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2431
def test_get_serializer_format(self):
2432
transport_path = 'hill'
2433
repo, client = self.setup_fake_client_and_repository(transport_path)
2434
client.add_success_response(b'ok', b'7')
2435
self.assertEqual(b'7', repo.get_serializer_format())
2437
[('call', b'VersionedFileRepository.get_serializer_format',
2442
class TestRepositoryReconcile(TestRemoteRepository):
2444
def test_reconcile(self):
2445
transport_path = 'hill'
2446
repo, client = self.setup_fake_client_and_repository(transport_path)
2447
body = (b"garbage_inventories: 2\n"
2448
b"inconsistent_parents: 3\n")
2449
client.add_expected_call(
2450
b'Repository.lock_write', (b'hill/', b''),
2451
b'success', (b'ok', b'a token'))
2452
client.add_success_response_with_body(body, b'ok')
2453
reconciler = repo.reconcile()
2455
[('call', b'Repository.lock_write', (b'hill/', b'')),
2456
('call_expecting_body', b'Repository.reconcile',
2457
(b'hill/', b'a token'))],
2459
self.assertEqual(2, reconciler.garbage_inventories)
2460
self.assertEqual(3, reconciler.inconsistent_parents)
2463
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2465
def test_text(self):
2466
# ('ok',), body with signature text
2467
transport_path = 'quack'
2468
repo, client = self.setup_fake_client_and_repository(transport_path)
2469
client.add_success_response_with_body(
2471
self.assertEqual(b"THETEXT", repo.get_signature_text(b"revid"))
2473
[('call_expecting_body', b'Repository.get_revision_signature_text',
2474
(b'quack/', b'revid'))],
2477
def test_no_signature(self):
2478
transport_path = 'quick'
2479
repo, client = self.setup_fake_client_and_repository(transport_path)
2480
client.add_error_response(b'nosuchrevision', b'unknown')
2481
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2484
[('call_expecting_body', b'Repository.get_revision_signature_text',
2485
(b'quick/', b'unknown'))],
2489
class TestRepositoryGetGraph(TestRemoteRepository):
2491
def test_get_graph(self):
2492
# get_graph returns a graph with a custom parents provider.
2493
transport_path = 'quack'
2494
repo, client = self.setup_fake_client_and_repository(transport_path)
2495
graph = repo.get_graph()
2496
self.assertNotEqual(graph._parents_provider, repo)
2499
class TestRepositoryAddSignatureText(TestRemoteRepository):
2501
def test_add_signature_text(self):
2502
transport_path = 'quack'
2503
repo, client = self.setup_fake_client_and_repository(transport_path)
2504
client.add_expected_call(
2505
b'Repository.lock_write', (b'quack/', b''),
2506
b'success', (b'ok', b'a token'))
2507
client.add_expected_call(
2508
b'Repository.start_write_group', (b'quack/', b'a token'),
2509
b'success', (b'ok', (b'token1', )))
2510
client.add_expected_call(
2511
b'Repository.add_signature_text', (b'quack/', b'a token', b'rev1',
2513
b'success', (b'ok', ), None)
2515
repo.start_write_group()
2517
repo.add_signature_text(b"rev1", b"every bloody emperor"))
2519
('call_with_body_bytes_expecting_body',
2520
b'Repository.add_signature_text',
2521
(b'quack/', b'a token', b'rev1', b'token1'),
2522
b'every bloody emperor'),
2526
class TestRepositoryGetParentMap(TestRemoteRepository):
2528
def test_get_parent_map_caching(self):
2529
# get_parent_map returns from cache until unlock()
2530
# setup a reponse with two revisions
2531
r1 = u'\u0e33'.encode('utf8')
2532
r2 = u'\u0dab'.encode('utf8')
2533
lines = [b' '.join([r2, r1]), r1]
2534
encoded_body = bz2.compress(b'\n'.join(lines))
2536
transport_path = 'quack'
2537
repo, client = self.setup_fake_client_and_repository(transport_path)
2538
client.add_success_response_with_body(encoded_body, b'ok')
2539
client.add_success_response_with_body(encoded_body, b'ok')
2541
graph = repo.get_graph()
2542
parents = graph.get_parent_map([r2])
2543
self.assertEqual({r2: (r1,)}, parents)
2544
# locking and unlocking deeper should not reset
2547
parents = graph.get_parent_map([r1])
2548
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2550
[('call_with_body_bytes_expecting_body',
2551
b'Repository.get_parent_map', (b'quack/', b'include-missing:', r2),
2555
# now we call again, and it should use the second response.
2557
graph = repo.get_graph()
2558
parents = graph.get_parent_map([r1])
2559
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2561
[('call_with_body_bytes_expecting_body',
2562
b'Repository.get_parent_map', (b'quack/', b'include-missing:', r2),
2564
('call_with_body_bytes_expecting_body',
2565
b'Repository.get_parent_map', (b'quack/', b'include-missing:', r1),
2571
def test_get_parent_map_reconnects_if_unknown_method(self):
2572
transport_path = 'quack'
2573
rev_id = b'revision-id'
2574
repo, client = self.setup_fake_client_and_repository(transport_path)
2575
client.add_unknown_method_response(b'Repository.get_parent_map')
2576
client.add_success_response_with_body(rev_id, b'ok')
2577
self.assertFalse(client._medium._is_remote_before((1, 2)))
2578
parents = repo.get_parent_map([rev_id])
2580
[('call_with_body_bytes_expecting_body',
2581
b'Repository.get_parent_map',
2582
(b'quack/', b'include-missing:', rev_id), b'\n\n0'),
2583
('disconnect medium',),
2584
('call_expecting_body', b'Repository.get_revision_graph',
2587
# The medium is now marked as being connected to an older server
2588
self.assertTrue(client._medium._is_remote_before((1, 2)))
2589
self.assertEqual({rev_id: (b'null:',)}, parents)
2591
def test_get_parent_map_fallback_parentless_node(self):
2592
"""get_parent_map falls back to get_revision_graph on old servers. The
2593
results from get_revision_graph are tweaked to match the get_parent_map
2596
Specifically, a {key: ()} result from get_revision_graph means "no
2597
parents" for that key, which in get_parent_map results should be
2598
represented as {key: ('null:',)}.
2600
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2602
rev_id = b'revision-id'
2603
transport_path = 'quack'
2604
repo, client = self.setup_fake_client_and_repository(transport_path)
2605
client.add_success_response_with_body(rev_id, b'ok')
2606
client._medium._remember_remote_is_before((1, 2))
2607
parents = repo.get_parent_map([rev_id])
2609
[('call_expecting_body', b'Repository.get_revision_graph',
2612
self.assertEqual({rev_id: (b'null:',)}, parents)
2614
def test_get_parent_map_unexpected_response(self):
2615
repo, client = self.setup_fake_client_and_repository('path')
2616
client.add_success_response(b'something unexpected!')
2618
errors.UnexpectedSmartServerResponse,
2619
repo.get_parent_map, [b'a-revision-id'])
2621
def test_get_parent_map_negative_caches_missing_keys(self):
2622
self.setup_smart_server_with_call_log()
2623
repo = self.make_repository('foo')
2624
self.assertIsInstance(repo, RemoteRepository)
2626
self.addCleanup(repo.unlock)
2627
self.reset_smart_call_log()
2628
graph = repo.get_graph()
2629
self.assertEqual({},
2630
graph.get_parent_map([b'some-missing', b'other-missing']))
2631
self.assertLength(1, self.hpss_calls)
2632
# No call if we repeat this
2633
self.reset_smart_call_log()
2634
graph = repo.get_graph()
2635
self.assertEqual({},
2636
graph.get_parent_map([b'some-missing', b'other-missing']))
2637
self.assertLength(0, self.hpss_calls)
2638
# Asking for more unknown keys makes a request.
2639
self.reset_smart_call_log()
2640
graph = repo.get_graph()
2641
self.assertEqual({},
2642
graph.get_parent_map([b'some-missing', b'other-missing',
2644
self.assertLength(1, self.hpss_calls)
2646
def disableExtraResults(self):
2647
self.overrideAttr(SmartServerRepositoryGetParentMap,
2648
'no_extra_results', True)
2650
def test_null_cached_missing_and_stop_key(self):
2651
self.setup_smart_server_with_call_log()
2652
# Make a branch with a single revision.
2653
builder = self.make_branch_builder('foo')
2654
builder.start_series()
2655
builder.build_snapshot(None, [
2656
('add', ('', b'root-id', 'directory', ''))],
2657
revision_id=b'first')
2658
builder.finish_series()
2659
branch = builder.get_branch()
2660
repo = branch.repository
2661
self.assertIsInstance(repo, RemoteRepository)
2662
# Stop the server from sending extra results.
2663
self.disableExtraResults()
2665
self.addCleanup(repo.unlock)
2666
self.reset_smart_call_log()
2667
graph = repo.get_graph()
2668
# Query for b'first' and b'null:'. Because b'null:' is a parent of
2669
# 'first' it will be a candidate for the stop_keys of subsequent
2670
# requests, and because b'null:' was queried but not returned it will be
2671
# cached as missing.
2672
self.assertEqual({b'first': (b'null:',)},
2673
graph.get_parent_map([b'first', b'null:']))
2674
# Now query for another key. This request will pass along a recipe of
2675
# start and stop keys describing the already cached results, and this
2676
# recipe's revision count must be correct (or else it will trigger an
2677
# error from the server).
2678
self.assertEqual({}, graph.get_parent_map([b'another-key']))
2679
# This assertion guards against disableExtraResults silently failing to
2680
# work, thus invalidating the test.
2681
self.assertLength(2, self.hpss_calls)
2683
def test_get_parent_map_gets_ghosts_from_result(self):
2684
# asking for a revision should negatively cache close ghosts in its
2686
self.setup_smart_server_with_call_log()
2687
tree = self.make_branch_and_memory_tree('foo')
2688
with tree.lock_write():
2689
builder = treebuilder.TreeBuilder()
2690
builder.start_tree(tree)
2692
builder.finish_tree()
2693
tree.set_parent_ids([b'non-existant'], allow_leftmost_as_ghost=True)
2694
rev_id = tree.commit('')
2696
self.addCleanup(tree.unlock)
2697
repo = tree.branch.repository
2698
self.assertIsInstance(repo, RemoteRepository)
2700
repo.get_parent_map([rev_id])
2701
self.reset_smart_call_log()
2702
# Now asking for rev_id's ghost parent should not make calls
2703
self.assertEqual({}, repo.get_parent_map([b'non-existant']))
2704
self.assertLength(0, self.hpss_calls)
2706
def test_exposes_get_cached_parent_map(self):
2707
"""RemoteRepository exposes get_cached_parent_map from
2710
r1 = u'\u0e33'.encode('utf8')
2711
r2 = u'\u0dab'.encode('utf8')
2712
lines = [b' '.join([r2, r1]), r1]
2713
encoded_body = bz2.compress(b'\n'.join(lines))
2715
transport_path = 'quack'
2716
repo, client = self.setup_fake_client_and_repository(transport_path)
2717
client.add_success_response_with_body(encoded_body, b'ok')
2719
# get_cached_parent_map should *not* trigger an RPC
2720
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2721
self.assertEqual([], client._calls)
2722
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2723
self.assertEqual({r1: (NULL_REVISION,)},
2724
repo.get_cached_parent_map([r1]))
2726
[('call_with_body_bytes_expecting_body',
2727
b'Repository.get_parent_map', (b'quack/', b'include-missing:', r2),
2733
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2735
def test_allows_new_revisions(self):
2736
"""get_parent_map's results can be updated by commit."""
2737
smart_server = test_server.SmartTCPServer_for_testing()
2738
self.start_server(smart_server)
2739
self.make_branch('branch')
2740
branch = Branch.open(smart_server.get_url() + '/branch')
2741
tree = branch.create_checkout('tree', lightweight=True)
2743
self.addCleanup(tree.unlock)
2744
graph = tree.branch.repository.get_graph()
2745
# This provides an opportunity for the missing rev-id to be cached.
2746
self.assertEqual({}, graph.get_parent_map([b'rev1']))
2747
tree.commit('message', rev_id=b'rev1')
2748
graph = tree.branch.repository.get_graph()
2749
self.assertEqual({b'rev1': (b'null:',)}, graph.get_parent_map([b'rev1']))
2752
class TestRepositoryGetRevisions(TestRemoteRepository):
2754
def test_hpss_missing_revision(self):
2755
transport_path = 'quack'
2756
repo, client = self.setup_fake_client_and_repository(transport_path)
2757
client.add_success_response_with_body(
2759
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2760
[b'somerev1', b'anotherrev2'])
2762
[('call_with_body_bytes_expecting_body', b'Repository.iter_revisions',
2763
(b'quack/', ), b"somerev1\nanotherrev2")],
2766
def test_hpss_get_single_revision(self):
2767
transport_path = 'quack'
2768
repo, client = self.setup_fake_client_and_repository(transport_path)
2769
somerev1 = Revision(b"somerev1")
2770
somerev1.committer = "Joe Committer <joe@example.com>"
2771
somerev1.timestamp = 1321828927
2772
somerev1.timezone = -60
2773
somerev1.inventory_sha1 = b"691b39be74c67b1212a75fcb19c433aaed903c2b"
2774
somerev1.message = "Message"
2775
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2777
# Split up body into two bits to make sure the zlib compression object
2778
# gets data fed twice.
2779
client.add_success_response_with_body(
2780
[body[:10], body[10:]], b'ok', b'10')
2781
revs = repo.get_revisions([b'somerev1'])
2782
self.assertEqual(revs, [somerev1])
2784
[('call_with_body_bytes_expecting_body', b'Repository.iter_revisions',
2785
(b'quack/', ), b"somerev1")],
2789
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2791
def test_null_revision(self):
2792
# a null revision has the predictable result {}, we should have no wire
2793
# traffic when calling it with this argument
2794
transport_path = 'empty'
2795
repo, client = self.setup_fake_client_and_repository(transport_path)
2796
client.add_success_response(b'notused')
2797
# actual RemoteRepository.get_revision_graph is gone, but there's an
2798
# equivalent private method for testing
2799
result = repo._get_revision_graph(NULL_REVISION)
2800
self.assertEqual([], client._calls)
2801
self.assertEqual({}, result)
2803
def test_none_revision(self):
2804
# with none we want the entire graph
2805
r1 = u'\u0e33'.encode('utf8')
2806
r2 = u'\u0dab'.encode('utf8')
2807
lines = [b' '.join([r2, r1]), r1]
2808
encoded_body = b'\n'.join(lines)
2810
transport_path = 'sinhala'
2811
repo, client = self.setup_fake_client_and_repository(transport_path)
2812
client.add_success_response_with_body(encoded_body, b'ok')
2813
# actual RemoteRepository.get_revision_graph is gone, but there's an
2814
# equivalent private method for testing
2815
result = repo._get_revision_graph(None)
2817
[('call_expecting_body', b'Repository.get_revision_graph',
2818
(b'sinhala/', b''))],
2820
self.assertEqual({r1: (), r2: (r1, )}, result)
2822
def test_specific_revision(self):
2823
# with a specific revision we want the graph for that
2824
# with none we want the entire graph
2825
r11 = u'\u0e33'.encode('utf8')
2826
r12 = u'\xc9'.encode('utf8')
2827
r2 = u'\u0dab'.encode('utf8')
2828
lines = [b' '.join([r2, r11, r12]), r11, r12]
2829
encoded_body = b'\n'.join(lines)
2831
transport_path = 'sinhala'
2832
repo, client = self.setup_fake_client_and_repository(transport_path)
2833
client.add_success_response_with_body(encoded_body, b'ok')
2834
result = repo._get_revision_graph(r2)
2836
[('call_expecting_body', b'Repository.get_revision_graph',
2837
(b'sinhala/', r2))],
2839
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2841
def test_no_such_revision(self):
2843
transport_path = 'sinhala'
2844
repo, client = self.setup_fake_client_and_repository(transport_path)
2845
client.add_error_response(b'nosuchrevision', revid)
2846
# also check that the right revision is reported in the error
2847
self.assertRaises(errors.NoSuchRevision,
2848
repo._get_revision_graph, revid)
2850
[('call_expecting_body', b'Repository.get_revision_graph',
2851
(b'sinhala/', revid))],
2854
def test_unexpected_error(self):
2856
transport_path = 'sinhala'
2857
repo, client = self.setup_fake_client_and_repository(transport_path)
2858
client.add_error_response(b'AnUnexpectedError')
2859
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2860
repo._get_revision_graph, revid)
2861
self.assertEqual((b'AnUnexpectedError',), e.error_tuple)
2864
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2867
repo, client = self.setup_fake_client_and_repository('quack')
2868
client.add_expected_call(
2869
b'Repository.get_rev_id_for_revno', (b'quack/', 5, (42, b'rev-foo')),
2870
b'success', (b'ok', b'rev-five'))
2871
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2872
self.assertEqual((True, b'rev-five'), result)
2873
self.assertFinished(client)
2875
def test_history_incomplete(self):
2876
repo, client = self.setup_fake_client_and_repository('quack')
2877
client.add_expected_call(
2878
b'Repository.get_rev_id_for_revno', (b'quack/', 5, (42, b'rev-foo')),
2879
b'success', (b'history-incomplete', 10, b'rev-ten'))
2880
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2881
self.assertEqual((False, (10, b'rev-ten')), result)
2882
self.assertFinished(client)
2884
def test_history_incomplete_with_fallback(self):
2885
"""A 'history-incomplete' response causes the fallback repository to be
2886
queried too, if one is set.
2888
# Make a repo with a fallback repo, both using a FakeClient.
2889
format = remote.response_tuple_to_repo_format(
2890
(b'yes', b'no', b'yes', self.get_repo_format().network_name()))
2891
repo, client = self.setup_fake_client_and_repository('quack')
2892
repo._format = format
2893
fallback_repo, ignored = self.setup_fake_client_and_repository(
2895
fallback_repo._client = client
2896
fallback_repo._format = format
2897
repo.add_fallback_repository(fallback_repo)
2898
# First the client should ask the primary repo
2899
client.add_expected_call(
2900
b'Repository.get_rev_id_for_revno', (b'quack/', 1, (42, b'rev-foo')),
2901
b'success', (b'history-incomplete', 2, b'rev-two'))
2902
# Then it should ask the fallback, using revno/revid from the
2903
# history-incomplete response as the known revno/revid.
2904
client.add_expected_call(
2905
b'Repository.get_rev_id_for_revno', (b'fallback/', 1, (2, b'rev-two')),
2906
b'success', (b'ok', b'rev-one'))
2907
result = repo.get_rev_id_for_revno(1, (42, b'rev-foo'))
2908
self.assertEqual((True, b'rev-one'), result)
2909
self.assertFinished(client)
2911
def test_nosuchrevision(self):
2912
# 'nosuchrevision' is returned when the known-revid is not found in the
2913
# remote repo. The client translates that response to NoSuchRevision.
2914
repo, client = self.setup_fake_client_and_repository('quack')
2915
client.add_expected_call(
2916
b'Repository.get_rev_id_for_revno', (b'quack/', 5, (42, b'rev-foo')),
2917
b'error', (b'nosuchrevision', b'rev-foo'))
2919
errors.NoSuchRevision,
2920
repo.get_rev_id_for_revno, 5, (42, b'rev-foo'))
2921
self.assertFinished(client)
2923
def test_branch_fallback_locking(self):
2924
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2925
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2926
will be invoked, which will fail if the repo is unlocked.
2928
self.setup_smart_server_with_call_log()
2929
tree = self.make_branch_and_memory_tree('.')
2932
rev1 = tree.commit('First')
2933
rev2 = tree.commit('Second')
2935
branch = tree.branch
2936
self.assertFalse(branch.is_locked())
2937
self.reset_smart_call_log()
2938
verb = b'Repository.get_rev_id_for_revno'
2939
self.disable_verb(verb)
2940
self.assertEqual(rev1, branch.get_rev_id(1))
2941
self.assertLength(1, [call for call in self.hpss_calls if
2942
call.call.method == verb])
2945
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2947
def test_has_signature_for_revision_id(self):
2948
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2949
transport_path = 'quack'
2950
repo, client = self.setup_fake_client_and_repository(transport_path)
2951
client.add_success_response(b'yes')
2952
result = repo.has_signature_for_revision_id(b'A')
2954
[('call', b'Repository.has_signature_for_revision_id',
2955
(b'quack/', b'A'))],
2957
self.assertEqual(True, result)
2959
def test_is_not_shared(self):
2960
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2961
transport_path = 'qwack'
2962
repo, client = self.setup_fake_client_and_repository(transport_path)
2963
client.add_success_response(b'no')
2964
result = repo.has_signature_for_revision_id(b'A')
2966
[('call', b'Repository.has_signature_for_revision_id',
2967
(b'qwack/', b'A'))],
2969
self.assertEqual(False, result)
2972
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2974
def test_get_physical_lock_status_yes(self):
2975
transport_path = 'qwack'
2976
repo, client = self.setup_fake_client_and_repository(transport_path)
2977
client.add_success_response(b'yes')
2978
result = repo.get_physical_lock_status()
2980
[('call', b'Repository.get_physical_lock_status',
2983
self.assertEqual(True, result)
2985
def test_get_physical_lock_status_no(self):
2986
transport_path = 'qwack'
2987
repo, client = self.setup_fake_client_and_repository(transport_path)
2988
client.add_success_response(b'no')
2989
result = repo.get_physical_lock_status()
2991
[('call', b'Repository.get_physical_lock_status',
2994
self.assertEqual(False, result)
2997
class TestRepositoryIsShared(TestRemoteRepository):
2999
def test_is_shared(self):
3000
# ('yes', ) for Repository.is_shared -> 'True'.
3001
transport_path = 'quack'
3002
repo, client = self.setup_fake_client_and_repository(transport_path)
3003
client.add_success_response(b'yes')
3004
result = repo.is_shared()
3006
[('call', b'Repository.is_shared', (b'quack/',))],
3008
self.assertEqual(True, result)
3010
def test_is_not_shared(self):
3011
# ('no', ) for Repository.is_shared -> 'False'.
3012
transport_path = 'qwack'
3013
repo, client = self.setup_fake_client_and_repository(transport_path)
3014
client.add_success_response(b'no')
3015
result = repo.is_shared()
3017
[('call', b'Repository.is_shared', (b'qwack/',))],
3019
self.assertEqual(False, result)
3022
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3024
def test_make_working_trees(self):
3025
# ('yes', ) for Repository.make_working_trees -> 'True'.
3026
transport_path = 'quack'
3027
repo, client = self.setup_fake_client_and_repository(transport_path)
3028
client.add_success_response(b'yes')
3029
result = repo.make_working_trees()
3031
[('call', b'Repository.make_working_trees', (b'quack/',))],
3033
self.assertEqual(True, result)
3035
def test_no_working_trees(self):
3036
# ('no', ) for Repository.make_working_trees -> 'False'.
3037
transport_path = 'qwack'
3038
repo, client = self.setup_fake_client_and_repository(transport_path)
3039
client.add_success_response(b'no')
3040
result = repo.make_working_trees()
3042
[('call', b'Repository.make_working_trees', (b'qwack/',))],
3044
self.assertEqual(False, result)
3047
class TestRepositoryLockWrite(TestRemoteRepository):
3049
def test_lock_write(self):
3050
transport_path = 'quack'
3051
repo, client = self.setup_fake_client_and_repository(transport_path)
3052
client.add_success_response(b'ok', b'a token')
3053
token = repo.lock_write().repository_token
3055
[('call', b'Repository.lock_write', (b'quack/', b''))],
3057
self.assertEqual(b'a token', token)
3059
def test_lock_write_already_locked(self):
3060
transport_path = 'quack'
3061
repo, client = self.setup_fake_client_and_repository(transport_path)
3062
client.add_error_response(b'LockContention')
3063
self.assertRaises(errors.LockContention, repo.lock_write)
3065
[('call', b'Repository.lock_write', (b'quack/', b''))],
3068
def test_lock_write_unlockable(self):
3069
transport_path = 'quack'
3070
repo, client = self.setup_fake_client_and_repository(transport_path)
3071
client.add_error_response(b'UnlockableTransport')
3072
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3074
[('call', b'Repository.lock_write', (b'quack/', b''))],
3078
class TestRepositoryWriteGroups(TestRemoteRepository):
3080
def test_start_write_group(self):
3081
transport_path = 'quack'
3082
repo, client = self.setup_fake_client_and_repository(transport_path)
3083
client.add_expected_call(
3084
b'Repository.lock_write', (b'quack/', b''),
3085
b'success', (b'ok', b'a token'))
3086
client.add_expected_call(
3087
b'Repository.start_write_group', (b'quack/', b'a token'),
3088
b'success', (b'ok', (b'token1', )))
3090
repo.start_write_group()
3092
def test_start_write_group_unsuspendable(self):
3093
# Some repositories do not support suspending write
3094
# groups. For those, fall back to the "real" repository.
3095
transport_path = 'quack'
3096
repo, client = self.setup_fake_client_and_repository(transport_path)
3097
def stub_ensure_real():
3098
client._calls.append(('_ensure_real',))
3099
repo._real_repository = _StubRealPackRepository(client._calls)
3100
repo._ensure_real = stub_ensure_real
3101
client.add_expected_call(
3102
b'Repository.lock_write', (b'quack/', b''),
3103
b'success', (b'ok', b'a token'))
3104
client.add_expected_call(
3105
b'Repository.start_write_group', (b'quack/', b'a token'),
3106
b'error', (b'UnsuspendableWriteGroup',))
3108
repo.start_write_group()
3109
self.assertEqual(client._calls[-2:], [
3111
('start_write_group',)])
3113
def test_commit_write_group(self):
3114
transport_path = 'quack'
3115
repo, client = self.setup_fake_client_and_repository(transport_path)
3116
client.add_expected_call(
3117
b'Repository.lock_write', (b'quack/', b''),
3118
b'success', (b'ok', b'a token'))
3119
client.add_expected_call(
3120
b'Repository.start_write_group', (b'quack/', b'a token'),
3121
b'success', (b'ok', [b'token1']))
3122
client.add_expected_call(
3123
b'Repository.commit_write_group', (b'quack/', b'a token', [b'token1']),
3124
b'success', (b'ok',))
3126
repo.start_write_group()
3127
repo.commit_write_group()
3129
def test_abort_write_group(self):
3130
transport_path = 'quack'
3131
repo, client = self.setup_fake_client_and_repository(transport_path)
3132
client.add_expected_call(
3133
b'Repository.lock_write', (b'quack/', b''),
3134
b'success', (b'ok', b'a token'))
3135
client.add_expected_call(
3136
b'Repository.start_write_group', (b'quack/', b'a token'),
3137
b'success', (b'ok', [b'token1']))
3138
client.add_expected_call(
3139
b'Repository.abort_write_group', (b'quack/', b'a token', [b'token1']),
3140
b'success', (b'ok',))
3142
repo.start_write_group()
3143
repo.abort_write_group(False)
3145
def test_suspend_write_group(self):
3146
transport_path = 'quack'
3147
repo, client = self.setup_fake_client_and_repository(transport_path)
3148
self.assertEqual([], repo.suspend_write_group())
3150
def test_resume_write_group(self):
3151
transport_path = 'quack'
3152
repo, client = self.setup_fake_client_and_repository(transport_path)
3153
client.add_expected_call(
3154
b'Repository.lock_write', (b'quack/', b''),
3155
b'success', (b'ok', b'a token'))
3156
client.add_expected_call(
3157
b'Repository.check_write_group', (b'quack/', b'a token', [b'token1']),
3158
b'success', (b'ok',))
3160
repo.resume_write_group(['token1'])
3163
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3165
def test_backwards_compat(self):
3166
self.setup_smart_server_with_call_log()
3167
repo = self.make_repository('.')
3168
self.reset_smart_call_log()
3169
verb = b'Repository.set_make_working_trees'
3170
self.disable_verb(verb)
3171
repo.set_make_working_trees(True)
3172
call_count = len([call for call in self.hpss_calls if
3173
call.call.method == verb])
3174
self.assertEqual(1, call_count)
3176
def test_current(self):
3177
transport_path = 'quack'
3178
repo, client = self.setup_fake_client_and_repository(transport_path)
3179
client.add_expected_call(
3180
b'Repository.set_make_working_trees', (b'quack/', b'True'),
3181
b'success', (b'ok',))
3182
client.add_expected_call(
3183
b'Repository.set_make_working_trees', (b'quack/', b'False'),
3184
b'success', (b'ok',))
3185
repo.set_make_working_trees(True)
3186
repo.set_make_working_trees(False)
3189
class TestRepositoryUnlock(TestRemoteRepository):
3191
def test_unlock(self):
3192
transport_path = 'quack'
3193
repo, client = self.setup_fake_client_and_repository(transport_path)
3194
client.add_success_response(b'ok', b'a token')
3195
client.add_success_response(b'ok')
3199
[('call', b'Repository.lock_write', (b'quack/', b'')),
3200
('call', b'Repository.unlock', (b'quack/', b'a token'))],
3203
def test_unlock_wrong_token(self):
3204
# If somehow the token is wrong, unlock will raise TokenMismatch.
3205
transport_path = 'quack'
3206
repo, client = self.setup_fake_client_and_repository(transport_path)
3207
client.add_success_response(b'ok', b'a token')
3208
client.add_error_response(b'TokenMismatch')
3210
self.assertRaises(errors.TokenMismatch, repo.unlock)
3213
class TestRepositoryHasRevision(TestRemoteRepository):
3215
def test_none(self):
3216
# repo.has_revision(None) should not cause any traffic.
3217
transport_path = 'quack'
3218
repo, client = self.setup_fake_client_and_repository(transport_path)
3220
# The null revision is always there, so has_revision(None) == True.
3221
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3223
# The remote repo shouldn't be accessed.
3224
self.assertEqual([], client._calls)
3227
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3228
"""Test Repository.iter_file_bytes."""
3230
def test_single(self):
3231
transport_path = 'quack'
3232
repo, client = self.setup_fake_client_and_repository(transport_path)
3233
client.add_expected_call(
3234
b'Repository.iter_files_bytes', (b'quack/', ),
3235
b'success', (b'ok',), iter([b"ok\x000", b"\n", zlib.compress(b"mydata" * 10)]))
3236
for (identifier, byte_stream) in repo.iter_files_bytes([(b"somefile",
3237
b"somerev", b"myid")]):
3238
self.assertEqual(b"myid", identifier)
3239
self.assertEqual(b"".join(byte_stream), b"mydata" * 10)
3241
def test_missing(self):
3242
transport_path = 'quack'
3243
repo, client = self.setup_fake_client_and_repository(transport_path)
3244
client.add_expected_call(
3245
b'Repository.iter_files_bytes',
3247
b'error', (b'RevisionNotPresent', b'somefile', b'somerev'),
3248
iter([b"absent\0somefile\0somerev\n"]))
3249
self.assertRaises(errors.RevisionNotPresent, list,
3250
repo.iter_files_bytes(
3251
[(b"somefile", b"somerev", b"myid")]))
3254
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3255
"""Base class for Repository.insert_stream and .insert_stream_1.19
3259
def checkInsertEmptyStream(self, repo, client):
3260
"""Insert an empty stream, checking the result.
3262
This checks that there are no resume_tokens or missing_keys, and that
3263
the client is finished.
3265
sink = repo._get_sink()
3266
fmt = repository.format_registry.get_default()
3267
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3268
self.assertEqual([], resume_tokens)
3269
self.assertEqual(set(), missing_keys)
3270
self.assertFinished(client)
3273
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3274
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3277
This test case is very similar to TestRepositoryInsertStream_1_19.
3281
super(TestRepositoryInsertStream, self).setUp()
3282
self.disable_verb(b'Repository.insert_stream_1.19')
3284
def test_unlocked_repo(self):
3285
transport_path = 'quack'
3286
repo, client = self.setup_fake_client_and_repository(transport_path)
3287
client.add_expected_call(
3288
b'Repository.insert_stream_1.19', (b'quack/', b''),
3289
b'unknown', (b'Repository.insert_stream_1.19',))
3290
client.add_expected_call(
3291
b'Repository.insert_stream', (b'quack/', b''),
3292
b'success', (b'ok',))
3293
client.add_expected_call(
3294
b'Repository.insert_stream', (b'quack/', b''),
3295
b'success', (b'ok',))
3296
self.checkInsertEmptyStream(repo, client)
3298
def test_locked_repo_with_no_lock_token(self):
3299
transport_path = 'quack'
3300
repo, client = self.setup_fake_client_and_repository(transport_path)
3301
client.add_expected_call(
3302
b'Repository.lock_write', (b'quack/', b''),
3303
b'success', (b'ok', b''))
3304
client.add_expected_call(
3305
b'Repository.insert_stream_1.19', (b'quack/', b''),
3306
b'unknown', (b'Repository.insert_stream_1.19',))
3307
client.add_expected_call(
3308
b'Repository.insert_stream', (b'quack/', b''),
3309
b'success', (b'ok',))
3310
client.add_expected_call(
3311
b'Repository.insert_stream', (b'quack/', b''),
3312
b'success', (b'ok',))
3314
self.checkInsertEmptyStream(repo, client)
3316
def test_locked_repo_with_lock_token(self):
3317
transport_path = 'quack'
3318
repo, client = self.setup_fake_client_and_repository(transport_path)
3319
client.add_expected_call(
3320
b'Repository.lock_write', (b'quack/', b''),
3321
b'success', (b'ok', b'a token'))
3322
client.add_expected_call(
3323
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3324
b'unknown', (b'Repository.insert_stream_1.19',))
3325
client.add_expected_call(
3326
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3327
b'success', (b'ok',))
3328
client.add_expected_call(
3329
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3330
b'success', (b'ok',))
3332
self.checkInsertEmptyStream(repo, client)
3334
def test_stream_with_inventory_deltas(self):
3335
"""'inventory-deltas' substreams cannot be sent to the
3336
Repository.insert_stream verb, because not all servers that implement
3337
that verb will accept them. So when one is encountered the RemoteSink
3338
immediately stops using that verb and falls back to VFS insert_stream.
3340
transport_path = 'quack'
3341
repo, client = self.setup_fake_client_and_repository(transport_path)
3342
client.add_expected_call(
3343
b'Repository.insert_stream_1.19', (b'quack/', b''),
3344
b'unknown', (b'Repository.insert_stream_1.19',))
3345
client.add_expected_call(
3346
b'Repository.insert_stream', (b'quack/', b''),
3347
b'success', (b'ok',))
3348
client.add_expected_call(
3349
b'Repository.insert_stream', (b'quack/', b''),
3350
b'success', (b'ok',))
3351
# Create a fake real repository for insert_stream to fall back on, so
3352
# that we can directly see the records the RemoteSink passes to the
3357
def insert_stream(self, stream, src_format, resume_tokens):
3358
for substream_kind, substream in stream:
3359
self.records.append(
3360
(substream_kind, [record.key for record in substream]))
3361
return [b'fake tokens'], [b'fake missing keys']
3362
fake_real_sink = FakeRealSink()
3363
class FakeRealRepository:
3364
def _get_sink(self):
3365
return fake_real_sink
3366
def is_in_write_group(self):
3368
def refresh_data(self):
3370
repo._real_repository = FakeRealRepository()
3371
sink = repo._get_sink()
3372
fmt = repository.format_registry.get_default()
3373
stream = self.make_stream_with_inv_deltas(fmt)
3374
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3375
# Every record from the first inventory delta should have been sent to
3377
expected_records = [
3378
('inventory-deltas', [(b'rev2',), (b'rev3',)]),
3379
('texts', [(b'some-rev', b'some-file')])]
3380
self.assertEqual(expected_records, fake_real_sink.records)
3381
# The return values from the real sink's insert_stream are propagated
3382
# back to the original caller.
3383
self.assertEqual([b'fake tokens'], resume_tokens)
3384
self.assertEqual([b'fake missing keys'], missing_keys)
3385
self.assertFinished(client)
3387
def make_stream_with_inv_deltas(self, fmt):
3388
"""Make a simple stream with an inventory delta followed by more
3389
records and more substreams to test that all records and substreams
3390
from that point on are used.
3392
This sends, in order:
3393
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3395
* texts substream: (some-rev, some-file)
3397
# Define a stream using generators so that it isn't rewindable.
3398
inv = inventory.Inventory(revision_id=b'rev1')
3399
inv.root.revision = b'rev1'
3400
def stream_with_inv_delta():
3401
yield ('inventories', inventories_substream())
3402
yield ('inventory-deltas', inventory_delta_substream())
3404
versionedfile.FulltextContentFactory(
3405
(b'some-rev', b'some-file'), (), None, b'content')])
3406
def inventories_substream():
3407
# An empty inventory fulltext. This will be streamed normally.
3408
text = fmt._serializer.write_inventory_to_string(inv)
3409
yield versionedfile.FulltextContentFactory(
3410
(b'rev1',), (), None, text)
3411
def inventory_delta_substream():
3412
# An inventory delta. This can't be streamed via this verb, so it
3413
# will trigger a fallback to VFS insert_stream.
3414
entry = inv.make_entry(
3415
'directory', 'newdir', inv.root.file_id, b'newdir-id')
3416
entry.revision = b'ghost'
3417
delta = [(None, 'newdir', b'newdir-id', entry)]
3418
serializer = inventory_delta.InventoryDeltaSerializer(
3419
versioned_root=True, tree_references=False)
3420
lines = serializer.delta_to_lines(b'rev1', b'rev2', delta)
3421
yield versionedfile.ChunkedContentFactory(
3422
(b'rev2',), ((b'rev1',)), None, lines)
3424
lines = serializer.delta_to_lines(b'rev1', b'rev3', delta)
3425
yield versionedfile.ChunkedContentFactory(
3426
(b'rev3',), ((b'rev1',)), None, lines)
3427
return stream_with_inv_delta()
3430
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3432
def test_unlocked_repo(self):
3433
transport_path = 'quack'
3434
repo, client = self.setup_fake_client_and_repository(transport_path)
3435
client.add_expected_call(
3436
b'Repository.insert_stream_1.19', (b'quack/', b''),
3437
b'success', (b'ok',))
3438
client.add_expected_call(
3439
b'Repository.insert_stream_1.19', (b'quack/', b''),
3440
b'success', (b'ok',))
3441
self.checkInsertEmptyStream(repo, client)
3443
def test_locked_repo_with_no_lock_token(self):
3444
transport_path = 'quack'
3445
repo, client = self.setup_fake_client_and_repository(transport_path)
3446
client.add_expected_call(
3447
b'Repository.lock_write', (b'quack/', b''),
3448
b'success', (b'ok', b''))
3449
client.add_expected_call(
3450
b'Repository.insert_stream_1.19', (b'quack/', b''),
3451
b'success', (b'ok',))
3452
client.add_expected_call(
3453
b'Repository.insert_stream_1.19', (b'quack/', b''),
3454
b'success', (b'ok',))
3456
self.checkInsertEmptyStream(repo, client)
3458
def test_locked_repo_with_lock_token(self):
3459
transport_path = 'quack'
3460
repo, client = self.setup_fake_client_and_repository(transport_path)
3461
client.add_expected_call(
3462
b'Repository.lock_write', (b'quack/', b''),
3463
b'success', (b'ok', b'a token'))
3464
client.add_expected_call(
3465
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3466
b'success', (b'ok',))
3467
client.add_expected_call(
3468
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3469
b'success', (b'ok',))
3471
self.checkInsertEmptyStream(repo, client)
3474
class TestRepositoryTarball(TestRemoteRepository):
3476
# This is a canned tarball reponse we can validate against
3477
tarball_content = base64.b64decode(
3478
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3479
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3480
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3481
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3482
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3483
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3484
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3485
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3486
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3487
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3488
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3489
'nWQ7QH/F3JFOFCQ0aSPfA='
3492
def test_repository_tarball(self):
3493
# Test that Repository.tarball generates the right operations
3494
transport_path = 'repo'
3495
expected_calls = [('call_expecting_body', b'Repository.tarball',
3496
(b'repo/', b'bz2',),),
3498
repo, client = self.setup_fake_client_and_repository(transport_path)
3499
client.add_success_response_with_body(self.tarball_content, b'ok')
3500
# Now actually ask for the tarball
3501
tarball_file = repo._get_tarball('bz2')
3503
self.assertEqual(expected_calls, client._calls)
3504
self.assertEqual(self.tarball_content, tarball_file.read())
3506
tarball_file.close()
3509
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3510
"""RemoteRepository.copy_content_into optimizations"""
3512
def test_copy_content_remote_to_local(self):
3513
self.transport_server = test_server.SmartTCPServer_for_testing
3514
src_repo = self.make_repository('repo1')
3515
src_repo = repository.Repository.open(self.get_url('repo1'))
3516
# At the moment the tarball-based copy_content_into can't write back
3517
# into a smart server. It would be good if it could upload the
3518
# tarball; once that works we'd have to create repositories of
3519
# different formats. -- mbp 20070410
3520
dest_url = self.get_vfs_only_url('repo2')
3521
dest_bzrdir = BzrDir.create(dest_url)
3522
dest_repo = dest_bzrdir.create_repository()
3523
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3524
self.assertTrue(isinstance(src_repo, RemoteRepository))
3525
src_repo.copy_content_into(dest_repo)
3528
class _StubRealPackRepository(object):
3530
def __init__(self, calls):
3532
self._pack_collection = _StubPackCollection(calls)
3534
def start_write_group(self):
3535
self.calls.append(('start_write_group',))
3537
def is_in_write_group(self):
3540
def refresh_data(self):
3541
self.calls.append(('pack collection reload_pack_names',))
3544
class _StubPackCollection(object):
3546
def __init__(self, calls):
3550
self.calls.append(('pack collection autopack',))
3553
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3554
"""Tests for RemoteRepository.autopack implementation."""
3557
"""When the server returns 'ok' and there's no _real_repository, then
3558
nothing else happens: the autopack method is done.
3560
transport_path = 'quack'
3561
repo, client = self.setup_fake_client_and_repository(transport_path)
3562
client.add_expected_call(
3563
b'PackRepository.autopack', (b'quack/',), b'success', (b'ok',))
3565
self.assertFinished(client)
3567
def test_ok_with_real_repo(self):
3568
"""When the server returns 'ok' and there is a _real_repository, then
3569
the _real_repository's reload_pack_name's method will be called.
3571
transport_path = 'quack'
3572
repo, client = self.setup_fake_client_and_repository(transport_path)
3573
client.add_expected_call(
3574
b'PackRepository.autopack', (b'quack/',),
3575
b'success', (b'ok',))
3576
repo._real_repository = _StubRealPackRepository(client._calls)
3579
[('call', b'PackRepository.autopack', (b'quack/',)),
3580
('pack collection reload_pack_names',)],
3583
def test_backwards_compatibility(self):
3584
"""If the server does not recognise the PackRepository.autopack verb,
3585
fallback to the real_repository's implementation.
3587
transport_path = 'quack'
3588
repo, client = self.setup_fake_client_and_repository(transport_path)
3589
client.add_unknown_method_response(b'PackRepository.autopack')
3590
def stub_ensure_real():
3591
client._calls.append(('_ensure_real',))
3592
repo._real_repository = _StubRealPackRepository(client._calls)
3593
repo._ensure_real = stub_ensure_real
3596
[('call', b'PackRepository.autopack', (b'quack/',)),
3598
('pack collection autopack',)],
3601
def test_oom_error_reporting(self):
3602
"""An out-of-memory condition on the server is reported clearly"""
3603
transport_path = 'quack'
3604
repo, client = self.setup_fake_client_and_repository(transport_path)
3605
client.add_expected_call(
3606
b'PackRepository.autopack', (b'quack/',),
3607
b'error', (b'MemoryError',))
3608
err = self.assertRaises(errors.BzrError, repo.autopack)
3609
self.assertContainsRe(str(err), "^remote server out of mem")
3612
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3613
"""Base class for unit tests for breezy.bzr.remote._translate_error."""
3615
def translateTuple(self, error_tuple, **context):
3616
"""Call _translate_error with an ErrorFromSmartServer built from the
3619
:param error_tuple: A tuple of a smart server response, as would be
3620
passed to an ErrorFromSmartServer.
3621
:kwargs context: context items to call _translate_error with.
3623
:returns: The error raised by _translate_error.
3625
# Raise the ErrorFromSmartServer before passing it as an argument,
3626
# because _translate_error may need to re-raise it with a bare 'raise'
3628
server_error = errors.ErrorFromSmartServer(error_tuple)
3629
translated_error = self.translateErrorFromSmartServer(
3630
server_error, **context)
3631
return translated_error
3633
def translateErrorFromSmartServer(self, error_object, **context):
3634
"""Like translateTuple, but takes an already constructed
3635
ErrorFromSmartServer rather than a tuple.
3639
except errors.ErrorFromSmartServer as server_error:
3640
translated_error = self.assertRaises(
3641
errors.BzrError, remote._translate_error, server_error,
3643
return translated_error
3646
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3647
"""Unit tests for breezy.bzr.remote._translate_error.
3649
Given an ErrorFromSmartServer (which has an error tuple from a smart
3650
server) and some context, _translate_error raises more specific errors from
3653
This test case covers the cases where _translate_error succeeds in
3654
translating an ErrorFromSmartServer to something better. See
3655
TestErrorTranslationRobustness for other cases.
3658
def test_NoSuchRevision(self):
3659
branch = self.make_branch('')
3661
translated_error = self.translateTuple(
3662
(b'NoSuchRevision', revid), branch=branch)
3663
expected_error = errors.NoSuchRevision(branch, revid)
3664
self.assertEqual(expected_error, translated_error)
3666
def test_nosuchrevision(self):
3667
repository = self.make_repository('')
3669
translated_error = self.translateTuple(
3670
(b'nosuchrevision', revid), repository=repository)
3671
expected_error = errors.NoSuchRevision(repository, revid)
3672
self.assertEqual(expected_error, translated_error)
3674
def test_nobranch(self):
3675
bzrdir = self.make_controldir('')
3676
translated_error = self.translateTuple((b'nobranch',), bzrdir=bzrdir)
3677
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3678
self.assertEqual(expected_error, translated_error)
3680
def test_nobranch_one_arg(self):
3681
bzrdir = self.make_controldir('')
3682
translated_error = self.translateTuple(
3683
(b'nobranch', b'extra detail'), bzrdir=bzrdir)
3684
expected_error = errors.NotBranchError(
3685
path=bzrdir.root_transport.base,
3686
detail='extra detail')
3687
self.assertEqual(expected_error, translated_error)
3689
def test_norepository(self):
3690
bzrdir = self.make_controldir('')
3691
translated_error = self.translateTuple((b'norepository',),
3693
expected_error = errors.NoRepositoryPresent(bzrdir)
3694
self.assertEqual(expected_error, translated_error)
3696
def test_LockContention(self):
3697
translated_error = self.translateTuple((b'LockContention',))
3698
expected_error = errors.LockContention('(remote lock)')
3699
self.assertEqual(expected_error, translated_error)
3701
def test_UnlockableTransport(self):
3702
bzrdir = self.make_controldir('')
3703
translated_error = self.translateTuple(
3704
(b'UnlockableTransport',), bzrdir=bzrdir)
3705
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3706
self.assertEqual(expected_error, translated_error)
3708
def test_LockFailed(self):
3709
lock = 'str() of a server lock'
3710
why = 'str() of why'
3711
translated_error = self.translateTuple((b'LockFailed', lock.encode('ascii'), why.encode('ascii')))
3712
expected_error = errors.LockFailed(lock, why)
3713
self.assertEqual(expected_error, translated_error)
3715
def test_TokenMismatch(self):
3716
token = 'a lock token'
3717
translated_error = self.translateTuple((b'TokenMismatch',), token=token)
3718
expected_error = errors.TokenMismatch(token, '(remote token)')
3719
self.assertEqual(expected_error, translated_error)
3721
def test_Diverged(self):
3722
branch = self.make_branch('a')
3723
other_branch = self.make_branch('b')
3724
translated_error = self.translateTuple(
3725
(b'Diverged',), branch=branch, other_branch=other_branch)
3726
expected_error = errors.DivergedBranches(branch, other_branch)
3727
self.assertEqual(expected_error, translated_error)
3729
def test_NotStacked(self):
3730
branch = self.make_branch('')
3731
translated_error = self.translateTuple((b'NotStacked',), branch=branch)
3732
expected_error = errors.NotStacked(branch)
3733
self.assertEqual(expected_error, translated_error)
3735
def test_ReadError_no_args(self):
3737
translated_error = self.translateTuple((b'ReadError',), path=path)
3738
expected_error = errors.ReadError(path)
3739
self.assertEqual(expected_error, translated_error)
3741
def test_ReadError(self):
3743
translated_error = self.translateTuple((b'ReadError', path.encode('utf-8')))
3744
expected_error = errors.ReadError(path)
3745
self.assertEqual(expected_error, translated_error)
3747
def test_IncompatibleRepositories(self):
3748
translated_error = self.translateTuple((b'IncompatibleRepositories',
3749
b"repo1", b"repo2", b"details here"))
3750
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3752
self.assertEqual(expected_error, translated_error)
3754
def test_GhostRevisionsHaveNoRevno(self):
3755
translated_error = self.translateTuple((b'GhostRevisionsHaveNoRevno',
3756
b"revid1", b"revid2"))
3757
expected_error = errors.GhostRevisionsHaveNoRevno(b"revid1", b"revid2")
3758
self.assertEqual(expected_error, translated_error)
3760
def test_PermissionDenied_no_args(self):
3762
translated_error = self.translateTuple((b'PermissionDenied',),
3764
expected_error = errors.PermissionDenied(path)
3765
self.assertEqual(expected_error, translated_error)
3767
def test_PermissionDenied_one_arg(self):
3769
translated_error = self.translateTuple((b'PermissionDenied', path.encode('utf-8')))
3770
expected_error = errors.PermissionDenied(path)
3771
self.assertEqual(expected_error, translated_error)
3773
def test_PermissionDenied_one_arg_and_context(self):
3774
"""Given a choice between a path from the local context and a path on
3775
the wire, _translate_error prefers the path from the local context.
3777
local_path = 'local path'
3778
remote_path = 'remote path'
3779
translated_error = self.translateTuple(
3780
(b'PermissionDenied', remote_path.encode('utf-8')), path=local_path)
3781
expected_error = errors.PermissionDenied(local_path)
3782
self.assertEqual(expected_error, translated_error)
3784
def test_PermissionDenied_two_args(self):
3786
extra = 'a string with extra info'
3787
translated_error = self.translateTuple(
3788
(b'PermissionDenied', path.encode('utf-8'), extra.encode('utf-8')))
3789
expected_error = errors.PermissionDenied(path, extra)
3790
self.assertEqual(expected_error, translated_error)
3792
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3794
def test_NoSuchFile_context_path(self):
3795
local_path = "local path"
3796
translated_error = self.translateTuple((b'ReadError', b"remote path"),
3798
expected_error = errors.ReadError(local_path)
3799
self.assertEqual(expected_error, translated_error)
3801
def test_NoSuchFile_without_context(self):
3802
remote_path = "remote path"
3803
translated_error = self.translateTuple((b'ReadError', remote_path.encode('utf-8')))
3804
expected_error = errors.ReadError(remote_path)
3805
self.assertEqual(expected_error, translated_error)
3807
def test_ReadOnlyError(self):
3808
translated_error = self.translateTuple((b'ReadOnlyError',))
3809
expected_error = errors.TransportNotPossible("readonly transport")
3810
self.assertEqual(expected_error, translated_error)
3812
def test_MemoryError(self):
3813
translated_error = self.translateTuple((b'MemoryError',))
3814
self.assertStartsWith(str(translated_error),
3815
"remote server out of memory")
3817
def test_generic_IndexError_no_classname(self):
3818
err = errors.ErrorFromSmartServer((b'error', b"list index out of range"))
3819
translated_error = self.translateErrorFromSmartServer(err)
3820
expected_error = errors.UnknownErrorFromSmartServer(err)
3821
self.assertEqual(expected_error, translated_error)
3823
# GZ 2011-03-02: TODO test generic non-ascii error string
3825
def test_generic_KeyError(self):
3826
err = errors.ErrorFromSmartServer((b'error', b'KeyError', b"1"))
3827
translated_error = self.translateErrorFromSmartServer(err)
3828
expected_error = errors.UnknownErrorFromSmartServer(err)
3829
self.assertEqual(expected_error, translated_error)
3832
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3833
"""Unit tests for breezy.bzr.remote._translate_error's robustness.
3835
TestErrorTranslationSuccess is for cases where _translate_error can
3836
translate successfully. This class about how _translate_err behaves when
3837
it fails to translate: it re-raises the original error.
3840
def test_unrecognised_server_error(self):
3841
"""If the error code from the server is not recognised, the original
3842
ErrorFromSmartServer is propagated unmodified.
3844
error_tuple = (b'An unknown error tuple',)
3845
server_error = errors.ErrorFromSmartServer(error_tuple)
3846
translated_error = self.translateErrorFromSmartServer(server_error)
3847
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3848
self.assertEqual(expected_error, translated_error)
3850
def test_context_missing_a_key(self):
3851
"""In case of a bug in the client, or perhaps an unexpected response
3852
from a server, _translate_error returns the original error tuple from
3853
the server and mutters a warning.
3855
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3856
# in the context dict. So let's give it an empty context dict instead
3857
# to exercise its error recovery.
3859
error_tuple = (b'NoSuchRevision', b'revid')
3860
server_error = errors.ErrorFromSmartServer(error_tuple)
3861
translated_error = self.translateErrorFromSmartServer(server_error)
3862
self.assertEqual(server_error, translated_error)
3863
# In addition to re-raising ErrorFromSmartServer, some debug info has
3864
# been muttered to the log file for developer to look at.
3865
self.assertContainsRe(
3867
"Missing key 'branch' in context")
3869
def test_path_missing(self):
3870
"""Some translations (PermissionDenied, ReadError) can determine the
3871
'path' variable from either the wire or the local context. If neither
3872
has it, then an error is raised.
3874
error_tuple = (b'ReadError',)
3875
server_error = errors.ErrorFromSmartServer(error_tuple)
3876
translated_error = self.translateErrorFromSmartServer(server_error)
3877
self.assertEqual(server_error, translated_error)
3878
# In addition to re-raising ErrorFromSmartServer, some debug info has
3879
# been muttered to the log file for developer to look at.
3880
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3883
class TestStacking(tests.TestCaseWithTransport):
3884
"""Tests for operations on stacked remote repositories.
3886
The underlying format type must support stacking.
3889
def test_access_stacked_remote(self):
3890
# based on <http://launchpad.net/bugs/261315>
3891
# make a branch stacked on another repository containing an empty
3892
# revision, then open it over hpss - we should be able to see that
3894
base_transport = self.get_transport()
3895
base_builder = self.make_branch_builder('base', format='1.9')
3896
base_builder.start_series()
3897
base_revid = base_builder.build_snapshot(None,
3898
[('add', ('', None, 'directory', None))],
3899
'message', revision_id=b'rev-id')
3900
base_builder.finish_series()
3901
stacked_branch = self.make_branch('stacked', format='1.9')
3902
stacked_branch.set_stacked_on_url('../base')
3903
# start a server looking at this
3904
smart_server = test_server.SmartTCPServer_for_testing()
3905
self.start_server(smart_server)
3906
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3907
# can get its branch and repository
3908
remote_branch = remote_bzrdir.open_branch()
3909
remote_repo = remote_branch.repository
3910
remote_repo.lock_read()
3912
# it should have an appropriate fallback repository, which should also
3913
# be a RemoteRepository
3914
self.assertLength(1, remote_repo._fallback_repositories)
3915
self.assertIsInstance(remote_repo._fallback_repositories[0],
3917
# and it has the revision committed to the underlying repository;
3918
# these have varying implementations so we try several of them
3919
self.assertTrue(remote_repo.has_revisions([base_revid]))
3920
self.assertTrue(remote_repo.has_revision(base_revid))
3921
self.assertEqual(remote_repo.get_revision(base_revid).message,
3924
remote_repo.unlock()
3926
def prepare_stacked_remote_branch(self):
3927
"""Get stacked_upon and stacked branches with content in each."""
3928
self.setup_smart_server_with_call_log()
3929
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3930
tree1.commit('rev1', rev_id=b'rev1')
3931
tree2 = tree1.branch.controldir.sprout('tree2', stacked=True
3932
).open_workingtree()
3933
local_tree = tree2.branch.create_checkout('local')
3934
local_tree.commit('local changes make me feel good.')
3935
branch2 = Branch.open(self.get_url('tree2'))
3937
self.addCleanup(branch2.unlock)
3938
return tree1.branch, branch2
3940
def test_stacked_get_parent_map(self):
3941
# the public implementation of get_parent_map obeys stacking
3942
_, branch = self.prepare_stacked_remote_branch()
3943
repo = branch.repository
3944
self.assertEqual({'rev1'}, set(repo.get_parent_map([b'rev1'])))
3946
def test_unstacked_get_parent_map(self):
3947
# _unstacked_provider.get_parent_map ignores stacking
3948
_, branch = self.prepare_stacked_remote_branch()
3949
provider = branch.repository._unstacked_provider
3950
self.assertEqual(set(), set(provider.get_parent_map([b'rev1'])))
3952
def fetch_stream_to_rev_order(self, stream):
3954
for kind, substream in stream:
3955
if not kind == 'revisions':
3958
for content in substream:
3959
result.append(content.key[-1])
3962
def get_ordered_revs(self, format, order, branch_factory=None):
3963
"""Get a list of the revisions in a stream to format format.
3965
:param format: The format of the target.
3966
:param order: the order that target should have requested.
3967
:param branch_factory: A callable to create a trunk and stacked branch
3968
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3969
:result: The revision ids in the stream, in the order seen,
3970
the topological order of revisions in the source.
3972
unordered_format = controldir.format_registry.get(format)()
3973
target_repository_format = unordered_format.repository_format
3975
self.assertEqual(order, target_repository_format._fetch_order)
3976
if branch_factory is None:
3977
branch_factory = self.prepare_stacked_remote_branch
3978
_, stacked = branch_factory()
3979
source = stacked.repository._get_source(target_repository_format)
3980
tip = stacked.last_revision()
3981
stacked.repository._ensure_real()
3982
graph = stacked.repository.get_graph()
3983
revs = [r for (r, ps) in graph.iter_ancestry([tip])
3984
if r != NULL_REVISION]
3986
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3987
self.reset_smart_call_log()
3988
stream = source.get_stream(search)
3989
# We trust that if a revision is in the stream the rest of the new
3990
# content for it is too, as per our main fetch tests; here we are
3991
# checking that the revisions are actually included at all, and their
3993
return self.fetch_stream_to_rev_order(stream), revs
3995
def test_stacked_get_stream_unordered(self):
3996
# Repository._get_source.get_stream() from a stacked repository with
3997
# unordered yields the full data from both stacked and stacked upon
3999
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
4000
self.assertEqual(set(expected_revs), set(rev_ord))
4001
# Getting unordered results should have made a streaming data request
4002
# from the server, then one from the backing branch.
4003
self.assertLength(2, self.hpss_calls)
4005
def test_stacked_on_stacked_get_stream_unordered(self):
4006
# Repository._get_source.get_stream() from a stacked repository which
4007
# is itself stacked yields the full data from all three sources.
4008
def make_stacked_stacked():
4009
_, stacked = self.prepare_stacked_remote_branch()
4010
tree = stacked.controldir.sprout('tree3', stacked=True
4011
).open_workingtree()
4012
local_tree = tree.branch.create_checkout('local-tree3')
4013
local_tree.commit('more local changes are better')
4014
branch = Branch.open(self.get_url('tree3'))
4016
self.addCleanup(branch.unlock)
4018
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
4019
branch_factory=make_stacked_stacked)
4020
self.assertEqual(set(expected_revs), set(rev_ord))
4021
# Getting unordered results should have made a streaming data request
4022
# from the server, and one from each backing repo
4023
self.assertLength(3, self.hpss_calls)
4025
def test_stacked_get_stream_topological(self):
4026
# Repository._get_source.get_stream() from a stacked repository with
4027
# topological sorting yields the full data from both stacked and
4028
# stacked upon sources in topological order.
4029
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4030
self.assertEqual(expected_revs, rev_ord)
4031
# Getting topological sort requires VFS calls still - one of which is
4032
# pushing up from the bound branch.
4033
self.assertLength(14, self.hpss_calls)
4035
def test_stacked_get_stream_groupcompress(self):
4036
# Repository._get_source.get_stream() from a stacked repository with
4037
# groupcompress sorting yields the full data from both stacked and
4038
# stacked upon sources in groupcompress order.
4039
raise tests.TestSkipped('No groupcompress ordered format available')
4040
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4041
self.assertEqual(expected_revs, reversed(rev_ord))
4042
# Getting unordered results should have made a streaming data request
4043
# from the backing branch, and one from the stacked on branch.
4044
self.assertLength(2, self.hpss_calls)
4046
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4047
# When pulling some fixed amount of content that is more than the
4048
# source has (because some is coming from a fallback branch, no error
4049
# should be received. This was reported as bug 360791.
4050
# Need three branches: a trunk, a stacked branch, and a preexisting
4051
# branch pulling content from stacked and trunk.
4052
self.setup_smart_server_with_call_log()
4053
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4054
r1 = trunk.commit('start')
4055
stacked_branch = trunk.branch.create_clone_on_transport(
4056
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4057
local = self.make_branch('local', format='1.9-rich-root')
4058
local.repository.fetch(stacked_branch.repository,
4059
stacked_branch.last_revision())
4062
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4065
super(TestRemoteBranchEffort, self).setUp()
4066
# Create a smart server that publishes whatever the backing VFS server
4068
self.smart_server = test_server.SmartTCPServer_for_testing()
4069
self.start_server(self.smart_server, self.get_server())
4070
# Log all HPSS calls into self.hpss_calls.
4071
_SmartClient.hooks.install_named_hook(
4072
'call', self.capture_hpss_call, None)
4073
self.hpss_calls = []
4075
def capture_hpss_call(self, params):
4076
self.hpss_calls.append(params.method)
4078
def test_copy_content_into_avoids_revision_history(self):
4079
local = self.make_branch('local')
4080
builder = self.make_branch_builder('remote')
4081
builder.build_commit(message="Commit.")
4082
remote_branch_url = self.smart_server.get_url() + 'remote'
4083
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4084
local.repository.fetch(remote_branch.repository)
4085
self.hpss_calls = []
4086
remote_branch.copy_content_into(local)
4087
self.assertFalse(b'Branch.revision_history' in self.hpss_calls)
4089
def test_fetch_everything_needs_just_one_call(self):
4090
local = self.make_branch('local')
4091
builder = self.make_branch_builder('remote')
4092
builder.build_commit(message="Commit.")
4093
remote_branch_url = self.smart_server.get_url() + 'remote'
4094
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4095
self.hpss_calls = []
4096
local.repository.fetch(
4097
remote_branch.repository,
4098
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4099
self.assertEqual([b'Repository.get_stream_1.19'], self.hpss_calls)
4101
def override_verb(self, verb_name, verb):
4102
request_handlers = request.request_handlers
4103
orig_verb = request_handlers.get(verb_name)
4104
orig_info = request_handlers.get_info(verb_name)
4105
request_handlers.register(verb_name, verb, override_existing=True)
4106
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4107
override_existing=True, info=orig_info)
4109
def test_fetch_everything_backwards_compat(self):
4110
"""Can fetch with EverythingResult even with pre 2.4 servers.
4112
Pre-2.4 do not support 'everything' searches with the
4113
Repository.get_stream_1.19 verb.
4116
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4117
"""A version of the Repository.get_stream_1.19 verb patched to
4118
reject 'everything' searches the way 2.3 and earlier do.
4120
def recreate_search(self, repository, search_bytes,
4121
discard_excess=False):
4122
verb_log.append(search_bytes.split(b'\n', 1)[0])
4123
if search_bytes == b'everything':
4125
request.FailedSmartServerResponse((b'BadSearch',)))
4126
return super(OldGetStreamVerb,
4127
self).recreate_search(repository, search_bytes,
4128
discard_excess=discard_excess)
4129
self.override_verb(b'Repository.get_stream_1.19', OldGetStreamVerb)
4130
local = self.make_branch('local')
4131
builder = self.make_branch_builder('remote')
4132
builder.build_commit(message="Commit.")
4133
remote_branch_url = self.smart_server.get_url() + 'remote'
4134
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4135
self.hpss_calls = []
4136
local.repository.fetch(
4137
remote_branch.repository,
4138
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4139
# make sure the overridden verb was used
4140
self.assertLength(1, verb_log)
4141
# more than one HPSS call is needed, but because it's a VFS callback
4142
# its hard to predict exactly how many.
4143
self.assertTrue(len(self.hpss_calls) > 1)
4146
class TestUpdateBoundBranchWithModifiedBoundLocation(
4147
tests.TestCaseWithTransport):
4148
"""Ensure correct handling of bound_location modifications.
4150
This is tested against a smart server as http://pad.lv/786980 was about a
4151
ReadOnlyError (write attempt during a read-only transaction) which can only
4152
happen in this context.
4156
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4157
self.transport_server = test_server.SmartTCPServer_for_testing
4159
def make_master_and_checkout(self, master_name, checkout_name):
4160
# Create the master branch and its associated checkout
4161
self.master = self.make_branch_and_tree(master_name)
4162
self.checkout = self.master.branch.create_checkout(checkout_name)
4163
# Modify the master branch so there is something to update
4164
self.master.commit('add stuff')
4165
self.last_revid = self.master.commit('even more stuff')
4166
self.bound_location = self.checkout.branch.get_bound_location()
4168
def assertUpdateSucceeds(self, new_location):
4169
self.checkout.branch.set_bound_location(new_location)
4170
self.checkout.update()
4171
self.assertEqual(self.last_revid, self.checkout.last_revision())
4173
def test_without_final_slash(self):
4174
self.make_master_and_checkout('master', 'checkout')
4175
# For unclear reasons some users have a bound_location without a final
4176
# '/', simulate that by forcing such a value
4177
self.assertEndsWith(self.bound_location, '/')
4178
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4180
def test_plus_sign(self):
4181
self.make_master_and_checkout('+master', 'checkout')
4182
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4184
def test_tilda(self):
4185
# Embed ~ in the middle of the path just to avoid any $HOME
4187
self.make_master_and_checkout('mas~ter', 'checkout')
4188
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4191
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4193
def test_no_context(self):
4194
class OutOfCoffee(errors.BzrError):
4195
"""A dummy exception for testing."""
4197
def __init__(self, urgency):
4198
self.urgency = urgency
4199
remote.no_context_error_translators.register(b"OutOfCoffee",
4200
lambda err: OutOfCoffee(err.error_args[0]))
4201
transport = MemoryTransport()
4202
client = FakeClient(transport.base)
4203
client.add_expected_call(
4204
b'Branch.get_stacked_on_url', (b'quack/',),
4205
b'error', (b'NotStacked',))
4206
client.add_expected_call(
4207
b'Branch.last_revision_info',
4209
b'error', (b'OutOfCoffee', b'low'))
4210
transport.mkdir('quack')
4211
transport = transport.clone('quack')
4212
branch = self.make_remote_branch(transport, client)
4213
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4214
self.assertFinished(client)
4216
def test_with_context(self):
4217
class OutOfTea(errors.BzrError):
4218
def __init__(self, branch, urgency):
4219
self.branch = branch
4220
self.urgency = urgency
4221
remote.error_translators.register(b"OutOfTea",
4222
lambda err, find, path: OutOfTea(
4223
err.error_args[0].decode('utf-8'),
4225
transport = MemoryTransport()
4226
client = FakeClient(transport.base)
4227
client.add_expected_call(
4228
b'Branch.get_stacked_on_url', (b'quack/',),
4229
b'error', (b'NotStacked',))
4230
client.add_expected_call(
4231
b'Branch.last_revision_info',
4233
b'error', (b'OutOfTea', b'low'))
4234
transport.mkdir('quack')
4235
transport = transport.clone('quack')
4236
branch = self.make_remote_branch(transport, client)
4237
self.assertRaises(OutOfTea, branch.last_revision_info)
4238
self.assertFinished(client)
4241
class TestRepositoryPack(TestRemoteRepository):
4243
def test_pack(self):
4244
transport_path = 'quack'
4245
repo, client = self.setup_fake_client_and_repository(transport_path)
4246
client.add_expected_call(
4247
b'Repository.lock_write', (b'quack/', b''),
4248
b'success', (b'ok', b'token'))
4249
client.add_expected_call(
4250
b'Repository.pack', (b'quack/', b'token', b'False'),
4251
b'success', (b'ok',), )
4252
client.add_expected_call(
4253
b'Repository.unlock', (b'quack/', b'token'),
4254
b'success', (b'ok', ))
4257
def test_pack_with_hint(self):
4258
transport_path = 'quack'
4259
repo, client = self.setup_fake_client_and_repository(transport_path)
4260
client.add_expected_call(
4261
b'Repository.lock_write', (b'quack/', b''),
4262
b'success', (b'ok', b'token'))
4263
client.add_expected_call(
4264
b'Repository.pack', (b'quack/', b'token', b'False'),
4265
b'success', (b'ok',), )
4266
client.add_expected_call(
4267
b'Repository.unlock', (b'quack/', b'token', b'False'),
4268
b'success', (b'ok', ))
4269
repo.pack([b'hinta', b'hintb'])
4272
class TestRepositoryIterInventories(TestRemoteRepository):
4273
"""Test Repository.iter_inventories."""
4275
def _serialize_inv_delta(self, old_name, new_name, delta):
4276
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4277
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4279
def test_single_empty(self):
4280
transport_path = 'quack'
4281
repo, client = self.setup_fake_client_and_repository(transport_path)
4282
fmt = controldir.format_registry.get('2a')().repository_format
4284
stream = [('inventory-deltas', [
4285
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4286
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4287
client.add_expected_call(
4288
b'VersionedFileRepository.get_inventories', (b'quack/', b'unordered'),
4289
b'success', (b'ok', ),
4290
_stream_to_byte_stream(stream, fmt))
4291
ret = list(repo.iter_inventories([b"somerevid"]))
4292
self.assertLength(1, ret)
4294
self.assertEqual(b"somerevid", inv.revision_id)
4296
def test_empty(self):
4297
transport_path = 'quack'
4298
repo, client = self.setup_fake_client_and_repository(transport_path)
4299
ret = list(repo.iter_inventories([]))
4300
self.assertEqual(ret, [])
4302
def test_missing(self):
4303
transport_path = 'quack'
4304
repo, client = self.setup_fake_client_and_repository(transport_path)
4305
client.add_expected_call(
4306
b'VersionedFileRepository.get_inventories', (b'quack/', b'unordered'),
4307
b'success', (b'ok', ), iter([]))
4308
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(
4312
class TestRepositoryRevisionTreeArchive(TestRemoteRepository):
4313
"""Test Repository.iter_inventories."""
4315
def _serialize_inv_delta(self, old_name, new_name, delta):
4316
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4317
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4319
def test_simple(self):
4320
transport_path = 'quack'
4321
repo, client = self.setup_fake_client_and_repository(transport_path)
4322
fmt = controldir.format_registry.get('2a')().repository_format
4324
stream = [('inventory-deltas', [
4325
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4326
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4327
client.add_expected_call(
4328
b'VersionedFileRepository.get_inventories', (b'quack/', b'unordered'),
4329
b'success', (b'ok', ),
4330
_stream_to_byte_stream(stream, fmt))
4332
with tarfile.open(mode='w', fileobj=f) as tf:
4333
info = tarfile.TarInfo('somefile')
4335
contents = b'some data'
4336
info.type = tarfile.REGTYPE
4338
info.size = len(contents)
4339
tf.addfile(info, BytesIO(contents))
4340
client.add_expected_call(
4341
b'Repository.revision_archive', (b'quack/', b'somerevid', b'tar', b'foo.tar', b'', b'', None),
4342
b'success', (b'ok', ),
4344
tree = repo.revision_tree(b'somerevid')
4345
self.assertEqual(f.getvalue(), b''.join(tree.archive('tar', 'foo.tar')))
4348
class TestRepositoryAnnotate(TestRemoteRepository):
4349
"""Test RemoteRevisionTree.annotate.."""
4351
def _serialize_inv_delta(self, old_name, new_name, delta):
4352
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4353
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4355
def test_simple(self):
4356
transport_path = 'quack'
4357
repo, client = self.setup_fake_client_and_repository(transport_path)
4358
fmt = controldir.format_registry.get('2a')().repository_format
4360
stream = [('inventory-deltas', [
4361
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4362
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4363
client.add_expected_call(
4364
b'VersionedFileRepository.get_inventories', (b'quack/', b'unordered'),
4365
b'success', (b'ok', ),
4366
_stream_to_byte_stream(stream, fmt))
4367
client.add_expected_call(
4368
b'Repository.annotate_file_revision',
4369
(b'quack/', b'somerevid', b'filename', b'', b'current:'),
4370
b'success', (b'ok', ),
4371
bencode.bencode([[b'baserevid', b'line 1\n'],
4372
[b'somerevid', b'line2\n']]))
4373
tree = repo.revision_tree(b'somerevid')
4375
(b'baserevid', b'line 1\n'),
4376
(b'somerevid', b'line2\n')],
4377
list(tree.annotate_iter('filename')))