1
# Copyright (C) 2006-2013, 2016 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for remote bzrdir/branch/repo/etc
19
These are proxy objects which act on remote objects by sending messages
20
through a smart client. The proxies are to be created when attempting to open
21
the object given a transport that supports smartserver rpc operations.
23
These tests correspond to tests.test_smart, which exercises the server side.
42
from ..branch import Branch
51
from ..bzr.bzrdir import (
58
from ..bzr.chk_serializer import chk_bencode_serializer
59
from ..bzr.remote import (
65
RemoteRepositoryFormat,
67
from ..bzr import groupcompress_repo, knitpack_repo
68
from ..revision import (
72
from ..sixish import (
77
from ..bzr.smart import medium, request
78
from ..bzr.smart.client import _SmartClient
79
from ..bzr.smart.repository import (
80
SmartServerRepositoryGetParentMap,
81
SmartServerRepositoryGetStream_1_19,
82
_stream_to_byte_stream,
87
from .scenarios import load_tests_apply_scenarios
88
from ..transport.memory import MemoryTransport
89
from ..transport.remote import (
96
load_tests = load_tests_apply_scenarios
99
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
104
test_server.SmartTCPServer_for_testing_v2_only}),
106
{'transport_server': test_server.SmartTCPServer_for_testing})]
109
super(BasicRemoteObjectTests, self).setUp()
110
self.transport = self.get_transport()
111
# make a branch that can be opened over the smart transport
112
self.local_wt = BzrDir.create_standalone_workingtree('.')
113
self.addCleanup(self.transport.disconnect)
115
def test_create_remote_bzrdir(self):
116
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
117
self.assertIsInstance(b, BzrDir)
119
def test_open_remote_branch(self):
120
# open a standalone branch in the working directory
121
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
122
branch = b.open_branch()
123
self.assertIsInstance(branch, Branch)
125
def test_remote_repository(self):
126
b = BzrDir.open_from_transport(self.transport)
127
repo = b.open_repository()
128
revid = u'\xc823123123'.encode('utf8')
129
self.assertFalse(repo.has_revision(revid))
130
self.local_wt.commit(message='test commit', rev_id=revid)
131
self.assertTrue(repo.has_revision(revid))
133
def test_find_correct_format(self):
134
"""Should open a RemoteBzrDir over a RemoteTransport"""
135
fmt = BzrDirFormat.find_format(self.transport)
136
self.assertTrue(RemoteBzrProber
137
in controldir.ControlDirFormat._server_probers)
138
self.assertIsInstance(fmt, RemoteBzrDirFormat)
140
def test_open_detected_smart_format(self):
141
fmt = BzrDirFormat.find_format(self.transport)
142
d = fmt.open(self.transport)
143
self.assertIsInstance(d, BzrDir)
145
def test_remote_branch_repr(self):
146
b = BzrDir.open_from_transport(self.transport).open_branch()
147
self.assertStartsWith(str(b), 'RemoteBranch(')
149
def test_remote_bzrdir_repr(self):
150
b = BzrDir.open_from_transport(self.transport)
151
self.assertStartsWith(str(b), 'RemoteBzrDir(')
153
def test_remote_branch_format_supports_stacking(self):
155
self.make_branch('unstackable', format='pack-0.92')
156
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
157
self.assertFalse(b._format.supports_stacking())
158
self.make_branch('stackable', format='1.9')
159
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
160
self.assertTrue(b._format.supports_stacking())
162
def test_remote_repo_format_supports_external_references(self):
164
bd = self.make_controldir('unstackable', format='pack-0.92')
165
r = bd.create_repository()
166
self.assertFalse(r._format.supports_external_lookups)
167
r = BzrDir.open_from_transport(
168
t.clone('unstackable')).open_repository()
169
self.assertFalse(r._format.supports_external_lookups)
170
bd = self.make_controldir('stackable', format='1.9')
171
r = bd.create_repository()
172
self.assertTrue(r._format.supports_external_lookups)
173
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
174
self.assertTrue(r._format.supports_external_lookups)
176
def test_remote_branch_set_append_revisions_only(self):
177
# Make a format 1.9 branch, which supports append_revisions_only
178
branch = self.make_branch('branch', format='1.9')
179
branch.set_append_revisions_only(True)
180
config = branch.get_config_stack()
182
True, config.get('append_revisions_only'))
183
branch.set_append_revisions_only(False)
184
config = branch.get_config_stack()
186
False, config.get('append_revisions_only'))
188
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
189
branch = self.make_branch('branch', format='knit')
191
errors.UpgradeRequired, branch.set_append_revisions_only, True)
194
class FakeProtocol(object):
195
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
197
def __init__(self, body, fake_client):
199
self._body_buffer = None
200
self._fake_client = fake_client
202
def read_body_bytes(self, count=-1):
203
if self._body_buffer is None:
204
self._body_buffer = BytesIO(self.body)
205
bytes = self._body_buffer.read(count)
206
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
207
self._fake_client.expecting_body = False
210
def cancel_read_body(self):
211
self._fake_client.expecting_body = False
213
def read_streamed_body(self):
217
class FakeClient(_SmartClient):
218
"""Lookalike for _SmartClient allowing testing."""
220
def __init__(self, fake_medium_base='fake base'):
221
"""Create a FakeClient."""
224
self.expecting_body = False
225
# if non-None, this is the list of expected calls, with only the
226
# method name and arguments included. the body might be hard to
227
# compute so is not included. If a call is None, that call can
229
self._expected_calls = None
230
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
232
def add_expected_call(self, call_name, call_args, response_type,
233
response_args, response_body=None):
234
if self._expected_calls is None:
235
self._expected_calls = []
236
self._expected_calls.append((call_name, call_args))
237
self.responses.append((response_type, response_args, response_body))
239
def add_success_response(self, *args):
240
self.responses.append((b'success', args, None))
242
def add_success_response_with_body(self, body, *args):
243
self.responses.append((b'success', args, body))
244
if self._expected_calls is not None:
245
self._expected_calls.append(None)
247
def add_error_response(self, *args):
248
self.responses.append((b'error', args))
250
def add_unknown_method_response(self, verb):
251
self.responses.append((b'unknown', verb))
253
def finished_test(self):
254
if self._expected_calls:
255
raise AssertionError("%r finished but was still expecting %r"
256
% (self, self._expected_calls[0]))
258
def _get_next_response(self):
260
response_tuple = self.responses.pop(0)
262
raise AssertionError("%r didn't expect any more calls" % (self,))
263
if response_tuple[0] == b'unknown':
264
raise errors.UnknownSmartMethod(response_tuple[1])
265
elif response_tuple[0] == b'error':
266
raise errors.ErrorFromSmartServer(response_tuple[1])
267
return response_tuple
269
def _check_call(self, method, args):
270
if self._expected_calls is None:
271
# the test should be updated to say what it expects
274
next_call = self._expected_calls.pop(0)
276
raise AssertionError("%r didn't expect any more calls "
278
% (self, method, args,))
279
if next_call is None:
281
if method != next_call[0] or args != next_call[1]:
282
raise AssertionError(
283
"%r expected %r%r but got %r%r" %
284
(self, next_call[0], next_call[1], method, args,))
286
def call(self, method, *args):
287
self._check_call(method, args)
288
self._calls.append(('call', method, args))
289
return self._get_next_response()[1]
291
def call_expecting_body(self, method, *args):
292
self._check_call(method, args)
293
self._calls.append(('call_expecting_body', method, args))
294
result = self._get_next_response()
295
self.expecting_body = True
296
return result[1], FakeProtocol(result[2], self)
298
def call_with_body_bytes(self, method, args, body):
299
self._check_call(method, args)
300
self._calls.append(('call_with_body_bytes', method, args, body))
301
result = self._get_next_response()
302
return result[1], FakeProtocol(result[2], self)
304
def call_with_body_bytes_expecting_body(self, method, args, body):
305
self._check_call(method, args)
306
self._calls.append(('call_with_body_bytes_expecting_body', method,
308
result = self._get_next_response()
309
self.expecting_body = True
310
return result[1], FakeProtocol(result[2], self)
312
def call_with_body_stream(self, args, stream):
313
# Explicitly consume the stream before checking for an error, because
314
# that's what happens a real medium.
315
stream = list(stream)
316
self._check_call(args[0], args[1:])
318
('call_with_body_stream', args[0], args[1:], stream))
319
result = self._get_next_response()
320
# The second value returned from call_with_body_stream is supposed to
321
# be a response_handler object, but so far no tests depend on that.
322
response_handler = None
323
return result[1], response_handler
326
class FakeMedium(medium.SmartClientMedium):
328
def __init__(self, client_calls, base):
329
medium.SmartClientMedium.__init__(self, base)
330
self._client_calls = client_calls
332
def disconnect(self):
333
self._client_calls.append(('disconnect medium',))
336
class TestVfsHas(tests.TestCase):
338
def test_unicode_path(self):
339
client = FakeClient('/')
340
client.add_success_response(b'yes',)
341
transport = RemoteTransport('bzr://localhost/', _client=client)
342
filename = u'/hell\u00d8'
344
result = transport.has(filename)
346
result = transport.has(filename.encode('utf-8'))
348
[('call', b'has', (filename.encode('utf-8'),))],
350
self.assertTrue(result)
353
class TestRemote(tests.TestCaseWithMemoryTransport):
355
def get_branch_format(self):
356
reference_bzrdir_format = controldir.format_registry.get('default')()
357
return reference_bzrdir_format.get_branch_format()
359
def get_repo_format(self):
360
reference_bzrdir_format = controldir.format_registry.get('default')()
361
return reference_bzrdir_format.repository_format
363
def assertFinished(self, fake_client):
364
"""Assert that all of a FakeClient's expected calls have occurred."""
365
fake_client.finished_test()
368
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
369
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
371
def assertRemotePath(self, expected, client_base, transport_base):
372
"""Assert that the result of
373
SmartClientMedium.remote_path_from_transport is the expected value for
374
a given client_base and transport_base.
376
client_medium = medium.SmartClientMedium(client_base)
377
t = transport.get_transport(transport_base)
378
result = client_medium.remote_path_from_transport(t)
379
self.assertEqual(expected, result)
381
def test_remote_path_from_transport(self):
382
"""SmartClientMedium.remote_path_from_transport calculates a URL for
383
the given transport relative to the root of the client base URL.
385
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
386
self.assertRemotePath(
387
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
389
def assertRemotePathHTTP(self, expected, transport_base, relpath):
390
"""Assert that the result of
391
HttpTransportBase.remote_path_from_transport is the expected value for
392
a given transport_base and relpath of that transport. (Note that
393
HttpTransportBase is a subclass of SmartClientMedium)
395
base_transport = transport.get_transport(transport_base)
396
client_medium = base_transport.get_smart_medium()
397
cloned_transport = base_transport.clone(relpath)
398
result = client_medium.remote_path_from_transport(cloned_transport)
399
self.assertEqual(expected, result)
401
def test_remote_path_from_transport_http(self):
402
"""Remote paths for HTTP transports are calculated differently to other
403
transports. They are just relative to the client base, not the root
404
directory of the host.
406
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
407
self.assertRemotePathHTTP(
408
'../xyz/', scheme + '//host/path', '../xyz/')
409
self.assertRemotePathHTTP(
410
'xyz/', scheme + '//host/path', 'xyz/')
413
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
414
"""Tests for the behaviour of client_medium.remote_is_at_least."""
416
def test_initially_unlimited(self):
417
"""A fresh medium assumes that the remote side supports all
420
client_medium = medium.SmartClientMedium('dummy base')
421
self.assertFalse(client_medium._is_remote_before((99, 99)))
423
def test__remember_remote_is_before(self):
424
"""Calling _remember_remote_is_before ratchets down the known remote
427
client_medium = medium.SmartClientMedium('dummy base')
428
# Mark the remote side as being less than 1.6. The remote side may
430
client_medium._remember_remote_is_before((1, 6))
431
self.assertTrue(client_medium._is_remote_before((1, 6)))
432
self.assertFalse(client_medium._is_remote_before((1, 5)))
433
# Calling _remember_remote_is_before again with a lower value works.
434
client_medium._remember_remote_is_before((1, 5))
435
self.assertTrue(client_medium._is_remote_before((1, 5)))
436
# If you call _remember_remote_is_before with a higher value it logs a
437
# warning, and continues to remember the lower value.
438
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
439
client_medium._remember_remote_is_before((1, 9))
440
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
441
self.assertTrue(client_medium._is_remote_before((1, 5)))
444
class TestBzrDirCloningMetaDir(TestRemote):
446
def test_backwards_compat(self):
447
self.setup_smart_server_with_call_log()
448
a_dir = self.make_controldir('.')
449
self.reset_smart_call_log()
450
verb = b'BzrDir.cloning_metadir'
451
self.disable_verb(verb)
452
a_dir.cloning_metadir()
453
call_count = len([call for call in self.hpss_calls if
454
call.call.method == verb])
455
self.assertEqual(1, call_count)
457
def test_branch_reference(self):
458
transport = self.get_transport('quack')
459
referenced = self.make_branch('referenced')
460
expected = referenced.controldir.cloning_metadir()
461
client = FakeClient(transport.base)
462
client.add_expected_call(
463
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
464
b'error', (b'BranchReference',)),
465
client.add_expected_call(
466
b'BzrDir.open_branchV3', (b'quack/',),
467
b'success', (b'ref', self.get_url('referenced').encode('utf-8'))),
468
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
470
result = a_controldir.cloning_metadir()
471
# We should have got a control dir matching the referenced branch.
472
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
473
self.assertEqual(expected._repository_format,
474
result._repository_format)
475
self.assertEqual(expected._branch_format, result._branch_format)
476
self.assertFinished(client)
478
def test_current_server(self):
479
transport = self.get_transport('.')
480
transport = transport.clone('quack')
481
self.make_controldir('quack')
482
client = FakeClient(transport.base)
483
reference_bzrdir_format = controldir.format_registry.get('default')()
484
control_name = reference_bzrdir_format.network_name()
485
client.add_expected_call(
486
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
487
b'success', (control_name, b'', (b'branch', b''))),
488
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
490
result = a_controldir.cloning_metadir()
491
# We should have got a reference control dir with default branch and
492
# repository formats.
493
# This pokes a little, just to be sure.
494
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
495
self.assertEqual(None, result._repository_format)
496
self.assertEqual(None, result._branch_format)
497
self.assertFinished(client)
499
def test_unknown(self):
500
transport = self.get_transport('quack')
501
referenced = self.make_branch('referenced')
502
referenced.controldir.cloning_metadir()
503
client = FakeClient(transport.base)
504
client.add_expected_call(
505
b'BzrDir.cloning_metadir', (b'quack/', b'False'),
506
b'success', (b'unknown', b'unknown', (b'branch', b''))),
507
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
509
self.assertRaises(errors.UnknownFormatError,
510
a_controldir.cloning_metadir)
513
class TestBzrDirCheckoutMetaDir(TestRemote):
515
def test__get_checkout_format(self):
516
transport = MemoryTransport()
517
client = FakeClient(transport.base)
518
reference_bzrdir_format = controldir.format_registry.get('default')()
519
control_name = reference_bzrdir_format.network_name()
520
client.add_expected_call(
521
b'BzrDir.checkout_metadir', (b'quack/', ),
522
b'success', (control_name, b'', b''))
523
transport.mkdir('quack')
524
transport = transport.clone('quack')
525
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
527
result = a_controldir.checkout_metadir()
528
# We should have got a reference control dir with default branch and
529
# repository formats.
530
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
531
self.assertEqual(None, result._repository_format)
532
self.assertEqual(None, result._branch_format)
533
self.assertFinished(client)
535
def test_unknown_format(self):
536
transport = MemoryTransport()
537
client = FakeClient(transport.base)
538
client.add_expected_call(
539
b'BzrDir.checkout_metadir', (b'quack/',),
540
b'success', (b'dontknow', b'', b''))
541
transport.mkdir('quack')
542
transport = transport.clone('quack')
543
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
545
self.assertRaises(errors.UnknownFormatError,
546
a_controldir.checkout_metadir)
547
self.assertFinished(client)
550
class TestBzrDirGetBranches(TestRemote):
552
def test_get_branches(self):
553
transport = MemoryTransport()
554
client = FakeClient(transport.base)
555
reference_bzrdir_format = controldir.format_registry.get('default')()
556
branch_name = reference_bzrdir_format.get_branch_format().network_name()
557
client.add_success_response_with_body(
559
b"foo": (b"branch", branch_name),
560
b"": (b"branch", branch_name)}), b"success")
561
client.add_success_response(
562
b'ok', b'', b'no', b'no', b'no',
563
reference_bzrdir_format.repository_format.network_name())
564
client.add_error_response(b'NotStacked')
565
client.add_success_response(
566
b'ok', b'', b'no', b'no', b'no',
567
reference_bzrdir_format.repository_format.network_name())
568
client.add_error_response(b'NotStacked')
569
transport.mkdir('quack')
570
transport = transport.clone('quack')
571
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
573
result = a_controldir.get_branches()
574
self.assertEqual({"", "foo"}, set(result.keys()))
576
[('call_expecting_body', b'BzrDir.get_branches', (b'quack/',)),
577
('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
578
('call', b'Branch.get_stacked_on_url', (b'quack/', )),
579
('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
580
('call', b'Branch.get_stacked_on_url', (b'quack/', ))],
584
class TestBzrDirDestroyBranch(TestRemote):
586
def test_destroy_default(self):
587
transport = self.get_transport('quack')
588
self.make_branch('referenced')
589
client = FakeClient(transport.base)
590
client.add_expected_call(
591
b'BzrDir.destroy_branch', (b'quack/', ),
592
b'success', (b'ok',)),
593
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
595
a_controldir.destroy_branch()
596
self.assertFinished(client)
599
class TestBzrDirHasWorkingTree(TestRemote):
601
def test_has_workingtree(self):
602
transport = self.get_transport('quack')
603
client = FakeClient(transport.base)
604
client.add_expected_call(
605
b'BzrDir.has_workingtree', (b'quack/',),
606
b'success', (b'yes',)),
607
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
609
self.assertTrue(a_controldir.has_workingtree())
610
self.assertFinished(client)
612
def test_no_workingtree(self):
613
transport = self.get_transport('quack')
614
client = FakeClient(transport.base)
615
client.add_expected_call(
616
b'BzrDir.has_workingtree', (b'quack/',),
617
b'success', (b'no',)),
618
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
620
self.assertFalse(a_controldir.has_workingtree())
621
self.assertFinished(client)
624
class TestBzrDirDestroyRepository(TestRemote):
626
def test_destroy_repository(self):
627
transport = self.get_transport('quack')
628
client = FakeClient(transport.base)
629
client.add_expected_call(
630
b'BzrDir.destroy_repository', (b'quack/',),
631
b'success', (b'ok',)),
632
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
634
a_controldir.destroy_repository()
635
self.assertFinished(client)
638
class TestBzrDirOpen(TestRemote):
640
def make_fake_client_and_transport(self, path='quack'):
641
transport = MemoryTransport()
642
transport.mkdir(path)
643
transport = transport.clone(path)
644
client = FakeClient(transport.base)
645
return client, transport
647
def test_absent(self):
648
client, transport = self.make_fake_client_and_transport()
649
client.add_expected_call(
650
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'no',))
651
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
652
RemoteBzrDirFormat(), _client=client,
654
self.assertFinished(client)
656
def test_present_without_workingtree(self):
657
client, transport = self.make_fake_client_and_transport()
658
client.add_expected_call(
659
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'no'))
660
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
661
_client=client, _force_probe=True)
662
self.assertIsInstance(bd, RemoteBzrDir)
663
self.assertFalse(bd.has_workingtree())
664
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
665
self.assertFinished(client)
667
def test_present_with_workingtree(self):
668
client, transport = self.make_fake_client_and_transport()
669
client.add_expected_call(
670
b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'yes'))
671
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
672
_client=client, _force_probe=True)
673
self.assertIsInstance(bd, RemoteBzrDir)
674
self.assertTrue(bd.has_workingtree())
675
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
676
self.assertFinished(client)
678
def test_backwards_compat(self):
679
client, transport = self.make_fake_client_and_transport()
680
client.add_expected_call(
681
b'BzrDir.open_2.1', (b'quack/',), b'unknown',
682
(b'BzrDir.open_2.1',))
683
client.add_expected_call(
684
b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
685
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
686
_client=client, _force_probe=True)
687
self.assertIsInstance(bd, RemoteBzrDir)
688
self.assertFinished(client)
690
def test_backwards_compat_hpss_v2(self):
691
client, transport = self.make_fake_client_and_transport()
692
# Monkey-patch fake client to simulate real-world behaviour with v2
693
# server: upon first RPC call detect the protocol version, and because
694
# the version is 2 also do _remember_remote_is_before((1, 6)) before
695
# continuing with the RPC.
696
orig_check_call = client._check_call
698
def check_call(method, args):
699
client._medium._protocol_version = 2
700
client._medium._remember_remote_is_before((1, 6))
701
client._check_call = orig_check_call
702
client._check_call(method, args)
703
client._check_call = check_call
704
client.add_expected_call(
705
b'BzrDir.open_2.1', (b'quack/',), b'unknown',
706
(b'BzrDir.open_2.1',))
707
client.add_expected_call(
708
b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
709
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
710
_client=client, _force_probe=True)
711
self.assertIsInstance(bd, RemoteBzrDir)
712
self.assertFinished(client)
715
class TestBzrDirOpenBranch(TestRemote):
717
def test_backwards_compat(self):
718
self.setup_smart_server_with_call_log()
719
self.make_branch('.')
720
a_dir = BzrDir.open(self.get_url('.'))
721
self.reset_smart_call_log()
722
verb = b'BzrDir.open_branchV3'
723
self.disable_verb(verb)
725
call_count = len([call for call in self.hpss_calls if
726
call.call.method == verb])
727
self.assertEqual(1, call_count)
729
def test_branch_present(self):
730
reference_format = self.get_repo_format()
731
network_name = reference_format.network_name()
732
branch_network_name = self.get_branch_format().network_name()
733
transport = MemoryTransport()
734
transport.mkdir('quack')
735
transport = transport.clone('quack')
736
client = FakeClient(transport.base)
737
client.add_expected_call(
738
b'BzrDir.open_branchV3', (b'quack/',),
739
b'success', (b'branch', branch_network_name))
740
client.add_expected_call(
741
b'BzrDir.find_repositoryV3', (b'quack/',),
742
b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
743
client.add_expected_call(
744
b'Branch.get_stacked_on_url', (b'quack/',),
745
b'error', (b'NotStacked',))
746
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
748
result = bzrdir.open_branch()
749
self.assertIsInstance(result, RemoteBranch)
750
self.assertEqual(bzrdir, result.controldir)
751
self.assertFinished(client)
753
def test_branch_missing(self):
754
transport = MemoryTransport()
755
transport.mkdir('quack')
756
transport = transport.clone('quack')
757
client = FakeClient(transport.base)
758
client.add_error_response(b'nobranch')
759
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
761
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
763
[('call', b'BzrDir.open_branchV3', (b'quack/',))],
766
def test__get_tree_branch(self):
767
# _get_tree_branch is a form of open_branch, but it should only ask for
768
# branch opening, not any other network requests.
771
def open_branch(name=None, possible_transports=None):
772
calls.append("Called")
774
transport = MemoryTransport()
775
# no requests on the network - catches other api calls being made.
776
client = FakeClient(transport.base)
777
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
779
# patch the open_branch call to record that it was called.
780
bzrdir.open_branch = open_branch
781
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
782
self.assertEqual(["Called"], calls)
783
self.assertEqual([], client._calls)
785
def test_url_quoting_of_path(self):
786
# Relpaths on the wire should not be URL-escaped. So "~" should be
787
# transmitted as "~", not "%7E".
788
transport = RemoteTCPTransport('bzr://localhost/~hello/')
789
client = FakeClient(transport.base)
790
reference_format = self.get_repo_format()
791
network_name = reference_format.network_name()
792
branch_network_name = self.get_branch_format().network_name()
793
client.add_expected_call(
794
b'BzrDir.open_branchV3', (b'~hello/',),
795
b'success', (b'branch', branch_network_name))
796
client.add_expected_call(
797
b'BzrDir.find_repositoryV3', (b'~hello/',),
798
b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
799
client.add_expected_call(
800
b'Branch.get_stacked_on_url', (b'~hello/',),
801
b'error', (b'NotStacked',))
802
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
805
self.assertFinished(client)
807
def check_open_repository(self, rich_root, subtrees,
808
external_lookup=b'no'):
809
reference_format = self.get_repo_format()
810
network_name = reference_format.network_name()
811
transport = MemoryTransport()
812
transport.mkdir('quack')
813
transport = transport.clone('quack')
815
rich_response = b'yes'
817
rich_response = b'no'
819
subtree_response = b'yes'
821
subtree_response = b'no'
822
client = FakeClient(transport.base)
823
client.add_success_response(
824
b'ok', b'', rich_response, subtree_response, external_lookup,
826
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
828
result = bzrdir.open_repository()
830
[('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
832
self.assertIsInstance(result, RemoteRepository)
833
self.assertEqual(bzrdir, result.controldir)
834
self.assertEqual(rich_root, result._format.rich_root_data)
835
self.assertEqual(subtrees, result._format.supports_tree_reference)
837
def test_open_repository_sets_format_attributes(self):
838
self.check_open_repository(True, True)
839
self.check_open_repository(False, True)
840
self.check_open_repository(True, False)
841
self.check_open_repository(False, False)
842
self.check_open_repository(False, False, b'yes')
844
def test_old_server(self):
845
"""RemoteBzrDirFormat should fail to probe if the server version is too
849
errors.NotBranchError,
850
RemoteBzrProber.probe_transport, OldServerTransport())
853
class TestBzrDirCreateBranch(TestRemote):
855
def test_backwards_compat(self):
856
self.setup_smart_server_with_call_log()
857
repo = self.make_repository('.')
858
self.reset_smart_call_log()
859
self.disable_verb(b'BzrDir.create_branch')
860
repo.controldir.create_branch()
861
create_branch_call_count = len(
862
[call for call in self.hpss_calls
863
if call.call.method == b'BzrDir.create_branch'])
864
self.assertEqual(1, create_branch_call_count)
866
def test_current_server(self):
867
transport = self.get_transport('.')
868
transport = transport.clone('quack')
869
self.make_repository('quack')
870
client = FakeClient(transport.base)
871
reference_bzrdir_format = controldir.format_registry.get('default')()
872
reference_format = reference_bzrdir_format.get_branch_format()
873
network_name = reference_format.network_name()
874
reference_repo_fmt = reference_bzrdir_format.repository_format
875
reference_repo_name = reference_repo_fmt.network_name()
876
client.add_expected_call(
877
b'BzrDir.create_branch', (b'quack/', network_name),
878
b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
879
reference_repo_name))
880
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
882
branch = a_controldir.create_branch()
883
# We should have got a remote branch
884
self.assertIsInstance(branch, remote.RemoteBranch)
885
# its format should have the settings from the response
886
format = branch._format
887
self.assertEqual(network_name, format.network_name())
889
def test_already_open_repo_and_reused_medium(self):
890
"""Bug 726584: create_branch(..., repository=repo) should work
891
regardless of what the smart medium's base URL is.
893
self.transport_server = test_server.SmartTCPServer_for_testing
894
transport = self.get_transport('.')
895
repo = self.make_repository('quack')
896
# Client's medium rooted a transport root (not at the bzrdir)
897
client = FakeClient(transport.base)
898
transport = transport.clone('quack')
899
reference_bzrdir_format = controldir.format_registry.get('default')()
900
reference_format = reference_bzrdir_format.get_branch_format()
901
network_name = reference_format.network_name()
902
reference_repo_fmt = reference_bzrdir_format.repository_format
903
reference_repo_name = reference_repo_fmt.network_name()
904
client.add_expected_call(
905
b'BzrDir.create_branch', (b'extra/quack/', network_name),
906
b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
907
reference_repo_name))
908
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
910
branch = a_controldir.create_branch(repository=repo)
911
# We should have got a remote branch
912
self.assertIsInstance(branch, remote.RemoteBranch)
913
# its format should have the settings from the response
914
format = branch._format
915
self.assertEqual(network_name, format.network_name())
918
class TestBzrDirCreateRepository(TestRemote):
920
def test_backwards_compat(self):
921
self.setup_smart_server_with_call_log()
922
bzrdir = self.make_controldir('.')
923
self.reset_smart_call_log()
924
self.disable_verb(b'BzrDir.create_repository')
925
bzrdir.create_repository()
926
create_repo_call_count = len([call for call in self.hpss_calls if
927
call.call.method == b'BzrDir.create_repository'])
928
self.assertEqual(1, create_repo_call_count)
930
def test_current_server(self):
931
transport = self.get_transport('.')
932
transport = transport.clone('quack')
933
self.make_controldir('quack')
934
client = FakeClient(transport.base)
935
reference_bzrdir_format = controldir.format_registry.get('default')()
936
reference_format = reference_bzrdir_format.repository_format
937
network_name = reference_format.network_name()
938
client.add_expected_call(
939
b'BzrDir.create_repository', (b'quack/',
940
b'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
942
b'success', (b'ok', b'yes', b'yes', b'yes', network_name))
943
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
945
repo = a_controldir.create_repository()
946
# We should have got a remote repository
947
self.assertIsInstance(repo, remote.RemoteRepository)
948
# its format should have the settings from the response
949
format = repo._format
950
self.assertTrue(format.rich_root_data)
951
self.assertTrue(format.supports_tree_reference)
952
self.assertTrue(format.supports_external_lookups)
953
self.assertEqual(network_name, format.network_name())
956
class TestBzrDirOpenRepository(TestRemote):
958
def test_backwards_compat_1_2_3(self):
959
# fallback all the way to the first version.
960
reference_format = self.get_repo_format()
961
network_name = reference_format.network_name()
962
server_url = 'bzr://example.com/'
963
self.permit_url(server_url)
964
client = FakeClient(server_url)
965
client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
966
client.add_unknown_method_response(b'BzrDir.find_repositoryV2')
967
client.add_success_response(b'ok', b'', b'no', b'no')
968
# A real repository instance will be created to determine the network
970
client.add_success_response_with_body(
971
b"Bazaar-NG meta directory, format 1\n", b'ok')
972
client.add_success_response(b'stat', b'0', b'65535')
973
client.add_success_response_with_body(
974
reference_format.get_format_string(), b'ok')
975
# PackRepository wants to do a stat
976
client.add_success_response(b'stat', b'0', b'65535')
977
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
979
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
981
repo = bzrdir.open_repository()
983
[('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
984
('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
985
('call', b'BzrDir.find_repository', (b'quack/',)),
986
('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
987
('call', b'stat', (b'/quack/.bzr',)),
988
('call_expecting_body', b'get', (b'/quack/.bzr/repository/format',)),
989
('call', b'stat', (b'/quack/.bzr/repository',)),
992
self.assertEqual(network_name, repo._format.network_name())
994
def test_backwards_compat_2(self):
995
# fallback to find_repositoryV2
996
reference_format = self.get_repo_format()
997
network_name = reference_format.network_name()
998
server_url = 'bzr://example.com/'
999
self.permit_url(server_url)
1000
client = FakeClient(server_url)
1001
client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
1002
client.add_success_response(b'ok', b'', b'no', b'no', b'no')
1003
# A real repository instance will be created to determine the network
1005
client.add_success_response_with_body(
1006
b"Bazaar-NG meta directory, format 1\n", b'ok')
1007
client.add_success_response(b'stat', b'0', b'65535')
1008
client.add_success_response_with_body(
1009
reference_format.get_format_string(), b'ok')
1010
# PackRepository wants to do a stat
1011
client.add_success_response(b'stat', b'0', b'65535')
1012
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
1014
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
1016
repo = bzrdir.open_repository()
1018
[('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
1019
('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
1020
('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
1021
('call', b'stat', (b'/quack/.bzr',)),
1022
('call_expecting_body', b'get',
1023
(b'/quack/.bzr/repository/format',)),
1024
('call', b'stat', (b'/quack/.bzr/repository',)),
1027
self.assertEqual(network_name, repo._format.network_name())
1029
def test_current_server(self):
1030
reference_format = self.get_repo_format()
1031
network_name = reference_format.network_name()
1032
transport = MemoryTransport()
1033
transport.mkdir('quack')
1034
transport = transport.clone('quack')
1035
client = FakeClient(transport.base)
1036
client.add_success_response(
1037
b'ok', b'', b'no', b'no', b'no', network_name)
1038
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1040
repo = bzrdir.open_repository()
1042
[('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
1044
self.assertEqual(network_name, repo._format.network_name())
1047
class TestBzrDirFormatInitializeEx(TestRemote):
1049
def test_success(self):
1050
"""Simple test for typical successful call."""
1051
fmt = RemoteBzrDirFormat()
1052
default_format_name = BzrDirFormat.get_default_format().network_name()
1053
transport = self.get_transport()
1054
client = FakeClient(transport.base)
1055
client.add_expected_call(
1056
b'BzrDirFormat.initialize_ex_1.16',
1057
(default_format_name, b'path', b'False', b'False', b'False', b'',
1058
b'', b'', b'', b'False'),
1060
(b'.', b'no', b'no', b'yes', b'repo fmt', b'repo bzrdir fmt',
1061
b'bzrdir fmt', b'False', b'', b'', b'repo lock token'))
1062
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1063
# it's currently hard to test that without supplying a real remote
1064
# transport connected to a real server.
1065
fmt._initialize_on_transport_ex_rpc(
1066
client, b'path', transport, False, False, False, None, None, None,
1068
self.assertFinished(client)
1070
def test_error(self):
1071
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1072
corresponding error from the client.
1074
fmt = RemoteBzrDirFormat()
1075
default_format_name = BzrDirFormat.get_default_format().network_name()
1076
transport = self.get_transport()
1077
client = FakeClient(transport.base)
1078
client.add_expected_call(
1079
b'BzrDirFormat.initialize_ex_1.16',
1080
(default_format_name, b'path', b'False', b'False', b'False', b'',
1081
b'', b'', b'', b'False'),
1083
(b'PermissionDenied', b'path', b'extra info'))
1084
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1085
# it's currently hard to test that without supplying a real remote
1086
# transport connected to a real server.
1087
err = self.assertRaises(
1088
errors.PermissionDenied,
1089
fmt._initialize_on_transport_ex_rpc, client, b'path', transport,
1090
False, False, False, None, None, None, None, False)
1091
self.assertEqual('path', err.path)
1092
self.assertEqual(': extra info', err.extra)
1093
self.assertFinished(client)
1095
def test_error_from_real_server(self):
1096
"""Integration test for error translation."""
1097
transport = self.make_smart_server('foo')
1098
transport = transport.clone('no-such-path')
1099
fmt = RemoteBzrDirFormat()
1101
errors.NoSuchFile, fmt.initialize_on_transport_ex, transport,
1102
create_prefix=False)
1105
class OldSmartClient(object):
1106
"""A fake smart client for test_old_version that just returns a version one
1107
response to the 'hello' (query version) command.
1110
def get_request(self):
1111
input_file = BytesIO(b'ok\x011\n')
1112
output_file = BytesIO()
1113
client_medium = medium.SmartSimplePipesClientMedium(
1114
input_file, output_file)
1115
return medium.SmartClientStreamMediumRequest(client_medium)
1117
def protocol_version(self):
1121
class OldServerTransport(object):
1122
"""A fake transport for test_old_server that reports it's smart server
1123
protocol version as version one.
1129
def get_smart_client(self):
1130
return OldSmartClient()
1133
class RemoteBzrDirTestCase(TestRemote):
1135
def make_remote_bzrdir(self, transport, client):
1136
"""Make a RemotebzrDir using 'client' as the _client."""
1137
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1141
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1143
def lock_remote_branch(self, branch):
1144
"""Trick a RemoteBranch into thinking it is locked."""
1145
branch._lock_mode = 'w'
1146
branch._lock_count = 2
1147
branch._lock_token = b'branch token'
1148
branch._repo_lock_token = b'repo token'
1149
branch.repository._lock_mode = 'w'
1150
branch.repository._lock_count = 2
1151
branch.repository._lock_token = b'repo token'
1153
def make_remote_branch(self, transport, client):
1154
"""Make a RemoteBranch using 'client' as its _SmartClient.
1156
A RemoteBzrDir and RemoteRepository will also be created to fill out
1157
the RemoteBranch, albeit with stub values for some of their attributes.
1159
# we do not want bzrdir to make any remote calls, so use False as its
1160
# _client. If it tries to make a remote call, this will fail
1162
bzrdir = self.make_remote_bzrdir(transport, False)
1163
repo = RemoteRepository(bzrdir, None, _client=client)
1164
branch_format = self.get_branch_format()
1165
format = RemoteBranchFormat(network_name=branch_format.network_name())
1166
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1169
class TestBranchBreakLock(RemoteBranchTestCase):
1171
def test_break_lock(self):
1172
transport = MemoryTransport()
1173
client = FakeClient(transport.base)
1174
client.add_expected_call(
1175
b'Branch.get_stacked_on_url', (b'quack/',),
1176
b'error', (b'NotStacked',))
1177
client.add_expected_call(
1178
b'Branch.break_lock', (b'quack/',),
1179
b'success', (b'ok',))
1180
transport.mkdir('quack')
1181
transport = transport.clone('quack')
1182
branch = self.make_remote_branch(transport, client)
1184
self.assertFinished(client)
1187
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1189
def test_get_physical_lock_status_yes(self):
1190
transport = MemoryTransport()
1191
client = FakeClient(transport.base)
1192
client.add_expected_call(
1193
b'Branch.get_stacked_on_url', (b'quack/',),
1194
b'error', (b'NotStacked',))
1195
client.add_expected_call(
1196
b'Branch.get_physical_lock_status', (b'quack/',),
1197
b'success', (b'yes',))
1198
transport.mkdir('quack')
1199
transport = transport.clone('quack')
1200
branch = self.make_remote_branch(transport, client)
1201
result = branch.get_physical_lock_status()
1202
self.assertFinished(client)
1203
self.assertEqual(True, result)
1205
def test_get_physical_lock_status_no(self):
1206
transport = MemoryTransport()
1207
client = FakeClient(transport.base)
1208
client.add_expected_call(
1209
b'Branch.get_stacked_on_url', (b'quack/',),
1210
b'error', (b'NotStacked',))
1211
client.add_expected_call(
1212
b'Branch.get_physical_lock_status', (b'quack/',),
1213
b'success', (b'no',))
1214
transport.mkdir('quack')
1215
transport = transport.clone('quack')
1216
branch = self.make_remote_branch(transport, client)
1217
result = branch.get_physical_lock_status()
1218
self.assertFinished(client)
1219
self.assertEqual(False, result)
1222
class TestBranchGetParent(RemoteBranchTestCase):
1224
def test_no_parent(self):
1225
# in an empty branch we decode the response properly
1226
transport = MemoryTransport()
1227
client = FakeClient(transport.base)
1228
client.add_expected_call(
1229
b'Branch.get_stacked_on_url', (b'quack/',),
1230
b'error', (b'NotStacked',))
1231
client.add_expected_call(
1232
b'Branch.get_parent', (b'quack/',),
1234
transport.mkdir('quack')
1235
transport = transport.clone('quack')
1236
branch = self.make_remote_branch(transport, client)
1237
result = branch.get_parent()
1238
self.assertFinished(client)
1239
self.assertEqual(None, result)
1241
def test_parent_relative(self):
1242
transport = MemoryTransport()
1243
client = FakeClient(transport.base)
1244
client.add_expected_call(
1245
b'Branch.get_stacked_on_url', (b'kwaak/',),
1246
b'error', (b'NotStacked',))
1247
client.add_expected_call(
1248
b'Branch.get_parent', (b'kwaak/',),
1249
b'success', (b'../foo/',))
1250
transport.mkdir('kwaak')
1251
transport = transport.clone('kwaak')
1252
branch = self.make_remote_branch(transport, client)
1253
result = branch.get_parent()
1254
self.assertEqual(transport.clone('../foo').base, result)
1256
def test_parent_absolute(self):
1257
transport = MemoryTransport()
1258
client = FakeClient(transport.base)
1259
client.add_expected_call(
1260
b'Branch.get_stacked_on_url', (b'kwaak/',),
1261
b'error', (b'NotStacked',))
1262
client.add_expected_call(
1263
b'Branch.get_parent', (b'kwaak/',),
1264
b'success', (b'http://foo/',))
1265
transport.mkdir('kwaak')
1266
transport = transport.clone('kwaak')
1267
branch = self.make_remote_branch(transport, client)
1268
result = branch.get_parent()
1269
self.assertEqual('http://foo/', result)
1270
self.assertFinished(client)
1273
class TestBranchSetParentLocation(RemoteBranchTestCase):
1275
def test_no_parent(self):
1276
# We call the verb when setting parent to None
1277
transport = MemoryTransport()
1278
client = FakeClient(transport.base)
1279
client.add_expected_call(
1280
b'Branch.get_stacked_on_url', (b'quack/',),
1281
b'error', (b'NotStacked',))
1282
client.add_expected_call(
1283
b'Branch.set_parent_location', (b'quack/', b'b', b'r', b''),
1285
transport.mkdir('quack')
1286
transport = transport.clone('quack')
1287
branch = self.make_remote_branch(transport, client)
1288
branch._lock_token = b'b'
1289
branch._repo_lock_token = b'r'
1290
branch._set_parent_location(None)
1291
self.assertFinished(client)
1293
def test_parent(self):
1294
transport = MemoryTransport()
1295
client = FakeClient(transport.base)
1296
client.add_expected_call(
1297
b'Branch.get_stacked_on_url', (b'kwaak/',),
1298
b'error', (b'NotStacked',))
1299
client.add_expected_call(
1300
b'Branch.set_parent_location', (b'kwaak/', b'b', b'r', b'foo'),
1302
transport.mkdir('kwaak')
1303
transport = transport.clone('kwaak')
1304
branch = self.make_remote_branch(transport, client)
1305
branch._lock_token = b'b'
1306
branch._repo_lock_token = b'r'
1307
branch._set_parent_location('foo')
1308
self.assertFinished(client)
1310
def test_backwards_compat(self):
1311
self.setup_smart_server_with_call_log()
1312
branch = self.make_branch('.')
1313
self.reset_smart_call_log()
1314
verb = b'Branch.set_parent_location'
1315
self.disable_verb(verb)
1316
branch.set_parent('http://foo/')
1317
self.assertLength(14, self.hpss_calls)
1320
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1322
def test_backwards_compat(self):
1323
self.setup_smart_server_with_call_log()
1324
branch = self.make_branch('.')
1325
self.reset_smart_call_log()
1326
verb = b'Branch.get_tags_bytes'
1327
self.disable_verb(verb)
1328
branch.tags.get_tag_dict()
1329
call_count = len([call for call in self.hpss_calls if
1330
call.call.method == verb])
1331
self.assertEqual(1, call_count)
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.get_tags_bytes', (b'quack/',),
1342
transport.mkdir('quack')
1343
transport = transport.clone('quack')
1344
branch = self.make_remote_branch(transport, client)
1345
result = branch.tags.get_tag_dict()
1346
self.assertFinished(client)
1347
self.assertEqual({}, result)
1350
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1352
def test_trivial(self):
1353
transport = MemoryTransport()
1354
client = FakeClient(transport.base)
1355
client.add_expected_call(
1356
b'Branch.get_stacked_on_url', (b'quack/',),
1357
b'error', (b'NotStacked',))
1358
client.add_expected_call(
1359
b'Branch.set_tags_bytes', (b'quack/',
1360
b'branch token', b'repo token'),
1362
transport.mkdir('quack')
1363
transport = transport.clone('quack')
1364
branch = self.make_remote_branch(transport, client)
1365
self.lock_remote_branch(branch)
1366
branch._set_tags_bytes(b'tags bytes')
1367
self.assertFinished(client)
1368
self.assertEqual(b'tags bytes', client._calls[-1][-1])
1370
def test_backwards_compatible(self):
1371
transport = MemoryTransport()
1372
client = FakeClient(transport.base)
1373
client.add_expected_call(
1374
b'Branch.get_stacked_on_url', (b'quack/',),
1375
b'error', (b'NotStacked',))
1376
client.add_expected_call(
1377
b'Branch.set_tags_bytes', (b'quack/',
1378
b'branch token', b'repo token'),
1379
b'unknown', (b'Branch.set_tags_bytes',))
1380
transport.mkdir('quack')
1381
transport = transport.clone('quack')
1382
branch = self.make_remote_branch(transport, client)
1383
self.lock_remote_branch(branch)
1385
class StubRealBranch(object):
1389
def _set_tags_bytes(self, bytes):
1390
self.calls.append(('set_tags_bytes', bytes))
1391
real_branch = StubRealBranch()
1392
branch._real_branch = real_branch
1393
branch._set_tags_bytes(b'tags bytes')
1394
# Call a second time, to exercise the 'remote version already inferred'
1396
branch._set_tags_bytes(b'tags bytes')
1397
self.assertFinished(client)
1399
[('set_tags_bytes', b'tags bytes')] * 2, real_branch.calls)
1402
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1404
def test_uses_last_revision_info_and_tags_by_default(self):
1405
transport = MemoryTransport()
1406
client = FakeClient(transport.base)
1407
client.add_expected_call(
1408
b'Branch.get_stacked_on_url', (b'quack/',),
1409
b'error', (b'NotStacked',))
1410
client.add_expected_call(
1411
b'Branch.last_revision_info', (b'quack/',),
1412
b'success', (b'ok', b'1', b'rev-tip'))
1413
client.add_expected_call(
1414
b'Branch.get_config_file', (b'quack/',),
1415
b'success', (b'ok',), b'')
1416
transport.mkdir('quack')
1417
transport = transport.clone('quack')
1418
branch = self.make_remote_branch(transport, client)
1419
result = branch.heads_to_fetch()
1420
self.assertFinished(client)
1421
self.assertEqual(({b'rev-tip'}, set()), result)
1423
def test_uses_last_revision_info_and_tags_when_set(self):
1424
transport = MemoryTransport()
1425
client = FakeClient(transport.base)
1426
client.add_expected_call(
1427
b'Branch.get_stacked_on_url', (b'quack/',),
1428
b'error', (b'NotStacked',))
1429
client.add_expected_call(
1430
b'Branch.last_revision_info', (b'quack/',),
1431
b'success', (b'ok', b'1', b'rev-tip'))
1432
client.add_expected_call(
1433
b'Branch.get_config_file', (b'quack/',),
1434
b'success', (b'ok',), b'branch.fetch_tags = True')
1435
# XXX: this will break if the default format's serialization of tags
1436
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1437
client.add_expected_call(
1438
b'Branch.get_tags_bytes', (b'quack/',),
1439
b'success', (b'd5:tag-17:rev-foo5:tag-27:rev-bare',))
1440
transport.mkdir('quack')
1441
transport = transport.clone('quack')
1442
branch = self.make_remote_branch(transport, client)
1443
result = branch.heads_to_fetch()
1444
self.assertFinished(client)
1446
({b'rev-tip'}, {b'rev-foo', b'rev-bar'}), result)
1448
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1449
transport = MemoryTransport()
1450
client = FakeClient(transport.base)
1451
client.add_expected_call(
1452
b'Branch.get_stacked_on_url', (b'quack/',),
1453
b'error', (b'NotStacked',))
1454
client.add_expected_call(
1455
b'Branch.heads_to_fetch', (b'quack/',),
1456
b'success', ([b'tip'], [b'tagged-1', b'tagged-2']))
1457
transport.mkdir('quack')
1458
transport = transport.clone('quack')
1459
branch = self.make_remote_branch(transport, client)
1460
branch._format._use_default_local_heads_to_fetch = lambda: False
1461
result = branch.heads_to_fetch()
1462
self.assertFinished(client)
1463
self.assertEqual(({b'tip'}, {b'tagged-1', b'tagged-2'}), result)
1465
def make_branch_with_tags(self):
1466
self.setup_smart_server_with_call_log()
1467
# Make a branch with a single revision.
1468
builder = self.make_branch_builder('foo')
1469
builder.start_series()
1470
builder.build_snapshot(None, [
1471
('add', ('', b'root-id', 'directory', ''))],
1473
builder.finish_series()
1474
branch = builder.get_branch()
1475
# Add two tags to that branch
1476
branch.tags.set_tag('tag-1', b'rev-1')
1477
branch.tags.set_tag('tag-2', b'rev-2')
1480
def test_backwards_compatible(self):
1481
br = self.make_branch_with_tags()
1482
br.get_config_stack().set('branch.fetch_tags', True)
1483
self.addCleanup(br.lock_read().unlock)
1484
# Disable the heads_to_fetch verb
1485
verb = b'Branch.heads_to_fetch'
1486
self.disable_verb(verb)
1487
self.reset_smart_call_log()
1488
result = br.heads_to_fetch()
1489
self.assertEqual(({b'tip'}, {b'rev-1', b'rev-2'}), result)
1491
[b'Branch.last_revision_info', b'Branch.get_tags_bytes'],
1492
[call.call.method for call in self.hpss_calls])
1494
def test_backwards_compatible_no_tags(self):
1495
br = self.make_branch_with_tags()
1496
br.get_config_stack().set('branch.fetch_tags', False)
1497
self.addCleanup(br.lock_read().unlock)
1498
# Disable the heads_to_fetch verb
1499
verb = b'Branch.heads_to_fetch'
1500
self.disable_verb(verb)
1501
self.reset_smart_call_log()
1502
result = br.heads_to_fetch()
1503
self.assertEqual(({b'tip'}, set()), result)
1505
[b'Branch.last_revision_info'],
1506
[call.call.method for call in self.hpss_calls])
1509
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1511
def test_empty_branch(self):
1512
# in an empty branch we decode the response properly
1513
transport = MemoryTransport()
1514
client = FakeClient(transport.base)
1515
client.add_expected_call(
1516
b'Branch.get_stacked_on_url', (b'quack/',),
1517
b'error', (b'NotStacked',))
1518
client.add_expected_call(
1519
b'Branch.last_revision_info', (b'quack/',),
1520
b'success', (b'ok', b'0', b'null:'))
1521
transport.mkdir('quack')
1522
transport = transport.clone('quack')
1523
branch = self.make_remote_branch(transport, client)
1524
result = branch.last_revision_info()
1525
self.assertFinished(client)
1526
self.assertEqual((0, NULL_REVISION), result)
1528
def test_non_empty_branch(self):
1529
# in a non-empty branch we also decode the response properly
1530
revid = u'\xc8'.encode('utf8')
1531
transport = MemoryTransport()
1532
client = FakeClient(transport.base)
1533
client.add_expected_call(
1534
b'Branch.get_stacked_on_url', (b'kwaak/',),
1535
b'error', (b'NotStacked',))
1536
client.add_expected_call(
1537
b'Branch.last_revision_info', (b'kwaak/',),
1538
b'success', (b'ok', b'2', revid))
1539
transport.mkdir('kwaak')
1540
transport = transport.clone('kwaak')
1541
branch = self.make_remote_branch(transport, client)
1542
result = branch.last_revision_info()
1543
self.assertEqual((2, revid), result)
1546
class TestBranch_get_stacked_on_url(TestRemote):
1547
"""Test Branch._get_stacked_on_url rpc"""
1549
def test_get_stacked_on_invalid_url(self):
1550
# test that asking for a stacked on url the server can't access works.
1551
# This isn't perfect, but then as we're in the same process there
1552
# really isn't anything we can do to be 100% sure that the server
1553
# doesn't just open in - this test probably needs to be rewritten using
1554
# a spawn()ed server.
1555
stacked_branch = self.make_branch('stacked', format='1.9')
1556
self.make_branch('base', format='1.9')
1557
vfs_url = self.get_vfs_only_url('base')
1558
stacked_branch.set_stacked_on_url(vfs_url)
1559
transport = stacked_branch.controldir.root_transport
1560
client = FakeClient(transport.base)
1561
client.add_expected_call(
1562
b'Branch.get_stacked_on_url', (b'stacked/',),
1563
b'success', (b'ok', vfs_url.encode('utf-8')))
1564
# XXX: Multiple calls are bad, this second call documents what is
1566
client.add_expected_call(
1567
b'Branch.get_stacked_on_url', (b'stacked/',),
1568
b'success', (b'ok', vfs_url.encode('utf-8')))
1569
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1571
repo_fmt = remote.RemoteRepositoryFormat()
1572
repo_fmt._custom_format = stacked_branch.repository._format
1573
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1575
result = branch.get_stacked_on_url()
1576
self.assertEqual(vfs_url, result)
1578
def test_backwards_compatible(self):
1579
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1580
self.make_branch('base', format='1.6')
1581
stacked_branch = self.make_branch('stacked', format='1.6')
1582
stacked_branch.set_stacked_on_url('../base')
1583
client = FakeClient(self.get_url())
1584
branch_network_name = self.get_branch_format().network_name()
1585
client.add_expected_call(
1586
b'BzrDir.open_branchV3', (b'stacked/',),
1587
b'success', (b'branch', branch_network_name))
1588
client.add_expected_call(
1589
b'BzrDir.find_repositoryV3', (b'stacked/',),
1590
b'success', (b'ok', b'', b'no', b'no', b'yes',
1591
stacked_branch.repository._format.network_name()))
1592
# called twice, once from constructor and then again by us
1593
client.add_expected_call(
1594
b'Branch.get_stacked_on_url', (b'stacked/',),
1595
b'unknown', (b'Branch.get_stacked_on_url',))
1596
client.add_expected_call(
1597
b'Branch.get_stacked_on_url', (b'stacked/',),
1598
b'unknown', (b'Branch.get_stacked_on_url',))
1599
# this will also do vfs access, but that goes direct to the transport
1600
# and isn't seen by the FakeClient.
1601
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1602
RemoteBzrDirFormat(), _client=client)
1603
branch = bzrdir.open_branch()
1604
result = branch.get_stacked_on_url()
1605
self.assertEqual('../base', result)
1606
self.assertFinished(client)
1607
# it's in the fallback list both for the RemoteRepository and its vfs
1609
self.assertEqual(1, len(branch.repository._fallback_repositories))
1611
len(branch.repository._real_repository._fallback_repositories))
1613
def test_get_stacked_on_real_branch(self):
1614
self.make_branch('base')
1615
stacked_branch = self.make_branch('stacked')
1616
stacked_branch.set_stacked_on_url('../base')
1617
reference_format = self.get_repo_format()
1618
network_name = reference_format.network_name()
1619
client = FakeClient(self.get_url())
1620
branch_network_name = self.get_branch_format().network_name()
1621
client.add_expected_call(
1622
b'BzrDir.open_branchV3', (b'stacked/',),
1623
b'success', (b'branch', branch_network_name))
1624
client.add_expected_call(
1625
b'BzrDir.find_repositoryV3', (b'stacked/',),
1626
b'success', (b'ok', b'', b'yes', b'no', b'yes', network_name))
1627
# called twice, once from constructor and then again by us
1628
client.add_expected_call(
1629
b'Branch.get_stacked_on_url', (b'stacked/',),
1630
b'success', (b'ok', b'../base'))
1631
client.add_expected_call(
1632
b'Branch.get_stacked_on_url', (b'stacked/',),
1633
b'success', (b'ok', b'../base'))
1634
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1635
RemoteBzrDirFormat(), _client=client)
1636
branch = bzrdir.open_branch()
1637
result = branch.get_stacked_on_url()
1638
self.assertEqual('../base', result)
1639
self.assertFinished(client)
1640
# it's in the fallback list both for the RemoteRepository.
1641
self.assertEqual(1, len(branch.repository._fallback_repositories))
1642
# And we haven't had to construct a real repository.
1643
self.assertEqual(None, branch.repository._real_repository)
1646
class TestBranchSetLastRevision(RemoteBranchTestCase):
1648
def test_set_empty(self):
1649
# _set_last_revision_info('null:') is translated to calling
1650
# Branch.set_last_revision(path, '') on the wire.
1651
transport = MemoryTransport()
1652
transport.mkdir('branch')
1653
transport = transport.clone('branch')
1655
client = FakeClient(transport.base)
1656
client.add_expected_call(
1657
b'Branch.get_stacked_on_url', (b'branch/',),
1658
b'error', (b'NotStacked',))
1659
client.add_expected_call(
1660
b'Branch.lock_write', (b'branch/', b'', b''),
1661
b'success', (b'ok', b'branch token', b'repo token'))
1662
client.add_expected_call(
1663
b'Branch.last_revision_info',
1665
b'success', (b'ok', b'0', b'null:'))
1666
client.add_expected_call(
1667
b'Branch.set_last_revision', (b'branch/',
1668
b'branch token', b'repo token', b'null:',),
1669
b'success', (b'ok',))
1670
client.add_expected_call(
1671
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1672
b'success', (b'ok',))
1673
branch = self.make_remote_branch(transport, client)
1675
result = branch._set_last_revision(NULL_REVISION)
1677
self.assertEqual(None, result)
1678
self.assertFinished(client)
1680
def test_set_nonempty(self):
1681
# set_last_revision_info(N, rev-idN) is translated to calling
1682
# Branch.set_last_revision(path, rev-idN) on the wire.
1683
transport = MemoryTransport()
1684
transport.mkdir('branch')
1685
transport = transport.clone('branch')
1687
client = FakeClient(transport.base)
1688
client.add_expected_call(
1689
b'Branch.get_stacked_on_url', (b'branch/',),
1690
b'error', (b'NotStacked',))
1691
client.add_expected_call(
1692
b'Branch.lock_write', (b'branch/', b'', b''),
1693
b'success', (b'ok', b'branch token', b'repo token'))
1694
client.add_expected_call(
1695
b'Branch.last_revision_info',
1697
b'success', (b'ok', b'0', b'null:'))
1698
lines = [b'rev-id2']
1699
encoded_body = bz2.compress(b'\n'.join(lines))
1700
client.add_success_response_with_body(encoded_body, b'ok')
1701
client.add_expected_call(
1702
b'Branch.set_last_revision', (b'branch/',
1703
b'branch token', b'repo token', b'rev-id2',),
1704
b'success', (b'ok',))
1705
client.add_expected_call(
1706
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1707
b'success', (b'ok',))
1708
branch = self.make_remote_branch(transport, client)
1709
# Lock the branch, reset the record of remote calls.
1711
result = branch._set_last_revision(b'rev-id2')
1713
self.assertEqual(None, result)
1714
self.assertFinished(client)
1716
def test_no_such_revision(self):
1717
transport = MemoryTransport()
1718
transport.mkdir('branch')
1719
transport = transport.clone('branch')
1720
# A response of 'NoSuchRevision' is translated into an exception.
1721
client = FakeClient(transport.base)
1722
client.add_expected_call(
1723
b'Branch.get_stacked_on_url', (b'branch/',),
1724
b'error', (b'NotStacked',))
1725
client.add_expected_call(
1726
b'Branch.lock_write', (b'branch/', b'', b''),
1727
b'success', (b'ok', b'branch token', b'repo token'))
1728
client.add_expected_call(
1729
b'Branch.last_revision_info',
1731
b'success', (b'ok', b'0', b'null:'))
1732
# get_graph calls to construct the revision history, for the set_rh
1735
encoded_body = bz2.compress(b'\n'.join(lines))
1736
client.add_success_response_with_body(encoded_body, b'ok')
1737
client.add_expected_call(
1738
b'Branch.set_last_revision', (b'branch/',
1739
b'branch token', b'repo token', b'rev-id',),
1740
b'error', (b'NoSuchRevision', b'rev-id'))
1741
client.add_expected_call(
1742
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1743
b'success', (b'ok',))
1745
branch = self.make_remote_branch(transport, client)
1748
errors.NoSuchRevision, branch._set_last_revision, b'rev-id')
1750
self.assertFinished(client)
1752
def test_tip_change_rejected(self):
1753
"""TipChangeRejected responses cause a TipChangeRejected exception to
1756
transport = MemoryTransport()
1757
transport.mkdir('branch')
1758
transport = transport.clone('branch')
1759
client = FakeClient(transport.base)
1760
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1761
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1762
client.add_expected_call(
1763
b'Branch.get_stacked_on_url', (b'branch/',),
1764
b'error', (b'NotStacked',))
1765
client.add_expected_call(
1766
b'Branch.lock_write', (b'branch/', b'', b''),
1767
b'success', (b'ok', b'branch token', b'repo token'))
1768
client.add_expected_call(
1769
b'Branch.last_revision_info',
1771
b'success', (b'ok', b'0', b'null:'))
1773
encoded_body = bz2.compress(b'\n'.join(lines))
1774
client.add_success_response_with_body(encoded_body, b'ok')
1775
client.add_expected_call(
1776
b'Branch.set_last_revision', (b'branch/',
1777
b'branch token', b'repo token', b'rev-id',),
1778
b'error', (b'TipChangeRejected', rejection_msg_utf8))
1779
client.add_expected_call(
1780
b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
1781
b'success', (b'ok',))
1782
branch = self.make_remote_branch(transport, client)
1784
# The 'TipChangeRejected' error response triggered by calling
1785
# set_last_revision_info causes a TipChangeRejected exception.
1786
err = self.assertRaises(
1787
errors.TipChangeRejected,
1788
branch._set_last_revision, b'rev-id')
1789
# The UTF-8 message from the response has been decoded into a unicode
1791
self.assertIsInstance(err.msg, text_type)
1792
self.assertEqual(rejection_msg_unicode, err.msg)
1794
self.assertFinished(client)
1797
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1799
def test_set_last_revision_info(self):
1800
# set_last_revision_info(num, b'rev-id') is translated to calling
1801
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1802
transport = MemoryTransport()
1803
transport.mkdir('branch')
1804
transport = transport.clone('branch')
1805
client = FakeClient(transport.base)
1806
# get_stacked_on_url
1807
client.add_error_response(b'NotStacked')
1809
client.add_success_response(b'ok', b'branch token', b'repo token')
1810
# query the current revision
1811
client.add_success_response(b'ok', b'0', b'null:')
1813
client.add_success_response(b'ok')
1815
client.add_success_response(b'ok')
1817
branch = self.make_remote_branch(transport, client)
1818
# Lock the branch, reset the record of remote calls.
1821
result = branch.set_last_revision_info(1234, b'a-revision-id')
1823
[('call', b'Branch.last_revision_info', (b'branch/',)),
1824
('call', b'Branch.set_last_revision_info',
1825
(b'branch/', b'branch token', b'repo token',
1826
b'1234', b'a-revision-id'))],
1828
self.assertEqual(None, result)
1830
def test_no_such_revision(self):
1831
# A response of 'NoSuchRevision' is translated into an exception.
1832
transport = MemoryTransport()
1833
transport.mkdir('branch')
1834
transport = transport.clone('branch')
1835
client = FakeClient(transport.base)
1836
# get_stacked_on_url
1837
client.add_error_response(b'NotStacked')
1839
client.add_success_response(b'ok', b'branch token', b'repo token')
1841
client.add_error_response(b'NoSuchRevision', b'revid')
1843
client.add_success_response(b'ok')
1845
branch = self.make_remote_branch(transport, client)
1846
# Lock the branch, reset the record of remote calls.
1851
errors.NoSuchRevision, branch.set_last_revision_info, 123, b'revid')
1854
def test_backwards_compatibility(self):
1855
"""If the server does not support the Branch.set_last_revision_info
1856
verb (which is new in 1.4), then the client falls back to VFS methods.
1858
# This test is a little messy. Unlike most tests in this file, it
1859
# doesn't purely test what a Remote* object sends over the wire, and
1860
# how it reacts to responses from the wire. It instead relies partly
1861
# on asserting that the RemoteBranch will call
1862
# self._real_branch.set_last_revision_info(...).
1864
# First, set up our RemoteBranch with a FakeClient that raises
1865
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1866
transport = MemoryTransport()
1867
transport.mkdir('branch')
1868
transport = transport.clone('branch')
1869
client = FakeClient(transport.base)
1870
client.add_expected_call(
1871
b'Branch.get_stacked_on_url', (b'branch/',),
1872
b'error', (b'NotStacked',))
1873
client.add_expected_call(
1874
b'Branch.last_revision_info',
1876
b'success', (b'ok', b'0', b'null:'))
1877
client.add_expected_call(
1878
b'Branch.set_last_revision_info',
1879
(b'branch/', b'branch token', b'repo token', b'1234', b'a-revision-id',),
1880
b'unknown', b'Branch.set_last_revision_info')
1882
branch = self.make_remote_branch(transport, client)
1884
class StubRealBranch(object):
1888
def set_last_revision_info(self, revno, revision_id):
1890
('set_last_revision_info', revno, revision_id))
1892
def _clear_cached_state(self):
1894
real_branch = StubRealBranch()
1895
branch._real_branch = real_branch
1896
self.lock_remote_branch(branch)
1898
# Call set_last_revision_info, and verify it behaved as expected.
1899
branch.set_last_revision_info(1234, b'a-revision-id')
1901
[('set_last_revision_info', 1234, b'a-revision-id')],
1903
self.assertFinished(client)
1905
def test_unexpected_error(self):
1906
# If the server sends an error the client doesn't understand, it gets
1907
# turned into an UnknownErrorFromSmartServer, which is presented as a
1908
# non-internal error to the user.
1909
transport = MemoryTransport()
1910
transport.mkdir('branch')
1911
transport = transport.clone('branch')
1912
client = FakeClient(transport.base)
1913
# get_stacked_on_url
1914
client.add_error_response(b'NotStacked')
1916
client.add_success_response(b'ok', b'branch token', b'repo token')
1918
client.add_error_response(b'UnexpectedError')
1920
client.add_success_response(b'ok')
1922
branch = self.make_remote_branch(transport, client)
1923
# Lock the branch, reset the record of remote calls.
1927
err = self.assertRaises(
1928
errors.UnknownErrorFromSmartServer,
1929
branch.set_last_revision_info, 123, b'revid')
1930
self.assertEqual((b'UnexpectedError',), err.error_tuple)
1933
def test_tip_change_rejected(self):
1934
"""TipChangeRejected responses cause a TipChangeRejected exception to
1937
transport = MemoryTransport()
1938
transport.mkdir('branch')
1939
transport = transport.clone('branch')
1940
client = FakeClient(transport.base)
1941
# get_stacked_on_url
1942
client.add_error_response(b'NotStacked')
1944
client.add_success_response(b'ok', b'branch token', b'repo token')
1946
client.add_error_response(b'TipChangeRejected', b'rejection message')
1948
client.add_success_response(b'ok')
1950
branch = self.make_remote_branch(transport, client)
1951
# Lock the branch, reset the record of remote calls.
1953
self.addCleanup(branch.unlock)
1956
# The 'TipChangeRejected' error response triggered by calling
1957
# set_last_revision_info causes a TipChangeRejected exception.
1958
err = self.assertRaises(
1959
errors.TipChangeRejected,
1960
branch.set_last_revision_info, 123, b'revid')
1961
self.assertEqual('rejection message', err.msg)
1964
class TestBranchGetSetConfig(RemoteBranchTestCase):
1966
def test_get_branch_conf(self):
1967
# in an empty branch we decode the response properly
1968
client = FakeClient()
1969
client.add_expected_call(
1970
b'Branch.get_stacked_on_url', (b'memory:///',),
1971
b'error', (b'NotStacked',),)
1972
client.add_success_response_with_body(b'# config file body', b'ok')
1973
transport = MemoryTransport()
1974
branch = self.make_remote_branch(transport, client)
1975
config = branch.get_config()
1976
config.has_explicit_nickname()
1978
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
1979
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
1982
def test_get_multi_line_branch_conf(self):
1983
# Make sure that multiple-line branch.conf files are supported
1985
# https://bugs.launchpad.net/bzr/+bug/354075
1986
client = FakeClient()
1987
client.add_expected_call(
1988
b'Branch.get_stacked_on_url', (b'memory:///',),
1989
b'error', (b'NotStacked',),)
1990
client.add_success_response_with_body(b'a = 1\nb = 2\nc = 3\n', b'ok')
1991
transport = MemoryTransport()
1992
branch = self.make_remote_branch(transport, client)
1993
config = branch.get_config()
1994
self.assertEqual(u'2', config.get_user_option('b'))
1996
def test_set_option(self):
1997
client = FakeClient()
1998
client.add_expected_call(
1999
b'Branch.get_stacked_on_url', (b'memory:///',),
2000
b'error', (b'NotStacked',),)
2001
client.add_expected_call(
2002
b'Branch.lock_write', (b'memory:///', b'', b''),
2003
b'success', (b'ok', b'branch token', b'repo token'))
2004
client.add_expected_call(
2005
b'Branch.set_config_option', (b'memory:///', b'branch token',
2006
b'repo token', b'foo', b'bar', b''),
2008
client.add_expected_call(
2009
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2010
b'success', (b'ok',))
2011
transport = MemoryTransport()
2012
branch = self.make_remote_branch(transport, client)
2014
config = branch._get_config()
2015
config.set_option('foo', 'bar')
2017
self.assertFinished(client)
2019
def test_set_option_with_dict(self):
2020
client = FakeClient()
2021
client.add_expected_call(
2022
b'Branch.get_stacked_on_url', (b'memory:///',),
2023
b'error', (b'NotStacked',),)
2024
client.add_expected_call(
2025
b'Branch.lock_write', (b'memory:///', b'', b''),
2026
b'success', (b'ok', b'branch token', b'repo token'))
2027
encoded_dict_value = b'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
2028
client.add_expected_call(
2029
b'Branch.set_config_option_dict', (b'memory:///', b'branch token',
2030
b'repo token', encoded_dict_value, b'foo', b''),
2032
client.add_expected_call(
2033
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2034
b'success', (b'ok',))
2035
transport = MemoryTransport()
2036
branch = self.make_remote_branch(transport, client)
2038
config = branch._get_config()
2040
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2043
self.assertFinished(client)
2045
def test_backwards_compat_set_option(self):
2046
self.setup_smart_server_with_call_log()
2047
branch = self.make_branch('.')
2048
verb = b'Branch.set_config_option'
2049
self.disable_verb(verb)
2051
self.addCleanup(branch.unlock)
2052
self.reset_smart_call_log()
2053
branch._get_config().set_option('value', 'name')
2054
self.assertLength(11, self.hpss_calls)
2055
self.assertEqual('value', branch._get_config().get_option('name'))
2057
def test_backwards_compat_set_option_with_dict(self):
2058
self.setup_smart_server_with_call_log()
2059
branch = self.make_branch('.')
2060
verb = b'Branch.set_config_option_dict'
2061
self.disable_verb(verb)
2063
self.addCleanup(branch.unlock)
2064
self.reset_smart_call_log()
2065
config = branch._get_config()
2066
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2067
config.set_option(value_dict, 'name')
2068
self.assertLength(11, self.hpss_calls)
2069
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2072
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2074
def test_get_branch_conf(self):
2075
# in an empty branch we decode the response properly
2076
client = FakeClient()
2077
client.add_expected_call(
2078
b'Branch.get_stacked_on_url', (b'memory:///',),
2079
b'error', (b'NotStacked',),)
2080
client.add_success_response_with_body(b'# config file body', b'ok')
2081
transport = MemoryTransport()
2082
branch = self.make_remote_branch(transport, client)
2083
config = branch.get_config_stack()
2085
config.get("log_format")
2087
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2088
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
2091
def test_set_branch_conf(self):
2092
client = FakeClient()
2093
client.add_expected_call(
2094
b'Branch.get_stacked_on_url', (b'memory:///',),
2095
b'error', (b'NotStacked',),)
2096
client.add_expected_call(
2097
b'Branch.lock_write', (b'memory:///', b'', b''),
2098
b'success', (b'ok', b'branch token', b'repo token'))
2099
client.add_expected_call(
2100
b'Branch.get_config_file', (b'memory:///', ),
2101
b'success', (b'ok', ), b"# line 1\n")
2102
client.add_expected_call(
2103
b'Branch.get_config_file', (b'memory:///', ),
2104
b'success', (b'ok', ), b"# line 1\n")
2105
client.add_expected_call(
2106
b'Branch.put_config_file', (b'memory:///', b'branch token',
2108
b'success', (b'ok',))
2109
client.add_expected_call(
2110
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2111
b'success', (b'ok',))
2112
transport = MemoryTransport()
2113
branch = self.make_remote_branch(transport, client)
2115
config = branch.get_config_stack()
2116
config.set('email', 'The Dude <lebowski@example.com>')
2118
self.assertFinished(client)
2120
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2121
('call', b'Branch.lock_write', (b'memory:///', b'', b'')),
2122
('call_expecting_body', b'Branch.get_config_file',
2124
('call_expecting_body', b'Branch.get_config_file',
2126
('call_with_body_bytes_expecting_body', b'Branch.put_config_file',
2127
(b'memory:///', b'branch token', b'repo token'),
2128
b'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2129
('call', b'Branch.unlock',
2130
(b'memory:///', b'branch token', b'repo token'))],
2134
class TestBranchLockWrite(RemoteBranchTestCase):
2136
def test_lock_write_unlockable(self):
2137
transport = MemoryTransport()
2138
client = FakeClient(transport.base)
2139
client.add_expected_call(
2140
b'Branch.get_stacked_on_url', (b'quack/',),
2141
b'error', (b'NotStacked',),)
2142
client.add_expected_call(
2143
b'Branch.lock_write', (b'quack/', b'', b''),
2144
b'error', (b'UnlockableTransport',))
2145
transport.mkdir('quack')
2146
transport = transport.clone('quack')
2147
branch = self.make_remote_branch(transport, client)
2148
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2149
self.assertFinished(client)
2152
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2154
def test_simple(self):
2155
transport = MemoryTransport()
2156
client = FakeClient(transport.base)
2157
client.add_expected_call(
2158
b'Branch.get_stacked_on_url', (b'quack/',),
2159
b'error', (b'NotStacked',),)
2160
client.add_expected_call(
2161
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2162
b'success', (b'ok', b'0',),)
2163
client.add_expected_call(
2164
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2165
b'error', (b'NoSuchRevision', b'unknown',),)
2166
transport.mkdir('quack')
2167
transport = transport.clone('quack')
2168
branch = self.make_remote_branch(transport, client)
2169
self.assertEqual(0, branch.revision_id_to_revno(b'null:'))
2170
self.assertRaises(errors.NoSuchRevision,
2171
branch.revision_id_to_revno, b'unknown')
2172
self.assertFinished(client)
2174
def test_dotted(self):
2175
transport = MemoryTransport()
2176
client = FakeClient(transport.base)
2177
client.add_expected_call(
2178
b'Branch.get_stacked_on_url', (b'quack/',),
2179
b'error', (b'NotStacked',),)
2180
client.add_expected_call(
2181
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2182
b'success', (b'ok', b'0',),)
2183
client.add_expected_call(
2184
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2185
b'error', (b'NoSuchRevision', b'unknown',),)
2186
transport.mkdir('quack')
2187
transport = transport.clone('quack')
2188
branch = self.make_remote_branch(transport, client)
2189
self.assertEqual((0, ), branch.revision_id_to_dotted_revno(b'null:'))
2190
self.assertRaises(errors.NoSuchRevision,
2191
branch.revision_id_to_dotted_revno, b'unknown')
2192
self.assertFinished(client)
2194
def test_dotted_no_smart_verb(self):
2195
self.setup_smart_server_with_call_log()
2196
branch = self.make_branch('.')
2197
self.disable_verb(b'Branch.revision_id_to_revno')
2198
self.reset_smart_call_log()
2199
self.assertEqual((0, ),
2200
branch.revision_id_to_dotted_revno(b'null:'))
2201
self.assertLength(8, self.hpss_calls)
2204
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2206
def test__get_config(self):
2207
client = FakeClient()
2208
client.add_success_response_with_body(b'default_stack_on = /\n', b'ok')
2209
transport = MemoryTransport()
2210
bzrdir = self.make_remote_bzrdir(transport, client)
2211
config = bzrdir.get_config()
2212
self.assertEqual('/', config.get_default_stack_on())
2214
[('call_expecting_body', b'BzrDir.get_config_file',
2218
def test_set_option_uses_vfs(self):
2219
self.setup_smart_server_with_call_log()
2220
bzrdir = self.make_controldir('.')
2221
self.reset_smart_call_log()
2222
config = bzrdir.get_config()
2223
config.set_default_stack_on('/')
2224
self.assertLength(4, self.hpss_calls)
2226
def test_backwards_compat_get_option(self):
2227
self.setup_smart_server_with_call_log()
2228
bzrdir = self.make_controldir('.')
2229
verb = b'BzrDir.get_config_file'
2230
self.disable_verb(verb)
2231
self.reset_smart_call_log()
2232
self.assertEqual(None,
2233
bzrdir._get_config().get_option('default_stack_on'))
2234
self.assertLength(4, self.hpss_calls)
2237
class TestTransportIsReadonly(tests.TestCase):
2239
def test_true(self):
2240
client = FakeClient()
2241
client.add_success_response(b'yes')
2242
transport = RemoteTransport('bzr://example.com/', medium=False,
2244
self.assertEqual(True, transport.is_readonly())
2246
[('call', b'Transport.is_readonly', ())],
2249
def test_false(self):
2250
client = FakeClient()
2251
client.add_success_response(b'no')
2252
transport = RemoteTransport('bzr://example.com/', medium=False,
2254
self.assertEqual(False, transport.is_readonly())
2256
[('call', b'Transport.is_readonly', ())],
2259
def test_error_from_old_server(self):
2260
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2262
Clients should treat it as a "no" response, because is_readonly is only
2263
advisory anyway (a transport could be read-write, but then the
2264
underlying filesystem could be readonly anyway).
2266
client = FakeClient()
2267
client.add_unknown_method_response(b'Transport.is_readonly')
2268
transport = RemoteTransport('bzr://example.com/', medium=False,
2270
self.assertEqual(False, transport.is_readonly())
2272
[('call', b'Transport.is_readonly', ())],
2276
class TestTransportMkdir(tests.TestCase):
2278
def test_permissiondenied(self):
2279
client = FakeClient()
2280
client.add_error_response(
2281
b'PermissionDenied', b'remote path', b'extra')
2282
transport = RemoteTransport('bzr://example.com/', medium=False,
2284
exc = self.assertRaises(
2285
errors.PermissionDenied, transport.mkdir, 'client path')
2286
expected_error = errors.PermissionDenied('/client path', 'extra')
2287
self.assertEqual(expected_error, exc)
2290
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2292
def test_defaults_to_none(self):
2293
t = RemoteSSHTransport('bzr+ssh://example.com')
2294
self.assertIs(None, t._get_credentials()[0])
2296
def test_uses_authentication_config(self):
2297
conf = config.AuthenticationConfig()
2298
conf._get_config().update(
2299
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2302
t = RemoteSSHTransport('bzr+ssh://example.com')
2303
self.assertEqual('bar', t._get_credentials()[0])
2306
class TestRemoteRepository(TestRemote):
2307
"""Base for testing RemoteRepository protocol usage.
2309
These tests contain frozen requests and responses. We want any changes to
2310
what is sent or expected to be require a thoughtful update to these tests
2311
because they might break compatibility with different-versioned servers.
2314
def setup_fake_client_and_repository(self, transport_path):
2315
"""Create the fake client and repository for testing with.
2317
There's no real server here; we just have canned responses sent
2320
:param transport_path: Path below the root of the MemoryTransport
2321
where the repository will be created.
2323
transport = MemoryTransport()
2324
transport.mkdir(transport_path)
2325
client = FakeClient(transport.base)
2326
transport = transport.clone(transport_path)
2327
# we do not want bzrdir to make any remote calls
2328
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2330
repo = RemoteRepository(bzrdir, None, _client=client)
2334
def remoted_description(format):
2335
return 'Remote: ' + format.get_format_description()
2338
class TestBranchFormat(tests.TestCase):
2340
def test_get_format_description(self):
2341
remote_format = RemoteBranchFormat()
2342
real_format = branch.format_registry.get_default()
2343
remote_format._network_name = real_format.network_name()
2344
self.assertEqual(remoted_description(real_format),
2345
remote_format.get_format_description())
2348
class TestRepositoryFormat(TestRemoteRepository):
2350
def test_fast_delta(self):
2351
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2352
true_format = RemoteRepositoryFormat()
2353
true_format._network_name = true_name
2354
self.assertEqual(True, true_format.fast_deltas)
2355
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2356
false_format = RemoteRepositoryFormat()
2357
false_format._network_name = false_name
2358
self.assertEqual(False, false_format.fast_deltas)
2360
def test_get_format_description(self):
2361
remote_repo_format = RemoteRepositoryFormat()
2362
real_format = repository.format_registry.get_default()
2363
remote_repo_format._network_name = real_format.network_name()
2364
self.assertEqual(remoted_description(real_format),
2365
remote_repo_format.get_format_description())
2368
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2370
def test_empty(self):
2371
transport_path = 'quack'
2372
repo, client = self.setup_fake_client_and_repository(transport_path)
2373
client.add_success_response_with_body(b'', b'ok')
2374
self.assertEqual([], repo.all_revision_ids())
2376
[('call_expecting_body', b'Repository.all_revision_ids',
2380
def test_with_some_content(self):
2381
transport_path = 'quack'
2382
repo, client = self.setup_fake_client_and_repository(transport_path)
2383
client.add_success_response_with_body(
2384
b'rev1\nrev2\nanotherrev\n', b'ok')
2386
set([b"rev1", b"rev2", b"anotherrev"]),
2387
set(repo.all_revision_ids()))
2389
[('call_expecting_body', b'Repository.all_revision_ids',
2394
class TestRepositoryGatherStats(TestRemoteRepository):
2396
def test_revid_none(self):
2397
# ('ok',), body with revisions and size
2398
transport_path = 'quack'
2399
repo, client = self.setup_fake_client_and_repository(transport_path)
2400
client.add_success_response_with_body(
2401
b'revisions: 2\nsize: 18\n', b'ok')
2402
result = repo.gather_stats(None)
2404
[('call_expecting_body', b'Repository.gather_stats',
2405
(b'quack/', b'', b'no'))],
2407
self.assertEqual({'revisions': 2, 'size': 18}, result)
2409
def test_revid_no_committers(self):
2410
# ('ok',), body without committers
2411
body = (b'firstrev: 123456.300 3600\n'
2412
b'latestrev: 654231.400 0\n'
2415
transport_path = 'quick'
2416
revid = u'\xc8'.encode('utf8')
2417
repo, client = self.setup_fake_client_and_repository(transport_path)
2418
client.add_success_response_with_body(body, b'ok')
2419
result = repo.gather_stats(revid)
2421
[('call_expecting_body', b'Repository.gather_stats',
2422
(b'quick/', revid, b'no'))],
2424
self.assertEqual({'revisions': 2, 'size': 18,
2425
'firstrev': (123456.300, 3600),
2426
'latestrev': (654231.400, 0), },
2429
def test_revid_with_committers(self):
2430
# ('ok',), body with committers
2431
body = (b'committers: 128\n'
2432
b'firstrev: 123456.300 3600\n'
2433
b'latestrev: 654231.400 0\n'
2436
transport_path = 'buick'
2437
revid = u'\xc8'.encode('utf8')
2438
repo, client = self.setup_fake_client_and_repository(transport_path)
2439
client.add_success_response_with_body(body, b'ok')
2440
result = repo.gather_stats(revid, True)
2442
[('call_expecting_body', b'Repository.gather_stats',
2443
(b'buick/', revid, b'yes'))],
2445
self.assertEqual({'revisions': 2, 'size': 18,
2447
'firstrev': (123456.300, 3600),
2448
'latestrev': (654231.400, 0), },
2452
class TestRepositoryBreakLock(TestRemoteRepository):
2454
def test_break_lock(self):
2455
transport_path = 'quack'
2456
repo, client = self.setup_fake_client_and_repository(transport_path)
2457
client.add_success_response(b'ok')
2460
[('call', b'Repository.break_lock', (b'quack/',))],
2464
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2466
def test_get_serializer_format(self):
2467
transport_path = 'hill'
2468
repo, client = self.setup_fake_client_and_repository(transport_path)
2469
client.add_success_response(b'ok', b'7')
2470
self.assertEqual(b'7', repo.get_serializer_format())
2472
[('call', b'VersionedFileRepository.get_serializer_format',
2477
class TestRepositoryReconcile(TestRemoteRepository):
2479
def test_reconcile(self):
2480
transport_path = 'hill'
2481
repo, client = self.setup_fake_client_and_repository(transport_path)
2482
body = (b"garbage_inventories: 2\n"
2483
b"inconsistent_parents: 3\n")
2484
client.add_expected_call(
2485
b'Repository.lock_write', (b'hill/', b''),
2486
b'success', (b'ok', b'a token'))
2487
client.add_success_response_with_body(body, b'ok')
2488
reconciler = repo.reconcile()
2490
[('call', b'Repository.lock_write', (b'hill/', b'')),
2491
('call_expecting_body', b'Repository.reconcile',
2492
(b'hill/', b'a token'))],
2494
self.assertEqual(2, reconciler.garbage_inventories)
2495
self.assertEqual(3, reconciler.inconsistent_parents)
2498
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2500
def test_text(self):
2501
# ('ok',), body with signature text
2502
transport_path = 'quack'
2503
repo, client = self.setup_fake_client_and_repository(transport_path)
2504
client.add_success_response_with_body(
2506
self.assertEqual(b"THETEXT", repo.get_signature_text(b"revid"))
2508
[('call_expecting_body', b'Repository.get_revision_signature_text',
2509
(b'quack/', b'revid'))],
2512
def test_no_signature(self):
2513
transport_path = 'quick'
2514
repo, client = self.setup_fake_client_and_repository(transport_path)
2515
client.add_error_response(b'nosuchrevision', b'unknown')
2516
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2519
[('call_expecting_body', b'Repository.get_revision_signature_text',
2520
(b'quick/', b'unknown'))],
2524
class TestRepositoryGetGraph(TestRemoteRepository):
2526
def test_get_graph(self):
2527
# get_graph returns a graph with a custom parents provider.
2528
transport_path = 'quack'
2529
repo, client = self.setup_fake_client_and_repository(transport_path)
2530
graph = repo.get_graph()
2531
self.assertNotEqual(graph._parents_provider, repo)
2534
class TestRepositoryAddSignatureText(TestRemoteRepository):
2536
def test_add_signature_text(self):
2537
transport_path = 'quack'
2538
repo, client = self.setup_fake_client_and_repository(transport_path)
2539
client.add_expected_call(
2540
b'Repository.lock_write', (b'quack/', b''),
2541
b'success', (b'ok', b'a token'))
2542
client.add_expected_call(
2543
b'Repository.start_write_group', (b'quack/', b'a token'),
2544
b'success', (b'ok', (b'token1', )))
2545
client.add_expected_call(
2546
b'Repository.add_signature_text', (b'quack/', b'a token', b'rev1',
2548
b'success', (b'ok', ), None)
2550
repo.start_write_group()
2552
None, repo.add_signature_text(b"rev1", b"every bloody emperor"))
2554
('call_with_body_bytes_expecting_body',
2555
b'Repository.add_signature_text',
2556
(b'quack/', b'a token', b'rev1', b'token1'),
2557
b'every bloody emperor'),
2561
class TestRepositoryGetParentMap(TestRemoteRepository):
2563
def test_get_parent_map_caching(self):
2564
# get_parent_map returns from cache until unlock()
2565
# setup a reponse with two revisions
2566
r1 = u'\u0e33'.encode('utf8')
2567
r2 = u'\u0dab'.encode('utf8')
2568
lines = [b' '.join([r2, r1]), r1]
2569
encoded_body = bz2.compress(b'\n'.join(lines))
2571
transport_path = 'quack'
2572
repo, client = self.setup_fake_client_and_repository(transport_path)
2573
client.add_success_response_with_body(encoded_body, b'ok')
2574
client.add_success_response_with_body(encoded_body, b'ok')
2576
graph = repo.get_graph()
2577
parents = graph.get_parent_map([r2])
2578
self.assertEqual({r2: (r1,)}, parents)
2579
# locking and unlocking deeper should not reset
2582
parents = graph.get_parent_map([r1])
2583
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2585
[('call_with_body_bytes_expecting_body',
2586
b'Repository.get_parent_map', (b'quack/',
2587
b'include-missing:', r2),
2591
# now we call again, and it should use the second response.
2593
graph = repo.get_graph()
2594
parents = graph.get_parent_map([r1])
2595
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2597
[('call_with_body_bytes_expecting_body',
2598
b'Repository.get_parent_map', (b'quack/',
2599
b'include-missing:', r2),
2601
('call_with_body_bytes_expecting_body',
2602
b'Repository.get_parent_map', (b'quack/',
2603
b'include-missing:', r1),
2609
def test_get_parent_map_reconnects_if_unknown_method(self):
2610
transport_path = 'quack'
2611
rev_id = b'revision-id'
2612
repo, client = self.setup_fake_client_and_repository(transport_path)
2613
client.add_unknown_method_response(b'Repository.get_parent_map')
2614
client.add_success_response_with_body(rev_id, b'ok')
2615
self.assertFalse(client._medium._is_remote_before((1, 2)))
2616
parents = repo.get_parent_map([rev_id])
2618
[('call_with_body_bytes_expecting_body',
2619
b'Repository.get_parent_map',
2620
(b'quack/', b'include-missing:', rev_id), b'\n\n0'),
2621
('disconnect medium',),
2622
('call_expecting_body', b'Repository.get_revision_graph',
2625
# The medium is now marked as being connected to an older server
2626
self.assertTrue(client._medium._is_remote_before((1, 2)))
2627
self.assertEqual({rev_id: (b'null:',)}, parents)
2629
def test_get_parent_map_fallback_parentless_node(self):
2630
"""get_parent_map falls back to get_revision_graph on old servers. The
2631
results from get_revision_graph are tweaked to match the get_parent_map
2634
Specifically, a {key: ()} result from get_revision_graph means "no
2635
parents" for that key, which in get_parent_map results should be
2636
represented as {key: ('null:',)}.
2638
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2640
rev_id = b'revision-id'
2641
transport_path = 'quack'
2642
repo, client = self.setup_fake_client_and_repository(transport_path)
2643
client.add_success_response_with_body(rev_id, b'ok')
2644
client._medium._remember_remote_is_before((1, 2))
2645
parents = repo.get_parent_map([rev_id])
2647
[('call_expecting_body', b'Repository.get_revision_graph',
2650
self.assertEqual({rev_id: (b'null:',)}, parents)
2652
def test_get_parent_map_unexpected_response(self):
2653
repo, client = self.setup_fake_client_and_repository('path')
2654
client.add_success_response(b'something unexpected!')
2656
errors.UnexpectedSmartServerResponse,
2657
repo.get_parent_map, [b'a-revision-id'])
2659
def test_get_parent_map_negative_caches_missing_keys(self):
2660
self.setup_smart_server_with_call_log()
2661
repo = self.make_repository('foo')
2662
self.assertIsInstance(repo, RemoteRepository)
2664
self.addCleanup(repo.unlock)
2665
self.reset_smart_call_log()
2666
graph = repo.get_graph()
2668
{}, graph.get_parent_map([b'some-missing', b'other-missing']))
2669
self.assertLength(1, self.hpss_calls)
2670
# No call if we repeat this
2671
self.reset_smart_call_log()
2672
graph = repo.get_graph()
2674
{}, graph.get_parent_map([b'some-missing', b'other-missing']))
2675
self.assertLength(0, self.hpss_calls)
2676
# Asking for more unknown keys makes a request.
2677
self.reset_smart_call_log()
2678
graph = repo.get_graph()
2680
{}, graph.get_parent_map([b'some-missing', b'other-missing',
2682
self.assertLength(1, self.hpss_calls)
2684
def disableExtraResults(self):
2685
self.overrideAttr(SmartServerRepositoryGetParentMap,
2686
'no_extra_results', True)
2688
def test_null_cached_missing_and_stop_key(self):
2689
self.setup_smart_server_with_call_log()
2690
# Make a branch with a single revision.
2691
builder = self.make_branch_builder('foo')
2692
builder.start_series()
2693
builder.build_snapshot(None, [
2694
('add', ('', b'root-id', 'directory', ''))],
2695
revision_id=b'first')
2696
builder.finish_series()
2697
branch = builder.get_branch()
2698
repo = branch.repository
2699
self.assertIsInstance(repo, RemoteRepository)
2700
# Stop the server from sending extra results.
2701
self.disableExtraResults()
2703
self.addCleanup(repo.unlock)
2704
self.reset_smart_call_log()
2705
graph = repo.get_graph()
2706
# Query for b'first' and b'null:'. Because b'null:' is a parent of
2707
# 'first' it will be a candidate for the stop_keys of subsequent
2708
# requests, and because b'null:' was queried but not returned it will
2709
# be cached as missing.
2710
self.assertEqual({b'first': (b'null:',)},
2711
graph.get_parent_map([b'first', b'null:']))
2712
# Now query for another key. This request will pass along a recipe of
2713
# start and stop keys describing the already cached results, and this
2714
# recipe's revision count must be correct (or else it will trigger an
2715
# error from the server).
2716
self.assertEqual({}, graph.get_parent_map([b'another-key']))
2717
# This assertion guards against disableExtraResults silently failing to
2718
# work, thus invalidating the test.
2719
self.assertLength(2, self.hpss_calls)
2721
def test_get_parent_map_gets_ghosts_from_result(self):
2722
# asking for a revision should negatively cache close ghosts in its
2724
self.setup_smart_server_with_call_log()
2725
tree = self.make_branch_and_memory_tree('foo')
2726
with tree.lock_write():
2727
builder = treebuilder.TreeBuilder()
2728
builder.start_tree(tree)
2730
builder.finish_tree()
2731
tree.set_parent_ids([b'non-existant'],
2732
allow_leftmost_as_ghost=True)
2733
rev_id = tree.commit('')
2735
self.addCleanup(tree.unlock)
2736
repo = tree.branch.repository
2737
self.assertIsInstance(repo, RemoteRepository)
2739
repo.get_parent_map([rev_id])
2740
self.reset_smart_call_log()
2741
# Now asking for rev_id's ghost parent should not make calls
2742
self.assertEqual({}, repo.get_parent_map([b'non-existant']))
2743
self.assertLength(0, self.hpss_calls)
2745
def test_exposes_get_cached_parent_map(self):
2746
"""RemoteRepository exposes get_cached_parent_map from
2749
r1 = u'\u0e33'.encode('utf8')
2750
r2 = u'\u0dab'.encode('utf8')
2751
lines = [b' '.join([r2, r1]), r1]
2752
encoded_body = bz2.compress(b'\n'.join(lines))
2754
transport_path = 'quack'
2755
repo, client = self.setup_fake_client_and_repository(transport_path)
2756
client.add_success_response_with_body(encoded_body, b'ok')
2758
# get_cached_parent_map should *not* trigger an RPC
2759
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2760
self.assertEqual([], client._calls)
2761
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2762
self.assertEqual({r1: (NULL_REVISION,)},
2763
repo.get_cached_parent_map([r1]))
2765
[('call_with_body_bytes_expecting_body',
2766
b'Repository.get_parent_map', (b'quack/',
2767
b'include-missing:', r2),
2773
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2775
def test_allows_new_revisions(self):
2776
"""get_parent_map's results can be updated by commit."""
2777
smart_server = test_server.SmartTCPServer_for_testing()
2778
self.start_server(smart_server)
2779
self.make_branch('branch')
2780
branch = Branch.open(smart_server.get_url() + '/branch')
2781
tree = branch.create_checkout('tree', lightweight=True)
2783
self.addCleanup(tree.unlock)
2784
graph = tree.branch.repository.get_graph()
2785
# This provides an opportunity for the missing rev-id to be cached.
2786
self.assertEqual({}, graph.get_parent_map([b'rev1']))
2787
tree.commit('message', rev_id=b'rev1')
2788
graph = tree.branch.repository.get_graph()
2789
self.assertEqual({b'rev1': (b'null:',)},
2790
graph.get_parent_map([b'rev1']))
2793
class TestRepositoryGetRevisions(TestRemoteRepository):
2795
def test_hpss_missing_revision(self):
2796
transport_path = 'quack'
2797
repo, client = self.setup_fake_client_and_repository(transport_path)
2798
client.add_success_response_with_body(
2800
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2801
[b'somerev1', b'anotherrev2'])
2803
[('call_with_body_bytes_expecting_body',
2804
b'Repository.iter_revisions', (b'quack/', ),
2805
b"somerev1\nanotherrev2")],
2808
def test_hpss_get_single_revision(self):
2809
transport_path = 'quack'
2810
repo, client = self.setup_fake_client_and_repository(transport_path)
2811
somerev1 = Revision(b"somerev1")
2812
somerev1.committer = "Joe Committer <joe@example.com>"
2813
somerev1.timestamp = 1321828927
2814
somerev1.timezone = -60
2815
somerev1.inventory_sha1 = b"691b39be74c67b1212a75fcb19c433aaed903c2b"
2816
somerev1.message = "Message"
2817
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2819
# Split up body into two bits to make sure the zlib compression object
2820
# gets data fed twice.
2821
client.add_success_response_with_body(
2822
[body[:10], body[10:]], b'ok', b'10')
2823
revs = repo.get_revisions([b'somerev1'])
2824
self.assertEqual(revs, [somerev1])
2826
[('call_with_body_bytes_expecting_body',
2827
b'Repository.iter_revisions',
2828
(b'quack/', ), b"somerev1")],
2832
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2834
def test_null_revision(self):
2835
# a null revision has the predictable result {}, we should have no wire
2836
# traffic when calling it with this argument
2837
transport_path = 'empty'
2838
repo, client = self.setup_fake_client_and_repository(transport_path)
2839
client.add_success_response(b'notused')
2840
# actual RemoteRepository.get_revision_graph is gone, but there's an
2841
# equivalent private method for testing
2842
result = repo._get_revision_graph(NULL_REVISION)
2843
self.assertEqual([], client._calls)
2844
self.assertEqual({}, result)
2846
def test_none_revision(self):
2847
# with none we want the entire graph
2848
r1 = u'\u0e33'.encode('utf8')
2849
r2 = u'\u0dab'.encode('utf8')
2850
lines = [b' '.join([r2, r1]), r1]
2851
encoded_body = b'\n'.join(lines)
2853
transport_path = 'sinhala'
2854
repo, client = self.setup_fake_client_and_repository(transport_path)
2855
client.add_success_response_with_body(encoded_body, b'ok')
2856
# actual RemoteRepository.get_revision_graph is gone, but there's an
2857
# equivalent private method for testing
2858
result = repo._get_revision_graph(None)
2860
[('call_expecting_body', b'Repository.get_revision_graph',
2861
(b'sinhala/', b''))],
2863
self.assertEqual({r1: (), r2: (r1, )}, result)
2865
def test_specific_revision(self):
2866
# with a specific revision we want the graph for that
2867
# with none we want the entire graph
2868
r11 = u'\u0e33'.encode('utf8')
2869
r12 = u'\xc9'.encode('utf8')
2870
r2 = u'\u0dab'.encode('utf8')
2871
lines = [b' '.join([r2, r11, r12]), r11, r12]
2872
encoded_body = b'\n'.join(lines)
2874
transport_path = 'sinhala'
2875
repo, client = self.setup_fake_client_and_repository(transport_path)
2876
client.add_success_response_with_body(encoded_body, b'ok')
2877
result = repo._get_revision_graph(r2)
2879
[('call_expecting_body', b'Repository.get_revision_graph',
2880
(b'sinhala/', r2))],
2882
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2884
def test_no_such_revision(self):
2886
transport_path = 'sinhala'
2887
repo, client = self.setup_fake_client_and_repository(transport_path)
2888
client.add_error_response(b'nosuchrevision', revid)
2889
# also check that the right revision is reported in the error
2890
self.assertRaises(errors.NoSuchRevision,
2891
repo._get_revision_graph, revid)
2893
[('call_expecting_body', b'Repository.get_revision_graph',
2894
(b'sinhala/', revid))],
2897
def test_unexpected_error(self):
2899
transport_path = 'sinhala'
2900
repo, client = self.setup_fake_client_and_repository(transport_path)
2901
client.add_error_response(b'AnUnexpectedError')
2902
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2903
repo._get_revision_graph, revid)
2904
self.assertEqual((b'AnUnexpectedError',), e.error_tuple)
2907
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2910
repo, client = self.setup_fake_client_and_repository('quack')
2911
client.add_expected_call(
2912
b'Repository.get_rev_id_for_revno', (b'quack/',
2913
5, (42, b'rev-foo')),
2914
b'success', (b'ok', b'rev-five'))
2915
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2916
self.assertEqual((True, b'rev-five'), result)
2917
self.assertFinished(client)
2919
def test_history_incomplete(self):
2920
repo, client = self.setup_fake_client_and_repository('quack')
2921
client.add_expected_call(
2922
b'Repository.get_rev_id_for_revno', (b'quack/',
2923
5, (42, b'rev-foo')),
2924
b'success', (b'history-incomplete', 10, b'rev-ten'))
2925
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2926
self.assertEqual((False, (10, b'rev-ten')), result)
2927
self.assertFinished(client)
2929
def test_history_incomplete_with_fallback(self):
2930
"""A 'history-incomplete' response causes the fallback repository to be
2931
queried too, if one is set.
2933
# Make a repo with a fallback repo, both using a FakeClient.
2934
format = remote.response_tuple_to_repo_format(
2935
(b'yes', b'no', b'yes', self.get_repo_format().network_name()))
2936
repo, client = self.setup_fake_client_and_repository('quack')
2937
repo._format = format
2938
fallback_repo, ignored = self.setup_fake_client_and_repository(
2940
fallback_repo._client = client
2941
fallback_repo._format = format
2942
repo.add_fallback_repository(fallback_repo)
2943
# First the client should ask the primary repo
2944
client.add_expected_call(
2945
b'Repository.get_rev_id_for_revno', (b'quack/',
2946
1, (42, b'rev-foo')),
2947
b'success', (b'history-incomplete', 2, b'rev-two'))
2948
# Then it should ask the fallback, using revno/revid from the
2949
# history-incomplete response as the known revno/revid.
2950
client.add_expected_call(
2951
b'Repository.get_rev_id_for_revno', (
2952
b'fallback/', 1, (2, b'rev-two')),
2953
b'success', (b'ok', b'rev-one'))
2954
result = repo.get_rev_id_for_revno(1, (42, b'rev-foo'))
2955
self.assertEqual((True, b'rev-one'), result)
2956
self.assertFinished(client)
2958
def test_nosuchrevision(self):
2959
# 'nosuchrevision' is returned when the known-revid is not found in the
2960
# remote repo. The client translates that response to NoSuchRevision.
2961
repo, client = self.setup_fake_client_and_repository('quack')
2962
client.add_expected_call(
2963
b'Repository.get_rev_id_for_revno', (b'quack/',
2964
5, (42, b'rev-foo')),
2965
b'error', (b'nosuchrevision', b'rev-foo'))
2967
errors.NoSuchRevision,
2968
repo.get_rev_id_for_revno, 5, (42, b'rev-foo'))
2969
self.assertFinished(client)
2971
def test_branch_fallback_locking(self):
2972
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2973
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2974
will be invoked, which will fail if the repo is unlocked.
2976
self.setup_smart_server_with_call_log()
2977
tree = self.make_branch_and_memory_tree('.')
2980
rev1 = tree.commit('First')
2981
tree.commit('Second')
2983
branch = tree.branch
2984
self.assertFalse(branch.is_locked())
2985
self.reset_smart_call_log()
2986
verb = b'Repository.get_rev_id_for_revno'
2987
self.disable_verb(verb)
2988
self.assertEqual(rev1, branch.get_rev_id(1))
2989
self.assertLength(1, [call for call in self.hpss_calls if
2990
call.call.method == verb])
2993
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2995
def test_has_signature_for_revision_id(self):
2996
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2997
transport_path = 'quack'
2998
repo, client = self.setup_fake_client_and_repository(transport_path)
2999
client.add_success_response(b'yes')
3000
result = repo.has_signature_for_revision_id(b'A')
3002
[('call', b'Repository.has_signature_for_revision_id',
3003
(b'quack/', b'A'))],
3005
self.assertEqual(True, result)
3007
def test_is_not_shared(self):
3008
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
3009
transport_path = 'qwack'
3010
repo, client = self.setup_fake_client_and_repository(transport_path)
3011
client.add_success_response(b'no')
3012
result = repo.has_signature_for_revision_id(b'A')
3014
[('call', b'Repository.has_signature_for_revision_id',
3015
(b'qwack/', b'A'))],
3017
self.assertEqual(False, result)
3020
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
3022
def test_get_physical_lock_status_yes(self):
3023
transport_path = 'qwack'
3024
repo, client = self.setup_fake_client_and_repository(transport_path)
3025
client.add_success_response(b'yes')
3026
result = repo.get_physical_lock_status()
3028
[('call', b'Repository.get_physical_lock_status',
3031
self.assertEqual(True, result)
3033
def test_get_physical_lock_status_no(self):
3034
transport_path = 'qwack'
3035
repo, client = self.setup_fake_client_and_repository(transport_path)
3036
client.add_success_response(b'no')
3037
result = repo.get_physical_lock_status()
3039
[('call', b'Repository.get_physical_lock_status',
3042
self.assertEqual(False, result)
3045
class TestRepositoryIsShared(TestRemoteRepository):
3047
def test_is_shared(self):
3048
# ('yes', ) for Repository.is_shared -> 'True'.
3049
transport_path = 'quack'
3050
repo, client = self.setup_fake_client_and_repository(transport_path)
3051
client.add_success_response(b'yes')
3052
result = repo.is_shared()
3054
[('call', b'Repository.is_shared', (b'quack/',))],
3056
self.assertEqual(True, result)
3058
def test_is_not_shared(self):
3059
# ('no', ) for Repository.is_shared -> 'False'.
3060
transport_path = 'qwack'
3061
repo, client = self.setup_fake_client_and_repository(transport_path)
3062
client.add_success_response(b'no')
3063
result = repo.is_shared()
3065
[('call', b'Repository.is_shared', (b'qwack/',))],
3067
self.assertEqual(False, result)
3070
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3072
def test_make_working_trees(self):
3073
# ('yes', ) for Repository.make_working_trees -> 'True'.
3074
transport_path = 'quack'
3075
repo, client = self.setup_fake_client_and_repository(transport_path)
3076
client.add_success_response(b'yes')
3077
result = repo.make_working_trees()
3079
[('call', b'Repository.make_working_trees', (b'quack/',))],
3081
self.assertEqual(True, result)
3083
def test_no_working_trees(self):
3084
# ('no', ) for Repository.make_working_trees -> 'False'.
3085
transport_path = 'qwack'
3086
repo, client = self.setup_fake_client_and_repository(transport_path)
3087
client.add_success_response(b'no')
3088
result = repo.make_working_trees()
3090
[('call', b'Repository.make_working_trees', (b'qwack/',))],
3092
self.assertEqual(False, result)
3095
class TestRepositoryLockWrite(TestRemoteRepository):
3097
def test_lock_write(self):
3098
transport_path = 'quack'
3099
repo, client = self.setup_fake_client_and_repository(transport_path)
3100
client.add_success_response(b'ok', b'a token')
3101
token = repo.lock_write().repository_token
3103
[('call', b'Repository.lock_write', (b'quack/', b''))],
3105
self.assertEqual(b'a token', token)
3107
def test_lock_write_already_locked(self):
3108
transport_path = 'quack'
3109
repo, client = self.setup_fake_client_and_repository(transport_path)
3110
client.add_error_response(b'LockContention')
3111
self.assertRaises(errors.LockContention, repo.lock_write)
3113
[('call', b'Repository.lock_write', (b'quack/', b''))],
3116
def test_lock_write_unlockable(self):
3117
transport_path = 'quack'
3118
repo, client = self.setup_fake_client_and_repository(transport_path)
3119
client.add_error_response(b'UnlockableTransport')
3120
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3122
[('call', b'Repository.lock_write', (b'quack/', b''))],
3126
class TestRepositoryWriteGroups(TestRemoteRepository):
3128
def test_start_write_group(self):
3129
transport_path = 'quack'
3130
repo, client = self.setup_fake_client_and_repository(transport_path)
3131
client.add_expected_call(
3132
b'Repository.lock_write', (b'quack/', b''),
3133
b'success', (b'ok', b'a token'))
3134
client.add_expected_call(
3135
b'Repository.start_write_group', (b'quack/', b'a token'),
3136
b'success', (b'ok', (b'token1', )))
3138
repo.start_write_group()
3140
def test_start_write_group_unsuspendable(self):
3141
# Some repositories do not support suspending write
3142
# groups. For those, fall back to the "real" repository.
3143
transport_path = 'quack'
3144
repo, client = self.setup_fake_client_and_repository(transport_path)
3146
def stub_ensure_real():
3147
client._calls.append(('_ensure_real',))
3148
repo._real_repository = _StubRealPackRepository(client._calls)
3149
repo._ensure_real = stub_ensure_real
3150
client.add_expected_call(
3151
b'Repository.lock_write', (b'quack/', b''),
3152
b'success', (b'ok', b'a token'))
3153
client.add_expected_call(
3154
b'Repository.start_write_group', (b'quack/', b'a token'),
3155
b'error', (b'UnsuspendableWriteGroup',))
3157
repo.start_write_group()
3158
self.assertEqual(client._calls[-2:], [
3160
('start_write_group',)])
3162
def test_commit_write_group(self):
3163
transport_path = 'quack'
3164
repo, client = self.setup_fake_client_and_repository(transport_path)
3165
client.add_expected_call(
3166
b'Repository.lock_write', (b'quack/', b''),
3167
b'success', (b'ok', b'a token'))
3168
client.add_expected_call(
3169
b'Repository.start_write_group', (b'quack/', b'a token'),
3170
b'success', (b'ok', [b'token1']))
3171
client.add_expected_call(
3172
b'Repository.commit_write_group', (b'quack/',
3173
b'a token', [b'token1']),
3174
b'success', (b'ok',))
3176
repo.start_write_group()
3177
repo.commit_write_group()
3179
def test_abort_write_group(self):
3180
transport_path = 'quack'
3181
repo, client = self.setup_fake_client_and_repository(transport_path)
3182
client.add_expected_call(
3183
b'Repository.lock_write', (b'quack/', b''),
3184
b'success', (b'ok', b'a token'))
3185
client.add_expected_call(
3186
b'Repository.start_write_group', (b'quack/', b'a token'),
3187
b'success', (b'ok', [b'token1']))
3188
client.add_expected_call(
3189
b'Repository.abort_write_group', (b'quack/',
3190
b'a token', [b'token1']),
3191
b'success', (b'ok',))
3193
repo.start_write_group()
3194
repo.abort_write_group(False)
3196
def test_suspend_write_group(self):
3197
transport_path = 'quack'
3198
repo, client = self.setup_fake_client_and_repository(transport_path)
3199
self.assertEqual([], repo.suspend_write_group())
3201
def test_resume_write_group(self):
3202
transport_path = 'quack'
3203
repo, client = self.setup_fake_client_and_repository(transport_path)
3204
client.add_expected_call(
3205
b'Repository.lock_write', (b'quack/', b''),
3206
b'success', (b'ok', b'a token'))
3207
client.add_expected_call(
3208
b'Repository.check_write_group', (b'quack/',
3209
b'a token', [b'token1']),
3210
b'success', (b'ok',))
3212
repo.resume_write_group(['token1'])
3215
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3217
def test_backwards_compat(self):
3218
self.setup_smart_server_with_call_log()
3219
repo = self.make_repository('.')
3220
self.reset_smart_call_log()
3221
verb = b'Repository.set_make_working_trees'
3222
self.disable_verb(verb)
3223
repo.set_make_working_trees(True)
3224
call_count = len([call for call in self.hpss_calls if
3225
call.call.method == verb])
3226
self.assertEqual(1, call_count)
3228
def test_current(self):
3229
transport_path = 'quack'
3230
repo, client = self.setup_fake_client_and_repository(transport_path)
3231
client.add_expected_call(
3232
b'Repository.set_make_working_trees', (b'quack/', b'True'),
3233
b'success', (b'ok',))
3234
client.add_expected_call(
3235
b'Repository.set_make_working_trees', (b'quack/', b'False'),
3236
b'success', (b'ok',))
3237
repo.set_make_working_trees(True)
3238
repo.set_make_working_trees(False)
3241
class TestRepositoryUnlock(TestRemoteRepository):
3243
def test_unlock(self):
3244
transport_path = 'quack'
3245
repo, client = self.setup_fake_client_and_repository(transport_path)
3246
client.add_success_response(b'ok', b'a token')
3247
client.add_success_response(b'ok')
3251
[('call', b'Repository.lock_write', (b'quack/', b'')),
3252
('call', b'Repository.unlock', (b'quack/', b'a token'))],
3255
def test_unlock_wrong_token(self):
3256
# If somehow the token is wrong, unlock will raise TokenMismatch.
3257
transport_path = 'quack'
3258
repo, client = self.setup_fake_client_and_repository(transport_path)
3259
client.add_success_response(b'ok', b'a token')
3260
client.add_error_response(b'TokenMismatch')
3262
self.assertRaises(errors.TokenMismatch, repo.unlock)
3265
class TestRepositoryHasRevision(TestRemoteRepository):
3267
def test_none(self):
3268
# repo.has_revision(None) should not cause any traffic.
3269
transport_path = 'quack'
3270
repo, client = self.setup_fake_client_and_repository(transport_path)
3272
# The null revision is always there, so has_revision(None) == True.
3273
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3275
# The remote repo shouldn't be accessed.
3276
self.assertEqual([], client._calls)
3279
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3280
"""Test Repository.iter_file_bytes."""
3282
def test_single(self):
3283
transport_path = 'quack'
3284
repo, client = self.setup_fake_client_and_repository(transport_path)
3285
client.add_expected_call(
3286
b'Repository.iter_files_bytes', (b'quack/', ),
3287
b'success', (b'ok',), iter([b"ok\x000", b"\n", zlib.compress(b"mydata" * 10)]))
3288
for (identifier, byte_stream) in repo.iter_files_bytes([(b"somefile",
3289
b"somerev", b"myid")]):
3290
self.assertEqual(b"myid", identifier)
3291
self.assertEqual(b"".join(byte_stream), b"mydata" * 10)
3293
def test_missing(self):
3294
transport_path = 'quack'
3295
repo, client = self.setup_fake_client_and_repository(transport_path)
3296
client.add_expected_call(
3297
b'Repository.iter_files_bytes',
3299
b'error', (b'RevisionNotPresent', b'somefile', b'somerev'),
3300
iter([b"absent\0somefile\0somerev\n"]))
3301
self.assertRaises(errors.RevisionNotPresent, list,
3302
repo.iter_files_bytes(
3303
[(b"somefile", b"somerev", b"myid")]))
3306
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3307
"""Base class for Repository.insert_stream and .insert_stream_1.19
3311
def checkInsertEmptyStream(self, repo, client):
3312
"""Insert an empty stream, checking the result.
3314
This checks that there are no resume_tokens or missing_keys, and that
3315
the client is finished.
3317
sink = repo._get_sink()
3318
fmt = repository.format_registry.get_default()
3319
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3320
self.assertEqual([], resume_tokens)
3321
self.assertEqual(set(), missing_keys)
3322
self.assertFinished(client)
3325
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3326
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3329
This test case is very similar to TestRepositoryInsertStream_1_19.
3333
super(TestRepositoryInsertStream, self).setUp()
3334
self.disable_verb(b'Repository.insert_stream_1.19')
3336
def test_unlocked_repo(self):
3337
transport_path = 'quack'
3338
repo, client = self.setup_fake_client_and_repository(transport_path)
3339
client.add_expected_call(
3340
b'Repository.insert_stream_1.19', (b'quack/', b''),
3341
b'unknown', (b'Repository.insert_stream_1.19',))
3342
client.add_expected_call(
3343
b'Repository.insert_stream', (b'quack/', b''),
3344
b'success', (b'ok',))
3345
client.add_expected_call(
3346
b'Repository.insert_stream', (b'quack/', b''),
3347
b'success', (b'ok',))
3348
self.checkInsertEmptyStream(repo, client)
3350
def test_locked_repo_with_no_lock_token(self):
3351
transport_path = 'quack'
3352
repo, client = self.setup_fake_client_and_repository(transport_path)
3353
client.add_expected_call(
3354
b'Repository.lock_write', (b'quack/', b''),
3355
b'success', (b'ok', b''))
3356
client.add_expected_call(
3357
b'Repository.insert_stream_1.19', (b'quack/', b''),
3358
b'unknown', (b'Repository.insert_stream_1.19',))
3359
client.add_expected_call(
3360
b'Repository.insert_stream', (b'quack/', b''),
3361
b'success', (b'ok',))
3362
client.add_expected_call(
3363
b'Repository.insert_stream', (b'quack/', b''),
3364
b'success', (b'ok',))
3366
self.checkInsertEmptyStream(repo, client)
3368
def test_locked_repo_with_lock_token(self):
3369
transport_path = 'quack'
3370
repo, client = self.setup_fake_client_and_repository(transport_path)
3371
client.add_expected_call(
3372
b'Repository.lock_write', (b'quack/', b''),
3373
b'success', (b'ok', b'a token'))
3374
client.add_expected_call(
3375
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3376
b'unknown', (b'Repository.insert_stream_1.19',))
3377
client.add_expected_call(
3378
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3379
b'success', (b'ok',))
3380
client.add_expected_call(
3381
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3382
b'success', (b'ok',))
3384
self.checkInsertEmptyStream(repo, client)
3386
def test_stream_with_inventory_deltas(self):
3387
"""'inventory-deltas' substreams cannot be sent to the
3388
Repository.insert_stream verb, because not all servers that implement
3389
that verb will accept them. So when one is encountered the RemoteSink
3390
immediately stops using that verb and falls back to VFS insert_stream.
3392
transport_path = 'quack'
3393
repo, client = self.setup_fake_client_and_repository(transport_path)
3394
client.add_expected_call(
3395
b'Repository.insert_stream_1.19', (b'quack/', b''),
3396
b'unknown', (b'Repository.insert_stream_1.19',))
3397
client.add_expected_call(
3398
b'Repository.insert_stream', (b'quack/', b''),
3399
b'success', (b'ok',))
3400
client.add_expected_call(
3401
b'Repository.insert_stream', (b'quack/', b''),
3402
b'success', (b'ok',))
3403
# Create a fake real repository for insert_stream to fall back on, so
3404
# that we can directly see the records the RemoteSink passes to the
3411
def insert_stream(self, stream, src_format, resume_tokens):
3412
for substream_kind, substream in stream:
3413
self.records.append(
3414
(substream_kind, [record.key for record in substream]))
3415
return [b'fake tokens'], [b'fake missing keys']
3416
fake_real_sink = FakeRealSink()
3418
class FakeRealRepository:
3419
def _get_sink(self):
3420
return fake_real_sink
3422
def is_in_write_group(self):
3425
def refresh_data(self):
3427
repo._real_repository = FakeRealRepository()
3428
sink = repo._get_sink()
3429
fmt = repository.format_registry.get_default()
3430
stream = self.make_stream_with_inv_deltas(fmt)
3431
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3432
# Every record from the first inventory delta should have been sent to
3434
expected_records = [
3435
('inventory-deltas', [(b'rev2',), (b'rev3',)]),
3436
('texts', [(b'some-rev', b'some-file')])]
3437
self.assertEqual(expected_records, fake_real_sink.records)
3438
# The return values from the real sink's insert_stream are propagated
3439
# back to the original caller.
3440
self.assertEqual([b'fake tokens'], resume_tokens)
3441
self.assertEqual([b'fake missing keys'], missing_keys)
3442
self.assertFinished(client)
3444
def make_stream_with_inv_deltas(self, fmt):
3445
"""Make a simple stream with an inventory delta followed by more
3446
records and more substreams to test that all records and substreams
3447
from that point on are used.
3449
This sends, in order:
3450
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3452
* texts substream: (some-rev, some-file)
3454
# Define a stream using generators so that it isn't rewindable.
3455
inv = inventory.Inventory(revision_id=b'rev1')
3456
inv.root.revision = b'rev1'
3458
def stream_with_inv_delta():
3459
yield ('inventories', inventories_substream())
3460
yield ('inventory-deltas', inventory_delta_substream())
3462
versionedfile.FulltextContentFactory(
3463
(b'some-rev', b'some-file'), (), None, b'content')])
3465
def inventories_substream():
3466
# An empty inventory fulltext. This will be streamed normally.
3467
text = fmt._serializer.write_inventory_to_string(inv)
3468
yield versionedfile.FulltextContentFactory(
3469
(b'rev1',), (), None, text)
3471
def inventory_delta_substream():
3472
# An inventory delta. This can't be streamed via this verb, so it
3473
# will trigger a fallback to VFS insert_stream.
3474
entry = inv.make_entry(
3475
'directory', 'newdir', inv.root.file_id, b'newdir-id')
3476
entry.revision = b'ghost'
3477
delta = [(None, 'newdir', b'newdir-id', entry)]
3478
serializer = inventory_delta.InventoryDeltaSerializer(
3479
versioned_root=True, tree_references=False)
3480
lines = serializer.delta_to_lines(b'rev1', b'rev2', delta)
3481
yield versionedfile.ChunkedContentFactory(
3482
(b'rev2',), ((b'rev1',)), None, lines)
3484
lines = serializer.delta_to_lines(b'rev1', b'rev3', delta)
3485
yield versionedfile.ChunkedContentFactory(
3486
(b'rev3',), ((b'rev1',)), None, lines)
3487
return stream_with_inv_delta()
3490
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3492
def test_unlocked_repo(self):
3493
transport_path = 'quack'
3494
repo, client = self.setup_fake_client_and_repository(transport_path)
3495
client.add_expected_call(
3496
b'Repository.insert_stream_1.19', (b'quack/', b''),
3497
b'success', (b'ok',))
3498
client.add_expected_call(
3499
b'Repository.insert_stream_1.19', (b'quack/', b''),
3500
b'success', (b'ok',))
3501
self.checkInsertEmptyStream(repo, client)
3503
def test_locked_repo_with_no_lock_token(self):
3504
transport_path = 'quack'
3505
repo, client = self.setup_fake_client_and_repository(transport_path)
3506
client.add_expected_call(
3507
b'Repository.lock_write', (b'quack/', b''),
3508
b'success', (b'ok', b''))
3509
client.add_expected_call(
3510
b'Repository.insert_stream_1.19', (b'quack/', b''),
3511
b'success', (b'ok',))
3512
client.add_expected_call(
3513
b'Repository.insert_stream_1.19', (b'quack/', b''),
3514
b'success', (b'ok',))
3516
self.checkInsertEmptyStream(repo, client)
3518
def test_locked_repo_with_lock_token(self):
3519
transport_path = 'quack'
3520
repo, client = self.setup_fake_client_and_repository(transport_path)
3521
client.add_expected_call(
3522
b'Repository.lock_write', (b'quack/', b''),
3523
b'success', (b'ok', b'a token'))
3524
client.add_expected_call(
3525
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3526
b'success', (b'ok',))
3527
client.add_expected_call(
3528
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3529
b'success', (b'ok',))
3531
self.checkInsertEmptyStream(repo, client)
3534
class TestRepositoryTarball(TestRemoteRepository):
3536
# This is a canned tarball reponse we can validate against
3537
tarball_content = base64.b64decode(
3538
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3539
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3540
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3541
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3542
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3543
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3544
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3545
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3546
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3547
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3548
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3549
'nWQ7QH/F3JFOFCQ0aSPfA='
3552
def test_repository_tarball(self):
3553
# Test that Repository.tarball generates the right operations
3554
transport_path = 'repo'
3555
expected_calls = [('call_expecting_body', b'Repository.tarball',
3556
(b'repo/', b'bz2',),),
3558
repo, client = self.setup_fake_client_and_repository(transport_path)
3559
client.add_success_response_with_body(self.tarball_content, b'ok')
3560
# Now actually ask for the tarball
3561
tarball_file = repo._get_tarball('bz2')
3563
self.assertEqual(expected_calls, client._calls)
3564
self.assertEqual(self.tarball_content, tarball_file.read())
3566
tarball_file.close()
3569
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3570
"""RemoteRepository.copy_content_into optimizations"""
3572
def test_copy_content_remote_to_local(self):
3573
self.transport_server = test_server.SmartTCPServer_for_testing
3574
src_repo = self.make_repository('repo1')
3575
src_repo = repository.Repository.open(self.get_url('repo1'))
3576
# At the moment the tarball-based copy_content_into can't write back
3577
# into a smart server. It would be good if it could upload the
3578
# tarball; once that works we'd have to create repositories of
3579
# different formats. -- mbp 20070410
3580
dest_url = self.get_vfs_only_url('repo2')
3581
dest_bzrdir = BzrDir.create(dest_url)
3582
dest_repo = dest_bzrdir.create_repository()
3583
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3584
self.assertTrue(isinstance(src_repo, RemoteRepository))
3585
src_repo.copy_content_into(dest_repo)
3588
class _StubRealPackRepository(object):
3590
def __init__(self, calls):
3592
self._pack_collection = _StubPackCollection(calls)
3594
def start_write_group(self):
3595
self.calls.append(('start_write_group',))
3597
def is_in_write_group(self):
3600
def refresh_data(self):
3601
self.calls.append(('pack collection reload_pack_names',))
3604
class _StubPackCollection(object):
3606
def __init__(self, calls):
3610
self.calls.append(('pack collection autopack',))
3613
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3614
"""Tests for RemoteRepository.autopack implementation."""
3617
"""When the server returns 'ok' and there's no _real_repository, then
3618
nothing else happens: the autopack method is done.
3620
transport_path = 'quack'
3621
repo, client = self.setup_fake_client_and_repository(transport_path)
3622
client.add_expected_call(
3623
b'PackRepository.autopack', (b'quack/',), b'success', (b'ok',))
3625
self.assertFinished(client)
3627
def test_ok_with_real_repo(self):
3628
"""When the server returns 'ok' and there is a _real_repository, then
3629
the _real_repository's reload_pack_name's method will be called.
3631
transport_path = 'quack'
3632
repo, client = self.setup_fake_client_and_repository(transport_path)
3633
client.add_expected_call(
3634
b'PackRepository.autopack', (b'quack/',),
3635
b'success', (b'ok',))
3636
repo._real_repository = _StubRealPackRepository(client._calls)
3639
[('call', b'PackRepository.autopack', (b'quack/',)),
3640
('pack collection reload_pack_names',)],
3643
def test_backwards_compatibility(self):
3644
"""If the server does not recognise the PackRepository.autopack verb,
3645
fallback to the real_repository's implementation.
3647
transport_path = 'quack'
3648
repo, client = self.setup_fake_client_and_repository(transport_path)
3649
client.add_unknown_method_response(b'PackRepository.autopack')
3651
def stub_ensure_real():
3652
client._calls.append(('_ensure_real',))
3653
repo._real_repository = _StubRealPackRepository(client._calls)
3654
repo._ensure_real = stub_ensure_real
3657
[('call', b'PackRepository.autopack', (b'quack/',)),
3659
('pack collection autopack',)],
3662
def test_oom_error_reporting(self):
3663
"""An out-of-memory condition on the server is reported clearly"""
3664
transport_path = 'quack'
3665
repo, client = self.setup_fake_client_and_repository(transport_path)
3666
client.add_expected_call(
3667
b'PackRepository.autopack', (b'quack/',),
3668
b'error', (b'MemoryError',))
3669
err = self.assertRaises(errors.BzrError, repo.autopack)
3670
self.assertContainsRe(str(err), "^remote server out of mem")
3673
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3674
"""Base class for unit tests for breezy.bzr.remote._translate_error."""
3676
def translateTuple(self, error_tuple, **context):
3677
"""Call _translate_error with an ErrorFromSmartServer built from the
3680
:param error_tuple: A tuple of a smart server response, as would be
3681
passed to an ErrorFromSmartServer.
3682
:kwargs context: context items to call _translate_error with.
3684
:returns: The error raised by _translate_error.
3686
# Raise the ErrorFromSmartServer before passing it as an argument,
3687
# because _translate_error may need to re-raise it with a bare 'raise'
3689
server_error = errors.ErrorFromSmartServer(error_tuple)
3690
translated_error = self.translateErrorFromSmartServer(
3691
server_error, **context)
3692
return translated_error
3694
def translateErrorFromSmartServer(self, error_object, **context):
3695
"""Like translateTuple, but takes an already constructed
3696
ErrorFromSmartServer rather than a tuple.
3700
except errors.ErrorFromSmartServer as server_error:
3701
translated_error = self.assertRaises(
3702
errors.BzrError, remote._translate_error, server_error,
3704
return translated_error
3707
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3708
"""Unit tests for breezy.bzr.remote._translate_error.
3710
Given an ErrorFromSmartServer (which has an error tuple from a smart
3711
server) and some context, _translate_error raises more specific errors from
3714
This test case covers the cases where _translate_error succeeds in
3715
translating an ErrorFromSmartServer to something better. See
3716
TestErrorTranslationRobustness for other cases.
3719
def test_NoSuchRevision(self):
3720
branch = self.make_branch('')
3722
translated_error = self.translateTuple(
3723
(b'NoSuchRevision', revid), branch=branch)
3724
expected_error = errors.NoSuchRevision(branch, revid)
3725
self.assertEqual(expected_error, translated_error)
3727
def test_nosuchrevision(self):
3728
repository = self.make_repository('')
3730
translated_error = self.translateTuple(
3731
(b'nosuchrevision', revid), repository=repository)
3732
expected_error = errors.NoSuchRevision(repository, revid)
3733
self.assertEqual(expected_error, translated_error)
3735
def test_nobranch(self):
3736
bzrdir = self.make_controldir('')
3737
translated_error = self.translateTuple((b'nobranch',), bzrdir=bzrdir)
3738
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3739
self.assertEqual(expected_error, translated_error)
3741
def test_nobranch_one_arg(self):
3742
bzrdir = self.make_controldir('')
3743
translated_error = self.translateTuple(
3744
(b'nobranch', b'extra detail'), bzrdir=bzrdir)
3745
expected_error = errors.NotBranchError(
3746
path=bzrdir.root_transport.base,
3747
detail='extra detail')
3748
self.assertEqual(expected_error, translated_error)
3750
def test_norepository(self):
3751
bzrdir = self.make_controldir('')
3752
translated_error = self.translateTuple((b'norepository',),
3754
expected_error = errors.NoRepositoryPresent(bzrdir)
3755
self.assertEqual(expected_error, translated_error)
3757
def test_LockContention(self):
3758
translated_error = self.translateTuple((b'LockContention',))
3759
expected_error = errors.LockContention('(remote lock)')
3760
self.assertEqual(expected_error, translated_error)
3762
def test_UnlockableTransport(self):
3763
bzrdir = self.make_controldir('')
3764
translated_error = self.translateTuple(
3765
(b'UnlockableTransport',), bzrdir=bzrdir)
3766
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3767
self.assertEqual(expected_error, translated_error)
3769
def test_LockFailed(self):
3770
lock = 'str() of a server lock'
3771
why = 'str() of why'
3772
translated_error = self.translateTuple(
3773
(b'LockFailed', lock.encode('ascii'), why.encode('ascii')))
3774
expected_error = errors.LockFailed(lock, why)
3775
self.assertEqual(expected_error, translated_error)
3777
def test_TokenMismatch(self):
3778
token = 'a lock token'
3779
translated_error = self.translateTuple(
3780
(b'TokenMismatch',), token=token)
3781
expected_error = errors.TokenMismatch(token, '(remote token)')
3782
self.assertEqual(expected_error, translated_error)
3784
def test_Diverged(self):
3785
branch = self.make_branch('a')
3786
other_branch = self.make_branch('b')
3787
translated_error = self.translateTuple(
3788
(b'Diverged',), branch=branch, other_branch=other_branch)
3789
expected_error = errors.DivergedBranches(branch, other_branch)
3790
self.assertEqual(expected_error, translated_error)
3792
def test_NotStacked(self):
3793
branch = self.make_branch('')
3794
translated_error = self.translateTuple((b'NotStacked',), branch=branch)
3795
expected_error = errors.NotStacked(branch)
3796
self.assertEqual(expected_error, translated_error)
3798
def test_ReadError_no_args(self):
3800
translated_error = self.translateTuple((b'ReadError',), path=path)
3801
expected_error = errors.ReadError(path)
3802
self.assertEqual(expected_error, translated_error)
3804
def test_ReadError(self):
3806
translated_error = self.translateTuple(
3807
(b'ReadError', path.encode('utf-8')))
3808
expected_error = errors.ReadError(path)
3809
self.assertEqual(expected_error, translated_error)
3811
def test_IncompatibleRepositories(self):
3812
translated_error = self.translateTuple((b'IncompatibleRepositories',
3813
b"repo1", b"repo2", b"details here"))
3814
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3816
self.assertEqual(expected_error, translated_error)
3818
def test_GhostRevisionsHaveNoRevno(self):
3819
translated_error = self.translateTuple((b'GhostRevisionsHaveNoRevno',
3820
b"revid1", b"revid2"))
3821
expected_error = errors.GhostRevisionsHaveNoRevno(b"revid1", b"revid2")
3822
self.assertEqual(expected_error, translated_error)
3824
def test_PermissionDenied_no_args(self):
3826
translated_error = self.translateTuple((b'PermissionDenied',),
3828
expected_error = errors.PermissionDenied(path)
3829
self.assertEqual(expected_error, translated_error)
3831
def test_PermissionDenied_one_arg(self):
3833
translated_error = self.translateTuple(
3834
(b'PermissionDenied', path.encode('utf-8')))
3835
expected_error = errors.PermissionDenied(path)
3836
self.assertEqual(expected_error, translated_error)
3838
def test_PermissionDenied_one_arg_and_context(self):
3839
"""Given a choice between a path from the local context and a path on
3840
the wire, _translate_error prefers the path from the local context.
3842
local_path = 'local path'
3843
remote_path = 'remote path'
3844
translated_error = self.translateTuple(
3845
(b'PermissionDenied', remote_path.encode('utf-8')), path=local_path)
3846
expected_error = errors.PermissionDenied(local_path)
3847
self.assertEqual(expected_error, translated_error)
3849
def test_PermissionDenied_two_args(self):
3851
extra = 'a string with extra info'
3852
translated_error = self.translateTuple(
3853
(b'PermissionDenied', path.encode('utf-8'), extra.encode('utf-8')))
3854
expected_error = errors.PermissionDenied(path, extra)
3855
self.assertEqual(expected_error, translated_error)
3857
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3859
def test_NoSuchFile_context_path(self):
3860
local_path = "local path"
3861
translated_error = self.translateTuple((b'ReadError', b"remote path"),
3863
expected_error = errors.ReadError(local_path)
3864
self.assertEqual(expected_error, translated_error)
3866
def test_NoSuchFile_without_context(self):
3867
remote_path = "remote path"
3868
translated_error = self.translateTuple(
3869
(b'ReadError', remote_path.encode('utf-8')))
3870
expected_error = errors.ReadError(remote_path)
3871
self.assertEqual(expected_error, translated_error)
3873
def test_ReadOnlyError(self):
3874
translated_error = self.translateTuple((b'ReadOnlyError',))
3875
expected_error = errors.TransportNotPossible("readonly transport")
3876
self.assertEqual(expected_error, translated_error)
3878
def test_MemoryError(self):
3879
translated_error = self.translateTuple((b'MemoryError',))
3880
self.assertStartsWith(str(translated_error),
3881
"remote server out of memory")
3883
def test_generic_IndexError_no_classname(self):
3884
err = errors.ErrorFromSmartServer(
3885
(b'error', b"list index out of range"))
3886
translated_error = self.translateErrorFromSmartServer(err)
3887
expected_error = errors.UnknownErrorFromSmartServer(err)
3888
self.assertEqual(expected_error, translated_error)
3890
# GZ 2011-03-02: TODO test generic non-ascii error string
3892
def test_generic_KeyError(self):
3893
err = errors.ErrorFromSmartServer((b'error', b'KeyError', b"1"))
3894
translated_error = self.translateErrorFromSmartServer(err)
3895
expected_error = errors.UnknownErrorFromSmartServer(err)
3896
self.assertEqual(expected_error, translated_error)
3899
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3900
"""Unit tests for breezy.bzr.remote._translate_error's robustness.
3902
TestErrorTranslationSuccess is for cases where _translate_error can
3903
translate successfully. This class about how _translate_err behaves when
3904
it fails to translate: it re-raises the original error.
3907
def test_unrecognised_server_error(self):
3908
"""If the error code from the server is not recognised, the original
3909
ErrorFromSmartServer is propagated unmodified.
3911
error_tuple = (b'An unknown error tuple',)
3912
server_error = errors.ErrorFromSmartServer(error_tuple)
3913
translated_error = self.translateErrorFromSmartServer(server_error)
3914
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3915
self.assertEqual(expected_error, translated_error)
3917
def test_context_missing_a_key(self):
3918
"""In case of a bug in the client, or perhaps an unexpected response
3919
from a server, _translate_error returns the original error tuple from
3920
the server and mutters a warning.
3922
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3923
# in the context dict. So let's give it an empty context dict instead
3924
# to exercise its error recovery.
3925
error_tuple = (b'NoSuchRevision', b'revid')
3926
server_error = errors.ErrorFromSmartServer(error_tuple)
3927
translated_error = self.translateErrorFromSmartServer(server_error)
3928
self.assertEqual(server_error, translated_error)
3929
# In addition to re-raising ErrorFromSmartServer, some debug info has
3930
# been muttered to the log file for developer to look at.
3931
self.assertContainsRe(
3933
"Missing key 'branch' in context")
3935
def test_path_missing(self):
3936
"""Some translations (PermissionDenied, ReadError) can determine the
3937
'path' variable from either the wire or the local context. If neither
3938
has it, then an error is raised.
3940
error_tuple = (b'ReadError',)
3941
server_error = errors.ErrorFromSmartServer(error_tuple)
3942
translated_error = self.translateErrorFromSmartServer(server_error)
3943
self.assertEqual(server_error, translated_error)
3944
# In addition to re-raising ErrorFromSmartServer, some debug info has
3945
# been muttered to the log file for developer to look at.
3946
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3949
class TestStacking(tests.TestCaseWithTransport):
3950
"""Tests for operations on stacked remote repositories.
3952
The underlying format type must support stacking.
3955
def test_access_stacked_remote(self):
3956
# based on <http://launchpad.net/bugs/261315>
3957
# make a branch stacked on another repository containing an empty
3958
# revision, then open it over hpss - we should be able to see that
3960
base_builder = self.make_branch_builder('base', format='1.9')
3961
base_builder.start_series()
3962
base_revid = base_builder.build_snapshot(None,
3963
[('add', ('', None, 'directory', None))],
3964
'message', revision_id=b'rev-id')
3965
base_builder.finish_series()
3966
stacked_branch = self.make_branch('stacked', format='1.9')
3967
stacked_branch.set_stacked_on_url('../base')
3968
# start a server looking at this
3969
smart_server = test_server.SmartTCPServer_for_testing()
3970
self.start_server(smart_server)
3971
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3972
# can get its branch and repository
3973
remote_branch = remote_bzrdir.open_branch()
3974
remote_repo = remote_branch.repository
3975
remote_repo.lock_read()
3977
# it should have an appropriate fallback repository, which should also
3978
# be a RemoteRepository
3979
self.assertLength(1, remote_repo._fallback_repositories)
3980
self.assertIsInstance(remote_repo._fallback_repositories[0],
3982
# and it has the revision committed to the underlying repository;
3983
# these have varying implementations so we try several of them
3984
self.assertTrue(remote_repo.has_revisions([base_revid]))
3985
self.assertTrue(remote_repo.has_revision(base_revid))
3986
self.assertEqual(remote_repo.get_revision(base_revid).message,
3989
remote_repo.unlock()
3991
def prepare_stacked_remote_branch(self):
3992
"""Get stacked_upon and stacked branches with content in each."""
3993
self.setup_smart_server_with_call_log()
3994
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3995
tree1.commit('rev1', rev_id=b'rev1')
3996
tree2 = tree1.branch.controldir.sprout('tree2', stacked=True
3997
).open_workingtree()
3998
local_tree = tree2.branch.create_checkout('local')
3999
local_tree.commit('local changes make me feel good.')
4000
branch2 = Branch.open(self.get_url('tree2'))
4002
self.addCleanup(branch2.unlock)
4003
return tree1.branch, branch2
4005
def test_stacked_get_parent_map(self):
4006
# the public implementation of get_parent_map obeys stacking
4007
_, branch = self.prepare_stacked_remote_branch()
4008
repo = branch.repository
4009
self.assertEqual({b'rev1'}, set(repo.get_parent_map([b'rev1'])))
4011
def test_unstacked_get_parent_map(self):
4012
# _unstacked_provider.get_parent_map ignores stacking
4013
_, branch = self.prepare_stacked_remote_branch()
4014
provider = branch.repository._unstacked_provider
4015
self.assertEqual(set(), set(provider.get_parent_map([b'rev1'])))
4017
def fetch_stream_to_rev_order(self, stream):
4019
for kind, substream in stream:
4020
if not kind == 'revisions':
4023
for content in substream:
4024
result.append(content.key[-1])
4027
def get_ordered_revs(self, format, order, branch_factory=None):
4028
"""Get a list of the revisions in a stream to format format.
4030
:param format: The format of the target.
4031
:param order: the order that target should have requested.
4032
:param branch_factory: A callable to create a trunk and stacked branch
4033
to fetch from. If none, self.prepare_stacked_remote_branch is used.
4034
:result: The revision ids in the stream, in the order seen,
4035
the topological order of revisions in the source.
4037
unordered_format = controldir.format_registry.get(format)()
4038
target_repository_format = unordered_format.repository_format
4040
self.assertEqual(order, target_repository_format._fetch_order)
4041
if branch_factory is None:
4042
branch_factory = self.prepare_stacked_remote_branch
4043
_, stacked = branch_factory()
4044
source = stacked.repository._get_source(target_repository_format)
4045
tip = stacked.last_revision()
4046
stacked.repository._ensure_real()
4047
graph = stacked.repository.get_graph()
4048
revs = [r for (r, ps) in graph.iter_ancestry([tip])
4049
if r != NULL_REVISION]
4051
search = vf_search.PendingAncestryResult([tip], stacked.repository)
4052
self.reset_smart_call_log()
4053
stream = source.get_stream(search)
4054
# We trust that if a revision is in the stream the rest of the new
4055
# content for it is too, as per our main fetch tests; here we are
4056
# checking that the revisions are actually included at all, and their
4058
return self.fetch_stream_to_rev_order(stream), revs
4060
def test_stacked_get_stream_unordered(self):
4061
# Repository._get_source.get_stream() from a stacked repository with
4062
# unordered yields the full data from both stacked and stacked upon
4064
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
4065
self.assertEqual(set(expected_revs), set(rev_ord))
4066
# Getting unordered results should have made a streaming data request
4067
# from the server, then one from the backing branch.
4068
self.assertLength(2, self.hpss_calls)
4070
def test_stacked_on_stacked_get_stream_unordered(self):
4071
# Repository._get_source.get_stream() from a stacked repository which
4072
# is itself stacked yields the full data from all three sources.
4073
def make_stacked_stacked():
4074
_, stacked = self.prepare_stacked_remote_branch()
4075
tree = stacked.controldir.sprout('tree3', stacked=True
4076
).open_workingtree()
4077
local_tree = tree.branch.create_checkout('local-tree3')
4078
local_tree.commit('more local changes are better')
4079
branch = Branch.open(self.get_url('tree3'))
4081
self.addCleanup(branch.unlock)
4083
rev_ord, expected_revs = self.get_ordered_revs(
4084
'1.9', 'unordered', branch_factory=make_stacked_stacked)
4085
self.assertEqual(set(expected_revs), set(rev_ord))
4086
# Getting unordered results should have made a streaming data request
4087
# from the server, and one from each backing repo
4088
self.assertLength(3, self.hpss_calls)
4090
def test_stacked_get_stream_topological(self):
4091
# Repository._get_source.get_stream() from a stacked repository with
4092
# topological sorting yields the full data from both stacked and
4093
# stacked upon sources in topological order.
4094
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4095
self.assertEqual(expected_revs, rev_ord)
4096
# Getting topological sort requires VFS calls still - one of which is
4097
# pushing up from the bound branch.
4098
self.assertLength(14, self.hpss_calls)
4100
def test_stacked_get_stream_groupcompress(self):
4101
# Repository._get_source.get_stream() from a stacked repository with
4102
# groupcompress sorting yields the full data from both stacked and
4103
# stacked upon sources in groupcompress order.
4104
raise tests.TestSkipped('No groupcompress ordered format available')
4105
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4106
self.assertEqual(expected_revs, reversed(rev_ord))
4107
# Getting unordered results should have made a streaming data request
4108
# from the backing branch, and one from the stacked on branch.
4109
self.assertLength(2, self.hpss_calls)
4111
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4112
# When pulling some fixed amount of content that is more than the
4113
# source has (because some is coming from a fallback branch, no error
4114
# should be received. This was reported as bug 360791.
4115
# Need three branches: a trunk, a stacked branch, and a preexisting
4116
# branch pulling content from stacked and trunk.
4117
self.setup_smart_server_with_call_log()
4118
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4119
trunk.commit('start')
4120
stacked_branch = trunk.branch.create_clone_on_transport(
4121
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4122
local = self.make_branch('local', format='1.9-rich-root')
4123
local.repository.fetch(stacked_branch.repository,
4124
stacked_branch.last_revision())
4127
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4130
super(TestRemoteBranchEffort, self).setUp()
4131
# Create a smart server that publishes whatever the backing VFS server
4133
self.smart_server = test_server.SmartTCPServer_for_testing()
4134
self.start_server(self.smart_server, self.get_server())
4135
# Log all HPSS calls into self.hpss_calls.
4136
_SmartClient.hooks.install_named_hook(
4137
'call', self.capture_hpss_call, None)
4138
self.hpss_calls = []
4140
def capture_hpss_call(self, params):
4141
self.hpss_calls.append(params.method)
4143
def test_copy_content_into_avoids_revision_history(self):
4144
local = self.make_branch('local')
4145
builder = self.make_branch_builder('remote')
4146
builder.build_commit(message="Commit.")
4147
remote_branch_url = self.smart_server.get_url() + 'remote'
4148
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4149
local.repository.fetch(remote_branch.repository)
4150
self.hpss_calls = []
4151
remote_branch.copy_content_into(local)
4152
self.assertFalse(b'Branch.revision_history' in self.hpss_calls)
4154
def test_fetch_everything_needs_just_one_call(self):
4155
local = self.make_branch('local')
4156
builder = self.make_branch_builder('remote')
4157
builder.build_commit(message="Commit.")
4158
remote_branch_url = self.smart_server.get_url() + 'remote'
4159
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4160
self.hpss_calls = []
4161
local.repository.fetch(
4162
remote_branch.repository,
4163
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4164
self.assertEqual([b'Repository.get_stream_1.19'], self.hpss_calls)
4166
def override_verb(self, verb_name, verb):
4167
request_handlers = request.request_handlers
4168
orig_verb = request_handlers.get(verb_name)
4169
orig_info = request_handlers.get_info(verb_name)
4170
request_handlers.register(verb_name, verb, override_existing=True)
4171
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4172
override_existing=True, info=orig_info)
4174
def test_fetch_everything_backwards_compat(self):
4175
"""Can fetch with EverythingResult even with pre 2.4 servers.
4177
Pre-2.4 do not support 'everything' searches with the
4178
Repository.get_stream_1.19 verb.
4182
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4183
"""A version of the Repository.get_stream_1.19 verb patched to
4184
reject 'everything' searches the way 2.3 and earlier do.
4187
def recreate_search(self, repository, search_bytes,
4188
discard_excess=False):
4189
verb_log.append(search_bytes.split(b'\n', 1)[0])
4190
if search_bytes == b'everything':
4192
request.FailedSmartServerResponse((b'BadSearch',)))
4193
return super(OldGetStreamVerb,
4194
self).recreate_search(repository, search_bytes,
4195
discard_excess=discard_excess)
4196
self.override_verb(b'Repository.get_stream_1.19', OldGetStreamVerb)
4197
local = self.make_branch('local')
4198
builder = self.make_branch_builder('remote')
4199
builder.build_commit(message="Commit.")
4200
remote_branch_url = self.smart_server.get_url() + 'remote'
4201
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4202
self.hpss_calls = []
4203
local.repository.fetch(
4204
remote_branch.repository,
4205
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4206
# make sure the overridden verb was used
4207
self.assertLength(1, verb_log)
4208
# more than one HPSS call is needed, but because it's a VFS callback
4209
# its hard to predict exactly how many.
4210
self.assertTrue(len(self.hpss_calls) > 1)
4213
class TestUpdateBoundBranchWithModifiedBoundLocation(
4214
tests.TestCaseWithTransport):
4215
"""Ensure correct handling of bound_location modifications.
4217
This is tested against a smart server as http://pad.lv/786980 was about a
4218
ReadOnlyError (write attempt during a read-only transaction) which can only
4219
happen in this context.
4223
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4224
self.transport_server = test_server.SmartTCPServer_for_testing
4226
def make_master_and_checkout(self, master_name, checkout_name):
4227
# Create the master branch and its associated checkout
4228
self.master = self.make_branch_and_tree(master_name)
4229
self.checkout = self.master.branch.create_checkout(checkout_name)
4230
# Modify the master branch so there is something to update
4231
self.master.commit('add stuff')
4232
self.last_revid = self.master.commit('even more stuff')
4233
self.bound_location = self.checkout.branch.get_bound_location()
4235
def assertUpdateSucceeds(self, new_location):
4236
self.checkout.branch.set_bound_location(new_location)
4237
self.checkout.update()
4238
self.assertEqual(self.last_revid, self.checkout.last_revision())
4240
def test_without_final_slash(self):
4241
self.make_master_and_checkout('master', 'checkout')
4242
# For unclear reasons some users have a bound_location without a final
4243
# '/', simulate that by forcing such a value
4244
self.assertEndsWith(self.bound_location, '/')
4245
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4247
def test_plus_sign(self):
4248
self.make_master_and_checkout('+master', 'checkout')
4249
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4251
def test_tilda(self):
4252
# Embed ~ in the middle of the path just to avoid any $HOME
4254
self.make_master_and_checkout('mas~ter', 'checkout')
4255
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4258
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4260
def test_no_context(self):
4261
class OutOfCoffee(errors.BzrError):
4262
"""A dummy exception for testing."""
4264
def __init__(self, urgency):
4265
self.urgency = urgency
4266
remote.no_context_error_translators.register(b"OutOfCoffee",
4267
lambda err: OutOfCoffee(err.error_args[0]))
4268
transport = MemoryTransport()
4269
client = FakeClient(transport.base)
4270
client.add_expected_call(
4271
b'Branch.get_stacked_on_url', (b'quack/',),
4272
b'error', (b'NotStacked',))
4273
client.add_expected_call(
4274
b'Branch.last_revision_info',
4276
b'error', (b'OutOfCoffee', b'low'))
4277
transport.mkdir('quack')
4278
transport = transport.clone('quack')
4279
branch = self.make_remote_branch(transport, client)
4280
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4281
self.assertFinished(client)
4283
def test_with_context(self):
4284
class OutOfTea(errors.BzrError):
4285
def __init__(self, branch, urgency):
4286
self.branch = branch
4287
self.urgency = urgency
4288
remote.error_translators.register(b"OutOfTea",
4289
lambda err, find, path: OutOfTea(
4290
err.error_args[0].decode(
4293
transport = MemoryTransport()
4294
client = FakeClient(transport.base)
4295
client.add_expected_call(
4296
b'Branch.get_stacked_on_url', (b'quack/',),
4297
b'error', (b'NotStacked',))
4298
client.add_expected_call(
4299
b'Branch.last_revision_info',
4301
b'error', (b'OutOfTea', b'low'))
4302
transport.mkdir('quack')
4303
transport = transport.clone('quack')
4304
branch = self.make_remote_branch(transport, client)
4305
self.assertRaises(OutOfTea, branch.last_revision_info)
4306
self.assertFinished(client)
4309
class TestRepositoryPack(TestRemoteRepository):
4311
def test_pack(self):
4312
transport_path = 'quack'
4313
repo, client = self.setup_fake_client_and_repository(transport_path)
4314
client.add_expected_call(
4315
b'Repository.lock_write', (b'quack/', b''),
4316
b'success', (b'ok', b'token'))
4317
client.add_expected_call(
4318
b'Repository.pack', (b'quack/', b'token', b'False'),
4319
b'success', (b'ok',), )
4320
client.add_expected_call(
4321
b'Repository.unlock', (b'quack/', b'token'),
4322
b'success', (b'ok', ))
4325
def test_pack_with_hint(self):
4326
transport_path = 'quack'
4327
repo, client = self.setup_fake_client_and_repository(transport_path)
4328
client.add_expected_call(
4329
b'Repository.lock_write', (b'quack/', b''),
4330
b'success', (b'ok', b'token'))
4331
client.add_expected_call(
4332
b'Repository.pack', (b'quack/', b'token', b'False'),
4333
b'success', (b'ok',), )
4334
client.add_expected_call(
4335
b'Repository.unlock', (b'quack/', b'token', b'False'),
4336
b'success', (b'ok', ))
4337
repo.pack(['hinta', 'hintb'])
4340
class TestRepositoryIterInventories(TestRemoteRepository):
4341
"""Test Repository.iter_inventories."""
4343
def _serialize_inv_delta(self, old_name, new_name, delta):
4344
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4345
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4347
def test_single_empty(self):
4348
transport_path = 'quack'
4349
repo, client = self.setup_fake_client_and_repository(transport_path)
4350
fmt = controldir.format_registry.get('2a')().repository_format
4352
stream = [('inventory-deltas', [
4353
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4354
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4355
client.add_expected_call(
4356
b'VersionedFileRepository.get_inventories', (
4357
b'quack/', b'unordered'),
4358
b'success', (b'ok', ),
4359
_stream_to_byte_stream(stream, fmt))
4360
ret = list(repo.iter_inventories([b"somerevid"]))
4361
self.assertLength(1, ret)
4363
self.assertEqual(b"somerevid", inv.revision_id)
4365
def test_empty(self):
4366
transport_path = 'quack'
4367
repo, client = self.setup_fake_client_and_repository(transport_path)
4368
ret = list(repo.iter_inventories([]))
4369
self.assertEqual(ret, [])
4371
def test_missing(self):
4372
transport_path = 'quack'
4373
repo, client = self.setup_fake_client_and_repository(transport_path)
4374
client.add_expected_call(
4375
b'VersionedFileRepository.get_inventories', (
4376
b'quack/', b'unordered'),
4377
b'success', (b'ok', ), iter([]))
4378
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(
4382
class TestRepositoryRevisionTreeArchive(TestRemoteRepository):
4383
"""Test Repository.iter_inventories."""
4385
def _serialize_inv_delta(self, old_name, new_name, delta):
4386
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4387
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4389
def test_simple(self):
4390
transport_path = 'quack'
4391
repo, client = self.setup_fake_client_and_repository(transport_path)
4392
fmt = controldir.format_registry.get('2a')().repository_format
4394
stream = [('inventory-deltas', [
4395
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4396
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4397
client.add_expected_call(
4398
b'VersionedFileRepository.get_inventories', (
4399
b'quack/', b'unordered'),
4400
b'success', (b'ok', ),
4401
_stream_to_byte_stream(stream, fmt))
4403
with tarfile.open(mode='w', fileobj=f) as tf:
4404
info = tarfile.TarInfo('somefile')
4406
contents = b'some data'
4407
info.type = tarfile.REGTYPE
4409
info.size = len(contents)
4410
tf.addfile(info, BytesIO(contents))
4411
client.add_expected_call(
4412
b'Repository.revision_archive', (b'quack/',
4413
b'somerevid', b'tar', b'foo.tar', b'', b'', None),
4414
b'success', (b'ok', ),
4416
tree = repo.revision_tree(b'somerevid')
4417
self.assertEqual(f.getvalue(), b''.join(
4418
tree.archive('tar', 'foo.tar')))
4421
class TestRepositoryAnnotate(TestRemoteRepository):
4422
"""Test RemoteRevisionTree.annotate.."""
4424
def _serialize_inv_delta(self, old_name, new_name, delta):
4425
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4426
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4428
def test_simple(self):
4429
transport_path = 'quack'
4430
repo, client = self.setup_fake_client_and_repository(transport_path)
4431
fmt = controldir.format_registry.get('2a')().repository_format
4434
('inventory-deltas', [
4435
versionedfile.FulltextContentFactory(
4436
b'somerevid', None, None,
4437
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4438
client.add_expected_call(
4439
b'VersionedFileRepository.get_inventories', (
4440
b'quack/', b'unordered'),
4441
b'success', (b'ok', ),
4442
_stream_to_byte_stream(stream, fmt))
4443
client.add_expected_call(
4444
b'Repository.annotate_file_revision',
4445
(b'quack/', b'somerevid', b'filename', b'', b'current:'),
4446
b'success', (b'ok', ),
4447
bencode.bencode([[b'baserevid', b'line 1\n'],
4448
[b'somerevid', b'line2\n']]))
4449
tree = repo.revision_tree(b'somerevid')
4451
(b'baserevid', b'line 1\n'),
4452
(b'somerevid', b'line2\n')],
4453
list(tree.annotate_iter('filename')))