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_set_option_with_bool(self):
2046
client = FakeClient()
2047
client.add_expected_call(
2048
b'Branch.get_stacked_on_url', (b'memory:///',),
2049
b'error', (b'NotStacked',),)
2050
client.add_expected_call(
2051
b'Branch.lock_write', (b'memory:///', b'', b''),
2052
b'success', (b'ok', b'branch token', b'repo token'))
2053
client.add_expected_call(
2054
b'Branch.set_config_option', (b'memory:///', b'branch token',
2055
b'repo token', b'True', b'foo', b''),
2057
client.add_expected_call(
2058
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2059
b'success', (b'ok',))
2060
transport = MemoryTransport()
2061
branch = self.make_remote_branch(transport, client)
2063
config = branch._get_config()
2064
config.set_option(True, 'foo')
2066
self.assertFinished(client)
2068
def test_backwards_compat_set_option(self):
2069
self.setup_smart_server_with_call_log()
2070
branch = self.make_branch('.')
2071
verb = b'Branch.set_config_option'
2072
self.disable_verb(verb)
2074
self.addCleanup(branch.unlock)
2075
self.reset_smart_call_log()
2076
branch._get_config().set_option('value', 'name')
2077
self.assertLength(11, self.hpss_calls)
2078
self.assertEqual('value', branch._get_config().get_option('name'))
2080
def test_backwards_compat_set_option_with_dict(self):
2081
self.setup_smart_server_with_call_log()
2082
branch = self.make_branch('.')
2083
verb = b'Branch.set_config_option_dict'
2084
self.disable_verb(verb)
2086
self.addCleanup(branch.unlock)
2087
self.reset_smart_call_log()
2088
config = branch._get_config()
2089
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2090
config.set_option(value_dict, 'name')
2091
self.assertLength(11, self.hpss_calls)
2092
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2095
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2097
def test_get_branch_conf(self):
2098
# in an empty branch we decode the response properly
2099
client = FakeClient()
2100
client.add_expected_call(
2101
b'Branch.get_stacked_on_url', (b'memory:///',),
2102
b'error', (b'NotStacked',),)
2103
client.add_success_response_with_body(b'# config file body', b'ok')
2104
transport = MemoryTransport()
2105
branch = self.make_remote_branch(transport, client)
2106
config = branch.get_config_stack()
2108
config.get("log_format")
2110
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2111
('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
2114
def test_set_branch_conf(self):
2115
client = FakeClient()
2116
client.add_expected_call(
2117
b'Branch.get_stacked_on_url', (b'memory:///',),
2118
b'error', (b'NotStacked',),)
2119
client.add_expected_call(
2120
b'Branch.lock_write', (b'memory:///', b'', b''),
2121
b'success', (b'ok', b'branch token', b'repo token'))
2122
client.add_expected_call(
2123
b'Branch.get_config_file', (b'memory:///', ),
2124
b'success', (b'ok', ), b"# line 1\n")
2125
client.add_expected_call(
2126
b'Branch.get_config_file', (b'memory:///', ),
2127
b'success', (b'ok', ), b"# line 1\n")
2128
client.add_expected_call(
2129
b'Branch.put_config_file', (b'memory:///', b'branch token',
2131
b'success', (b'ok',))
2132
client.add_expected_call(
2133
b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
2134
b'success', (b'ok',))
2135
transport = MemoryTransport()
2136
branch = self.make_remote_branch(transport, client)
2138
config = branch.get_config_stack()
2139
config.set('email', 'The Dude <lebowski@example.com>')
2141
self.assertFinished(client)
2143
[('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
2144
('call', b'Branch.lock_write', (b'memory:///', b'', b'')),
2145
('call_expecting_body', b'Branch.get_config_file',
2147
('call_expecting_body', b'Branch.get_config_file',
2149
('call_with_body_bytes_expecting_body', b'Branch.put_config_file',
2150
(b'memory:///', b'branch token', b'repo token'),
2151
b'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2152
('call', b'Branch.unlock',
2153
(b'memory:///', b'branch token', b'repo token'))],
2157
class TestBranchLockWrite(RemoteBranchTestCase):
2159
def test_lock_write_unlockable(self):
2160
transport = MemoryTransport()
2161
client = FakeClient(transport.base)
2162
client.add_expected_call(
2163
b'Branch.get_stacked_on_url', (b'quack/',),
2164
b'error', (b'NotStacked',),)
2165
client.add_expected_call(
2166
b'Branch.lock_write', (b'quack/', b'', b''),
2167
b'error', (b'UnlockableTransport',))
2168
transport.mkdir('quack')
2169
transport = transport.clone('quack')
2170
branch = self.make_remote_branch(transport, client)
2171
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2172
self.assertFinished(client)
2175
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2177
def test_simple(self):
2178
transport = MemoryTransport()
2179
client = FakeClient(transport.base)
2180
client.add_expected_call(
2181
b'Branch.get_stacked_on_url', (b'quack/',),
2182
b'error', (b'NotStacked',),)
2183
client.add_expected_call(
2184
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2185
b'success', (b'ok', b'0',),)
2186
client.add_expected_call(
2187
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2188
b'error', (b'NoSuchRevision', b'unknown',),)
2189
transport.mkdir('quack')
2190
transport = transport.clone('quack')
2191
branch = self.make_remote_branch(transport, client)
2192
self.assertEqual(0, branch.revision_id_to_revno(b'null:'))
2193
self.assertRaises(errors.NoSuchRevision,
2194
branch.revision_id_to_revno, b'unknown')
2195
self.assertFinished(client)
2197
def test_dotted(self):
2198
transport = MemoryTransport()
2199
client = FakeClient(transport.base)
2200
client.add_expected_call(
2201
b'Branch.get_stacked_on_url', (b'quack/',),
2202
b'error', (b'NotStacked',),)
2203
client.add_expected_call(
2204
b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
2205
b'success', (b'ok', b'0',),)
2206
client.add_expected_call(
2207
b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
2208
b'error', (b'NoSuchRevision', b'unknown',),)
2209
transport.mkdir('quack')
2210
transport = transport.clone('quack')
2211
branch = self.make_remote_branch(transport, client)
2212
self.assertEqual((0, ), branch.revision_id_to_dotted_revno(b'null:'))
2213
self.assertRaises(errors.NoSuchRevision,
2214
branch.revision_id_to_dotted_revno, b'unknown')
2215
self.assertFinished(client)
2217
def test_ghost_revid(self):
2218
transport = MemoryTransport()
2219
client = FakeClient(transport.base)
2220
client.add_expected_call(
2221
b'Branch.get_stacked_on_url', (b'quack/',),
2222
b'error', (b'NotStacked',),)
2223
# Some older versions of bzr/brz didn't explicitly return
2224
# GhostRevisionsHaveNoRevno
2225
client.add_expected_call(
2226
b'Branch.revision_id_to_revno', (b'quack/', b'revid'),
2227
b'error', (b'error', b'GhostRevisionsHaveNoRevno',
2228
b'The reivison {revid} was not found because there was '
2229
b'a ghost at {ghost-revid}'))
2230
client.add_expected_call(
2231
b'Branch.revision_id_to_revno', (b'quack/', b'revid'),
2232
b'error', (b'GhostRevisionsHaveNoRevno', b'revid', b'ghost-revid',))
2233
transport.mkdir('quack')
2234
transport = transport.clone('quack')
2235
branch = self.make_remote_branch(transport, client)
2236
self.assertRaises(errors.GhostRevisionsHaveNoRevno,
2237
branch.revision_id_to_dotted_revno, b'revid')
2238
self.assertRaises(errors.GhostRevisionsHaveNoRevno,
2239
branch.revision_id_to_dotted_revno, b'revid')
2240
self.assertFinished(client)
2242
def test_dotted_no_smart_verb(self):
2243
self.setup_smart_server_with_call_log()
2244
branch = self.make_branch('.')
2245
self.disable_verb(b'Branch.revision_id_to_revno')
2246
self.reset_smart_call_log()
2247
self.assertEqual((0, ),
2248
branch.revision_id_to_dotted_revno(b'null:'))
2249
self.assertLength(8, self.hpss_calls)
2252
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2254
def test__get_config(self):
2255
client = FakeClient()
2256
client.add_success_response_with_body(b'default_stack_on = /\n', b'ok')
2257
transport = MemoryTransport()
2258
bzrdir = self.make_remote_bzrdir(transport, client)
2259
config = bzrdir.get_config()
2260
self.assertEqual('/', config.get_default_stack_on())
2262
[('call_expecting_body', b'BzrDir.get_config_file',
2266
def test_set_option_uses_vfs(self):
2267
self.setup_smart_server_with_call_log()
2268
bzrdir = self.make_controldir('.')
2269
self.reset_smart_call_log()
2270
config = bzrdir.get_config()
2271
config.set_default_stack_on('/')
2272
self.assertLength(4, self.hpss_calls)
2274
def test_backwards_compat_get_option(self):
2275
self.setup_smart_server_with_call_log()
2276
bzrdir = self.make_controldir('.')
2277
verb = b'BzrDir.get_config_file'
2278
self.disable_verb(verb)
2279
self.reset_smart_call_log()
2280
self.assertEqual(None,
2281
bzrdir._get_config().get_option('default_stack_on'))
2282
self.assertLength(4, self.hpss_calls)
2285
class TestTransportIsReadonly(tests.TestCase):
2287
def test_true(self):
2288
client = FakeClient()
2289
client.add_success_response(b'yes')
2290
transport = RemoteTransport('bzr://example.com/', medium=False,
2292
self.assertEqual(True, transport.is_readonly())
2294
[('call', b'Transport.is_readonly', ())],
2297
def test_false(self):
2298
client = FakeClient()
2299
client.add_success_response(b'no')
2300
transport = RemoteTransport('bzr://example.com/', medium=False,
2302
self.assertEqual(False, transport.is_readonly())
2304
[('call', b'Transport.is_readonly', ())],
2307
def test_error_from_old_server(self):
2308
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2310
Clients should treat it as a "no" response, because is_readonly is only
2311
advisory anyway (a transport could be read-write, but then the
2312
underlying filesystem could be readonly anyway).
2314
client = FakeClient()
2315
client.add_unknown_method_response(b'Transport.is_readonly')
2316
transport = RemoteTransport('bzr://example.com/', medium=False,
2318
self.assertEqual(False, transport.is_readonly())
2320
[('call', b'Transport.is_readonly', ())],
2324
class TestTransportMkdir(tests.TestCase):
2326
def test_permissiondenied(self):
2327
client = FakeClient()
2328
client.add_error_response(
2329
b'PermissionDenied', b'remote path', b'extra')
2330
transport = RemoteTransport('bzr://example.com/', medium=False,
2332
exc = self.assertRaises(
2333
errors.PermissionDenied, transport.mkdir, 'client path')
2334
expected_error = errors.PermissionDenied('/client path', 'extra')
2335
self.assertEqual(expected_error, exc)
2338
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2340
def test_defaults_to_none(self):
2341
t = RemoteSSHTransport('bzr+ssh://example.com')
2342
self.assertIs(None, t._get_credentials()[0])
2344
def test_uses_authentication_config(self):
2345
conf = config.AuthenticationConfig()
2346
conf._get_config().update(
2347
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2350
t = RemoteSSHTransport('bzr+ssh://example.com')
2351
self.assertEqual('bar', t._get_credentials()[0])
2354
class TestRemoteRepository(TestRemote):
2355
"""Base for testing RemoteRepository protocol usage.
2357
These tests contain frozen requests and responses. We want any changes to
2358
what is sent or expected to be require a thoughtful update to these tests
2359
because they might break compatibility with different-versioned servers.
2362
def setup_fake_client_and_repository(self, transport_path):
2363
"""Create the fake client and repository for testing with.
2365
There's no real server here; we just have canned responses sent
2368
:param transport_path: Path below the root of the MemoryTransport
2369
where the repository will be created.
2371
transport = MemoryTransport()
2372
transport.mkdir(transport_path)
2373
client = FakeClient(transport.base)
2374
transport = transport.clone(transport_path)
2375
# we do not want bzrdir to make any remote calls
2376
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2378
repo = RemoteRepository(bzrdir, None, _client=client)
2382
def remoted_description(format):
2383
return 'Remote: ' + format.get_format_description()
2386
class TestBranchFormat(tests.TestCase):
2388
def test_get_format_description(self):
2389
remote_format = RemoteBranchFormat()
2390
real_format = branch.format_registry.get_default()
2391
remote_format._network_name = real_format.network_name()
2392
self.assertEqual(remoted_description(real_format),
2393
remote_format.get_format_description())
2396
class TestRepositoryFormat(TestRemoteRepository):
2398
def test_fast_delta(self):
2399
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2400
true_format = RemoteRepositoryFormat()
2401
true_format._network_name = true_name
2402
self.assertEqual(True, true_format.fast_deltas)
2403
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2404
false_format = RemoteRepositoryFormat()
2405
false_format._network_name = false_name
2406
self.assertEqual(False, false_format.fast_deltas)
2408
def test_get_format_description(self):
2409
remote_repo_format = RemoteRepositoryFormat()
2410
real_format = repository.format_registry.get_default()
2411
remote_repo_format._network_name = real_format.network_name()
2412
self.assertEqual(remoted_description(real_format),
2413
remote_repo_format.get_format_description())
2416
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2418
def test_empty(self):
2419
transport_path = 'quack'
2420
repo, client = self.setup_fake_client_and_repository(transport_path)
2421
client.add_success_response_with_body(b'', b'ok')
2422
self.assertEqual([], repo.all_revision_ids())
2424
[('call_expecting_body', b'Repository.all_revision_ids',
2428
def test_with_some_content(self):
2429
transport_path = 'quack'
2430
repo, client = self.setup_fake_client_and_repository(transport_path)
2431
client.add_success_response_with_body(
2432
b'rev1\nrev2\nanotherrev\n', b'ok')
2434
set([b"rev1", b"rev2", b"anotherrev"]),
2435
set(repo.all_revision_ids()))
2437
[('call_expecting_body', b'Repository.all_revision_ids',
2442
class TestRepositoryGatherStats(TestRemoteRepository):
2444
def test_revid_none(self):
2445
# ('ok',), body with revisions and size
2446
transport_path = 'quack'
2447
repo, client = self.setup_fake_client_and_repository(transport_path)
2448
client.add_success_response_with_body(
2449
b'revisions: 2\nsize: 18\n', b'ok')
2450
result = repo.gather_stats(None)
2452
[('call_expecting_body', b'Repository.gather_stats',
2453
(b'quack/', b'', b'no'))],
2455
self.assertEqual({'revisions': 2, 'size': 18}, result)
2457
def test_revid_no_committers(self):
2458
# ('ok',), body without committers
2459
body = (b'firstrev: 123456.300 3600\n'
2460
b'latestrev: 654231.400 0\n'
2463
transport_path = 'quick'
2464
revid = u'\xc8'.encode('utf8')
2465
repo, client = self.setup_fake_client_and_repository(transport_path)
2466
client.add_success_response_with_body(body, b'ok')
2467
result = repo.gather_stats(revid)
2469
[('call_expecting_body', b'Repository.gather_stats',
2470
(b'quick/', revid, b'no'))],
2472
self.assertEqual({'revisions': 2, 'size': 18,
2473
'firstrev': (123456.300, 3600),
2474
'latestrev': (654231.400, 0), },
2477
def test_revid_with_committers(self):
2478
# ('ok',), body with committers
2479
body = (b'committers: 128\n'
2480
b'firstrev: 123456.300 3600\n'
2481
b'latestrev: 654231.400 0\n'
2484
transport_path = 'buick'
2485
revid = u'\xc8'.encode('utf8')
2486
repo, client = self.setup_fake_client_and_repository(transport_path)
2487
client.add_success_response_with_body(body, b'ok')
2488
result = repo.gather_stats(revid, True)
2490
[('call_expecting_body', b'Repository.gather_stats',
2491
(b'buick/', revid, b'yes'))],
2493
self.assertEqual({'revisions': 2, 'size': 18,
2495
'firstrev': (123456.300, 3600),
2496
'latestrev': (654231.400, 0), },
2500
class TestRepositoryBreakLock(TestRemoteRepository):
2502
def test_break_lock(self):
2503
transport_path = 'quack'
2504
repo, client = self.setup_fake_client_and_repository(transport_path)
2505
client.add_success_response(b'ok')
2508
[('call', b'Repository.break_lock', (b'quack/',))],
2512
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2514
def test_get_serializer_format(self):
2515
transport_path = 'hill'
2516
repo, client = self.setup_fake_client_and_repository(transport_path)
2517
client.add_success_response(b'ok', b'7')
2518
self.assertEqual(b'7', repo.get_serializer_format())
2520
[('call', b'VersionedFileRepository.get_serializer_format',
2525
class TestRepositoryReconcile(TestRemoteRepository):
2527
def test_reconcile(self):
2528
transport_path = 'hill'
2529
repo, client = self.setup_fake_client_and_repository(transport_path)
2530
body = (b"garbage_inventories: 2\n"
2531
b"inconsistent_parents: 3\n")
2532
client.add_expected_call(
2533
b'Repository.lock_write', (b'hill/', b''),
2534
b'success', (b'ok', b'a token'))
2535
client.add_success_response_with_body(body, b'ok')
2536
reconciler = repo.reconcile()
2538
[('call', b'Repository.lock_write', (b'hill/', b'')),
2539
('call_expecting_body', b'Repository.reconcile',
2540
(b'hill/', b'a token'))],
2542
self.assertEqual(2, reconciler.garbage_inventories)
2543
self.assertEqual(3, reconciler.inconsistent_parents)
2546
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2548
def test_text(self):
2549
# ('ok',), body with signature text
2550
transport_path = 'quack'
2551
repo, client = self.setup_fake_client_and_repository(transport_path)
2552
client.add_success_response_with_body(
2554
self.assertEqual(b"THETEXT", repo.get_signature_text(b"revid"))
2556
[('call_expecting_body', b'Repository.get_revision_signature_text',
2557
(b'quack/', b'revid'))],
2560
def test_no_signature(self):
2561
transport_path = 'quick'
2562
repo, client = self.setup_fake_client_and_repository(transport_path)
2563
client.add_error_response(b'nosuchrevision', b'unknown')
2564
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2567
[('call_expecting_body', b'Repository.get_revision_signature_text',
2568
(b'quick/', b'unknown'))],
2572
class TestRepositoryGetGraph(TestRemoteRepository):
2574
def test_get_graph(self):
2575
# get_graph returns a graph with a custom parents provider.
2576
transport_path = 'quack'
2577
repo, client = self.setup_fake_client_and_repository(transport_path)
2578
graph = repo.get_graph()
2579
self.assertNotEqual(graph._parents_provider, repo)
2582
class TestRepositoryAddSignatureText(TestRemoteRepository):
2584
def test_add_signature_text(self):
2585
transport_path = 'quack'
2586
repo, client = self.setup_fake_client_and_repository(transport_path)
2587
client.add_expected_call(
2588
b'Repository.lock_write', (b'quack/', b''),
2589
b'success', (b'ok', b'a token'))
2590
client.add_expected_call(
2591
b'Repository.start_write_group', (b'quack/', b'a token'),
2592
b'success', (b'ok', (b'token1', )))
2593
client.add_expected_call(
2594
b'Repository.add_signature_text', (b'quack/', b'a token', b'rev1',
2596
b'success', (b'ok', ), None)
2598
repo.start_write_group()
2600
None, repo.add_signature_text(b"rev1", b"every bloody emperor"))
2602
('call_with_body_bytes_expecting_body',
2603
b'Repository.add_signature_text',
2604
(b'quack/', b'a token', b'rev1', b'token1'),
2605
b'every bloody emperor'),
2609
class TestRepositoryGetParentMap(TestRemoteRepository):
2611
def test_get_parent_map_caching(self):
2612
# get_parent_map returns from cache until unlock()
2613
# setup a reponse with two revisions
2614
r1 = u'\u0e33'.encode('utf8')
2615
r2 = u'\u0dab'.encode('utf8')
2616
lines = [b' '.join([r2, r1]), r1]
2617
encoded_body = bz2.compress(b'\n'.join(lines))
2619
transport_path = 'quack'
2620
repo, client = self.setup_fake_client_and_repository(transport_path)
2621
client.add_success_response_with_body(encoded_body, b'ok')
2622
client.add_success_response_with_body(encoded_body, b'ok')
2624
graph = repo.get_graph()
2625
parents = graph.get_parent_map([r2])
2626
self.assertEqual({r2: (r1,)}, parents)
2627
# locking and unlocking deeper should not reset
2630
parents = graph.get_parent_map([r1])
2631
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2633
[('call_with_body_bytes_expecting_body',
2634
b'Repository.get_parent_map', (b'quack/',
2635
b'include-missing:', r2),
2639
# now we call again, and it should use the second response.
2641
graph = repo.get_graph()
2642
parents = graph.get_parent_map([r1])
2643
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2645
[('call_with_body_bytes_expecting_body',
2646
b'Repository.get_parent_map', (b'quack/',
2647
b'include-missing:', r2),
2649
('call_with_body_bytes_expecting_body',
2650
b'Repository.get_parent_map', (b'quack/',
2651
b'include-missing:', r1),
2657
def test_get_parent_map_reconnects_if_unknown_method(self):
2658
transport_path = 'quack'
2659
rev_id = b'revision-id'
2660
repo, client = self.setup_fake_client_and_repository(transport_path)
2661
client.add_unknown_method_response(b'Repository.get_parent_map')
2662
client.add_success_response_with_body(rev_id, b'ok')
2663
self.assertFalse(client._medium._is_remote_before((1, 2)))
2664
parents = repo.get_parent_map([rev_id])
2666
[('call_with_body_bytes_expecting_body',
2667
b'Repository.get_parent_map',
2668
(b'quack/', b'include-missing:', rev_id), b'\n\n0'),
2669
('disconnect medium',),
2670
('call_expecting_body', b'Repository.get_revision_graph',
2673
# The medium is now marked as being connected to an older server
2674
self.assertTrue(client._medium._is_remote_before((1, 2)))
2675
self.assertEqual({rev_id: (b'null:',)}, parents)
2677
def test_get_parent_map_fallback_parentless_node(self):
2678
"""get_parent_map falls back to get_revision_graph on old servers. The
2679
results from get_revision_graph are tweaked to match the get_parent_map
2682
Specifically, a {key: ()} result from get_revision_graph means "no
2683
parents" for that key, which in get_parent_map results should be
2684
represented as {key: ('null:',)}.
2686
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2688
rev_id = b'revision-id'
2689
transport_path = 'quack'
2690
repo, client = self.setup_fake_client_and_repository(transport_path)
2691
client.add_success_response_with_body(rev_id, b'ok')
2692
client._medium._remember_remote_is_before((1, 2))
2693
parents = repo.get_parent_map([rev_id])
2695
[('call_expecting_body', b'Repository.get_revision_graph',
2698
self.assertEqual({rev_id: (b'null:',)}, parents)
2700
def test_get_parent_map_unexpected_response(self):
2701
repo, client = self.setup_fake_client_and_repository('path')
2702
client.add_success_response(b'something unexpected!')
2704
errors.UnexpectedSmartServerResponse,
2705
repo.get_parent_map, [b'a-revision-id'])
2707
def test_get_parent_map_negative_caches_missing_keys(self):
2708
self.setup_smart_server_with_call_log()
2709
repo = self.make_repository('foo')
2710
self.assertIsInstance(repo, RemoteRepository)
2712
self.addCleanup(repo.unlock)
2713
self.reset_smart_call_log()
2714
graph = repo.get_graph()
2716
{}, graph.get_parent_map([b'some-missing', b'other-missing']))
2717
self.assertLength(1, self.hpss_calls)
2718
# No call if we repeat this
2719
self.reset_smart_call_log()
2720
graph = repo.get_graph()
2722
{}, graph.get_parent_map([b'some-missing', b'other-missing']))
2723
self.assertLength(0, self.hpss_calls)
2724
# Asking for more unknown keys makes a request.
2725
self.reset_smart_call_log()
2726
graph = repo.get_graph()
2728
{}, graph.get_parent_map([b'some-missing', b'other-missing',
2730
self.assertLength(1, self.hpss_calls)
2732
def disableExtraResults(self):
2733
self.overrideAttr(SmartServerRepositoryGetParentMap,
2734
'no_extra_results', True)
2736
def test_null_cached_missing_and_stop_key(self):
2737
self.setup_smart_server_with_call_log()
2738
# Make a branch with a single revision.
2739
builder = self.make_branch_builder('foo')
2740
builder.start_series()
2741
builder.build_snapshot(None, [
2742
('add', ('', b'root-id', 'directory', ''))],
2743
revision_id=b'first')
2744
builder.finish_series()
2745
branch = builder.get_branch()
2746
repo = branch.repository
2747
self.assertIsInstance(repo, RemoteRepository)
2748
# Stop the server from sending extra results.
2749
self.disableExtraResults()
2751
self.addCleanup(repo.unlock)
2752
self.reset_smart_call_log()
2753
graph = repo.get_graph()
2754
# Query for b'first' and b'null:'. Because b'null:' is a parent of
2755
# 'first' it will be a candidate for the stop_keys of subsequent
2756
# requests, and because b'null:' was queried but not returned it will
2757
# be cached as missing.
2758
self.assertEqual({b'first': (b'null:',)},
2759
graph.get_parent_map([b'first', b'null:']))
2760
# Now query for another key. This request will pass along a recipe of
2761
# start and stop keys describing the already cached results, and this
2762
# recipe's revision count must be correct (or else it will trigger an
2763
# error from the server).
2764
self.assertEqual({}, graph.get_parent_map([b'another-key']))
2765
# This assertion guards against disableExtraResults silently failing to
2766
# work, thus invalidating the test.
2767
self.assertLength(2, self.hpss_calls)
2769
def test_get_parent_map_gets_ghosts_from_result(self):
2770
# asking for a revision should negatively cache close ghosts in its
2772
self.setup_smart_server_with_call_log()
2773
tree = self.make_branch_and_memory_tree('foo')
2774
with tree.lock_write():
2775
builder = treebuilder.TreeBuilder()
2776
builder.start_tree(tree)
2778
builder.finish_tree()
2779
tree.set_parent_ids([b'non-existant'],
2780
allow_leftmost_as_ghost=True)
2781
rev_id = tree.commit('')
2783
self.addCleanup(tree.unlock)
2784
repo = tree.branch.repository
2785
self.assertIsInstance(repo, RemoteRepository)
2787
repo.get_parent_map([rev_id])
2788
self.reset_smart_call_log()
2789
# Now asking for rev_id's ghost parent should not make calls
2790
self.assertEqual({}, repo.get_parent_map([b'non-existant']))
2791
self.assertLength(0, self.hpss_calls)
2793
def test_exposes_get_cached_parent_map(self):
2794
"""RemoteRepository exposes get_cached_parent_map from
2797
r1 = u'\u0e33'.encode('utf8')
2798
r2 = u'\u0dab'.encode('utf8')
2799
lines = [b' '.join([r2, r1]), r1]
2800
encoded_body = bz2.compress(b'\n'.join(lines))
2802
transport_path = 'quack'
2803
repo, client = self.setup_fake_client_and_repository(transport_path)
2804
client.add_success_response_with_body(encoded_body, b'ok')
2806
# get_cached_parent_map should *not* trigger an RPC
2807
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2808
self.assertEqual([], client._calls)
2809
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2810
self.assertEqual({r1: (NULL_REVISION,)},
2811
repo.get_cached_parent_map([r1]))
2813
[('call_with_body_bytes_expecting_body',
2814
b'Repository.get_parent_map', (b'quack/',
2815
b'include-missing:', r2),
2821
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2823
def test_allows_new_revisions(self):
2824
"""get_parent_map's results can be updated by commit."""
2825
smart_server = test_server.SmartTCPServer_for_testing()
2826
self.start_server(smart_server)
2827
self.make_branch('branch')
2828
branch = Branch.open(smart_server.get_url() + '/branch')
2829
tree = branch.create_checkout('tree', lightweight=True)
2831
self.addCleanup(tree.unlock)
2832
graph = tree.branch.repository.get_graph()
2833
# This provides an opportunity for the missing rev-id to be cached.
2834
self.assertEqual({}, graph.get_parent_map([b'rev1']))
2835
tree.commit('message', rev_id=b'rev1')
2836
graph = tree.branch.repository.get_graph()
2837
self.assertEqual({b'rev1': (b'null:',)},
2838
graph.get_parent_map([b'rev1']))
2841
class TestRepositoryGetRevisions(TestRemoteRepository):
2843
def test_hpss_missing_revision(self):
2844
transport_path = 'quack'
2845
repo, client = self.setup_fake_client_and_repository(transport_path)
2846
client.add_success_response_with_body(
2848
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2849
[b'somerev1', b'anotherrev2'])
2851
[('call_with_body_bytes_expecting_body',
2852
b'Repository.iter_revisions', (b'quack/', ),
2853
b"somerev1\nanotherrev2")],
2856
def test_hpss_get_single_revision(self):
2857
transport_path = 'quack'
2858
repo, client = self.setup_fake_client_and_repository(transport_path)
2859
somerev1 = Revision(b"somerev1")
2860
somerev1.committer = "Joe Committer <joe@example.com>"
2861
somerev1.timestamp = 1321828927
2862
somerev1.timezone = -60
2863
somerev1.inventory_sha1 = b"691b39be74c67b1212a75fcb19c433aaed903c2b"
2864
somerev1.message = "Message"
2865
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2867
# Split up body into two bits to make sure the zlib compression object
2868
# gets data fed twice.
2869
client.add_success_response_with_body(
2870
[body[:10], body[10:]], b'ok', b'10')
2871
revs = repo.get_revisions([b'somerev1'])
2872
self.assertEqual(revs, [somerev1])
2874
[('call_with_body_bytes_expecting_body',
2875
b'Repository.iter_revisions',
2876
(b'quack/', ), b"somerev1")],
2880
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2882
def test_null_revision(self):
2883
# a null revision has the predictable result {}, we should have no wire
2884
# traffic when calling it with this argument
2885
transport_path = 'empty'
2886
repo, client = self.setup_fake_client_and_repository(transport_path)
2887
client.add_success_response(b'notused')
2888
# actual RemoteRepository.get_revision_graph is gone, but there's an
2889
# equivalent private method for testing
2890
result = repo._get_revision_graph(NULL_REVISION)
2891
self.assertEqual([], client._calls)
2892
self.assertEqual({}, result)
2894
def test_none_revision(self):
2895
# with none we want the entire graph
2896
r1 = u'\u0e33'.encode('utf8')
2897
r2 = u'\u0dab'.encode('utf8')
2898
lines = [b' '.join([r2, r1]), r1]
2899
encoded_body = b'\n'.join(lines)
2901
transport_path = 'sinhala'
2902
repo, client = self.setup_fake_client_and_repository(transport_path)
2903
client.add_success_response_with_body(encoded_body, b'ok')
2904
# actual RemoteRepository.get_revision_graph is gone, but there's an
2905
# equivalent private method for testing
2906
result = repo._get_revision_graph(None)
2908
[('call_expecting_body', b'Repository.get_revision_graph',
2909
(b'sinhala/', b''))],
2911
self.assertEqual({r1: (), r2: (r1, )}, result)
2913
def test_specific_revision(self):
2914
# with a specific revision we want the graph for that
2915
# with none we want the entire graph
2916
r11 = u'\u0e33'.encode('utf8')
2917
r12 = u'\xc9'.encode('utf8')
2918
r2 = u'\u0dab'.encode('utf8')
2919
lines = [b' '.join([r2, r11, r12]), r11, r12]
2920
encoded_body = b'\n'.join(lines)
2922
transport_path = 'sinhala'
2923
repo, client = self.setup_fake_client_and_repository(transport_path)
2924
client.add_success_response_with_body(encoded_body, b'ok')
2925
result = repo._get_revision_graph(r2)
2927
[('call_expecting_body', b'Repository.get_revision_graph',
2928
(b'sinhala/', r2))],
2930
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2932
def test_no_such_revision(self):
2934
transport_path = 'sinhala'
2935
repo, client = self.setup_fake_client_and_repository(transport_path)
2936
client.add_error_response(b'nosuchrevision', revid)
2937
# also check that the right revision is reported in the error
2938
self.assertRaises(errors.NoSuchRevision,
2939
repo._get_revision_graph, revid)
2941
[('call_expecting_body', b'Repository.get_revision_graph',
2942
(b'sinhala/', revid))],
2945
def test_unexpected_error(self):
2947
transport_path = 'sinhala'
2948
repo, client = self.setup_fake_client_and_repository(transport_path)
2949
client.add_error_response(b'AnUnexpectedError')
2950
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2951
repo._get_revision_graph, revid)
2952
self.assertEqual((b'AnUnexpectedError',), e.error_tuple)
2955
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2958
repo, client = self.setup_fake_client_and_repository('quack')
2959
client.add_expected_call(
2960
b'Repository.get_rev_id_for_revno', (b'quack/',
2961
5, (42, b'rev-foo')),
2962
b'success', (b'ok', b'rev-five'))
2963
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2964
self.assertEqual((True, b'rev-five'), result)
2965
self.assertFinished(client)
2967
def test_history_incomplete(self):
2968
repo, client = self.setup_fake_client_and_repository('quack')
2969
client.add_expected_call(
2970
b'Repository.get_rev_id_for_revno', (b'quack/',
2971
5, (42, b'rev-foo')),
2972
b'success', (b'history-incomplete', 10, b'rev-ten'))
2973
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2974
self.assertEqual((False, (10, b'rev-ten')), result)
2975
self.assertFinished(client)
2977
def test_history_incomplete_with_fallback(self):
2978
"""A 'history-incomplete' response causes the fallback repository to be
2979
queried too, if one is set.
2981
# Make a repo with a fallback repo, both using a FakeClient.
2982
format = remote.response_tuple_to_repo_format(
2983
(b'yes', b'no', b'yes', self.get_repo_format().network_name()))
2984
repo, client = self.setup_fake_client_and_repository('quack')
2985
repo._format = format
2986
fallback_repo, ignored = self.setup_fake_client_and_repository(
2988
fallback_repo._client = client
2989
fallback_repo._format = format
2990
repo.add_fallback_repository(fallback_repo)
2991
# First the client should ask the primary repo
2992
client.add_expected_call(
2993
b'Repository.get_rev_id_for_revno', (b'quack/',
2994
1, (42, b'rev-foo')),
2995
b'success', (b'history-incomplete', 2, b'rev-two'))
2996
# Then it should ask the fallback, using revno/revid from the
2997
# history-incomplete response as the known revno/revid.
2998
client.add_expected_call(
2999
b'Repository.get_rev_id_for_revno', (
3000
b'fallback/', 1, (2, b'rev-two')),
3001
b'success', (b'ok', b'rev-one'))
3002
result = repo.get_rev_id_for_revno(1, (42, b'rev-foo'))
3003
self.assertEqual((True, b'rev-one'), result)
3004
self.assertFinished(client)
3006
def test_nosuchrevision(self):
3007
# 'nosuchrevision' is returned when the known-revid is not found in the
3008
# remote repo. The client translates that response to NoSuchRevision.
3009
repo, client = self.setup_fake_client_and_repository('quack')
3010
client.add_expected_call(
3011
b'Repository.get_rev_id_for_revno', (b'quack/',
3012
5, (42, b'rev-foo')),
3013
b'error', (b'nosuchrevision', b'rev-foo'))
3015
errors.NoSuchRevision,
3016
repo.get_rev_id_for_revno, 5, (42, b'rev-foo'))
3017
self.assertFinished(client)
3019
def test_branch_fallback_locking(self):
3020
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
3021
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
3022
will be invoked, which will fail if the repo is unlocked.
3024
self.setup_smart_server_with_call_log()
3025
tree = self.make_branch_and_memory_tree('.')
3028
rev1 = tree.commit('First')
3029
tree.commit('Second')
3031
branch = tree.branch
3032
self.assertFalse(branch.is_locked())
3033
self.reset_smart_call_log()
3034
verb = b'Repository.get_rev_id_for_revno'
3035
self.disable_verb(verb)
3036
self.assertEqual(rev1, branch.get_rev_id(1))
3037
self.assertLength(1, [call for call in self.hpss_calls if
3038
call.call.method == verb])
3041
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
3043
def test_has_signature_for_revision_id(self):
3044
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
3045
transport_path = 'quack'
3046
repo, client = self.setup_fake_client_and_repository(transport_path)
3047
client.add_success_response(b'yes')
3048
result = repo.has_signature_for_revision_id(b'A')
3050
[('call', b'Repository.has_signature_for_revision_id',
3051
(b'quack/', b'A'))],
3053
self.assertEqual(True, result)
3055
def test_is_not_shared(self):
3056
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
3057
transport_path = 'qwack'
3058
repo, client = self.setup_fake_client_and_repository(transport_path)
3059
client.add_success_response(b'no')
3060
result = repo.has_signature_for_revision_id(b'A')
3062
[('call', b'Repository.has_signature_for_revision_id',
3063
(b'qwack/', b'A'))],
3065
self.assertEqual(False, result)
3068
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
3070
def test_get_physical_lock_status_yes(self):
3071
transport_path = 'qwack'
3072
repo, client = self.setup_fake_client_and_repository(transport_path)
3073
client.add_success_response(b'yes')
3074
result = repo.get_physical_lock_status()
3076
[('call', b'Repository.get_physical_lock_status',
3079
self.assertEqual(True, result)
3081
def test_get_physical_lock_status_no(self):
3082
transport_path = 'qwack'
3083
repo, client = self.setup_fake_client_and_repository(transport_path)
3084
client.add_success_response(b'no')
3085
result = repo.get_physical_lock_status()
3087
[('call', b'Repository.get_physical_lock_status',
3090
self.assertEqual(False, result)
3093
class TestRepositoryIsShared(TestRemoteRepository):
3095
def test_is_shared(self):
3096
# ('yes', ) for Repository.is_shared -> 'True'.
3097
transport_path = 'quack'
3098
repo, client = self.setup_fake_client_and_repository(transport_path)
3099
client.add_success_response(b'yes')
3100
result = repo.is_shared()
3102
[('call', b'Repository.is_shared', (b'quack/',))],
3104
self.assertEqual(True, result)
3106
def test_is_not_shared(self):
3107
# ('no', ) for Repository.is_shared -> 'False'.
3108
transport_path = 'qwack'
3109
repo, client = self.setup_fake_client_and_repository(transport_path)
3110
client.add_success_response(b'no')
3111
result = repo.is_shared()
3113
[('call', b'Repository.is_shared', (b'qwack/',))],
3115
self.assertEqual(False, result)
3118
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3120
def test_make_working_trees(self):
3121
# ('yes', ) for Repository.make_working_trees -> 'True'.
3122
transport_path = 'quack'
3123
repo, client = self.setup_fake_client_and_repository(transport_path)
3124
client.add_success_response(b'yes')
3125
result = repo.make_working_trees()
3127
[('call', b'Repository.make_working_trees', (b'quack/',))],
3129
self.assertEqual(True, result)
3131
def test_no_working_trees(self):
3132
# ('no', ) for Repository.make_working_trees -> 'False'.
3133
transport_path = 'qwack'
3134
repo, client = self.setup_fake_client_and_repository(transport_path)
3135
client.add_success_response(b'no')
3136
result = repo.make_working_trees()
3138
[('call', b'Repository.make_working_trees', (b'qwack/',))],
3140
self.assertEqual(False, result)
3143
class TestRepositoryLockWrite(TestRemoteRepository):
3145
def test_lock_write(self):
3146
transport_path = 'quack'
3147
repo, client = self.setup_fake_client_and_repository(transport_path)
3148
client.add_success_response(b'ok', b'a token')
3149
token = repo.lock_write().repository_token
3151
[('call', b'Repository.lock_write', (b'quack/', b''))],
3153
self.assertEqual(b'a token', token)
3155
def test_lock_write_already_locked(self):
3156
transport_path = 'quack'
3157
repo, client = self.setup_fake_client_and_repository(transport_path)
3158
client.add_error_response(b'LockContention')
3159
self.assertRaises(errors.LockContention, repo.lock_write)
3161
[('call', b'Repository.lock_write', (b'quack/', b''))],
3164
def test_lock_write_unlockable(self):
3165
transport_path = 'quack'
3166
repo, client = self.setup_fake_client_and_repository(transport_path)
3167
client.add_error_response(b'UnlockableTransport')
3168
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3170
[('call', b'Repository.lock_write', (b'quack/', b''))],
3174
class TestRepositoryWriteGroups(TestRemoteRepository):
3176
def test_start_write_group(self):
3177
transport_path = 'quack'
3178
repo, client = self.setup_fake_client_and_repository(transport_path)
3179
client.add_expected_call(
3180
b'Repository.lock_write', (b'quack/', b''),
3181
b'success', (b'ok', b'a token'))
3182
client.add_expected_call(
3183
b'Repository.start_write_group', (b'quack/', b'a token'),
3184
b'success', (b'ok', (b'token1', )))
3186
repo.start_write_group()
3188
def test_start_write_group_unsuspendable(self):
3189
# Some repositories do not support suspending write
3190
# groups. For those, fall back to the "real" repository.
3191
transport_path = 'quack'
3192
repo, client = self.setup_fake_client_and_repository(transport_path)
3194
def stub_ensure_real():
3195
client._calls.append(('_ensure_real',))
3196
repo._real_repository = _StubRealPackRepository(client._calls)
3197
repo._ensure_real = stub_ensure_real
3198
client.add_expected_call(
3199
b'Repository.lock_write', (b'quack/', b''),
3200
b'success', (b'ok', b'a token'))
3201
client.add_expected_call(
3202
b'Repository.start_write_group', (b'quack/', b'a token'),
3203
b'error', (b'UnsuspendableWriteGroup',))
3205
repo.start_write_group()
3206
self.assertEqual(client._calls[-2:], [
3208
('start_write_group',)])
3210
def test_commit_write_group(self):
3211
transport_path = 'quack'
3212
repo, client = self.setup_fake_client_and_repository(transport_path)
3213
client.add_expected_call(
3214
b'Repository.lock_write', (b'quack/', b''),
3215
b'success', (b'ok', b'a token'))
3216
client.add_expected_call(
3217
b'Repository.start_write_group', (b'quack/', b'a token'),
3218
b'success', (b'ok', [b'token1']))
3219
client.add_expected_call(
3220
b'Repository.commit_write_group', (b'quack/',
3221
b'a token', [b'token1']),
3222
b'success', (b'ok',))
3224
repo.start_write_group()
3225
repo.commit_write_group()
3227
def test_abort_write_group(self):
3228
transport_path = 'quack'
3229
repo, client = self.setup_fake_client_and_repository(transport_path)
3230
client.add_expected_call(
3231
b'Repository.lock_write', (b'quack/', b''),
3232
b'success', (b'ok', b'a token'))
3233
client.add_expected_call(
3234
b'Repository.start_write_group', (b'quack/', b'a token'),
3235
b'success', (b'ok', [b'token1']))
3236
client.add_expected_call(
3237
b'Repository.abort_write_group', (b'quack/',
3238
b'a token', [b'token1']),
3239
b'success', (b'ok',))
3241
repo.start_write_group()
3242
repo.abort_write_group(False)
3244
def test_suspend_write_group(self):
3245
transport_path = 'quack'
3246
repo, client = self.setup_fake_client_and_repository(transport_path)
3247
self.assertEqual([], repo.suspend_write_group())
3249
def test_resume_write_group(self):
3250
transport_path = 'quack'
3251
repo, client = self.setup_fake_client_and_repository(transport_path)
3252
client.add_expected_call(
3253
b'Repository.lock_write', (b'quack/', b''),
3254
b'success', (b'ok', b'a token'))
3255
client.add_expected_call(
3256
b'Repository.check_write_group', (b'quack/',
3257
b'a token', [b'token1']),
3258
b'success', (b'ok',))
3260
repo.resume_write_group(['token1'])
3263
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3265
def test_backwards_compat(self):
3266
self.setup_smart_server_with_call_log()
3267
repo = self.make_repository('.')
3268
self.reset_smart_call_log()
3269
verb = b'Repository.set_make_working_trees'
3270
self.disable_verb(verb)
3271
repo.set_make_working_trees(True)
3272
call_count = len([call for call in self.hpss_calls if
3273
call.call.method == verb])
3274
self.assertEqual(1, call_count)
3276
def test_current(self):
3277
transport_path = 'quack'
3278
repo, client = self.setup_fake_client_and_repository(transport_path)
3279
client.add_expected_call(
3280
b'Repository.set_make_working_trees', (b'quack/', b'True'),
3281
b'success', (b'ok',))
3282
client.add_expected_call(
3283
b'Repository.set_make_working_trees', (b'quack/', b'False'),
3284
b'success', (b'ok',))
3285
repo.set_make_working_trees(True)
3286
repo.set_make_working_trees(False)
3289
class TestRepositoryUnlock(TestRemoteRepository):
3291
def test_unlock(self):
3292
transport_path = 'quack'
3293
repo, client = self.setup_fake_client_and_repository(transport_path)
3294
client.add_success_response(b'ok', b'a token')
3295
client.add_success_response(b'ok')
3299
[('call', b'Repository.lock_write', (b'quack/', b'')),
3300
('call', b'Repository.unlock', (b'quack/', b'a token'))],
3303
def test_unlock_wrong_token(self):
3304
# If somehow the token is wrong, unlock will raise TokenMismatch.
3305
transport_path = 'quack'
3306
repo, client = self.setup_fake_client_and_repository(transport_path)
3307
client.add_success_response(b'ok', b'a token')
3308
client.add_error_response(b'TokenMismatch')
3310
self.assertRaises(errors.TokenMismatch, repo.unlock)
3313
class TestRepositoryHasRevision(TestRemoteRepository):
3315
def test_none(self):
3316
# repo.has_revision(None) should not cause any traffic.
3317
transport_path = 'quack'
3318
repo, client = self.setup_fake_client_and_repository(transport_path)
3320
# The null revision is always there, so has_revision(None) == True.
3321
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3323
# The remote repo shouldn't be accessed.
3324
self.assertEqual([], client._calls)
3327
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3328
"""Test Repository.iter_file_bytes."""
3330
def test_single(self):
3331
transport_path = 'quack'
3332
repo, client = self.setup_fake_client_and_repository(transport_path)
3333
client.add_expected_call(
3334
b'Repository.iter_files_bytes', (b'quack/', ),
3335
b'success', (b'ok',), iter([b"ok\x000", b"\n", zlib.compress(b"mydata" * 10)]))
3336
for (identifier, byte_stream) in repo.iter_files_bytes([(b"somefile",
3337
b"somerev", b"myid")]):
3338
self.assertEqual(b"myid", identifier)
3339
self.assertEqual(b"".join(byte_stream), b"mydata" * 10)
3341
def test_missing(self):
3342
transport_path = 'quack'
3343
repo, client = self.setup_fake_client_and_repository(transport_path)
3344
client.add_expected_call(
3345
b'Repository.iter_files_bytes',
3347
b'error', (b'RevisionNotPresent', b'somefile', b'somerev'),
3348
iter([b"absent\0somefile\0somerev\n"]))
3349
self.assertRaises(errors.RevisionNotPresent, list,
3350
repo.iter_files_bytes(
3351
[(b"somefile", b"somerev", b"myid")]))
3354
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3355
"""Base class for Repository.insert_stream and .insert_stream_1.19
3359
def checkInsertEmptyStream(self, repo, client):
3360
"""Insert an empty stream, checking the result.
3362
This checks that there are no resume_tokens or missing_keys, and that
3363
the client is finished.
3365
sink = repo._get_sink()
3366
fmt = repository.format_registry.get_default()
3367
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3368
self.assertEqual([], resume_tokens)
3369
self.assertEqual(set(), missing_keys)
3370
self.assertFinished(client)
3373
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3374
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3377
This test case is very similar to TestRepositoryInsertStream_1_19.
3381
super(TestRepositoryInsertStream, self).setUp()
3382
self.disable_verb(b'Repository.insert_stream_1.19')
3384
def test_unlocked_repo(self):
3385
transport_path = 'quack'
3386
repo, client = self.setup_fake_client_and_repository(transport_path)
3387
client.add_expected_call(
3388
b'Repository.insert_stream_1.19', (b'quack/', b''),
3389
b'unknown', (b'Repository.insert_stream_1.19',))
3390
client.add_expected_call(
3391
b'Repository.insert_stream', (b'quack/', b''),
3392
b'success', (b'ok',))
3393
client.add_expected_call(
3394
b'Repository.insert_stream', (b'quack/', b''),
3395
b'success', (b'ok',))
3396
self.checkInsertEmptyStream(repo, client)
3398
def test_locked_repo_with_no_lock_token(self):
3399
transport_path = 'quack'
3400
repo, client = self.setup_fake_client_and_repository(transport_path)
3401
client.add_expected_call(
3402
b'Repository.lock_write', (b'quack/', b''),
3403
b'success', (b'ok', b''))
3404
client.add_expected_call(
3405
b'Repository.insert_stream_1.19', (b'quack/', b''),
3406
b'unknown', (b'Repository.insert_stream_1.19',))
3407
client.add_expected_call(
3408
b'Repository.insert_stream', (b'quack/', b''),
3409
b'success', (b'ok',))
3410
client.add_expected_call(
3411
b'Repository.insert_stream', (b'quack/', b''),
3412
b'success', (b'ok',))
3414
self.checkInsertEmptyStream(repo, client)
3416
def test_locked_repo_with_lock_token(self):
3417
transport_path = 'quack'
3418
repo, client = self.setup_fake_client_and_repository(transport_path)
3419
client.add_expected_call(
3420
b'Repository.lock_write', (b'quack/', b''),
3421
b'success', (b'ok', b'a token'))
3422
client.add_expected_call(
3423
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3424
b'unknown', (b'Repository.insert_stream_1.19',))
3425
client.add_expected_call(
3426
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3427
b'success', (b'ok',))
3428
client.add_expected_call(
3429
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3430
b'success', (b'ok',))
3432
self.checkInsertEmptyStream(repo, client)
3434
def test_stream_with_inventory_deltas(self):
3435
"""'inventory-deltas' substreams cannot be sent to the
3436
Repository.insert_stream verb, because not all servers that implement
3437
that verb will accept them. So when one is encountered the RemoteSink
3438
immediately stops using that verb and falls back to VFS insert_stream.
3440
transport_path = 'quack'
3441
repo, client = self.setup_fake_client_and_repository(transport_path)
3442
client.add_expected_call(
3443
b'Repository.insert_stream_1.19', (b'quack/', b''),
3444
b'unknown', (b'Repository.insert_stream_1.19',))
3445
client.add_expected_call(
3446
b'Repository.insert_stream', (b'quack/', b''),
3447
b'success', (b'ok',))
3448
client.add_expected_call(
3449
b'Repository.insert_stream', (b'quack/', b''),
3450
b'success', (b'ok',))
3451
# Create a fake real repository for insert_stream to fall back on, so
3452
# that we can directly see the records the RemoteSink passes to the
3459
def insert_stream(self, stream, src_format, resume_tokens):
3460
for substream_kind, substream in stream:
3461
self.records.append(
3462
(substream_kind, [record.key for record in substream]))
3463
return [b'fake tokens'], [b'fake missing keys']
3464
fake_real_sink = FakeRealSink()
3466
class FakeRealRepository:
3467
def _get_sink(self):
3468
return fake_real_sink
3470
def is_in_write_group(self):
3473
def refresh_data(self):
3475
repo._real_repository = FakeRealRepository()
3476
sink = repo._get_sink()
3477
fmt = repository.format_registry.get_default()
3478
stream = self.make_stream_with_inv_deltas(fmt)
3479
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3480
# Every record from the first inventory delta should have been sent to
3482
expected_records = [
3483
('inventory-deltas', [(b'rev2',), (b'rev3',)]),
3484
('texts', [(b'some-rev', b'some-file')])]
3485
self.assertEqual(expected_records, fake_real_sink.records)
3486
# The return values from the real sink's insert_stream are propagated
3487
# back to the original caller.
3488
self.assertEqual([b'fake tokens'], resume_tokens)
3489
self.assertEqual([b'fake missing keys'], missing_keys)
3490
self.assertFinished(client)
3492
def make_stream_with_inv_deltas(self, fmt):
3493
"""Make a simple stream with an inventory delta followed by more
3494
records and more substreams to test that all records and substreams
3495
from that point on are used.
3497
This sends, in order:
3498
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3500
* texts substream: (some-rev, some-file)
3502
# Define a stream using generators so that it isn't rewindable.
3503
inv = inventory.Inventory(revision_id=b'rev1')
3504
inv.root.revision = b'rev1'
3506
def stream_with_inv_delta():
3507
yield ('inventories', inventories_substream())
3508
yield ('inventory-deltas', inventory_delta_substream())
3510
versionedfile.FulltextContentFactory(
3511
(b'some-rev', b'some-file'), (), None, b'content')])
3513
def inventories_substream():
3514
# An empty inventory fulltext. This will be streamed normally.
3515
text = fmt._serializer.write_inventory_to_string(inv)
3516
yield versionedfile.FulltextContentFactory(
3517
(b'rev1',), (), None, text)
3519
def inventory_delta_substream():
3520
# An inventory delta. This can't be streamed via this verb, so it
3521
# will trigger a fallback to VFS insert_stream.
3522
entry = inv.make_entry(
3523
'directory', 'newdir', inv.root.file_id, b'newdir-id')
3524
entry.revision = b'ghost'
3525
delta = [(None, 'newdir', b'newdir-id', entry)]
3526
serializer = inventory_delta.InventoryDeltaSerializer(
3527
versioned_root=True, tree_references=False)
3528
lines = serializer.delta_to_lines(b'rev1', b'rev2', delta)
3529
yield versionedfile.ChunkedContentFactory(
3530
(b'rev2',), ((b'rev1',)), None, lines)
3532
lines = serializer.delta_to_lines(b'rev1', b'rev3', delta)
3533
yield versionedfile.ChunkedContentFactory(
3534
(b'rev3',), ((b'rev1',)), None, lines)
3535
return stream_with_inv_delta()
3538
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3540
def test_unlocked_repo(self):
3541
transport_path = 'quack'
3542
repo, client = self.setup_fake_client_and_repository(transport_path)
3543
client.add_expected_call(
3544
b'Repository.insert_stream_1.19', (b'quack/', b''),
3545
b'success', (b'ok',))
3546
client.add_expected_call(
3547
b'Repository.insert_stream_1.19', (b'quack/', b''),
3548
b'success', (b'ok',))
3549
self.checkInsertEmptyStream(repo, client)
3551
def test_locked_repo_with_no_lock_token(self):
3552
transport_path = 'quack'
3553
repo, client = self.setup_fake_client_and_repository(transport_path)
3554
client.add_expected_call(
3555
b'Repository.lock_write', (b'quack/', b''),
3556
b'success', (b'ok', b''))
3557
client.add_expected_call(
3558
b'Repository.insert_stream_1.19', (b'quack/', b''),
3559
b'success', (b'ok',))
3560
client.add_expected_call(
3561
b'Repository.insert_stream_1.19', (b'quack/', b''),
3562
b'success', (b'ok',))
3564
self.checkInsertEmptyStream(repo, client)
3566
def test_locked_repo_with_lock_token(self):
3567
transport_path = 'quack'
3568
repo, client = self.setup_fake_client_and_repository(transport_path)
3569
client.add_expected_call(
3570
b'Repository.lock_write', (b'quack/', b''),
3571
b'success', (b'ok', b'a token'))
3572
client.add_expected_call(
3573
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3574
b'success', (b'ok',))
3575
client.add_expected_call(
3576
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3577
b'success', (b'ok',))
3579
self.checkInsertEmptyStream(repo, client)
3582
class TestRepositoryTarball(TestRemoteRepository):
3584
# This is a canned tarball reponse we can validate against
3585
tarball_content = base64.b64decode(
3586
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3587
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3588
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3589
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3590
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3591
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3592
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3593
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3594
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3595
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3596
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3597
'nWQ7QH/F3JFOFCQ0aSPfA='
3600
def test_repository_tarball(self):
3601
# Test that Repository.tarball generates the right operations
3602
transport_path = 'repo'
3603
expected_calls = [('call_expecting_body', b'Repository.tarball',
3604
(b'repo/', b'bz2',),),
3606
repo, client = self.setup_fake_client_and_repository(transport_path)
3607
client.add_success_response_with_body(self.tarball_content, b'ok')
3608
# Now actually ask for the tarball
3609
tarball_file = repo._get_tarball('bz2')
3611
self.assertEqual(expected_calls, client._calls)
3612
self.assertEqual(self.tarball_content, tarball_file.read())
3614
tarball_file.close()
3617
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3618
"""RemoteRepository.copy_content_into optimizations"""
3620
def test_copy_content_remote_to_local(self):
3621
self.transport_server = test_server.SmartTCPServer_for_testing
3622
src_repo = self.make_repository('repo1')
3623
src_repo = repository.Repository.open(self.get_url('repo1'))
3624
# At the moment the tarball-based copy_content_into can't write back
3625
# into a smart server. It would be good if it could upload the
3626
# tarball; once that works we'd have to create repositories of
3627
# different formats. -- mbp 20070410
3628
dest_url = self.get_vfs_only_url('repo2')
3629
dest_bzrdir = BzrDir.create(dest_url)
3630
dest_repo = dest_bzrdir.create_repository()
3631
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3632
self.assertTrue(isinstance(src_repo, RemoteRepository))
3633
src_repo.copy_content_into(dest_repo)
3636
class _StubRealPackRepository(object):
3638
def __init__(self, calls):
3640
self._pack_collection = _StubPackCollection(calls)
3642
def start_write_group(self):
3643
self.calls.append(('start_write_group',))
3645
def is_in_write_group(self):
3648
def refresh_data(self):
3649
self.calls.append(('pack collection reload_pack_names',))
3652
class _StubPackCollection(object):
3654
def __init__(self, calls):
3658
self.calls.append(('pack collection autopack',))
3661
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3662
"""Tests for RemoteRepository.autopack implementation."""
3665
"""When the server returns 'ok' and there's no _real_repository, then
3666
nothing else happens: the autopack method is done.
3668
transport_path = 'quack'
3669
repo, client = self.setup_fake_client_and_repository(transport_path)
3670
client.add_expected_call(
3671
b'PackRepository.autopack', (b'quack/',), b'success', (b'ok',))
3673
self.assertFinished(client)
3675
def test_ok_with_real_repo(self):
3676
"""When the server returns 'ok' and there is a _real_repository, then
3677
the _real_repository's reload_pack_name's method will be called.
3679
transport_path = 'quack'
3680
repo, client = self.setup_fake_client_and_repository(transport_path)
3681
client.add_expected_call(
3682
b'PackRepository.autopack', (b'quack/',),
3683
b'success', (b'ok',))
3684
repo._real_repository = _StubRealPackRepository(client._calls)
3687
[('call', b'PackRepository.autopack', (b'quack/',)),
3688
('pack collection reload_pack_names',)],
3691
def test_backwards_compatibility(self):
3692
"""If the server does not recognise the PackRepository.autopack verb,
3693
fallback to the real_repository's implementation.
3695
transport_path = 'quack'
3696
repo, client = self.setup_fake_client_and_repository(transport_path)
3697
client.add_unknown_method_response(b'PackRepository.autopack')
3699
def stub_ensure_real():
3700
client._calls.append(('_ensure_real',))
3701
repo._real_repository = _StubRealPackRepository(client._calls)
3702
repo._ensure_real = stub_ensure_real
3705
[('call', b'PackRepository.autopack', (b'quack/',)),
3707
('pack collection autopack',)],
3710
def test_oom_error_reporting(self):
3711
"""An out-of-memory condition on the server is reported clearly"""
3712
transport_path = 'quack'
3713
repo, client = self.setup_fake_client_and_repository(transport_path)
3714
client.add_expected_call(
3715
b'PackRepository.autopack', (b'quack/',),
3716
b'error', (b'MemoryError',))
3717
err = self.assertRaises(errors.BzrError, repo.autopack)
3718
self.assertContainsRe(str(err), "^remote server out of mem")
3721
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3722
"""Base class for unit tests for breezy.bzr.remote._translate_error."""
3724
def translateTuple(self, error_tuple, **context):
3725
"""Call _translate_error with an ErrorFromSmartServer built from the
3728
:param error_tuple: A tuple of a smart server response, as would be
3729
passed to an ErrorFromSmartServer.
3730
:kwargs context: context items to call _translate_error with.
3732
:returns: The error raised by _translate_error.
3734
# Raise the ErrorFromSmartServer before passing it as an argument,
3735
# because _translate_error may need to re-raise it with a bare 'raise'
3737
server_error = errors.ErrorFromSmartServer(error_tuple)
3738
translated_error = self.translateErrorFromSmartServer(
3739
server_error, **context)
3740
return translated_error
3742
def translateErrorFromSmartServer(self, error_object, **context):
3743
"""Like translateTuple, but takes an already constructed
3744
ErrorFromSmartServer rather than a tuple.
3748
except errors.ErrorFromSmartServer as server_error:
3749
translated_error = self.assertRaises(
3750
errors.BzrError, remote._translate_error, server_error,
3752
return translated_error
3755
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3756
"""Unit tests for breezy.bzr.remote._translate_error.
3758
Given an ErrorFromSmartServer (which has an error tuple from a smart
3759
server) and some context, _translate_error raises more specific errors from
3762
This test case covers the cases where _translate_error succeeds in
3763
translating an ErrorFromSmartServer to something better. See
3764
TestErrorTranslationRobustness for other cases.
3767
def test_NoSuchRevision(self):
3768
branch = self.make_branch('')
3770
translated_error = self.translateTuple(
3771
(b'NoSuchRevision', revid), branch=branch)
3772
expected_error = errors.NoSuchRevision(branch, revid)
3773
self.assertEqual(expected_error, translated_error)
3775
def test_nosuchrevision(self):
3776
repository = self.make_repository('')
3778
translated_error = self.translateTuple(
3779
(b'nosuchrevision', revid), repository=repository)
3780
expected_error = errors.NoSuchRevision(repository, revid)
3781
self.assertEqual(expected_error, translated_error)
3783
def test_nobranch(self):
3784
bzrdir = self.make_controldir('')
3785
translated_error = self.translateTuple((b'nobranch',), bzrdir=bzrdir)
3786
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3787
self.assertEqual(expected_error, translated_error)
3789
def test_nobranch_one_arg(self):
3790
bzrdir = self.make_controldir('')
3791
translated_error = self.translateTuple(
3792
(b'nobranch', b'extra detail'), bzrdir=bzrdir)
3793
expected_error = errors.NotBranchError(
3794
path=bzrdir.root_transport.base,
3795
detail='extra detail')
3796
self.assertEqual(expected_error, translated_error)
3798
def test_norepository(self):
3799
bzrdir = self.make_controldir('')
3800
translated_error = self.translateTuple((b'norepository',),
3802
expected_error = errors.NoRepositoryPresent(bzrdir)
3803
self.assertEqual(expected_error, translated_error)
3805
def test_LockContention(self):
3806
translated_error = self.translateTuple((b'LockContention',))
3807
expected_error = errors.LockContention('(remote lock)')
3808
self.assertEqual(expected_error, translated_error)
3810
def test_UnlockableTransport(self):
3811
bzrdir = self.make_controldir('')
3812
translated_error = self.translateTuple(
3813
(b'UnlockableTransport',), bzrdir=bzrdir)
3814
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3815
self.assertEqual(expected_error, translated_error)
3817
def test_LockFailed(self):
3818
lock = 'str() of a server lock'
3819
why = 'str() of why'
3820
translated_error = self.translateTuple(
3821
(b'LockFailed', lock.encode('ascii'), why.encode('ascii')))
3822
expected_error = errors.LockFailed(lock, why)
3823
self.assertEqual(expected_error, translated_error)
3825
def test_TokenMismatch(self):
3826
token = 'a lock token'
3827
translated_error = self.translateTuple(
3828
(b'TokenMismatch',), token=token)
3829
expected_error = errors.TokenMismatch(token, '(remote token)')
3830
self.assertEqual(expected_error, translated_error)
3832
def test_Diverged(self):
3833
branch = self.make_branch('a')
3834
other_branch = self.make_branch('b')
3835
translated_error = self.translateTuple(
3836
(b'Diverged',), branch=branch, other_branch=other_branch)
3837
expected_error = errors.DivergedBranches(branch, other_branch)
3838
self.assertEqual(expected_error, translated_error)
3840
def test_NotStacked(self):
3841
branch = self.make_branch('')
3842
translated_error = self.translateTuple((b'NotStacked',), branch=branch)
3843
expected_error = errors.NotStacked(branch)
3844
self.assertEqual(expected_error, translated_error)
3846
def test_ReadError_no_args(self):
3848
translated_error = self.translateTuple((b'ReadError',), path=path)
3849
expected_error = errors.ReadError(path)
3850
self.assertEqual(expected_error, translated_error)
3852
def test_ReadError(self):
3854
translated_error = self.translateTuple(
3855
(b'ReadError', path.encode('utf-8')))
3856
expected_error = errors.ReadError(path)
3857
self.assertEqual(expected_error, translated_error)
3859
def test_IncompatibleRepositories(self):
3860
translated_error = self.translateTuple((b'IncompatibleRepositories',
3861
b"repo1", b"repo2", b"details here"))
3862
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3864
self.assertEqual(expected_error, translated_error)
3866
def test_GhostRevisionsHaveNoRevno(self):
3867
translated_error = self.translateTuple((b'GhostRevisionsHaveNoRevno',
3868
b"revid1", b"revid2"))
3869
expected_error = errors.GhostRevisionsHaveNoRevno(b"revid1", b"revid2")
3870
self.assertEqual(expected_error, translated_error)
3872
def test_PermissionDenied_no_args(self):
3874
translated_error = self.translateTuple((b'PermissionDenied',),
3876
expected_error = errors.PermissionDenied(path)
3877
self.assertEqual(expected_error, translated_error)
3879
def test_PermissionDenied_one_arg(self):
3881
translated_error = self.translateTuple(
3882
(b'PermissionDenied', path.encode('utf-8')))
3883
expected_error = errors.PermissionDenied(path)
3884
self.assertEqual(expected_error, translated_error)
3886
def test_PermissionDenied_one_arg_and_context(self):
3887
"""Given a choice between a path from the local context and a path on
3888
the wire, _translate_error prefers the path from the local context.
3890
local_path = 'local path'
3891
remote_path = 'remote path'
3892
translated_error = self.translateTuple(
3893
(b'PermissionDenied', remote_path.encode('utf-8')), path=local_path)
3894
expected_error = errors.PermissionDenied(local_path)
3895
self.assertEqual(expected_error, translated_error)
3897
def test_PermissionDenied_two_args(self):
3899
extra = 'a string with extra info'
3900
translated_error = self.translateTuple(
3901
(b'PermissionDenied', path.encode('utf-8'), extra.encode('utf-8')))
3902
expected_error = errors.PermissionDenied(path, extra)
3903
self.assertEqual(expected_error, translated_error)
3905
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3907
def test_NoSuchFile_context_path(self):
3908
local_path = "local path"
3909
translated_error = self.translateTuple((b'ReadError', b"remote path"),
3911
expected_error = errors.ReadError(local_path)
3912
self.assertEqual(expected_error, translated_error)
3914
def test_NoSuchFile_without_context(self):
3915
remote_path = "remote path"
3916
translated_error = self.translateTuple(
3917
(b'ReadError', remote_path.encode('utf-8')))
3918
expected_error = errors.ReadError(remote_path)
3919
self.assertEqual(expected_error, translated_error)
3921
def test_ReadOnlyError(self):
3922
translated_error = self.translateTuple((b'ReadOnlyError',))
3923
expected_error = errors.TransportNotPossible("readonly transport")
3924
self.assertEqual(expected_error, translated_error)
3926
def test_MemoryError(self):
3927
translated_error = self.translateTuple((b'MemoryError',))
3928
self.assertStartsWith(str(translated_error),
3929
"remote server out of memory")
3931
def test_generic_IndexError_no_classname(self):
3932
err = errors.ErrorFromSmartServer(
3933
(b'error', b"list index out of range"))
3934
translated_error = self.translateErrorFromSmartServer(err)
3935
expected_error = errors.UnknownErrorFromSmartServer(err)
3936
self.assertEqual(expected_error, translated_error)
3938
# GZ 2011-03-02: TODO test generic non-ascii error string
3940
def test_generic_KeyError(self):
3941
err = errors.ErrorFromSmartServer((b'error', b'KeyError', b"1"))
3942
translated_error = self.translateErrorFromSmartServer(err)
3943
expected_error = errors.UnknownErrorFromSmartServer(err)
3944
self.assertEqual(expected_error, translated_error)
3947
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3948
"""Unit tests for breezy.bzr.remote._translate_error's robustness.
3950
TestErrorTranslationSuccess is for cases where _translate_error can
3951
translate successfully. This class about how _translate_err behaves when
3952
it fails to translate: it re-raises the original error.
3955
def test_unrecognised_server_error(self):
3956
"""If the error code from the server is not recognised, the original
3957
ErrorFromSmartServer is propagated unmodified.
3959
error_tuple = (b'An unknown error tuple',)
3960
server_error = errors.ErrorFromSmartServer(error_tuple)
3961
translated_error = self.translateErrorFromSmartServer(server_error)
3962
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3963
self.assertEqual(expected_error, translated_error)
3965
def test_context_missing_a_key(self):
3966
"""In case of a bug in the client, or perhaps an unexpected response
3967
from a server, _translate_error returns the original error tuple from
3968
the server and mutters a warning.
3970
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3971
# in the context dict. So let's give it an empty context dict instead
3972
# to exercise its error recovery.
3973
error_tuple = (b'NoSuchRevision', b'revid')
3974
server_error = errors.ErrorFromSmartServer(error_tuple)
3975
translated_error = self.translateErrorFromSmartServer(server_error)
3976
self.assertEqual(server_error, translated_error)
3977
# In addition to re-raising ErrorFromSmartServer, some debug info has
3978
# been muttered to the log file for developer to look at.
3979
self.assertContainsRe(
3981
"Missing key 'branch' in context")
3983
def test_path_missing(self):
3984
"""Some translations (PermissionDenied, ReadError) can determine the
3985
'path' variable from either the wire or the local context. If neither
3986
has it, then an error is raised.
3988
error_tuple = (b'ReadError',)
3989
server_error = errors.ErrorFromSmartServer(error_tuple)
3990
translated_error = self.translateErrorFromSmartServer(server_error)
3991
self.assertEqual(server_error, translated_error)
3992
# In addition to re-raising ErrorFromSmartServer, some debug info has
3993
# been muttered to the log file for developer to look at.
3994
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3997
class TestStacking(tests.TestCaseWithTransport):
3998
"""Tests for operations on stacked remote repositories.
4000
The underlying format type must support stacking.
4003
def test_access_stacked_remote(self):
4004
# based on <http://launchpad.net/bugs/261315>
4005
# make a branch stacked on another repository containing an empty
4006
# revision, then open it over hpss - we should be able to see that
4008
base_builder = self.make_branch_builder('base', format='1.9')
4009
base_builder.start_series()
4010
base_revid = base_builder.build_snapshot(None,
4011
[('add', ('', None, 'directory', None))],
4012
'message', revision_id=b'rev-id')
4013
base_builder.finish_series()
4014
stacked_branch = self.make_branch('stacked', format='1.9')
4015
stacked_branch.set_stacked_on_url('../base')
4016
# start a server looking at this
4017
smart_server = test_server.SmartTCPServer_for_testing()
4018
self.start_server(smart_server)
4019
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
4020
# can get its branch and repository
4021
remote_branch = remote_bzrdir.open_branch()
4022
remote_repo = remote_branch.repository
4023
remote_repo.lock_read()
4025
# it should have an appropriate fallback repository, which should also
4026
# be a RemoteRepository
4027
self.assertLength(1, remote_repo._fallback_repositories)
4028
self.assertIsInstance(remote_repo._fallback_repositories[0],
4030
# and it has the revision committed to the underlying repository;
4031
# these have varying implementations so we try several of them
4032
self.assertTrue(remote_repo.has_revisions([base_revid]))
4033
self.assertTrue(remote_repo.has_revision(base_revid))
4034
self.assertEqual(remote_repo.get_revision(base_revid).message,
4037
remote_repo.unlock()
4039
def prepare_stacked_remote_branch(self):
4040
"""Get stacked_upon and stacked branches with content in each."""
4041
self.setup_smart_server_with_call_log()
4042
tree1 = self.make_branch_and_tree('tree1', format='1.9')
4043
tree1.commit('rev1', rev_id=b'rev1')
4044
tree2 = tree1.branch.controldir.sprout('tree2', stacked=True
4045
).open_workingtree()
4046
local_tree = tree2.branch.create_checkout('local')
4047
local_tree.commit('local changes make me feel good.')
4048
branch2 = Branch.open(self.get_url('tree2'))
4050
self.addCleanup(branch2.unlock)
4051
return tree1.branch, branch2
4053
def test_stacked_get_parent_map(self):
4054
# the public implementation of get_parent_map obeys stacking
4055
_, branch = self.prepare_stacked_remote_branch()
4056
repo = branch.repository
4057
self.assertEqual({b'rev1'}, set(repo.get_parent_map([b'rev1'])))
4059
def test_unstacked_get_parent_map(self):
4060
# _unstacked_provider.get_parent_map ignores stacking
4061
_, branch = self.prepare_stacked_remote_branch()
4062
provider = branch.repository._unstacked_provider
4063
self.assertEqual(set(), set(provider.get_parent_map([b'rev1'])))
4065
def fetch_stream_to_rev_order(self, stream):
4067
for kind, substream in stream:
4068
if not kind == 'revisions':
4071
for content in substream:
4072
result.append(content.key[-1])
4075
def get_ordered_revs(self, format, order, branch_factory=None):
4076
"""Get a list of the revisions in a stream to format format.
4078
:param format: The format of the target.
4079
:param order: the order that target should have requested.
4080
:param branch_factory: A callable to create a trunk and stacked branch
4081
to fetch from. If none, self.prepare_stacked_remote_branch is used.
4082
:result: The revision ids in the stream, in the order seen,
4083
the topological order of revisions in the source.
4085
unordered_format = controldir.format_registry.get(format)()
4086
target_repository_format = unordered_format.repository_format
4088
self.assertEqual(order, target_repository_format._fetch_order)
4089
if branch_factory is None:
4090
branch_factory = self.prepare_stacked_remote_branch
4091
_, stacked = branch_factory()
4092
source = stacked.repository._get_source(target_repository_format)
4093
tip = stacked.last_revision()
4094
stacked.repository._ensure_real()
4095
graph = stacked.repository.get_graph()
4096
revs = [r for (r, ps) in graph.iter_ancestry([tip])
4097
if r != NULL_REVISION]
4099
search = vf_search.PendingAncestryResult([tip], stacked.repository)
4100
self.reset_smart_call_log()
4101
stream = source.get_stream(search)
4102
# We trust that if a revision is in the stream the rest of the new
4103
# content for it is too, as per our main fetch tests; here we are
4104
# checking that the revisions are actually included at all, and their
4106
return self.fetch_stream_to_rev_order(stream), revs
4108
def test_stacked_get_stream_unordered(self):
4109
# Repository._get_source.get_stream() from a stacked repository with
4110
# unordered yields the full data from both stacked and stacked upon
4112
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
4113
self.assertEqual(set(expected_revs), set(rev_ord))
4114
# Getting unordered results should have made a streaming data request
4115
# from the server, then one from the backing branch.
4116
self.assertLength(2, self.hpss_calls)
4118
def test_stacked_on_stacked_get_stream_unordered(self):
4119
# Repository._get_source.get_stream() from a stacked repository which
4120
# is itself stacked yields the full data from all three sources.
4121
def make_stacked_stacked():
4122
_, stacked = self.prepare_stacked_remote_branch()
4123
tree = stacked.controldir.sprout('tree3', stacked=True
4124
).open_workingtree()
4125
local_tree = tree.branch.create_checkout('local-tree3')
4126
local_tree.commit('more local changes are better')
4127
branch = Branch.open(self.get_url('tree3'))
4129
self.addCleanup(branch.unlock)
4131
rev_ord, expected_revs = self.get_ordered_revs(
4132
'1.9', 'unordered', branch_factory=make_stacked_stacked)
4133
self.assertEqual(set(expected_revs), set(rev_ord))
4134
# Getting unordered results should have made a streaming data request
4135
# from the server, and one from each backing repo
4136
self.assertLength(3, self.hpss_calls)
4138
def test_stacked_get_stream_topological(self):
4139
# Repository._get_source.get_stream() from a stacked repository with
4140
# topological sorting yields the full data from both stacked and
4141
# stacked upon sources in topological order.
4142
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4143
self.assertEqual(expected_revs, rev_ord)
4144
# Getting topological sort requires VFS calls still - one of which is
4145
# pushing up from the bound branch.
4146
self.assertLength(14, self.hpss_calls)
4148
def test_stacked_get_stream_groupcompress(self):
4149
# Repository._get_source.get_stream() from a stacked repository with
4150
# groupcompress sorting yields the full data from both stacked and
4151
# stacked upon sources in groupcompress order.
4152
raise tests.TestSkipped('No groupcompress ordered format available')
4153
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4154
self.assertEqual(expected_revs, reversed(rev_ord))
4155
# Getting unordered results should have made a streaming data request
4156
# from the backing branch, and one from the stacked on branch.
4157
self.assertLength(2, self.hpss_calls)
4159
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4160
# When pulling some fixed amount of content that is more than the
4161
# source has (because some is coming from a fallback branch, no error
4162
# should be received. This was reported as bug 360791.
4163
# Need three branches: a trunk, a stacked branch, and a preexisting
4164
# branch pulling content from stacked and trunk.
4165
self.setup_smart_server_with_call_log()
4166
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4167
trunk.commit('start')
4168
stacked_branch = trunk.branch.create_clone_on_transport(
4169
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4170
local = self.make_branch('local', format='1.9-rich-root')
4171
local.repository.fetch(stacked_branch.repository,
4172
stacked_branch.last_revision())
4175
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4178
super(TestRemoteBranchEffort, self).setUp()
4179
# Create a smart server that publishes whatever the backing VFS server
4181
self.smart_server = test_server.SmartTCPServer_for_testing()
4182
self.start_server(self.smart_server, self.get_server())
4183
# Log all HPSS calls into self.hpss_calls.
4184
_SmartClient.hooks.install_named_hook(
4185
'call', self.capture_hpss_call, None)
4186
self.hpss_calls = []
4188
def capture_hpss_call(self, params):
4189
self.hpss_calls.append(params.method)
4191
def test_copy_content_into_avoids_revision_history(self):
4192
local = self.make_branch('local')
4193
builder = self.make_branch_builder('remote')
4194
builder.build_commit(message="Commit.")
4195
remote_branch_url = self.smart_server.get_url() + 'remote'
4196
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4197
local.repository.fetch(remote_branch.repository)
4198
self.hpss_calls = []
4199
remote_branch.copy_content_into(local)
4200
self.assertFalse(b'Branch.revision_history' in self.hpss_calls)
4202
def test_fetch_everything_needs_just_one_call(self):
4203
local = self.make_branch('local')
4204
builder = self.make_branch_builder('remote')
4205
builder.build_commit(message="Commit.")
4206
remote_branch_url = self.smart_server.get_url() + 'remote'
4207
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4208
self.hpss_calls = []
4209
local.repository.fetch(
4210
remote_branch.repository,
4211
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4212
self.assertEqual([b'Repository.get_stream_1.19'], self.hpss_calls)
4214
def override_verb(self, verb_name, verb):
4215
request_handlers = request.request_handlers
4216
orig_verb = request_handlers.get(verb_name)
4217
orig_info = request_handlers.get_info(verb_name)
4218
request_handlers.register(verb_name, verb, override_existing=True)
4219
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4220
override_existing=True, info=orig_info)
4222
def test_fetch_everything_backwards_compat(self):
4223
"""Can fetch with EverythingResult even with pre 2.4 servers.
4225
Pre-2.4 do not support 'everything' searches with the
4226
Repository.get_stream_1.19 verb.
4230
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4231
"""A version of the Repository.get_stream_1.19 verb patched to
4232
reject 'everything' searches the way 2.3 and earlier do.
4235
def recreate_search(self, repository, search_bytes,
4236
discard_excess=False):
4237
verb_log.append(search_bytes.split(b'\n', 1)[0])
4238
if search_bytes == b'everything':
4240
request.FailedSmartServerResponse((b'BadSearch',)))
4241
return super(OldGetStreamVerb,
4242
self).recreate_search(repository, search_bytes,
4243
discard_excess=discard_excess)
4244
self.override_verb(b'Repository.get_stream_1.19', OldGetStreamVerb)
4245
local = self.make_branch('local')
4246
builder = self.make_branch_builder('remote')
4247
builder.build_commit(message="Commit.")
4248
remote_branch_url = self.smart_server.get_url() + 'remote'
4249
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4250
self.hpss_calls = []
4251
local.repository.fetch(
4252
remote_branch.repository,
4253
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4254
# make sure the overridden verb was used
4255
self.assertLength(1, verb_log)
4256
# more than one HPSS call is needed, but because it's a VFS callback
4257
# its hard to predict exactly how many.
4258
self.assertTrue(len(self.hpss_calls) > 1)
4261
class TestUpdateBoundBranchWithModifiedBoundLocation(
4262
tests.TestCaseWithTransport):
4263
"""Ensure correct handling of bound_location modifications.
4265
This is tested against a smart server as http://pad.lv/786980 was about a
4266
ReadOnlyError (write attempt during a read-only transaction) which can only
4267
happen in this context.
4271
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4272
self.transport_server = test_server.SmartTCPServer_for_testing
4274
def make_master_and_checkout(self, master_name, checkout_name):
4275
# Create the master branch and its associated checkout
4276
self.master = self.make_branch_and_tree(master_name)
4277
self.checkout = self.master.branch.create_checkout(checkout_name)
4278
# Modify the master branch so there is something to update
4279
self.master.commit('add stuff')
4280
self.last_revid = self.master.commit('even more stuff')
4281
self.bound_location = self.checkout.branch.get_bound_location()
4283
def assertUpdateSucceeds(self, new_location):
4284
self.checkout.branch.set_bound_location(new_location)
4285
self.checkout.update()
4286
self.assertEqual(self.last_revid, self.checkout.last_revision())
4288
def test_without_final_slash(self):
4289
self.make_master_and_checkout('master', 'checkout')
4290
# For unclear reasons some users have a bound_location without a final
4291
# '/', simulate that by forcing such a value
4292
self.assertEndsWith(self.bound_location, '/')
4293
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4295
def test_plus_sign(self):
4296
self.make_master_and_checkout('+master', 'checkout')
4297
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4299
def test_tilda(self):
4300
# Embed ~ in the middle of the path just to avoid any $HOME
4302
self.make_master_and_checkout('mas~ter', 'checkout')
4303
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4306
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4308
def test_no_context(self):
4309
class OutOfCoffee(errors.BzrError):
4310
"""A dummy exception for testing."""
4312
def __init__(self, urgency):
4313
self.urgency = urgency
4314
remote.no_context_error_translators.register(b"OutOfCoffee",
4315
lambda err: OutOfCoffee(err.error_args[0]))
4316
transport = MemoryTransport()
4317
client = FakeClient(transport.base)
4318
client.add_expected_call(
4319
b'Branch.get_stacked_on_url', (b'quack/',),
4320
b'error', (b'NotStacked',))
4321
client.add_expected_call(
4322
b'Branch.last_revision_info',
4324
b'error', (b'OutOfCoffee', b'low'))
4325
transport.mkdir('quack')
4326
transport = transport.clone('quack')
4327
branch = self.make_remote_branch(transport, client)
4328
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4329
self.assertFinished(client)
4331
def test_with_context(self):
4332
class OutOfTea(errors.BzrError):
4333
def __init__(self, branch, urgency):
4334
self.branch = branch
4335
self.urgency = urgency
4336
remote.error_translators.register(b"OutOfTea",
4337
lambda err, find, path: OutOfTea(
4338
err.error_args[0].decode(
4341
transport = MemoryTransport()
4342
client = FakeClient(transport.base)
4343
client.add_expected_call(
4344
b'Branch.get_stacked_on_url', (b'quack/',),
4345
b'error', (b'NotStacked',))
4346
client.add_expected_call(
4347
b'Branch.last_revision_info',
4349
b'error', (b'OutOfTea', b'low'))
4350
transport.mkdir('quack')
4351
transport = transport.clone('quack')
4352
branch = self.make_remote_branch(transport, client)
4353
self.assertRaises(OutOfTea, branch.last_revision_info)
4354
self.assertFinished(client)
4357
class TestRepositoryPack(TestRemoteRepository):
4359
def test_pack(self):
4360
transport_path = 'quack'
4361
repo, client = self.setup_fake_client_and_repository(transport_path)
4362
client.add_expected_call(
4363
b'Repository.lock_write', (b'quack/', b''),
4364
b'success', (b'ok', b'token'))
4365
client.add_expected_call(
4366
b'Repository.pack', (b'quack/', b'token', b'False'),
4367
b'success', (b'ok',), )
4368
client.add_expected_call(
4369
b'Repository.unlock', (b'quack/', b'token'),
4370
b'success', (b'ok', ))
4373
def test_pack_with_hint(self):
4374
transport_path = 'quack'
4375
repo, client = self.setup_fake_client_and_repository(transport_path)
4376
client.add_expected_call(
4377
b'Repository.lock_write', (b'quack/', b''),
4378
b'success', (b'ok', b'token'))
4379
client.add_expected_call(
4380
b'Repository.pack', (b'quack/', b'token', b'False'),
4381
b'success', (b'ok',), )
4382
client.add_expected_call(
4383
b'Repository.unlock', (b'quack/', b'token', b'False'),
4384
b'success', (b'ok', ))
4385
repo.pack(['hinta', 'hintb'])
4388
class TestRepositoryIterInventories(TestRemoteRepository):
4389
"""Test Repository.iter_inventories."""
4391
def _serialize_inv_delta(self, old_name, new_name, delta):
4392
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4393
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4395
def test_single_empty(self):
4396
transport_path = 'quack'
4397
repo, client = self.setup_fake_client_and_repository(transport_path)
4398
fmt = controldir.format_registry.get('2a')().repository_format
4400
stream = [('inventory-deltas', [
4401
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4402
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4403
client.add_expected_call(
4404
b'VersionedFileRepository.get_inventories', (
4405
b'quack/', b'unordered'),
4406
b'success', (b'ok', ),
4407
_stream_to_byte_stream(stream, fmt))
4408
ret = list(repo.iter_inventories([b"somerevid"]))
4409
self.assertLength(1, ret)
4411
self.assertEqual(b"somerevid", inv.revision_id)
4413
def test_empty(self):
4414
transport_path = 'quack'
4415
repo, client = self.setup_fake_client_and_repository(transport_path)
4416
ret = list(repo.iter_inventories([]))
4417
self.assertEqual(ret, [])
4419
def test_missing(self):
4420
transport_path = 'quack'
4421
repo, client = self.setup_fake_client_and_repository(transport_path)
4422
client.add_expected_call(
4423
b'VersionedFileRepository.get_inventories', (
4424
b'quack/', b'unordered'),
4425
b'success', (b'ok', ), iter([]))
4426
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(
4430
class TestRepositoryRevisionTreeArchive(TestRemoteRepository):
4431
"""Test Repository.iter_inventories."""
4433
def _serialize_inv_delta(self, old_name, new_name, delta):
4434
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4435
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4437
def test_simple(self):
4438
transport_path = 'quack'
4439
repo, client = self.setup_fake_client_and_repository(transport_path)
4440
fmt = controldir.format_registry.get('2a')().repository_format
4442
stream = [('inventory-deltas', [
4443
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4444
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4445
client.add_expected_call(
4446
b'VersionedFileRepository.get_inventories', (
4447
b'quack/', b'unordered'),
4448
b'success', (b'ok', ),
4449
_stream_to_byte_stream(stream, fmt))
4451
with tarfile.open(mode='w', fileobj=f) as tf:
4452
info = tarfile.TarInfo('somefile')
4454
contents = b'some data'
4455
info.type = tarfile.REGTYPE
4457
info.size = len(contents)
4458
tf.addfile(info, BytesIO(contents))
4459
client.add_expected_call(
4460
b'Repository.revision_archive', (b'quack/',
4461
b'somerevid', b'tar', b'foo.tar', b'', b'', None),
4462
b'success', (b'ok', ),
4464
tree = repo.revision_tree(b'somerevid')
4465
self.assertEqual(f.getvalue(), b''.join(
4466
tree.archive('tar', 'foo.tar')))
4469
class TestRepositoryAnnotate(TestRemoteRepository):
4470
"""Test RemoteRevisionTree.annotate.."""
4472
def _serialize_inv_delta(self, old_name, new_name, delta):
4473
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4474
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4476
def test_simple(self):
4477
transport_path = 'quack'
4478
repo, client = self.setup_fake_client_and_repository(transport_path)
4479
fmt = controldir.format_registry.get('2a')().repository_format
4482
('inventory-deltas', [
4483
versionedfile.FulltextContentFactory(
4484
b'somerevid', None, None,
4485
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4486
client.add_expected_call(
4487
b'VersionedFileRepository.get_inventories', (
4488
b'quack/', b'unordered'),
4489
b'success', (b'ok', ),
4490
_stream_to_byte_stream(stream, fmt))
4491
client.add_expected_call(
4492
b'Repository.annotate_file_revision',
4493
(b'quack/', b'somerevid', b'filename', b'', b'current:'),
4494
b'success', (b'ok', ),
4495
bencode.bencode([[b'baserevid', b'line 1\n'],
4496
[b'somerevid', b'line2\n']]))
4497
tree = repo.revision_tree(b'somerevid')
4499
(b'baserevid', b'line 1\n'),
4500
(b'somerevid', b'line2\n')],
4501
list(tree.annotate_iter('filename')))