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_dotted_no_smart_verb(self):
2218
self.setup_smart_server_with_call_log()
2219
branch = self.make_branch('.')
2220
self.disable_verb(b'Branch.revision_id_to_revno')
2221
self.reset_smart_call_log()
2222
self.assertEqual((0, ),
2223
branch.revision_id_to_dotted_revno(b'null:'))
2224
self.assertLength(8, self.hpss_calls)
2227
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2229
def test__get_config(self):
2230
client = FakeClient()
2231
client.add_success_response_with_body(b'default_stack_on = /\n', b'ok')
2232
transport = MemoryTransport()
2233
bzrdir = self.make_remote_bzrdir(transport, client)
2234
config = bzrdir.get_config()
2235
self.assertEqual('/', config.get_default_stack_on())
2237
[('call_expecting_body', b'BzrDir.get_config_file',
2241
def test_set_option_uses_vfs(self):
2242
self.setup_smart_server_with_call_log()
2243
bzrdir = self.make_controldir('.')
2244
self.reset_smart_call_log()
2245
config = bzrdir.get_config()
2246
config.set_default_stack_on('/')
2247
self.assertLength(4, self.hpss_calls)
2249
def test_backwards_compat_get_option(self):
2250
self.setup_smart_server_with_call_log()
2251
bzrdir = self.make_controldir('.')
2252
verb = b'BzrDir.get_config_file'
2253
self.disable_verb(verb)
2254
self.reset_smart_call_log()
2255
self.assertEqual(None,
2256
bzrdir._get_config().get_option('default_stack_on'))
2257
self.assertLength(4, self.hpss_calls)
2260
class TestTransportIsReadonly(tests.TestCase):
2262
def test_true(self):
2263
client = FakeClient()
2264
client.add_success_response(b'yes')
2265
transport = RemoteTransport('bzr://example.com/', medium=False,
2267
self.assertEqual(True, transport.is_readonly())
2269
[('call', b'Transport.is_readonly', ())],
2272
def test_false(self):
2273
client = FakeClient()
2274
client.add_success_response(b'no')
2275
transport = RemoteTransport('bzr://example.com/', medium=False,
2277
self.assertEqual(False, transport.is_readonly())
2279
[('call', b'Transport.is_readonly', ())],
2282
def test_error_from_old_server(self):
2283
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2285
Clients should treat it as a "no" response, because is_readonly is only
2286
advisory anyway (a transport could be read-write, but then the
2287
underlying filesystem could be readonly anyway).
2289
client = FakeClient()
2290
client.add_unknown_method_response(b'Transport.is_readonly')
2291
transport = RemoteTransport('bzr://example.com/', medium=False,
2293
self.assertEqual(False, transport.is_readonly())
2295
[('call', b'Transport.is_readonly', ())],
2299
class TestTransportMkdir(tests.TestCase):
2301
def test_permissiondenied(self):
2302
client = FakeClient()
2303
client.add_error_response(
2304
b'PermissionDenied', b'remote path', b'extra')
2305
transport = RemoteTransport('bzr://example.com/', medium=False,
2307
exc = self.assertRaises(
2308
errors.PermissionDenied, transport.mkdir, 'client path')
2309
expected_error = errors.PermissionDenied('/client path', 'extra')
2310
self.assertEqual(expected_error, exc)
2313
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2315
def test_defaults_to_none(self):
2316
t = RemoteSSHTransport('bzr+ssh://example.com')
2317
self.assertIs(None, t._get_credentials()[0])
2319
def test_uses_authentication_config(self):
2320
conf = config.AuthenticationConfig()
2321
conf._get_config().update(
2322
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2325
t = RemoteSSHTransport('bzr+ssh://example.com')
2326
self.assertEqual('bar', t._get_credentials()[0])
2329
class TestRemoteRepository(TestRemote):
2330
"""Base for testing RemoteRepository protocol usage.
2332
These tests contain frozen requests and responses. We want any changes to
2333
what is sent or expected to be require a thoughtful update to these tests
2334
because they might break compatibility with different-versioned servers.
2337
def setup_fake_client_and_repository(self, transport_path):
2338
"""Create the fake client and repository for testing with.
2340
There's no real server here; we just have canned responses sent
2343
:param transport_path: Path below the root of the MemoryTransport
2344
where the repository will be created.
2346
transport = MemoryTransport()
2347
transport.mkdir(transport_path)
2348
client = FakeClient(transport.base)
2349
transport = transport.clone(transport_path)
2350
# we do not want bzrdir to make any remote calls
2351
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2353
repo = RemoteRepository(bzrdir, None, _client=client)
2357
def remoted_description(format):
2358
return 'Remote: ' + format.get_format_description()
2361
class TestBranchFormat(tests.TestCase):
2363
def test_get_format_description(self):
2364
remote_format = RemoteBranchFormat()
2365
real_format = branch.format_registry.get_default()
2366
remote_format._network_name = real_format.network_name()
2367
self.assertEqual(remoted_description(real_format),
2368
remote_format.get_format_description())
2371
class TestRepositoryFormat(TestRemoteRepository):
2373
def test_fast_delta(self):
2374
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2375
true_format = RemoteRepositoryFormat()
2376
true_format._network_name = true_name
2377
self.assertEqual(True, true_format.fast_deltas)
2378
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2379
false_format = RemoteRepositoryFormat()
2380
false_format._network_name = false_name
2381
self.assertEqual(False, false_format.fast_deltas)
2383
def test_get_format_description(self):
2384
remote_repo_format = RemoteRepositoryFormat()
2385
real_format = repository.format_registry.get_default()
2386
remote_repo_format._network_name = real_format.network_name()
2387
self.assertEqual(remoted_description(real_format),
2388
remote_repo_format.get_format_description())
2391
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2393
def test_empty(self):
2394
transport_path = 'quack'
2395
repo, client = self.setup_fake_client_and_repository(transport_path)
2396
client.add_success_response_with_body(b'', b'ok')
2397
self.assertEqual([], repo.all_revision_ids())
2399
[('call_expecting_body', b'Repository.all_revision_ids',
2403
def test_with_some_content(self):
2404
transport_path = 'quack'
2405
repo, client = self.setup_fake_client_and_repository(transport_path)
2406
client.add_success_response_with_body(
2407
b'rev1\nrev2\nanotherrev\n', b'ok')
2409
set([b"rev1", b"rev2", b"anotherrev"]),
2410
set(repo.all_revision_ids()))
2412
[('call_expecting_body', b'Repository.all_revision_ids',
2417
class TestRepositoryGatherStats(TestRemoteRepository):
2419
def test_revid_none(self):
2420
# ('ok',), body with revisions and size
2421
transport_path = 'quack'
2422
repo, client = self.setup_fake_client_and_repository(transport_path)
2423
client.add_success_response_with_body(
2424
b'revisions: 2\nsize: 18\n', b'ok')
2425
result = repo.gather_stats(None)
2427
[('call_expecting_body', b'Repository.gather_stats',
2428
(b'quack/', b'', b'no'))],
2430
self.assertEqual({'revisions': 2, 'size': 18}, result)
2432
def test_revid_no_committers(self):
2433
# ('ok',), body without committers
2434
body = (b'firstrev: 123456.300 3600\n'
2435
b'latestrev: 654231.400 0\n'
2438
transport_path = 'quick'
2439
revid = u'\xc8'.encode('utf8')
2440
repo, client = self.setup_fake_client_and_repository(transport_path)
2441
client.add_success_response_with_body(body, b'ok')
2442
result = repo.gather_stats(revid)
2444
[('call_expecting_body', b'Repository.gather_stats',
2445
(b'quick/', revid, b'no'))],
2447
self.assertEqual({'revisions': 2, 'size': 18,
2448
'firstrev': (123456.300, 3600),
2449
'latestrev': (654231.400, 0), },
2452
def test_revid_with_committers(self):
2453
# ('ok',), body with committers
2454
body = (b'committers: 128\n'
2455
b'firstrev: 123456.300 3600\n'
2456
b'latestrev: 654231.400 0\n'
2459
transport_path = 'buick'
2460
revid = u'\xc8'.encode('utf8')
2461
repo, client = self.setup_fake_client_and_repository(transport_path)
2462
client.add_success_response_with_body(body, b'ok')
2463
result = repo.gather_stats(revid, True)
2465
[('call_expecting_body', b'Repository.gather_stats',
2466
(b'buick/', revid, b'yes'))],
2468
self.assertEqual({'revisions': 2, 'size': 18,
2470
'firstrev': (123456.300, 3600),
2471
'latestrev': (654231.400, 0), },
2475
class TestRepositoryBreakLock(TestRemoteRepository):
2477
def test_break_lock(self):
2478
transport_path = 'quack'
2479
repo, client = self.setup_fake_client_and_repository(transport_path)
2480
client.add_success_response(b'ok')
2483
[('call', b'Repository.break_lock', (b'quack/',))],
2487
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2489
def test_get_serializer_format(self):
2490
transport_path = 'hill'
2491
repo, client = self.setup_fake_client_and_repository(transport_path)
2492
client.add_success_response(b'ok', b'7')
2493
self.assertEqual(b'7', repo.get_serializer_format())
2495
[('call', b'VersionedFileRepository.get_serializer_format',
2500
class TestRepositoryReconcile(TestRemoteRepository):
2502
def test_reconcile(self):
2503
transport_path = 'hill'
2504
repo, client = self.setup_fake_client_and_repository(transport_path)
2505
body = (b"garbage_inventories: 2\n"
2506
b"inconsistent_parents: 3\n")
2507
client.add_expected_call(
2508
b'Repository.lock_write', (b'hill/', b''),
2509
b'success', (b'ok', b'a token'))
2510
client.add_success_response_with_body(body, b'ok')
2511
reconciler = repo.reconcile()
2513
[('call', b'Repository.lock_write', (b'hill/', b'')),
2514
('call_expecting_body', b'Repository.reconcile',
2515
(b'hill/', b'a token'))],
2517
self.assertEqual(2, reconciler.garbage_inventories)
2518
self.assertEqual(3, reconciler.inconsistent_parents)
2521
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2523
def test_text(self):
2524
# ('ok',), body with signature text
2525
transport_path = 'quack'
2526
repo, client = self.setup_fake_client_and_repository(transport_path)
2527
client.add_success_response_with_body(
2529
self.assertEqual(b"THETEXT", repo.get_signature_text(b"revid"))
2531
[('call_expecting_body', b'Repository.get_revision_signature_text',
2532
(b'quack/', b'revid'))],
2535
def test_no_signature(self):
2536
transport_path = 'quick'
2537
repo, client = self.setup_fake_client_and_repository(transport_path)
2538
client.add_error_response(b'nosuchrevision', b'unknown')
2539
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2542
[('call_expecting_body', b'Repository.get_revision_signature_text',
2543
(b'quick/', b'unknown'))],
2547
class TestRepositoryGetGraph(TestRemoteRepository):
2549
def test_get_graph(self):
2550
# get_graph returns a graph with a custom parents provider.
2551
transport_path = 'quack'
2552
repo, client = self.setup_fake_client_and_repository(transport_path)
2553
graph = repo.get_graph()
2554
self.assertNotEqual(graph._parents_provider, repo)
2557
class TestRepositoryAddSignatureText(TestRemoteRepository):
2559
def test_add_signature_text(self):
2560
transport_path = 'quack'
2561
repo, client = self.setup_fake_client_and_repository(transport_path)
2562
client.add_expected_call(
2563
b'Repository.lock_write', (b'quack/', b''),
2564
b'success', (b'ok', b'a token'))
2565
client.add_expected_call(
2566
b'Repository.start_write_group', (b'quack/', b'a token'),
2567
b'success', (b'ok', (b'token1', )))
2568
client.add_expected_call(
2569
b'Repository.add_signature_text', (b'quack/', b'a token', b'rev1',
2571
b'success', (b'ok', ), None)
2573
repo.start_write_group()
2575
None, repo.add_signature_text(b"rev1", b"every bloody emperor"))
2577
('call_with_body_bytes_expecting_body',
2578
b'Repository.add_signature_text',
2579
(b'quack/', b'a token', b'rev1', b'token1'),
2580
b'every bloody emperor'),
2584
class TestRepositoryGetParentMap(TestRemoteRepository):
2586
def test_get_parent_map_caching(self):
2587
# get_parent_map returns from cache until unlock()
2588
# setup a reponse with two revisions
2589
r1 = u'\u0e33'.encode('utf8')
2590
r2 = u'\u0dab'.encode('utf8')
2591
lines = [b' '.join([r2, r1]), r1]
2592
encoded_body = bz2.compress(b'\n'.join(lines))
2594
transport_path = 'quack'
2595
repo, client = self.setup_fake_client_and_repository(transport_path)
2596
client.add_success_response_with_body(encoded_body, b'ok')
2597
client.add_success_response_with_body(encoded_body, b'ok')
2599
graph = repo.get_graph()
2600
parents = graph.get_parent_map([r2])
2601
self.assertEqual({r2: (r1,)}, parents)
2602
# locking and unlocking deeper should not reset
2605
parents = graph.get_parent_map([r1])
2606
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2608
[('call_with_body_bytes_expecting_body',
2609
b'Repository.get_parent_map', (b'quack/',
2610
b'include-missing:', r2),
2614
# now we call again, and it should use the second response.
2616
graph = repo.get_graph()
2617
parents = graph.get_parent_map([r1])
2618
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2620
[('call_with_body_bytes_expecting_body',
2621
b'Repository.get_parent_map', (b'quack/',
2622
b'include-missing:', r2),
2624
('call_with_body_bytes_expecting_body',
2625
b'Repository.get_parent_map', (b'quack/',
2626
b'include-missing:', r1),
2632
def test_get_parent_map_reconnects_if_unknown_method(self):
2633
transport_path = 'quack'
2634
rev_id = b'revision-id'
2635
repo, client = self.setup_fake_client_and_repository(transport_path)
2636
client.add_unknown_method_response(b'Repository.get_parent_map')
2637
client.add_success_response_with_body(rev_id, b'ok')
2638
self.assertFalse(client._medium._is_remote_before((1, 2)))
2639
parents = repo.get_parent_map([rev_id])
2641
[('call_with_body_bytes_expecting_body',
2642
b'Repository.get_parent_map',
2643
(b'quack/', b'include-missing:', rev_id), b'\n\n0'),
2644
('disconnect medium',),
2645
('call_expecting_body', b'Repository.get_revision_graph',
2648
# The medium is now marked as being connected to an older server
2649
self.assertTrue(client._medium._is_remote_before((1, 2)))
2650
self.assertEqual({rev_id: (b'null:',)}, parents)
2652
def test_get_parent_map_fallback_parentless_node(self):
2653
"""get_parent_map falls back to get_revision_graph on old servers. The
2654
results from get_revision_graph are tweaked to match the get_parent_map
2657
Specifically, a {key: ()} result from get_revision_graph means "no
2658
parents" for that key, which in get_parent_map results should be
2659
represented as {key: ('null:',)}.
2661
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2663
rev_id = b'revision-id'
2664
transport_path = 'quack'
2665
repo, client = self.setup_fake_client_and_repository(transport_path)
2666
client.add_success_response_with_body(rev_id, b'ok')
2667
client._medium._remember_remote_is_before((1, 2))
2668
parents = repo.get_parent_map([rev_id])
2670
[('call_expecting_body', b'Repository.get_revision_graph',
2673
self.assertEqual({rev_id: (b'null:',)}, parents)
2675
def test_get_parent_map_unexpected_response(self):
2676
repo, client = self.setup_fake_client_and_repository('path')
2677
client.add_success_response(b'something unexpected!')
2679
errors.UnexpectedSmartServerResponse,
2680
repo.get_parent_map, [b'a-revision-id'])
2682
def test_get_parent_map_negative_caches_missing_keys(self):
2683
self.setup_smart_server_with_call_log()
2684
repo = self.make_repository('foo')
2685
self.assertIsInstance(repo, RemoteRepository)
2687
self.addCleanup(repo.unlock)
2688
self.reset_smart_call_log()
2689
graph = repo.get_graph()
2691
{}, graph.get_parent_map([b'some-missing', b'other-missing']))
2692
self.assertLength(1, self.hpss_calls)
2693
# No call if we repeat this
2694
self.reset_smart_call_log()
2695
graph = repo.get_graph()
2697
{}, graph.get_parent_map([b'some-missing', b'other-missing']))
2698
self.assertLength(0, self.hpss_calls)
2699
# Asking for more unknown keys makes a request.
2700
self.reset_smart_call_log()
2701
graph = repo.get_graph()
2703
{}, graph.get_parent_map([b'some-missing', b'other-missing',
2705
self.assertLength(1, self.hpss_calls)
2707
def disableExtraResults(self):
2708
self.overrideAttr(SmartServerRepositoryGetParentMap,
2709
'no_extra_results', True)
2711
def test_null_cached_missing_and_stop_key(self):
2712
self.setup_smart_server_with_call_log()
2713
# Make a branch with a single revision.
2714
builder = self.make_branch_builder('foo')
2715
builder.start_series()
2716
builder.build_snapshot(None, [
2717
('add', ('', b'root-id', 'directory', ''))],
2718
revision_id=b'first')
2719
builder.finish_series()
2720
branch = builder.get_branch()
2721
repo = branch.repository
2722
self.assertIsInstance(repo, RemoteRepository)
2723
# Stop the server from sending extra results.
2724
self.disableExtraResults()
2726
self.addCleanup(repo.unlock)
2727
self.reset_smart_call_log()
2728
graph = repo.get_graph()
2729
# Query for b'first' and b'null:'. Because b'null:' is a parent of
2730
# 'first' it will be a candidate for the stop_keys of subsequent
2731
# requests, and because b'null:' was queried but not returned it will
2732
# be cached as missing.
2733
self.assertEqual({b'first': (b'null:',)},
2734
graph.get_parent_map([b'first', b'null:']))
2735
# Now query for another key. This request will pass along a recipe of
2736
# start and stop keys describing the already cached results, and this
2737
# recipe's revision count must be correct (or else it will trigger an
2738
# error from the server).
2739
self.assertEqual({}, graph.get_parent_map([b'another-key']))
2740
# This assertion guards against disableExtraResults silently failing to
2741
# work, thus invalidating the test.
2742
self.assertLength(2, self.hpss_calls)
2744
def test_get_parent_map_gets_ghosts_from_result(self):
2745
# asking for a revision should negatively cache close ghosts in its
2747
self.setup_smart_server_with_call_log()
2748
tree = self.make_branch_and_memory_tree('foo')
2749
with tree.lock_write():
2750
builder = treebuilder.TreeBuilder()
2751
builder.start_tree(tree)
2753
builder.finish_tree()
2754
tree.set_parent_ids([b'non-existant'],
2755
allow_leftmost_as_ghost=True)
2756
rev_id = tree.commit('')
2758
self.addCleanup(tree.unlock)
2759
repo = tree.branch.repository
2760
self.assertIsInstance(repo, RemoteRepository)
2762
repo.get_parent_map([rev_id])
2763
self.reset_smart_call_log()
2764
# Now asking for rev_id's ghost parent should not make calls
2765
self.assertEqual({}, repo.get_parent_map([b'non-existant']))
2766
self.assertLength(0, self.hpss_calls)
2768
def test_exposes_get_cached_parent_map(self):
2769
"""RemoteRepository exposes get_cached_parent_map from
2772
r1 = u'\u0e33'.encode('utf8')
2773
r2 = u'\u0dab'.encode('utf8')
2774
lines = [b' '.join([r2, r1]), r1]
2775
encoded_body = bz2.compress(b'\n'.join(lines))
2777
transport_path = 'quack'
2778
repo, client = self.setup_fake_client_and_repository(transport_path)
2779
client.add_success_response_with_body(encoded_body, b'ok')
2781
# get_cached_parent_map should *not* trigger an RPC
2782
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2783
self.assertEqual([], client._calls)
2784
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2785
self.assertEqual({r1: (NULL_REVISION,)},
2786
repo.get_cached_parent_map([r1]))
2788
[('call_with_body_bytes_expecting_body',
2789
b'Repository.get_parent_map', (b'quack/',
2790
b'include-missing:', r2),
2796
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2798
def test_allows_new_revisions(self):
2799
"""get_parent_map's results can be updated by commit."""
2800
smart_server = test_server.SmartTCPServer_for_testing()
2801
self.start_server(smart_server)
2802
self.make_branch('branch')
2803
branch = Branch.open(smart_server.get_url() + '/branch')
2804
tree = branch.create_checkout('tree', lightweight=True)
2806
self.addCleanup(tree.unlock)
2807
graph = tree.branch.repository.get_graph()
2808
# This provides an opportunity for the missing rev-id to be cached.
2809
self.assertEqual({}, graph.get_parent_map([b'rev1']))
2810
tree.commit('message', rev_id=b'rev1')
2811
graph = tree.branch.repository.get_graph()
2812
self.assertEqual({b'rev1': (b'null:',)},
2813
graph.get_parent_map([b'rev1']))
2816
class TestRepositoryGetRevisions(TestRemoteRepository):
2818
def test_hpss_missing_revision(self):
2819
transport_path = 'quack'
2820
repo, client = self.setup_fake_client_and_repository(transport_path)
2821
client.add_success_response_with_body(
2823
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2824
[b'somerev1', b'anotherrev2'])
2826
[('call_with_body_bytes_expecting_body',
2827
b'Repository.iter_revisions', (b'quack/', ),
2828
b"somerev1\nanotherrev2")],
2831
def test_hpss_get_single_revision(self):
2832
transport_path = 'quack'
2833
repo, client = self.setup_fake_client_and_repository(transport_path)
2834
somerev1 = Revision(b"somerev1")
2835
somerev1.committer = "Joe Committer <joe@example.com>"
2836
somerev1.timestamp = 1321828927
2837
somerev1.timezone = -60
2838
somerev1.inventory_sha1 = b"691b39be74c67b1212a75fcb19c433aaed903c2b"
2839
somerev1.message = "Message"
2840
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2842
# Split up body into two bits to make sure the zlib compression object
2843
# gets data fed twice.
2844
client.add_success_response_with_body(
2845
[body[:10], body[10:]], b'ok', b'10')
2846
revs = repo.get_revisions([b'somerev1'])
2847
self.assertEqual(revs, [somerev1])
2849
[('call_with_body_bytes_expecting_body',
2850
b'Repository.iter_revisions',
2851
(b'quack/', ), b"somerev1")],
2855
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2857
def test_null_revision(self):
2858
# a null revision has the predictable result {}, we should have no wire
2859
# traffic when calling it with this argument
2860
transport_path = 'empty'
2861
repo, client = self.setup_fake_client_and_repository(transport_path)
2862
client.add_success_response(b'notused')
2863
# actual RemoteRepository.get_revision_graph is gone, but there's an
2864
# equivalent private method for testing
2865
result = repo._get_revision_graph(NULL_REVISION)
2866
self.assertEqual([], client._calls)
2867
self.assertEqual({}, result)
2869
def test_none_revision(self):
2870
# with none we want the entire graph
2871
r1 = u'\u0e33'.encode('utf8')
2872
r2 = u'\u0dab'.encode('utf8')
2873
lines = [b' '.join([r2, r1]), r1]
2874
encoded_body = b'\n'.join(lines)
2876
transport_path = 'sinhala'
2877
repo, client = self.setup_fake_client_and_repository(transport_path)
2878
client.add_success_response_with_body(encoded_body, b'ok')
2879
# actual RemoteRepository.get_revision_graph is gone, but there's an
2880
# equivalent private method for testing
2881
result = repo._get_revision_graph(None)
2883
[('call_expecting_body', b'Repository.get_revision_graph',
2884
(b'sinhala/', b''))],
2886
self.assertEqual({r1: (), r2: (r1, )}, result)
2888
def test_specific_revision(self):
2889
# with a specific revision we want the graph for that
2890
# with none we want the entire graph
2891
r11 = u'\u0e33'.encode('utf8')
2892
r12 = u'\xc9'.encode('utf8')
2893
r2 = u'\u0dab'.encode('utf8')
2894
lines = [b' '.join([r2, r11, r12]), r11, r12]
2895
encoded_body = b'\n'.join(lines)
2897
transport_path = 'sinhala'
2898
repo, client = self.setup_fake_client_and_repository(transport_path)
2899
client.add_success_response_with_body(encoded_body, b'ok')
2900
result = repo._get_revision_graph(r2)
2902
[('call_expecting_body', b'Repository.get_revision_graph',
2903
(b'sinhala/', r2))],
2905
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2907
def test_no_such_revision(self):
2909
transport_path = 'sinhala'
2910
repo, client = self.setup_fake_client_and_repository(transport_path)
2911
client.add_error_response(b'nosuchrevision', revid)
2912
# also check that the right revision is reported in the error
2913
self.assertRaises(errors.NoSuchRevision,
2914
repo._get_revision_graph, revid)
2916
[('call_expecting_body', b'Repository.get_revision_graph',
2917
(b'sinhala/', revid))],
2920
def test_unexpected_error(self):
2922
transport_path = 'sinhala'
2923
repo, client = self.setup_fake_client_and_repository(transport_path)
2924
client.add_error_response(b'AnUnexpectedError')
2925
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2926
repo._get_revision_graph, revid)
2927
self.assertEqual((b'AnUnexpectedError',), e.error_tuple)
2930
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2933
repo, client = self.setup_fake_client_and_repository('quack')
2934
client.add_expected_call(
2935
b'Repository.get_rev_id_for_revno', (b'quack/',
2936
5, (42, b'rev-foo')),
2937
b'success', (b'ok', b'rev-five'))
2938
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2939
self.assertEqual((True, b'rev-five'), result)
2940
self.assertFinished(client)
2942
def test_history_incomplete(self):
2943
repo, client = self.setup_fake_client_and_repository('quack')
2944
client.add_expected_call(
2945
b'Repository.get_rev_id_for_revno', (b'quack/',
2946
5, (42, b'rev-foo')),
2947
b'success', (b'history-incomplete', 10, b'rev-ten'))
2948
result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
2949
self.assertEqual((False, (10, b'rev-ten')), result)
2950
self.assertFinished(client)
2952
def test_history_incomplete_with_fallback(self):
2953
"""A 'history-incomplete' response causes the fallback repository to be
2954
queried too, if one is set.
2956
# Make a repo with a fallback repo, both using a FakeClient.
2957
format = remote.response_tuple_to_repo_format(
2958
(b'yes', b'no', b'yes', self.get_repo_format().network_name()))
2959
repo, client = self.setup_fake_client_and_repository('quack')
2960
repo._format = format
2961
fallback_repo, ignored = self.setup_fake_client_and_repository(
2963
fallback_repo._client = client
2964
fallback_repo._format = format
2965
repo.add_fallback_repository(fallback_repo)
2966
# First the client should ask the primary repo
2967
client.add_expected_call(
2968
b'Repository.get_rev_id_for_revno', (b'quack/',
2969
1, (42, b'rev-foo')),
2970
b'success', (b'history-incomplete', 2, b'rev-two'))
2971
# Then it should ask the fallback, using revno/revid from the
2972
# history-incomplete response as the known revno/revid.
2973
client.add_expected_call(
2974
b'Repository.get_rev_id_for_revno', (
2975
b'fallback/', 1, (2, b'rev-two')),
2976
b'success', (b'ok', b'rev-one'))
2977
result = repo.get_rev_id_for_revno(1, (42, b'rev-foo'))
2978
self.assertEqual((True, b'rev-one'), result)
2979
self.assertFinished(client)
2981
def test_nosuchrevision(self):
2982
# 'nosuchrevision' is returned when the known-revid is not found in the
2983
# remote repo. The client translates that response to NoSuchRevision.
2984
repo, client = self.setup_fake_client_and_repository('quack')
2985
client.add_expected_call(
2986
b'Repository.get_rev_id_for_revno', (b'quack/',
2987
5, (42, b'rev-foo')),
2988
b'error', (b'nosuchrevision', b'rev-foo'))
2990
errors.NoSuchRevision,
2991
repo.get_rev_id_for_revno, 5, (42, b'rev-foo'))
2992
self.assertFinished(client)
2994
def test_branch_fallback_locking(self):
2995
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2996
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2997
will be invoked, which will fail if the repo is unlocked.
2999
self.setup_smart_server_with_call_log()
3000
tree = self.make_branch_and_memory_tree('.')
3003
rev1 = tree.commit('First')
3004
tree.commit('Second')
3006
branch = tree.branch
3007
self.assertFalse(branch.is_locked())
3008
self.reset_smart_call_log()
3009
verb = b'Repository.get_rev_id_for_revno'
3010
self.disable_verb(verb)
3011
self.assertEqual(rev1, branch.get_rev_id(1))
3012
self.assertLength(1, [call for call in self.hpss_calls if
3013
call.call.method == verb])
3016
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
3018
def test_has_signature_for_revision_id(self):
3019
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
3020
transport_path = 'quack'
3021
repo, client = self.setup_fake_client_and_repository(transport_path)
3022
client.add_success_response(b'yes')
3023
result = repo.has_signature_for_revision_id(b'A')
3025
[('call', b'Repository.has_signature_for_revision_id',
3026
(b'quack/', b'A'))],
3028
self.assertEqual(True, result)
3030
def test_is_not_shared(self):
3031
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
3032
transport_path = 'qwack'
3033
repo, client = self.setup_fake_client_and_repository(transport_path)
3034
client.add_success_response(b'no')
3035
result = repo.has_signature_for_revision_id(b'A')
3037
[('call', b'Repository.has_signature_for_revision_id',
3038
(b'qwack/', b'A'))],
3040
self.assertEqual(False, result)
3043
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
3045
def test_get_physical_lock_status_yes(self):
3046
transport_path = 'qwack'
3047
repo, client = self.setup_fake_client_and_repository(transport_path)
3048
client.add_success_response(b'yes')
3049
result = repo.get_physical_lock_status()
3051
[('call', b'Repository.get_physical_lock_status',
3054
self.assertEqual(True, result)
3056
def test_get_physical_lock_status_no(self):
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.get_physical_lock_status()
3062
[('call', b'Repository.get_physical_lock_status',
3065
self.assertEqual(False, result)
3068
class TestRepositoryIsShared(TestRemoteRepository):
3070
def test_is_shared(self):
3071
# ('yes', ) for Repository.is_shared -> 'True'.
3072
transport_path = 'quack'
3073
repo, client = self.setup_fake_client_and_repository(transport_path)
3074
client.add_success_response(b'yes')
3075
result = repo.is_shared()
3077
[('call', b'Repository.is_shared', (b'quack/',))],
3079
self.assertEqual(True, result)
3081
def test_is_not_shared(self):
3082
# ('no', ) for Repository.is_shared -> 'False'.
3083
transport_path = 'qwack'
3084
repo, client = self.setup_fake_client_and_repository(transport_path)
3085
client.add_success_response(b'no')
3086
result = repo.is_shared()
3088
[('call', b'Repository.is_shared', (b'qwack/',))],
3090
self.assertEqual(False, result)
3093
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3095
def test_make_working_trees(self):
3096
# ('yes', ) for Repository.make_working_trees -> '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.make_working_trees()
3102
[('call', b'Repository.make_working_trees', (b'quack/',))],
3104
self.assertEqual(True, result)
3106
def test_no_working_trees(self):
3107
# ('no', ) for Repository.make_working_trees -> '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.make_working_trees()
3113
[('call', b'Repository.make_working_trees', (b'qwack/',))],
3115
self.assertEqual(False, result)
3118
class TestRepositoryLockWrite(TestRemoteRepository):
3120
def test_lock_write(self):
3121
transport_path = 'quack'
3122
repo, client = self.setup_fake_client_and_repository(transport_path)
3123
client.add_success_response(b'ok', b'a token')
3124
token = repo.lock_write().repository_token
3126
[('call', b'Repository.lock_write', (b'quack/', b''))],
3128
self.assertEqual(b'a token', token)
3130
def test_lock_write_already_locked(self):
3131
transport_path = 'quack'
3132
repo, client = self.setup_fake_client_and_repository(transport_path)
3133
client.add_error_response(b'LockContention')
3134
self.assertRaises(errors.LockContention, repo.lock_write)
3136
[('call', b'Repository.lock_write', (b'quack/', b''))],
3139
def test_lock_write_unlockable(self):
3140
transport_path = 'quack'
3141
repo, client = self.setup_fake_client_and_repository(transport_path)
3142
client.add_error_response(b'UnlockableTransport')
3143
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3145
[('call', b'Repository.lock_write', (b'quack/', b''))],
3149
class TestRepositoryWriteGroups(TestRemoteRepository):
3151
def test_start_write_group(self):
3152
transport_path = 'quack'
3153
repo, client = self.setup_fake_client_and_repository(transport_path)
3154
client.add_expected_call(
3155
b'Repository.lock_write', (b'quack/', b''),
3156
b'success', (b'ok', b'a token'))
3157
client.add_expected_call(
3158
b'Repository.start_write_group', (b'quack/', b'a token'),
3159
b'success', (b'ok', (b'token1', )))
3161
repo.start_write_group()
3163
def test_start_write_group_unsuspendable(self):
3164
# Some repositories do not support suspending write
3165
# groups. For those, fall back to the "real" repository.
3166
transport_path = 'quack'
3167
repo, client = self.setup_fake_client_and_repository(transport_path)
3169
def stub_ensure_real():
3170
client._calls.append(('_ensure_real',))
3171
repo._real_repository = _StubRealPackRepository(client._calls)
3172
repo._ensure_real = stub_ensure_real
3173
client.add_expected_call(
3174
b'Repository.lock_write', (b'quack/', b''),
3175
b'success', (b'ok', b'a token'))
3176
client.add_expected_call(
3177
b'Repository.start_write_group', (b'quack/', b'a token'),
3178
b'error', (b'UnsuspendableWriteGroup',))
3180
repo.start_write_group()
3181
self.assertEqual(client._calls[-2:], [
3183
('start_write_group',)])
3185
def test_commit_write_group(self):
3186
transport_path = 'quack'
3187
repo, client = self.setup_fake_client_and_repository(transport_path)
3188
client.add_expected_call(
3189
b'Repository.lock_write', (b'quack/', b''),
3190
b'success', (b'ok', b'a token'))
3191
client.add_expected_call(
3192
b'Repository.start_write_group', (b'quack/', b'a token'),
3193
b'success', (b'ok', [b'token1']))
3194
client.add_expected_call(
3195
b'Repository.commit_write_group', (b'quack/',
3196
b'a token', [b'token1']),
3197
b'success', (b'ok',))
3199
repo.start_write_group()
3200
repo.commit_write_group()
3202
def test_abort_write_group(self):
3203
transport_path = 'quack'
3204
repo, client = self.setup_fake_client_and_repository(transport_path)
3205
client.add_expected_call(
3206
b'Repository.lock_write', (b'quack/', b''),
3207
b'success', (b'ok', b'a token'))
3208
client.add_expected_call(
3209
b'Repository.start_write_group', (b'quack/', b'a token'),
3210
b'success', (b'ok', [b'token1']))
3211
client.add_expected_call(
3212
b'Repository.abort_write_group', (b'quack/',
3213
b'a token', [b'token1']),
3214
b'success', (b'ok',))
3216
repo.start_write_group()
3217
repo.abort_write_group(False)
3219
def test_suspend_write_group(self):
3220
transport_path = 'quack'
3221
repo, client = self.setup_fake_client_and_repository(transport_path)
3222
self.assertEqual([], repo.suspend_write_group())
3224
def test_resume_write_group(self):
3225
transport_path = 'quack'
3226
repo, client = self.setup_fake_client_and_repository(transport_path)
3227
client.add_expected_call(
3228
b'Repository.lock_write', (b'quack/', b''),
3229
b'success', (b'ok', b'a token'))
3230
client.add_expected_call(
3231
b'Repository.check_write_group', (b'quack/',
3232
b'a token', [b'token1']),
3233
b'success', (b'ok',))
3235
repo.resume_write_group(['token1'])
3238
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3240
def test_backwards_compat(self):
3241
self.setup_smart_server_with_call_log()
3242
repo = self.make_repository('.')
3243
self.reset_smart_call_log()
3244
verb = b'Repository.set_make_working_trees'
3245
self.disable_verb(verb)
3246
repo.set_make_working_trees(True)
3247
call_count = len([call for call in self.hpss_calls if
3248
call.call.method == verb])
3249
self.assertEqual(1, call_count)
3251
def test_current(self):
3252
transport_path = 'quack'
3253
repo, client = self.setup_fake_client_and_repository(transport_path)
3254
client.add_expected_call(
3255
b'Repository.set_make_working_trees', (b'quack/', b'True'),
3256
b'success', (b'ok',))
3257
client.add_expected_call(
3258
b'Repository.set_make_working_trees', (b'quack/', b'False'),
3259
b'success', (b'ok',))
3260
repo.set_make_working_trees(True)
3261
repo.set_make_working_trees(False)
3264
class TestRepositoryUnlock(TestRemoteRepository):
3266
def test_unlock(self):
3267
transport_path = 'quack'
3268
repo, client = self.setup_fake_client_and_repository(transport_path)
3269
client.add_success_response(b'ok', b'a token')
3270
client.add_success_response(b'ok')
3274
[('call', b'Repository.lock_write', (b'quack/', b'')),
3275
('call', b'Repository.unlock', (b'quack/', b'a token'))],
3278
def test_unlock_wrong_token(self):
3279
# If somehow the token is wrong, unlock will raise TokenMismatch.
3280
transport_path = 'quack'
3281
repo, client = self.setup_fake_client_and_repository(transport_path)
3282
client.add_success_response(b'ok', b'a token')
3283
client.add_error_response(b'TokenMismatch')
3285
self.assertRaises(errors.TokenMismatch, repo.unlock)
3288
class TestRepositoryHasRevision(TestRemoteRepository):
3290
def test_none(self):
3291
# repo.has_revision(None) should not cause any traffic.
3292
transport_path = 'quack'
3293
repo, client = self.setup_fake_client_and_repository(transport_path)
3295
# The null revision is always there, so has_revision(None) == True.
3296
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3298
# The remote repo shouldn't be accessed.
3299
self.assertEqual([], client._calls)
3302
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3303
"""Test Repository.iter_file_bytes."""
3305
def test_single(self):
3306
transport_path = 'quack'
3307
repo, client = self.setup_fake_client_and_repository(transport_path)
3308
client.add_expected_call(
3309
b'Repository.iter_files_bytes', (b'quack/', ),
3310
b'success', (b'ok',), iter([b"ok\x000", b"\n", zlib.compress(b"mydata" * 10)]))
3311
for (identifier, byte_stream) in repo.iter_files_bytes([(b"somefile",
3312
b"somerev", b"myid")]):
3313
self.assertEqual(b"myid", identifier)
3314
self.assertEqual(b"".join(byte_stream), b"mydata" * 10)
3316
def test_missing(self):
3317
transport_path = 'quack'
3318
repo, client = self.setup_fake_client_and_repository(transport_path)
3319
client.add_expected_call(
3320
b'Repository.iter_files_bytes',
3322
b'error', (b'RevisionNotPresent', b'somefile', b'somerev'),
3323
iter([b"absent\0somefile\0somerev\n"]))
3324
self.assertRaises(errors.RevisionNotPresent, list,
3325
repo.iter_files_bytes(
3326
[(b"somefile", b"somerev", b"myid")]))
3329
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3330
"""Base class for Repository.insert_stream and .insert_stream_1.19
3334
def checkInsertEmptyStream(self, repo, client):
3335
"""Insert an empty stream, checking the result.
3337
This checks that there are no resume_tokens or missing_keys, and that
3338
the client is finished.
3340
sink = repo._get_sink()
3341
fmt = repository.format_registry.get_default()
3342
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3343
self.assertEqual([], resume_tokens)
3344
self.assertEqual(set(), missing_keys)
3345
self.assertFinished(client)
3348
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3349
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3352
This test case is very similar to TestRepositoryInsertStream_1_19.
3356
super(TestRepositoryInsertStream, self).setUp()
3357
self.disable_verb(b'Repository.insert_stream_1.19')
3359
def test_unlocked_repo(self):
3360
transport_path = 'quack'
3361
repo, client = self.setup_fake_client_and_repository(transport_path)
3362
client.add_expected_call(
3363
b'Repository.insert_stream_1.19', (b'quack/', b''),
3364
b'unknown', (b'Repository.insert_stream_1.19',))
3365
client.add_expected_call(
3366
b'Repository.insert_stream', (b'quack/', b''),
3367
b'success', (b'ok',))
3368
client.add_expected_call(
3369
b'Repository.insert_stream', (b'quack/', b''),
3370
b'success', (b'ok',))
3371
self.checkInsertEmptyStream(repo, client)
3373
def test_locked_repo_with_no_lock_token(self):
3374
transport_path = 'quack'
3375
repo, client = self.setup_fake_client_and_repository(transport_path)
3376
client.add_expected_call(
3377
b'Repository.lock_write', (b'quack/', b''),
3378
b'success', (b'ok', b''))
3379
client.add_expected_call(
3380
b'Repository.insert_stream_1.19', (b'quack/', b''),
3381
b'unknown', (b'Repository.insert_stream_1.19',))
3382
client.add_expected_call(
3383
b'Repository.insert_stream', (b'quack/', b''),
3384
b'success', (b'ok',))
3385
client.add_expected_call(
3386
b'Repository.insert_stream', (b'quack/', b''),
3387
b'success', (b'ok',))
3389
self.checkInsertEmptyStream(repo, client)
3391
def test_locked_repo_with_lock_token(self):
3392
transport_path = 'quack'
3393
repo, client = self.setup_fake_client_and_repository(transport_path)
3394
client.add_expected_call(
3395
b'Repository.lock_write', (b'quack/', b''),
3396
b'success', (b'ok', b'a token'))
3397
client.add_expected_call(
3398
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3399
b'unknown', (b'Repository.insert_stream_1.19',))
3400
client.add_expected_call(
3401
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3402
b'success', (b'ok',))
3403
client.add_expected_call(
3404
b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
3405
b'success', (b'ok',))
3407
self.checkInsertEmptyStream(repo, client)
3409
def test_stream_with_inventory_deltas(self):
3410
"""'inventory-deltas' substreams cannot be sent to the
3411
Repository.insert_stream verb, because not all servers that implement
3412
that verb will accept them. So when one is encountered the RemoteSink
3413
immediately stops using that verb and falls back to VFS insert_stream.
3415
transport_path = 'quack'
3416
repo, client = self.setup_fake_client_and_repository(transport_path)
3417
client.add_expected_call(
3418
b'Repository.insert_stream_1.19', (b'quack/', b''),
3419
b'unknown', (b'Repository.insert_stream_1.19',))
3420
client.add_expected_call(
3421
b'Repository.insert_stream', (b'quack/', b''),
3422
b'success', (b'ok',))
3423
client.add_expected_call(
3424
b'Repository.insert_stream', (b'quack/', b''),
3425
b'success', (b'ok',))
3426
# Create a fake real repository for insert_stream to fall back on, so
3427
# that we can directly see the records the RemoteSink passes to the
3434
def insert_stream(self, stream, src_format, resume_tokens):
3435
for substream_kind, substream in stream:
3436
self.records.append(
3437
(substream_kind, [record.key for record in substream]))
3438
return [b'fake tokens'], [b'fake missing keys']
3439
fake_real_sink = FakeRealSink()
3441
class FakeRealRepository:
3442
def _get_sink(self):
3443
return fake_real_sink
3445
def is_in_write_group(self):
3448
def refresh_data(self):
3450
repo._real_repository = FakeRealRepository()
3451
sink = repo._get_sink()
3452
fmt = repository.format_registry.get_default()
3453
stream = self.make_stream_with_inv_deltas(fmt)
3454
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3455
# Every record from the first inventory delta should have been sent to
3457
expected_records = [
3458
('inventory-deltas', [(b'rev2',), (b'rev3',)]),
3459
('texts', [(b'some-rev', b'some-file')])]
3460
self.assertEqual(expected_records, fake_real_sink.records)
3461
# The return values from the real sink's insert_stream are propagated
3462
# back to the original caller.
3463
self.assertEqual([b'fake tokens'], resume_tokens)
3464
self.assertEqual([b'fake missing keys'], missing_keys)
3465
self.assertFinished(client)
3467
def make_stream_with_inv_deltas(self, fmt):
3468
"""Make a simple stream with an inventory delta followed by more
3469
records and more substreams to test that all records and substreams
3470
from that point on are used.
3472
This sends, in order:
3473
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3475
* texts substream: (some-rev, some-file)
3477
# Define a stream using generators so that it isn't rewindable.
3478
inv = inventory.Inventory(revision_id=b'rev1')
3479
inv.root.revision = b'rev1'
3481
def stream_with_inv_delta():
3482
yield ('inventories', inventories_substream())
3483
yield ('inventory-deltas', inventory_delta_substream())
3485
versionedfile.FulltextContentFactory(
3486
(b'some-rev', b'some-file'), (), None, b'content')])
3488
def inventories_substream():
3489
# An empty inventory fulltext. This will be streamed normally.
3490
text = fmt._serializer.write_inventory_to_string(inv)
3491
yield versionedfile.FulltextContentFactory(
3492
(b'rev1',), (), None, text)
3494
def inventory_delta_substream():
3495
# An inventory delta. This can't be streamed via this verb, so it
3496
# will trigger a fallback to VFS insert_stream.
3497
entry = inv.make_entry(
3498
'directory', 'newdir', inv.root.file_id, b'newdir-id')
3499
entry.revision = b'ghost'
3500
delta = [(None, 'newdir', b'newdir-id', entry)]
3501
serializer = inventory_delta.InventoryDeltaSerializer(
3502
versioned_root=True, tree_references=False)
3503
lines = serializer.delta_to_lines(b'rev1', b'rev2', delta)
3504
yield versionedfile.ChunkedContentFactory(
3505
(b'rev2',), ((b'rev1',)), None, lines)
3507
lines = serializer.delta_to_lines(b'rev1', b'rev3', delta)
3508
yield versionedfile.ChunkedContentFactory(
3509
(b'rev3',), ((b'rev1',)), None, lines)
3510
return stream_with_inv_delta()
3513
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3515
def test_unlocked_repo(self):
3516
transport_path = 'quack'
3517
repo, client = self.setup_fake_client_and_repository(transport_path)
3518
client.add_expected_call(
3519
b'Repository.insert_stream_1.19', (b'quack/', b''),
3520
b'success', (b'ok',))
3521
client.add_expected_call(
3522
b'Repository.insert_stream_1.19', (b'quack/', b''),
3523
b'success', (b'ok',))
3524
self.checkInsertEmptyStream(repo, client)
3526
def test_locked_repo_with_no_lock_token(self):
3527
transport_path = 'quack'
3528
repo, client = self.setup_fake_client_and_repository(transport_path)
3529
client.add_expected_call(
3530
b'Repository.lock_write', (b'quack/', b''),
3531
b'success', (b'ok', b''))
3532
client.add_expected_call(
3533
b'Repository.insert_stream_1.19', (b'quack/', b''),
3534
b'success', (b'ok',))
3535
client.add_expected_call(
3536
b'Repository.insert_stream_1.19', (b'quack/', b''),
3537
b'success', (b'ok',))
3539
self.checkInsertEmptyStream(repo, client)
3541
def test_locked_repo_with_lock_token(self):
3542
transport_path = 'quack'
3543
repo, client = self.setup_fake_client_and_repository(transport_path)
3544
client.add_expected_call(
3545
b'Repository.lock_write', (b'quack/', b''),
3546
b'success', (b'ok', b'a token'))
3547
client.add_expected_call(
3548
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3549
b'success', (b'ok',))
3550
client.add_expected_call(
3551
b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
3552
b'success', (b'ok',))
3554
self.checkInsertEmptyStream(repo, client)
3557
class TestRepositoryTarball(TestRemoteRepository):
3559
# This is a canned tarball reponse we can validate against
3560
tarball_content = base64.b64decode(
3561
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3562
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3563
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3564
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3565
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3566
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3567
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3568
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3569
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3570
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3571
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3572
'nWQ7QH/F3JFOFCQ0aSPfA='
3575
def test_repository_tarball(self):
3576
# Test that Repository.tarball generates the right operations
3577
transport_path = 'repo'
3578
expected_calls = [('call_expecting_body', b'Repository.tarball',
3579
(b'repo/', b'bz2',),),
3581
repo, client = self.setup_fake_client_and_repository(transport_path)
3582
client.add_success_response_with_body(self.tarball_content, b'ok')
3583
# Now actually ask for the tarball
3584
tarball_file = repo._get_tarball('bz2')
3586
self.assertEqual(expected_calls, client._calls)
3587
self.assertEqual(self.tarball_content, tarball_file.read())
3589
tarball_file.close()
3592
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3593
"""RemoteRepository.copy_content_into optimizations"""
3595
def test_copy_content_remote_to_local(self):
3596
self.transport_server = test_server.SmartTCPServer_for_testing
3597
src_repo = self.make_repository('repo1')
3598
src_repo = repository.Repository.open(self.get_url('repo1'))
3599
# At the moment the tarball-based copy_content_into can't write back
3600
# into a smart server. It would be good if it could upload the
3601
# tarball; once that works we'd have to create repositories of
3602
# different formats. -- mbp 20070410
3603
dest_url = self.get_vfs_only_url('repo2')
3604
dest_bzrdir = BzrDir.create(dest_url)
3605
dest_repo = dest_bzrdir.create_repository()
3606
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3607
self.assertTrue(isinstance(src_repo, RemoteRepository))
3608
src_repo.copy_content_into(dest_repo)
3611
class _StubRealPackRepository(object):
3613
def __init__(self, calls):
3615
self._pack_collection = _StubPackCollection(calls)
3617
def start_write_group(self):
3618
self.calls.append(('start_write_group',))
3620
def is_in_write_group(self):
3623
def refresh_data(self):
3624
self.calls.append(('pack collection reload_pack_names',))
3627
class _StubPackCollection(object):
3629
def __init__(self, calls):
3633
self.calls.append(('pack collection autopack',))
3636
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3637
"""Tests for RemoteRepository.autopack implementation."""
3640
"""When the server returns 'ok' and there's no _real_repository, then
3641
nothing else happens: the autopack method is done.
3643
transport_path = 'quack'
3644
repo, client = self.setup_fake_client_and_repository(transport_path)
3645
client.add_expected_call(
3646
b'PackRepository.autopack', (b'quack/',), b'success', (b'ok',))
3648
self.assertFinished(client)
3650
def test_ok_with_real_repo(self):
3651
"""When the server returns 'ok' and there is a _real_repository, then
3652
the _real_repository's reload_pack_name's method will be called.
3654
transport_path = 'quack'
3655
repo, client = self.setup_fake_client_and_repository(transport_path)
3656
client.add_expected_call(
3657
b'PackRepository.autopack', (b'quack/',),
3658
b'success', (b'ok',))
3659
repo._real_repository = _StubRealPackRepository(client._calls)
3662
[('call', b'PackRepository.autopack', (b'quack/',)),
3663
('pack collection reload_pack_names',)],
3666
def test_backwards_compatibility(self):
3667
"""If the server does not recognise the PackRepository.autopack verb,
3668
fallback to the real_repository's implementation.
3670
transport_path = 'quack'
3671
repo, client = self.setup_fake_client_and_repository(transport_path)
3672
client.add_unknown_method_response(b'PackRepository.autopack')
3674
def stub_ensure_real():
3675
client._calls.append(('_ensure_real',))
3676
repo._real_repository = _StubRealPackRepository(client._calls)
3677
repo._ensure_real = stub_ensure_real
3680
[('call', b'PackRepository.autopack', (b'quack/',)),
3682
('pack collection autopack',)],
3685
def test_oom_error_reporting(self):
3686
"""An out-of-memory condition on the server is reported clearly"""
3687
transport_path = 'quack'
3688
repo, client = self.setup_fake_client_and_repository(transport_path)
3689
client.add_expected_call(
3690
b'PackRepository.autopack', (b'quack/',),
3691
b'error', (b'MemoryError',))
3692
err = self.assertRaises(errors.BzrError, repo.autopack)
3693
self.assertContainsRe(str(err), "^remote server out of mem")
3696
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3697
"""Base class for unit tests for breezy.bzr.remote._translate_error."""
3699
def translateTuple(self, error_tuple, **context):
3700
"""Call _translate_error with an ErrorFromSmartServer built from the
3703
:param error_tuple: A tuple of a smart server response, as would be
3704
passed to an ErrorFromSmartServer.
3705
:kwargs context: context items to call _translate_error with.
3707
:returns: The error raised by _translate_error.
3709
# Raise the ErrorFromSmartServer before passing it as an argument,
3710
# because _translate_error may need to re-raise it with a bare 'raise'
3712
server_error = errors.ErrorFromSmartServer(error_tuple)
3713
translated_error = self.translateErrorFromSmartServer(
3714
server_error, **context)
3715
return translated_error
3717
def translateErrorFromSmartServer(self, error_object, **context):
3718
"""Like translateTuple, but takes an already constructed
3719
ErrorFromSmartServer rather than a tuple.
3723
except errors.ErrorFromSmartServer as server_error:
3724
translated_error = self.assertRaises(
3725
errors.BzrError, remote._translate_error, server_error,
3727
return translated_error
3730
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3731
"""Unit tests for breezy.bzr.remote._translate_error.
3733
Given an ErrorFromSmartServer (which has an error tuple from a smart
3734
server) and some context, _translate_error raises more specific errors from
3737
This test case covers the cases where _translate_error succeeds in
3738
translating an ErrorFromSmartServer to something better. See
3739
TestErrorTranslationRobustness for other cases.
3742
def test_NoSuchRevision(self):
3743
branch = self.make_branch('')
3745
translated_error = self.translateTuple(
3746
(b'NoSuchRevision', revid), branch=branch)
3747
expected_error = errors.NoSuchRevision(branch, revid)
3748
self.assertEqual(expected_error, translated_error)
3750
def test_nosuchrevision(self):
3751
repository = self.make_repository('')
3753
translated_error = self.translateTuple(
3754
(b'nosuchrevision', revid), repository=repository)
3755
expected_error = errors.NoSuchRevision(repository, revid)
3756
self.assertEqual(expected_error, translated_error)
3758
def test_nobranch(self):
3759
bzrdir = self.make_controldir('')
3760
translated_error = self.translateTuple((b'nobranch',), bzrdir=bzrdir)
3761
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3762
self.assertEqual(expected_error, translated_error)
3764
def test_nobranch_one_arg(self):
3765
bzrdir = self.make_controldir('')
3766
translated_error = self.translateTuple(
3767
(b'nobranch', b'extra detail'), bzrdir=bzrdir)
3768
expected_error = errors.NotBranchError(
3769
path=bzrdir.root_transport.base,
3770
detail='extra detail')
3771
self.assertEqual(expected_error, translated_error)
3773
def test_norepository(self):
3774
bzrdir = self.make_controldir('')
3775
translated_error = self.translateTuple((b'norepository',),
3777
expected_error = errors.NoRepositoryPresent(bzrdir)
3778
self.assertEqual(expected_error, translated_error)
3780
def test_LockContention(self):
3781
translated_error = self.translateTuple((b'LockContention',))
3782
expected_error = errors.LockContention('(remote lock)')
3783
self.assertEqual(expected_error, translated_error)
3785
def test_UnlockableTransport(self):
3786
bzrdir = self.make_controldir('')
3787
translated_error = self.translateTuple(
3788
(b'UnlockableTransport',), bzrdir=bzrdir)
3789
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3790
self.assertEqual(expected_error, translated_error)
3792
def test_LockFailed(self):
3793
lock = 'str() of a server lock'
3794
why = 'str() of why'
3795
translated_error = self.translateTuple(
3796
(b'LockFailed', lock.encode('ascii'), why.encode('ascii')))
3797
expected_error = errors.LockFailed(lock, why)
3798
self.assertEqual(expected_error, translated_error)
3800
def test_TokenMismatch(self):
3801
token = 'a lock token'
3802
translated_error = self.translateTuple(
3803
(b'TokenMismatch',), token=token)
3804
expected_error = errors.TokenMismatch(token, '(remote token)')
3805
self.assertEqual(expected_error, translated_error)
3807
def test_Diverged(self):
3808
branch = self.make_branch('a')
3809
other_branch = self.make_branch('b')
3810
translated_error = self.translateTuple(
3811
(b'Diverged',), branch=branch, other_branch=other_branch)
3812
expected_error = errors.DivergedBranches(branch, other_branch)
3813
self.assertEqual(expected_error, translated_error)
3815
def test_NotStacked(self):
3816
branch = self.make_branch('')
3817
translated_error = self.translateTuple((b'NotStacked',), branch=branch)
3818
expected_error = errors.NotStacked(branch)
3819
self.assertEqual(expected_error, translated_error)
3821
def test_ReadError_no_args(self):
3823
translated_error = self.translateTuple((b'ReadError',), path=path)
3824
expected_error = errors.ReadError(path)
3825
self.assertEqual(expected_error, translated_error)
3827
def test_ReadError(self):
3829
translated_error = self.translateTuple(
3830
(b'ReadError', path.encode('utf-8')))
3831
expected_error = errors.ReadError(path)
3832
self.assertEqual(expected_error, translated_error)
3834
def test_IncompatibleRepositories(self):
3835
translated_error = self.translateTuple((b'IncompatibleRepositories',
3836
b"repo1", b"repo2", b"details here"))
3837
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3839
self.assertEqual(expected_error, translated_error)
3841
def test_GhostRevisionsHaveNoRevno(self):
3842
translated_error = self.translateTuple((b'GhostRevisionsHaveNoRevno',
3843
b"revid1", b"revid2"))
3844
expected_error = errors.GhostRevisionsHaveNoRevno(b"revid1", b"revid2")
3845
self.assertEqual(expected_error, translated_error)
3847
def test_PermissionDenied_no_args(self):
3849
translated_error = self.translateTuple((b'PermissionDenied',),
3851
expected_error = errors.PermissionDenied(path)
3852
self.assertEqual(expected_error, translated_error)
3854
def test_PermissionDenied_one_arg(self):
3856
translated_error = self.translateTuple(
3857
(b'PermissionDenied', path.encode('utf-8')))
3858
expected_error = errors.PermissionDenied(path)
3859
self.assertEqual(expected_error, translated_error)
3861
def test_PermissionDenied_one_arg_and_context(self):
3862
"""Given a choice between a path from the local context and a path on
3863
the wire, _translate_error prefers the path from the local context.
3865
local_path = 'local path'
3866
remote_path = 'remote path'
3867
translated_error = self.translateTuple(
3868
(b'PermissionDenied', remote_path.encode('utf-8')), path=local_path)
3869
expected_error = errors.PermissionDenied(local_path)
3870
self.assertEqual(expected_error, translated_error)
3872
def test_PermissionDenied_two_args(self):
3874
extra = 'a string with extra info'
3875
translated_error = self.translateTuple(
3876
(b'PermissionDenied', path.encode('utf-8'), extra.encode('utf-8')))
3877
expected_error = errors.PermissionDenied(path, extra)
3878
self.assertEqual(expected_error, translated_error)
3880
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3882
def test_NoSuchFile_context_path(self):
3883
local_path = "local path"
3884
translated_error = self.translateTuple((b'ReadError', b"remote path"),
3886
expected_error = errors.ReadError(local_path)
3887
self.assertEqual(expected_error, translated_error)
3889
def test_NoSuchFile_without_context(self):
3890
remote_path = "remote path"
3891
translated_error = self.translateTuple(
3892
(b'ReadError', remote_path.encode('utf-8')))
3893
expected_error = errors.ReadError(remote_path)
3894
self.assertEqual(expected_error, translated_error)
3896
def test_ReadOnlyError(self):
3897
translated_error = self.translateTuple((b'ReadOnlyError',))
3898
expected_error = errors.TransportNotPossible("readonly transport")
3899
self.assertEqual(expected_error, translated_error)
3901
def test_MemoryError(self):
3902
translated_error = self.translateTuple((b'MemoryError',))
3903
self.assertStartsWith(str(translated_error),
3904
"remote server out of memory")
3906
def test_generic_IndexError_no_classname(self):
3907
err = errors.ErrorFromSmartServer(
3908
(b'error', b"list index out of range"))
3909
translated_error = self.translateErrorFromSmartServer(err)
3910
expected_error = errors.UnknownErrorFromSmartServer(err)
3911
self.assertEqual(expected_error, translated_error)
3913
# GZ 2011-03-02: TODO test generic non-ascii error string
3915
def test_generic_KeyError(self):
3916
err = errors.ErrorFromSmartServer((b'error', b'KeyError', b"1"))
3917
translated_error = self.translateErrorFromSmartServer(err)
3918
expected_error = errors.UnknownErrorFromSmartServer(err)
3919
self.assertEqual(expected_error, translated_error)
3922
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3923
"""Unit tests for breezy.bzr.remote._translate_error's robustness.
3925
TestErrorTranslationSuccess is for cases where _translate_error can
3926
translate successfully. This class about how _translate_err behaves when
3927
it fails to translate: it re-raises the original error.
3930
def test_unrecognised_server_error(self):
3931
"""If the error code from the server is not recognised, the original
3932
ErrorFromSmartServer is propagated unmodified.
3934
error_tuple = (b'An unknown error tuple',)
3935
server_error = errors.ErrorFromSmartServer(error_tuple)
3936
translated_error = self.translateErrorFromSmartServer(server_error)
3937
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3938
self.assertEqual(expected_error, translated_error)
3940
def test_context_missing_a_key(self):
3941
"""In case of a bug in the client, or perhaps an unexpected response
3942
from a server, _translate_error returns the original error tuple from
3943
the server and mutters a warning.
3945
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3946
# in the context dict. So let's give it an empty context dict instead
3947
# to exercise its error recovery.
3948
error_tuple = (b'NoSuchRevision', b'revid')
3949
server_error = errors.ErrorFromSmartServer(error_tuple)
3950
translated_error = self.translateErrorFromSmartServer(server_error)
3951
self.assertEqual(server_error, translated_error)
3952
# In addition to re-raising ErrorFromSmartServer, some debug info has
3953
# been muttered to the log file for developer to look at.
3954
self.assertContainsRe(
3956
"Missing key 'branch' in context")
3958
def test_path_missing(self):
3959
"""Some translations (PermissionDenied, ReadError) can determine the
3960
'path' variable from either the wire or the local context. If neither
3961
has it, then an error is raised.
3963
error_tuple = (b'ReadError',)
3964
server_error = errors.ErrorFromSmartServer(error_tuple)
3965
translated_error = self.translateErrorFromSmartServer(server_error)
3966
self.assertEqual(server_error, translated_error)
3967
# In addition to re-raising ErrorFromSmartServer, some debug info has
3968
# been muttered to the log file for developer to look at.
3969
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3972
class TestStacking(tests.TestCaseWithTransport):
3973
"""Tests for operations on stacked remote repositories.
3975
The underlying format type must support stacking.
3978
def test_access_stacked_remote(self):
3979
# based on <http://launchpad.net/bugs/261315>
3980
# make a branch stacked on another repository containing an empty
3981
# revision, then open it over hpss - we should be able to see that
3983
base_builder = self.make_branch_builder('base', format='1.9')
3984
base_builder.start_series()
3985
base_revid = base_builder.build_snapshot(None,
3986
[('add', ('', None, 'directory', None))],
3987
'message', revision_id=b'rev-id')
3988
base_builder.finish_series()
3989
stacked_branch = self.make_branch('stacked', format='1.9')
3990
stacked_branch.set_stacked_on_url('../base')
3991
# start a server looking at this
3992
smart_server = test_server.SmartTCPServer_for_testing()
3993
self.start_server(smart_server)
3994
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3995
# can get its branch and repository
3996
remote_branch = remote_bzrdir.open_branch()
3997
remote_repo = remote_branch.repository
3998
remote_repo.lock_read()
4000
# it should have an appropriate fallback repository, which should also
4001
# be a RemoteRepository
4002
self.assertLength(1, remote_repo._fallback_repositories)
4003
self.assertIsInstance(remote_repo._fallback_repositories[0],
4005
# and it has the revision committed to the underlying repository;
4006
# these have varying implementations so we try several of them
4007
self.assertTrue(remote_repo.has_revisions([base_revid]))
4008
self.assertTrue(remote_repo.has_revision(base_revid))
4009
self.assertEqual(remote_repo.get_revision(base_revid).message,
4012
remote_repo.unlock()
4014
def prepare_stacked_remote_branch(self):
4015
"""Get stacked_upon and stacked branches with content in each."""
4016
self.setup_smart_server_with_call_log()
4017
tree1 = self.make_branch_and_tree('tree1', format='1.9')
4018
tree1.commit('rev1', rev_id=b'rev1')
4019
tree2 = tree1.branch.controldir.sprout('tree2', stacked=True
4020
).open_workingtree()
4021
local_tree = tree2.branch.create_checkout('local')
4022
local_tree.commit('local changes make me feel good.')
4023
branch2 = Branch.open(self.get_url('tree2'))
4025
self.addCleanup(branch2.unlock)
4026
return tree1.branch, branch2
4028
def test_stacked_get_parent_map(self):
4029
# the public implementation of get_parent_map obeys stacking
4030
_, branch = self.prepare_stacked_remote_branch()
4031
repo = branch.repository
4032
self.assertEqual({b'rev1'}, set(repo.get_parent_map([b'rev1'])))
4034
def test_unstacked_get_parent_map(self):
4035
# _unstacked_provider.get_parent_map ignores stacking
4036
_, branch = self.prepare_stacked_remote_branch()
4037
provider = branch.repository._unstacked_provider
4038
self.assertEqual(set(), set(provider.get_parent_map([b'rev1'])))
4040
def fetch_stream_to_rev_order(self, stream):
4042
for kind, substream in stream:
4043
if not kind == 'revisions':
4046
for content in substream:
4047
result.append(content.key[-1])
4050
def get_ordered_revs(self, format, order, branch_factory=None):
4051
"""Get a list of the revisions in a stream to format format.
4053
:param format: The format of the target.
4054
:param order: the order that target should have requested.
4055
:param branch_factory: A callable to create a trunk and stacked branch
4056
to fetch from. If none, self.prepare_stacked_remote_branch is used.
4057
:result: The revision ids in the stream, in the order seen,
4058
the topological order of revisions in the source.
4060
unordered_format = controldir.format_registry.get(format)()
4061
target_repository_format = unordered_format.repository_format
4063
self.assertEqual(order, target_repository_format._fetch_order)
4064
if branch_factory is None:
4065
branch_factory = self.prepare_stacked_remote_branch
4066
_, stacked = branch_factory()
4067
source = stacked.repository._get_source(target_repository_format)
4068
tip = stacked.last_revision()
4069
stacked.repository._ensure_real()
4070
graph = stacked.repository.get_graph()
4071
revs = [r for (r, ps) in graph.iter_ancestry([tip])
4072
if r != NULL_REVISION]
4074
search = vf_search.PendingAncestryResult([tip], stacked.repository)
4075
self.reset_smart_call_log()
4076
stream = source.get_stream(search)
4077
# We trust that if a revision is in the stream the rest of the new
4078
# content for it is too, as per our main fetch tests; here we are
4079
# checking that the revisions are actually included at all, and their
4081
return self.fetch_stream_to_rev_order(stream), revs
4083
def test_stacked_get_stream_unordered(self):
4084
# Repository._get_source.get_stream() from a stacked repository with
4085
# unordered yields the full data from both stacked and stacked upon
4087
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
4088
self.assertEqual(set(expected_revs), set(rev_ord))
4089
# Getting unordered results should have made a streaming data request
4090
# from the server, then one from the backing branch.
4091
self.assertLength(2, self.hpss_calls)
4093
def test_stacked_on_stacked_get_stream_unordered(self):
4094
# Repository._get_source.get_stream() from a stacked repository which
4095
# is itself stacked yields the full data from all three sources.
4096
def make_stacked_stacked():
4097
_, stacked = self.prepare_stacked_remote_branch()
4098
tree = stacked.controldir.sprout('tree3', stacked=True
4099
).open_workingtree()
4100
local_tree = tree.branch.create_checkout('local-tree3')
4101
local_tree.commit('more local changes are better')
4102
branch = Branch.open(self.get_url('tree3'))
4104
self.addCleanup(branch.unlock)
4106
rev_ord, expected_revs = self.get_ordered_revs(
4107
'1.9', 'unordered', branch_factory=make_stacked_stacked)
4108
self.assertEqual(set(expected_revs), set(rev_ord))
4109
# Getting unordered results should have made a streaming data request
4110
# from the server, and one from each backing repo
4111
self.assertLength(3, self.hpss_calls)
4113
def test_stacked_get_stream_topological(self):
4114
# Repository._get_source.get_stream() from a stacked repository with
4115
# topological sorting yields the full data from both stacked and
4116
# stacked upon sources in topological order.
4117
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4118
self.assertEqual(expected_revs, rev_ord)
4119
# Getting topological sort requires VFS calls still - one of which is
4120
# pushing up from the bound branch.
4121
self.assertLength(14, self.hpss_calls)
4123
def test_stacked_get_stream_groupcompress(self):
4124
# Repository._get_source.get_stream() from a stacked repository with
4125
# groupcompress sorting yields the full data from both stacked and
4126
# stacked upon sources in groupcompress order.
4127
raise tests.TestSkipped('No groupcompress ordered format available')
4128
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4129
self.assertEqual(expected_revs, reversed(rev_ord))
4130
# Getting unordered results should have made a streaming data request
4131
# from the backing branch, and one from the stacked on branch.
4132
self.assertLength(2, self.hpss_calls)
4134
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4135
# When pulling some fixed amount of content that is more than the
4136
# source has (because some is coming from a fallback branch, no error
4137
# should be received. This was reported as bug 360791.
4138
# Need three branches: a trunk, a stacked branch, and a preexisting
4139
# branch pulling content from stacked and trunk.
4140
self.setup_smart_server_with_call_log()
4141
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4142
trunk.commit('start')
4143
stacked_branch = trunk.branch.create_clone_on_transport(
4144
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4145
local = self.make_branch('local', format='1.9-rich-root')
4146
local.repository.fetch(stacked_branch.repository,
4147
stacked_branch.last_revision())
4150
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4153
super(TestRemoteBranchEffort, self).setUp()
4154
# Create a smart server that publishes whatever the backing VFS server
4156
self.smart_server = test_server.SmartTCPServer_for_testing()
4157
self.start_server(self.smart_server, self.get_server())
4158
# Log all HPSS calls into self.hpss_calls.
4159
_SmartClient.hooks.install_named_hook(
4160
'call', self.capture_hpss_call, None)
4161
self.hpss_calls = []
4163
def capture_hpss_call(self, params):
4164
self.hpss_calls.append(params.method)
4166
def test_copy_content_into_avoids_revision_history(self):
4167
local = self.make_branch('local')
4168
builder = self.make_branch_builder('remote')
4169
builder.build_commit(message="Commit.")
4170
remote_branch_url = self.smart_server.get_url() + 'remote'
4171
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4172
local.repository.fetch(remote_branch.repository)
4173
self.hpss_calls = []
4174
remote_branch.copy_content_into(local)
4175
self.assertFalse(b'Branch.revision_history' in self.hpss_calls)
4177
def test_fetch_everything_needs_just_one_call(self):
4178
local = self.make_branch('local')
4179
builder = self.make_branch_builder('remote')
4180
builder.build_commit(message="Commit.")
4181
remote_branch_url = self.smart_server.get_url() + 'remote'
4182
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4183
self.hpss_calls = []
4184
local.repository.fetch(
4185
remote_branch.repository,
4186
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4187
self.assertEqual([b'Repository.get_stream_1.19'], self.hpss_calls)
4189
def override_verb(self, verb_name, verb):
4190
request_handlers = request.request_handlers
4191
orig_verb = request_handlers.get(verb_name)
4192
orig_info = request_handlers.get_info(verb_name)
4193
request_handlers.register(verb_name, verb, override_existing=True)
4194
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4195
override_existing=True, info=orig_info)
4197
def test_fetch_everything_backwards_compat(self):
4198
"""Can fetch with EverythingResult even with pre 2.4 servers.
4200
Pre-2.4 do not support 'everything' searches with the
4201
Repository.get_stream_1.19 verb.
4205
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4206
"""A version of the Repository.get_stream_1.19 verb patched to
4207
reject 'everything' searches the way 2.3 and earlier do.
4210
def recreate_search(self, repository, search_bytes,
4211
discard_excess=False):
4212
verb_log.append(search_bytes.split(b'\n', 1)[0])
4213
if search_bytes == b'everything':
4215
request.FailedSmartServerResponse((b'BadSearch',)))
4216
return super(OldGetStreamVerb,
4217
self).recreate_search(repository, search_bytes,
4218
discard_excess=discard_excess)
4219
self.override_verb(b'Repository.get_stream_1.19', OldGetStreamVerb)
4220
local = self.make_branch('local')
4221
builder = self.make_branch_builder('remote')
4222
builder.build_commit(message="Commit.")
4223
remote_branch_url = self.smart_server.get_url() + 'remote'
4224
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4225
self.hpss_calls = []
4226
local.repository.fetch(
4227
remote_branch.repository,
4228
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4229
# make sure the overridden verb was used
4230
self.assertLength(1, verb_log)
4231
# more than one HPSS call is needed, but because it's a VFS callback
4232
# its hard to predict exactly how many.
4233
self.assertTrue(len(self.hpss_calls) > 1)
4236
class TestUpdateBoundBranchWithModifiedBoundLocation(
4237
tests.TestCaseWithTransport):
4238
"""Ensure correct handling of bound_location modifications.
4240
This is tested against a smart server as http://pad.lv/786980 was about a
4241
ReadOnlyError (write attempt during a read-only transaction) which can only
4242
happen in this context.
4246
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4247
self.transport_server = test_server.SmartTCPServer_for_testing
4249
def make_master_and_checkout(self, master_name, checkout_name):
4250
# Create the master branch and its associated checkout
4251
self.master = self.make_branch_and_tree(master_name)
4252
self.checkout = self.master.branch.create_checkout(checkout_name)
4253
# Modify the master branch so there is something to update
4254
self.master.commit('add stuff')
4255
self.last_revid = self.master.commit('even more stuff')
4256
self.bound_location = self.checkout.branch.get_bound_location()
4258
def assertUpdateSucceeds(self, new_location):
4259
self.checkout.branch.set_bound_location(new_location)
4260
self.checkout.update()
4261
self.assertEqual(self.last_revid, self.checkout.last_revision())
4263
def test_without_final_slash(self):
4264
self.make_master_and_checkout('master', 'checkout')
4265
# For unclear reasons some users have a bound_location without a final
4266
# '/', simulate that by forcing such a value
4267
self.assertEndsWith(self.bound_location, '/')
4268
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4270
def test_plus_sign(self):
4271
self.make_master_and_checkout('+master', 'checkout')
4272
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4274
def test_tilda(self):
4275
# Embed ~ in the middle of the path just to avoid any $HOME
4277
self.make_master_and_checkout('mas~ter', 'checkout')
4278
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4281
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4283
def test_no_context(self):
4284
class OutOfCoffee(errors.BzrError):
4285
"""A dummy exception for testing."""
4287
def __init__(self, urgency):
4288
self.urgency = urgency
4289
remote.no_context_error_translators.register(b"OutOfCoffee",
4290
lambda err: OutOfCoffee(err.error_args[0]))
4291
transport = MemoryTransport()
4292
client = FakeClient(transport.base)
4293
client.add_expected_call(
4294
b'Branch.get_stacked_on_url', (b'quack/',),
4295
b'error', (b'NotStacked',))
4296
client.add_expected_call(
4297
b'Branch.last_revision_info',
4299
b'error', (b'OutOfCoffee', b'low'))
4300
transport.mkdir('quack')
4301
transport = transport.clone('quack')
4302
branch = self.make_remote_branch(transport, client)
4303
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4304
self.assertFinished(client)
4306
def test_with_context(self):
4307
class OutOfTea(errors.BzrError):
4308
def __init__(self, branch, urgency):
4309
self.branch = branch
4310
self.urgency = urgency
4311
remote.error_translators.register(b"OutOfTea",
4312
lambda err, find, path: OutOfTea(
4313
err.error_args[0].decode(
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'OutOfTea', b'low'))
4325
transport.mkdir('quack')
4326
transport = transport.clone('quack')
4327
branch = self.make_remote_branch(transport, client)
4328
self.assertRaises(OutOfTea, branch.last_revision_info)
4329
self.assertFinished(client)
4332
class TestRepositoryPack(TestRemoteRepository):
4334
def test_pack(self):
4335
transport_path = 'quack'
4336
repo, client = self.setup_fake_client_and_repository(transport_path)
4337
client.add_expected_call(
4338
b'Repository.lock_write', (b'quack/', b''),
4339
b'success', (b'ok', b'token'))
4340
client.add_expected_call(
4341
b'Repository.pack', (b'quack/', b'token', b'False'),
4342
b'success', (b'ok',), )
4343
client.add_expected_call(
4344
b'Repository.unlock', (b'quack/', b'token'),
4345
b'success', (b'ok', ))
4348
def test_pack_with_hint(self):
4349
transport_path = 'quack'
4350
repo, client = self.setup_fake_client_and_repository(transport_path)
4351
client.add_expected_call(
4352
b'Repository.lock_write', (b'quack/', b''),
4353
b'success', (b'ok', b'token'))
4354
client.add_expected_call(
4355
b'Repository.pack', (b'quack/', b'token', b'False'),
4356
b'success', (b'ok',), )
4357
client.add_expected_call(
4358
b'Repository.unlock', (b'quack/', b'token', b'False'),
4359
b'success', (b'ok', ))
4360
repo.pack(['hinta', 'hintb'])
4363
class TestRepositoryIterInventories(TestRemoteRepository):
4364
"""Test Repository.iter_inventories."""
4366
def _serialize_inv_delta(self, old_name, new_name, delta):
4367
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4368
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4370
def test_single_empty(self):
4371
transport_path = 'quack'
4372
repo, client = self.setup_fake_client_and_repository(transport_path)
4373
fmt = controldir.format_registry.get('2a')().repository_format
4375
stream = [('inventory-deltas', [
4376
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4377
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4378
client.add_expected_call(
4379
b'VersionedFileRepository.get_inventories', (
4380
b'quack/', b'unordered'),
4381
b'success', (b'ok', ),
4382
_stream_to_byte_stream(stream, fmt))
4383
ret = list(repo.iter_inventories([b"somerevid"]))
4384
self.assertLength(1, ret)
4386
self.assertEqual(b"somerevid", inv.revision_id)
4388
def test_empty(self):
4389
transport_path = 'quack'
4390
repo, client = self.setup_fake_client_and_repository(transport_path)
4391
ret = list(repo.iter_inventories([]))
4392
self.assertEqual(ret, [])
4394
def test_missing(self):
4395
transport_path = 'quack'
4396
repo, client = self.setup_fake_client_and_repository(transport_path)
4397
client.add_expected_call(
4398
b'VersionedFileRepository.get_inventories', (
4399
b'quack/', b'unordered'),
4400
b'success', (b'ok', ), iter([]))
4401
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(
4405
class TestRepositoryRevisionTreeArchive(TestRemoteRepository):
4406
"""Test Repository.iter_inventories."""
4408
def _serialize_inv_delta(self, old_name, new_name, delta):
4409
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4410
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4412
def test_simple(self):
4413
transport_path = 'quack'
4414
repo, client = self.setup_fake_client_and_repository(transport_path)
4415
fmt = controldir.format_registry.get('2a')().repository_format
4417
stream = [('inventory-deltas', [
4418
versionedfile.FulltextContentFactory(b'somerevid', None, None,
4419
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4420
client.add_expected_call(
4421
b'VersionedFileRepository.get_inventories', (
4422
b'quack/', b'unordered'),
4423
b'success', (b'ok', ),
4424
_stream_to_byte_stream(stream, fmt))
4426
with tarfile.open(mode='w', fileobj=f) as tf:
4427
info = tarfile.TarInfo('somefile')
4429
contents = b'some data'
4430
info.type = tarfile.REGTYPE
4432
info.size = len(contents)
4433
tf.addfile(info, BytesIO(contents))
4434
client.add_expected_call(
4435
b'Repository.revision_archive', (b'quack/',
4436
b'somerevid', b'tar', b'foo.tar', b'', b'', None),
4437
b'success', (b'ok', ),
4439
tree = repo.revision_tree(b'somerevid')
4440
self.assertEqual(f.getvalue(), b''.join(
4441
tree.archive('tar', 'foo.tar')))
4444
class TestRepositoryAnnotate(TestRemoteRepository):
4445
"""Test RemoteRevisionTree.annotate.."""
4447
def _serialize_inv_delta(self, old_name, new_name, delta):
4448
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4449
return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
4451
def test_simple(self):
4452
transport_path = 'quack'
4453
repo, client = self.setup_fake_client_and_repository(transport_path)
4454
fmt = controldir.format_registry.get('2a')().repository_format
4457
('inventory-deltas', [
4458
versionedfile.FulltextContentFactory(
4459
b'somerevid', None, None,
4460
self._serialize_inv_delta(b'null:', b'somerevid', []))])]
4461
client.add_expected_call(
4462
b'VersionedFileRepository.get_inventories', (
4463
b'quack/', b'unordered'),
4464
b'success', (b'ok', ),
4465
_stream_to_byte_stream(stream, fmt))
4466
client.add_expected_call(
4467
b'Repository.annotate_file_revision',
4468
(b'quack/', b'somerevid', b'filename', b'', b'current:'),
4469
b'success', (b'ok', ),
4470
bencode.bencode([[b'baserevid', b'line 1\n'],
4471
[b'somerevid', b'line2\n']]))
4472
tree = repo.revision_tree(b'somerevid')
4474
(b'baserevid', b'line 1\n'),
4475
(b'somerevid', b'line2\n')],
4476
list(tree.annotate_iter('filename')))