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.
46
from ..branch import Branch
47
from ..bzrdir import (
52
from ..chk_serializer import chk_bencode_serializer
53
from ..remote import (
59
RemoteRepositoryFormat,
61
from ..repofmt import groupcompress_repo, knitpack_repo
62
from ..revision import (
66
from ..sixish import (
69
from ..smart import medium, request
70
from ..smart.client import _SmartClient
71
from ..smart.repository import (
72
SmartServerRepositoryGetParentMap,
73
SmartServerRepositoryGetStream_1_19,
74
_stream_to_byte_stream,
79
from .scenarios import load_tests_apply_scenarios
80
from ..transport.memory import MemoryTransport
81
from ..transport.remote import (
88
load_tests = load_tests_apply_scenarios
91
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
95
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
97
{'transport_server': test_server.SmartTCPServer_for_testing})]
101
super(BasicRemoteObjectTests, self).setUp()
102
self.transport = self.get_transport()
103
# make a branch that can be opened over the smart transport
104
self.local_wt = BzrDir.create_standalone_workingtree('.')
105
self.addCleanup(self.transport.disconnect)
107
def test_create_remote_bzrdir(self):
108
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
109
self.assertIsInstance(b, BzrDir)
111
def test_open_remote_branch(self):
112
# open a standalone branch in the working directory
113
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
114
branch = b.open_branch()
115
self.assertIsInstance(branch, Branch)
117
def test_remote_repository(self):
118
b = BzrDir.open_from_transport(self.transport)
119
repo = b.open_repository()
120
revid = u'\xc823123123'.encode('utf8')
121
self.assertFalse(repo.has_revision(revid))
122
self.local_wt.commit(message='test commit', rev_id=revid)
123
self.assertTrue(repo.has_revision(revid))
125
def test_find_correct_format(self):
126
"""Should open a RemoteBzrDir over a RemoteTransport"""
127
fmt = BzrDirFormat.find_format(self.transport)
128
self.assertTrue(bzrdir.RemoteBzrProber
129
in controldir.ControlDirFormat._server_probers)
130
self.assertIsInstance(fmt, RemoteBzrDirFormat)
132
def test_open_detected_smart_format(self):
133
fmt = BzrDirFormat.find_format(self.transport)
134
d = fmt.open(self.transport)
135
self.assertIsInstance(d, BzrDir)
137
def test_remote_branch_repr(self):
138
b = BzrDir.open_from_transport(self.transport).open_branch()
139
self.assertStartsWith(str(b), 'RemoteBranch(')
141
def test_remote_bzrdir_repr(self):
142
b = BzrDir.open_from_transport(self.transport)
143
self.assertStartsWith(str(b), 'RemoteBzrDir(')
145
def test_remote_branch_format_supports_stacking(self):
147
self.make_branch('unstackable', format='pack-0.92')
148
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
149
self.assertFalse(b._format.supports_stacking())
150
self.make_branch('stackable', format='1.9')
151
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
152
self.assertTrue(b._format.supports_stacking())
154
def test_remote_repo_format_supports_external_references(self):
156
bd = self.make_bzrdir('unstackable', format='pack-0.92')
157
r = bd.create_repository()
158
self.assertFalse(r._format.supports_external_lookups)
159
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
160
self.assertFalse(r._format.supports_external_lookups)
161
bd = self.make_bzrdir('stackable', format='1.9')
162
r = bd.create_repository()
163
self.assertTrue(r._format.supports_external_lookups)
164
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
165
self.assertTrue(r._format.supports_external_lookups)
167
def test_remote_branch_set_append_revisions_only(self):
168
# Make a format 1.9 branch, which supports append_revisions_only
169
branch = self.make_branch('branch', format='1.9')
170
branch.set_append_revisions_only(True)
171
config = branch.get_config_stack()
173
True, config.get('append_revisions_only'))
174
branch.set_append_revisions_only(False)
175
config = branch.get_config_stack()
177
False, config.get('append_revisions_only'))
179
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
180
branch = self.make_branch('branch', format='knit')
182
errors.UpgradeRequired, branch.set_append_revisions_only, True)
185
class FakeProtocol(object):
186
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
188
def __init__(self, body, fake_client):
190
self._body_buffer = None
191
self._fake_client = fake_client
193
def read_body_bytes(self, count=-1):
194
if self._body_buffer is None:
195
self._body_buffer = BytesIO(self.body)
196
bytes = self._body_buffer.read(count)
197
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
198
self._fake_client.expecting_body = False
201
def cancel_read_body(self):
202
self._fake_client.expecting_body = False
204
def read_streamed_body(self):
208
class FakeClient(_SmartClient):
209
"""Lookalike for _SmartClient allowing testing."""
211
def __init__(self, fake_medium_base='fake base'):
212
"""Create a FakeClient."""
215
self.expecting_body = False
216
# if non-None, this is the list of expected calls, with only the
217
# method name and arguments included. the body might be hard to
218
# compute so is not included. If a call is None, that call can
220
self._expected_calls = None
221
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
223
def add_expected_call(self, call_name, call_args, response_type,
224
response_args, response_body=None):
225
if self._expected_calls is None:
226
self._expected_calls = []
227
self._expected_calls.append((call_name, call_args))
228
self.responses.append((response_type, response_args, response_body))
230
def add_success_response(self, *args):
231
self.responses.append(('success', args, None))
233
def add_success_response_with_body(self, body, *args):
234
self.responses.append(('success', args, body))
235
if self._expected_calls is not None:
236
self._expected_calls.append(None)
238
def add_error_response(self, *args):
239
self.responses.append(('error', args))
241
def add_unknown_method_response(self, verb):
242
self.responses.append(('unknown', verb))
244
def finished_test(self):
245
if self._expected_calls:
246
raise AssertionError("%r finished but was still expecting %r"
247
% (self, self._expected_calls[0]))
249
def _get_next_response(self):
251
response_tuple = self.responses.pop(0)
252
except IndexError as e:
253
raise AssertionError("%r didn't expect any more calls"
255
if response_tuple[0] == 'unknown':
256
raise errors.UnknownSmartMethod(response_tuple[1])
257
elif response_tuple[0] == 'error':
258
raise errors.ErrorFromSmartServer(response_tuple[1])
259
return response_tuple
261
def _check_call(self, method, args):
262
if self._expected_calls is None:
263
# the test should be updated to say what it expects
266
next_call = self._expected_calls.pop(0)
268
raise AssertionError("%r didn't expect any more calls "
270
% (self, method, args,))
271
if next_call is None:
273
if method != next_call[0] or args != next_call[1]:
274
raise AssertionError("%r expected %r%r "
276
% (self, next_call[0], next_call[1], method, args,))
278
def call(self, method, *args):
279
self._check_call(method, args)
280
self._calls.append(('call', method, args))
281
return self._get_next_response()[1]
283
def call_expecting_body(self, method, *args):
284
self._check_call(method, args)
285
self._calls.append(('call_expecting_body', method, args))
286
result = self._get_next_response()
287
self.expecting_body = True
288
return result[1], FakeProtocol(result[2], self)
290
def call_with_body_bytes(self, method, args, body):
291
self._check_call(method, args)
292
self._calls.append(('call_with_body_bytes', method, args, body))
293
result = self._get_next_response()
294
return result[1], FakeProtocol(result[2], self)
296
def call_with_body_bytes_expecting_body(self, method, args, body):
297
self._check_call(method, args)
298
self._calls.append(('call_with_body_bytes_expecting_body', method,
300
result = self._get_next_response()
301
self.expecting_body = True
302
return result[1], FakeProtocol(result[2], self)
304
def call_with_body_stream(self, args, stream):
305
# Explicitly consume the stream before checking for an error, because
306
# that's what happens a real medium.
307
stream = list(stream)
308
self._check_call(args[0], args[1:])
309
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
310
result = self._get_next_response()
311
# The second value returned from call_with_body_stream is supposed to
312
# be a response_handler object, but so far no tests depend on that.
313
response_handler = None
314
return result[1], response_handler
317
class FakeMedium(medium.SmartClientMedium):
319
def __init__(self, client_calls, base):
320
medium.SmartClientMedium.__init__(self, base)
321
self._client_calls = client_calls
323
def disconnect(self):
324
self._client_calls.append(('disconnect medium',))
327
class TestVfsHas(tests.TestCase):
329
def test_unicode_path(self):
330
client = FakeClient('/')
331
client.add_success_response('yes',)
332
transport = RemoteTransport('bzr://localhost/', _client=client)
333
filename = u'/hell\u00d8'.encode('utf8')
334
result = transport.has(filename)
336
[('call', 'has', (filename,))],
338
self.assertTrue(result)
341
class TestRemote(tests.TestCaseWithMemoryTransport):
343
def get_branch_format(self):
344
reference_bzrdir_format = controldir.format_registry.get('default')()
345
return reference_bzrdir_format.get_branch_format()
347
def get_repo_format(self):
348
reference_bzrdir_format = controldir.format_registry.get('default')()
349
return reference_bzrdir_format.repository_format
351
def assertFinished(self, fake_client):
352
"""Assert that all of a FakeClient's expected calls have occurred."""
353
fake_client.finished_test()
356
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
357
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
359
def assertRemotePath(self, expected, client_base, transport_base):
360
"""Assert that the result of
361
SmartClientMedium.remote_path_from_transport is the expected value for
362
a given client_base and transport_base.
364
client_medium = medium.SmartClientMedium(client_base)
365
t = transport.get_transport(transport_base)
366
result = client_medium.remote_path_from_transport(t)
367
self.assertEqual(expected, result)
369
def test_remote_path_from_transport(self):
370
"""SmartClientMedium.remote_path_from_transport calculates a URL for
371
the given transport relative to the root of the client base URL.
373
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
374
self.assertRemotePath(
375
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
377
def assertRemotePathHTTP(self, expected, transport_base, relpath):
378
"""Assert that the result of
379
HttpTransportBase.remote_path_from_transport is the expected value for
380
a given transport_base and relpath of that transport. (Note that
381
HttpTransportBase is a subclass of SmartClientMedium)
383
base_transport = transport.get_transport(transport_base)
384
client_medium = base_transport.get_smart_medium()
385
cloned_transport = base_transport.clone(relpath)
386
result = client_medium.remote_path_from_transport(cloned_transport)
387
self.assertEqual(expected, result)
389
def test_remote_path_from_transport_http(self):
390
"""Remote paths for HTTP transports are calculated differently to other
391
transports. They are just relative to the client base, not the root
392
directory of the host.
394
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
395
self.assertRemotePathHTTP(
396
'../xyz/', scheme + '//host/path', '../xyz/')
397
self.assertRemotePathHTTP(
398
'xyz/', scheme + '//host/path', 'xyz/')
401
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
402
"""Tests for the behaviour of client_medium.remote_is_at_least."""
404
def test_initially_unlimited(self):
405
"""A fresh medium assumes that the remote side supports all
408
client_medium = medium.SmartClientMedium('dummy base')
409
self.assertFalse(client_medium._is_remote_before((99, 99)))
411
def test__remember_remote_is_before(self):
412
"""Calling _remember_remote_is_before ratchets down the known remote
415
client_medium = medium.SmartClientMedium('dummy base')
416
# Mark the remote side as being less than 1.6. The remote side may
418
client_medium._remember_remote_is_before((1, 6))
419
self.assertTrue(client_medium._is_remote_before((1, 6)))
420
self.assertFalse(client_medium._is_remote_before((1, 5)))
421
# Calling _remember_remote_is_before again with a lower value works.
422
client_medium._remember_remote_is_before((1, 5))
423
self.assertTrue(client_medium._is_remote_before((1, 5)))
424
# If you call _remember_remote_is_before with a higher value it logs a
425
# warning, and continues to remember the lower value.
426
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
427
client_medium._remember_remote_is_before((1, 9))
428
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
429
self.assertTrue(client_medium._is_remote_before((1, 5)))
432
class TestBzrDirCloningMetaDir(TestRemote):
434
def test_backwards_compat(self):
435
self.setup_smart_server_with_call_log()
436
a_dir = self.make_bzrdir('.')
437
self.reset_smart_call_log()
438
verb = 'BzrDir.cloning_metadir'
439
self.disable_verb(verb)
440
format = a_dir.cloning_metadir()
441
call_count = len([call for call in self.hpss_calls if
442
call.call.method == verb])
443
self.assertEqual(1, call_count)
445
def test_branch_reference(self):
446
transport = self.get_transport('quack')
447
referenced = self.make_branch('referenced')
448
expected = referenced.bzrdir.cloning_metadir()
449
client = FakeClient(transport.base)
450
client.add_expected_call(
451
'BzrDir.cloning_metadir', ('quack/', 'False'),
452
'error', ('BranchReference',)),
453
client.add_expected_call(
454
'BzrDir.open_branchV3', ('quack/',),
455
'success', ('ref', self.get_url('referenced'))),
456
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
458
result = a_bzrdir.cloning_metadir()
459
# We should have got a control dir matching the referenced branch.
460
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
461
self.assertEqual(expected._repository_format, result._repository_format)
462
self.assertEqual(expected._branch_format, result._branch_format)
463
self.assertFinished(client)
465
def test_current_server(self):
466
transport = self.get_transport('.')
467
transport = transport.clone('quack')
468
self.make_bzrdir('quack')
469
client = FakeClient(transport.base)
470
reference_bzrdir_format = controldir.format_registry.get('default')()
471
control_name = reference_bzrdir_format.network_name()
472
client.add_expected_call(
473
'BzrDir.cloning_metadir', ('quack/', 'False'),
474
'success', (control_name, '', ('branch', ''))),
475
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
477
result = a_bzrdir.cloning_metadir()
478
# We should have got a reference control dir with default branch and
479
# repository formats.
480
# This pokes a little, just to be sure.
481
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
482
self.assertEqual(None, result._repository_format)
483
self.assertEqual(None, result._branch_format)
484
self.assertFinished(client)
486
def test_unknown(self):
487
transport = self.get_transport('quack')
488
referenced = self.make_branch('referenced')
489
expected = referenced.bzrdir.cloning_metadir()
490
client = FakeClient(transport.base)
491
client.add_expected_call(
492
'BzrDir.cloning_metadir', ('quack/', 'False'),
493
'success', ('unknown', 'unknown', ('branch', ''))),
494
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
496
self.assertRaises(errors.UnknownFormatError, a_bzrdir.cloning_metadir)
499
class TestBzrDirCheckoutMetaDir(TestRemote):
501
def test__get_checkout_format(self):
502
transport = MemoryTransport()
503
client = FakeClient(transport.base)
504
reference_bzrdir_format = controldir.format_registry.get('default')()
505
control_name = reference_bzrdir_format.network_name()
506
client.add_expected_call(
507
'BzrDir.checkout_metadir', ('quack/', ),
508
'success', (control_name, '', ''))
509
transport.mkdir('quack')
510
transport = transport.clone('quack')
511
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
513
result = a_bzrdir.checkout_metadir()
514
# We should have got a reference control dir with default branch and
515
# repository formats.
516
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
517
self.assertEqual(None, result._repository_format)
518
self.assertEqual(None, result._branch_format)
519
self.assertFinished(client)
521
def test_unknown_format(self):
522
transport = MemoryTransport()
523
client = FakeClient(transport.base)
524
client.add_expected_call(
525
'BzrDir.checkout_metadir', ('quack/',),
526
'success', ('dontknow', '', ''))
527
transport.mkdir('quack')
528
transport = transport.clone('quack')
529
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
531
self.assertRaises(errors.UnknownFormatError,
532
a_bzrdir.checkout_metadir)
533
self.assertFinished(client)
536
class TestBzrDirGetBranches(TestRemote):
538
def test_get_branches(self):
539
transport = MemoryTransport()
540
client = FakeClient(transport.base)
541
reference_bzrdir_format = controldir.format_registry.get('default')()
542
branch_name = reference_bzrdir_format.get_branch_format().network_name()
543
client.add_success_response_with_body(
545
"foo": ("branch", branch_name),
546
"": ("branch", branch_name)}), "success")
547
client.add_success_response(
548
'ok', '', 'no', 'no', 'no',
549
reference_bzrdir_format.repository_format.network_name())
550
client.add_error_response('NotStacked')
551
client.add_success_response(
552
'ok', '', 'no', 'no', 'no',
553
reference_bzrdir_format.repository_format.network_name())
554
client.add_error_response('NotStacked')
555
transport.mkdir('quack')
556
transport = transport.clone('quack')
557
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
559
result = a_bzrdir.get_branches()
560
self.assertEqual({"", "foo"}, set(result.keys()))
562
[('call_expecting_body', 'BzrDir.get_branches', ('quack/',)),
563
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
564
('call', 'Branch.get_stacked_on_url', ('quack/', )),
565
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
566
('call', 'Branch.get_stacked_on_url', ('quack/', ))],
570
class TestBzrDirDestroyBranch(TestRemote):
572
def test_destroy_default(self):
573
transport = self.get_transport('quack')
574
referenced = self.make_branch('referenced')
575
client = FakeClient(transport.base)
576
client.add_expected_call(
577
'BzrDir.destroy_branch', ('quack/', ),
579
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
581
a_bzrdir.destroy_branch()
582
self.assertFinished(client)
585
class TestBzrDirHasWorkingTree(TestRemote):
587
def test_has_workingtree(self):
588
transport = self.get_transport('quack')
589
client = FakeClient(transport.base)
590
client.add_expected_call(
591
'BzrDir.has_workingtree', ('quack/',),
592
'success', ('yes',)),
593
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
595
self.assertTrue(a_bzrdir.has_workingtree())
596
self.assertFinished(client)
598
def test_no_workingtree(self):
599
transport = self.get_transport('quack')
600
client = FakeClient(transport.base)
601
client.add_expected_call(
602
'BzrDir.has_workingtree', ('quack/',),
604
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
606
self.assertFalse(a_bzrdir.has_workingtree())
607
self.assertFinished(client)
610
class TestBzrDirDestroyRepository(TestRemote):
612
def test_destroy_repository(self):
613
transport = self.get_transport('quack')
614
client = FakeClient(transport.base)
615
client.add_expected_call(
616
'BzrDir.destroy_repository', ('quack/',),
618
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
620
a_bzrdir.destroy_repository()
621
self.assertFinished(client)
624
class TestBzrDirOpen(TestRemote):
626
def make_fake_client_and_transport(self, path='quack'):
627
transport = MemoryTransport()
628
transport.mkdir(path)
629
transport = transport.clone(path)
630
client = FakeClient(transport.base)
631
return client, transport
633
def test_absent(self):
634
client, transport = self.make_fake_client_and_transport()
635
client.add_expected_call(
636
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
637
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
638
RemoteBzrDirFormat(), _client=client, _force_probe=True)
639
self.assertFinished(client)
641
def test_present_without_workingtree(self):
642
client, transport = self.make_fake_client_and_transport()
643
client.add_expected_call(
644
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
645
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
646
_client=client, _force_probe=True)
647
self.assertIsInstance(bd, RemoteBzrDir)
648
self.assertFalse(bd.has_workingtree())
649
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
650
self.assertFinished(client)
652
def test_present_with_workingtree(self):
653
client, transport = self.make_fake_client_and_transport()
654
client.add_expected_call(
655
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
656
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
657
_client=client, _force_probe=True)
658
self.assertIsInstance(bd, RemoteBzrDir)
659
self.assertTrue(bd.has_workingtree())
660
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
661
self.assertFinished(client)
663
def test_backwards_compat(self):
664
client, transport = self.make_fake_client_and_transport()
665
client.add_expected_call(
666
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
667
client.add_expected_call(
668
'BzrDir.open', ('quack/',), 'success', ('yes',))
669
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
670
_client=client, _force_probe=True)
671
self.assertIsInstance(bd, RemoteBzrDir)
672
self.assertFinished(client)
674
def test_backwards_compat_hpss_v2(self):
675
client, transport = self.make_fake_client_and_transport()
676
# Monkey-patch fake client to simulate real-world behaviour with v2
677
# server: upon first RPC call detect the protocol version, and because
678
# the version is 2 also do _remember_remote_is_before((1, 6)) before
679
# continuing with the RPC.
680
orig_check_call = client._check_call
681
def check_call(method, args):
682
client._medium._protocol_version = 2
683
client._medium._remember_remote_is_before((1, 6))
684
client._check_call = orig_check_call
685
client._check_call(method, args)
686
client._check_call = check_call
687
client.add_expected_call(
688
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
689
client.add_expected_call(
690
'BzrDir.open', ('quack/',), 'success', ('yes',))
691
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
692
_client=client, _force_probe=True)
693
self.assertIsInstance(bd, RemoteBzrDir)
694
self.assertFinished(client)
697
class TestBzrDirOpenBranch(TestRemote):
699
def test_backwards_compat(self):
700
self.setup_smart_server_with_call_log()
701
self.make_branch('.')
702
a_dir = BzrDir.open(self.get_url('.'))
703
self.reset_smart_call_log()
704
verb = 'BzrDir.open_branchV3'
705
self.disable_verb(verb)
706
format = a_dir.open_branch()
707
call_count = len([call for call in self.hpss_calls if
708
call.call.method == verb])
709
self.assertEqual(1, call_count)
711
def test_branch_present(self):
712
reference_format = self.get_repo_format()
713
network_name = reference_format.network_name()
714
branch_network_name = self.get_branch_format().network_name()
715
transport = MemoryTransport()
716
transport.mkdir('quack')
717
transport = transport.clone('quack')
718
client = FakeClient(transport.base)
719
client.add_expected_call(
720
'BzrDir.open_branchV3', ('quack/',),
721
'success', ('branch', branch_network_name))
722
client.add_expected_call(
723
'BzrDir.find_repositoryV3', ('quack/',),
724
'success', ('ok', '', 'no', 'no', 'no', network_name))
725
client.add_expected_call(
726
'Branch.get_stacked_on_url', ('quack/',),
727
'error', ('NotStacked',))
728
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
730
result = bzrdir.open_branch()
731
self.assertIsInstance(result, RemoteBranch)
732
self.assertEqual(bzrdir, result.bzrdir)
733
self.assertFinished(client)
735
def test_branch_missing(self):
736
transport = MemoryTransport()
737
transport.mkdir('quack')
738
transport = transport.clone('quack')
739
client = FakeClient(transport.base)
740
client.add_error_response('nobranch')
741
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
743
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
745
[('call', 'BzrDir.open_branchV3', ('quack/',))],
748
def test__get_tree_branch(self):
749
# _get_tree_branch is a form of open_branch, but it should only ask for
750
# branch opening, not any other network requests.
752
def open_branch(name=None, possible_transports=None):
753
calls.append("Called")
755
transport = MemoryTransport()
756
# no requests on the network - catches other api calls being made.
757
client = FakeClient(transport.base)
758
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
760
# patch the open_branch call to record that it was called.
761
bzrdir.open_branch = open_branch
762
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
763
self.assertEqual(["Called"], calls)
764
self.assertEqual([], client._calls)
766
def test_url_quoting_of_path(self):
767
# Relpaths on the wire should not be URL-escaped. So "~" should be
768
# transmitted as "~", not "%7E".
769
transport = RemoteTCPTransport('bzr://localhost/~hello/')
770
client = FakeClient(transport.base)
771
reference_format = self.get_repo_format()
772
network_name = reference_format.network_name()
773
branch_network_name = self.get_branch_format().network_name()
774
client.add_expected_call(
775
'BzrDir.open_branchV3', ('~hello/',),
776
'success', ('branch', branch_network_name))
777
client.add_expected_call(
778
'BzrDir.find_repositoryV3', ('~hello/',),
779
'success', ('ok', '', 'no', 'no', 'no', network_name))
780
client.add_expected_call(
781
'Branch.get_stacked_on_url', ('~hello/',),
782
'error', ('NotStacked',))
783
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
785
result = bzrdir.open_branch()
786
self.assertFinished(client)
788
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
789
reference_format = self.get_repo_format()
790
network_name = reference_format.network_name()
791
transport = MemoryTransport()
792
transport.mkdir('quack')
793
transport = transport.clone('quack')
795
rich_response = 'yes'
799
subtree_response = 'yes'
801
subtree_response = 'no'
802
client = FakeClient(transport.base)
803
client.add_success_response(
804
'ok', '', rich_response, subtree_response, external_lookup,
806
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
808
result = bzrdir.open_repository()
810
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
812
self.assertIsInstance(result, RemoteRepository)
813
self.assertEqual(bzrdir, result.bzrdir)
814
self.assertEqual(rich_root, result._format.rich_root_data)
815
self.assertEqual(subtrees, result._format.supports_tree_reference)
817
def test_open_repository_sets_format_attributes(self):
818
self.check_open_repository(True, True)
819
self.check_open_repository(False, True)
820
self.check_open_repository(True, False)
821
self.check_open_repository(False, False)
822
self.check_open_repository(False, False, 'yes')
824
def test_old_server(self):
825
"""RemoteBzrDirFormat should fail to probe if the server version is too
828
self.assertRaises(errors.NotBranchError,
829
RemoteBzrProber.probe_transport, OldServerTransport())
832
class TestBzrDirCreateBranch(TestRemote):
834
def test_backwards_compat(self):
835
self.setup_smart_server_with_call_log()
836
repo = self.make_repository('.')
837
self.reset_smart_call_log()
838
self.disable_verb('BzrDir.create_branch')
839
branch = repo.bzrdir.create_branch()
840
create_branch_call_count = len([call for call in self.hpss_calls if
841
call.call.method == 'BzrDir.create_branch'])
842
self.assertEqual(1, create_branch_call_count)
844
def test_current_server(self):
845
transport = self.get_transport('.')
846
transport = transport.clone('quack')
847
self.make_repository('quack')
848
client = FakeClient(transport.base)
849
reference_bzrdir_format = controldir.format_registry.get('default')()
850
reference_format = reference_bzrdir_format.get_branch_format()
851
network_name = reference_format.network_name()
852
reference_repo_fmt = reference_bzrdir_format.repository_format
853
reference_repo_name = reference_repo_fmt.network_name()
854
client.add_expected_call(
855
'BzrDir.create_branch', ('quack/', network_name),
856
'success', ('ok', network_name, '', 'no', 'no', 'yes',
857
reference_repo_name))
858
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
860
branch = a_bzrdir.create_branch()
861
# We should have got a remote branch
862
self.assertIsInstance(branch, remote.RemoteBranch)
863
# its format should have the settings from the response
864
format = branch._format
865
self.assertEqual(network_name, format.network_name())
867
def test_already_open_repo_and_reused_medium(self):
868
"""Bug 726584: create_branch(..., repository=repo) should work
869
regardless of what the smart medium's base URL is.
871
self.transport_server = test_server.SmartTCPServer_for_testing
872
transport = self.get_transport('.')
873
repo = self.make_repository('quack')
874
# Client's medium rooted a transport root (not at the bzrdir)
875
client = FakeClient(transport.base)
876
transport = transport.clone('quack')
877
reference_bzrdir_format = controldir.format_registry.get('default')()
878
reference_format = reference_bzrdir_format.get_branch_format()
879
network_name = reference_format.network_name()
880
reference_repo_fmt = reference_bzrdir_format.repository_format
881
reference_repo_name = reference_repo_fmt.network_name()
882
client.add_expected_call(
883
'BzrDir.create_branch', ('extra/quack/', network_name),
884
'success', ('ok', network_name, '', 'no', 'no', 'yes',
885
reference_repo_name))
886
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
888
branch = a_bzrdir.create_branch(repository=repo)
889
# We should have got a remote branch
890
self.assertIsInstance(branch, remote.RemoteBranch)
891
# its format should have the settings from the response
892
format = branch._format
893
self.assertEqual(network_name, format.network_name())
896
class TestBzrDirCreateRepository(TestRemote):
898
def test_backwards_compat(self):
899
self.setup_smart_server_with_call_log()
900
bzrdir = self.make_bzrdir('.')
901
self.reset_smart_call_log()
902
self.disable_verb('BzrDir.create_repository')
903
repo = bzrdir.create_repository()
904
create_repo_call_count = len([call for call in self.hpss_calls if
905
call.call.method == 'BzrDir.create_repository'])
906
self.assertEqual(1, create_repo_call_count)
908
def test_current_server(self):
909
transport = self.get_transport('.')
910
transport = transport.clone('quack')
911
self.make_bzrdir('quack')
912
client = FakeClient(transport.base)
913
reference_bzrdir_format = controldir.format_registry.get('default')()
914
reference_format = reference_bzrdir_format.repository_format
915
network_name = reference_format.network_name()
916
client.add_expected_call(
917
'BzrDir.create_repository', ('quack/',
918
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
920
'success', ('ok', 'yes', 'yes', 'yes', network_name))
921
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
923
repo = a_bzrdir.create_repository()
924
# We should have got a remote repository
925
self.assertIsInstance(repo, remote.RemoteRepository)
926
# its format should have the settings from the response
927
format = repo._format
928
self.assertTrue(format.rich_root_data)
929
self.assertTrue(format.supports_tree_reference)
930
self.assertTrue(format.supports_external_lookups)
931
self.assertEqual(network_name, format.network_name())
934
class TestBzrDirOpenRepository(TestRemote):
936
def test_backwards_compat_1_2_3(self):
937
# fallback all the way to the first version.
938
reference_format = self.get_repo_format()
939
network_name = reference_format.network_name()
940
server_url = 'bzr://example.com/'
941
self.permit_url(server_url)
942
client = FakeClient(server_url)
943
client.add_unknown_method_response('BzrDir.find_repositoryV3')
944
client.add_unknown_method_response('BzrDir.find_repositoryV2')
945
client.add_success_response('ok', '', 'no', 'no')
946
# A real repository instance will be created to determine the network
948
client.add_success_response_with_body(
949
"Bazaar-NG meta directory, format 1\n", 'ok')
950
client.add_success_response('stat', '0', '65535')
951
client.add_success_response_with_body(
952
reference_format.get_format_string(), 'ok')
953
# PackRepository wants to do a stat
954
client.add_success_response('stat', '0', '65535')
955
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
957
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
959
repo = bzrdir.open_repository()
961
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
962
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
963
('call', 'BzrDir.find_repository', ('quack/',)),
964
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
965
('call', 'stat', ('/quack/.bzr',)),
966
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
967
('call', 'stat', ('/quack/.bzr/repository',)),
970
self.assertEqual(network_name, repo._format.network_name())
972
def test_backwards_compat_2(self):
973
# fallback to find_repositoryV2
974
reference_format = self.get_repo_format()
975
network_name = reference_format.network_name()
976
server_url = 'bzr://example.com/'
977
self.permit_url(server_url)
978
client = FakeClient(server_url)
979
client.add_unknown_method_response('BzrDir.find_repositoryV3')
980
client.add_success_response('ok', '', 'no', 'no', 'no')
981
# A real repository instance will be created to determine the network
983
client.add_success_response_with_body(
984
"Bazaar-NG meta directory, format 1\n", 'ok')
985
client.add_success_response('stat', '0', '65535')
986
client.add_success_response_with_body(
987
reference_format.get_format_string(), 'ok')
988
# PackRepository wants to do a stat
989
client.add_success_response('stat', '0', '65535')
990
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
992
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
994
repo = bzrdir.open_repository()
996
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
997
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
998
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
999
('call', 'stat', ('/quack/.bzr',)),
1000
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
1001
('call', 'stat', ('/quack/.bzr/repository',)),
1004
self.assertEqual(network_name, repo._format.network_name())
1006
def test_current_server(self):
1007
reference_format = self.get_repo_format()
1008
network_name = reference_format.network_name()
1009
transport = MemoryTransport()
1010
transport.mkdir('quack')
1011
transport = transport.clone('quack')
1012
client = FakeClient(transport.base)
1013
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
1014
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1016
repo = bzrdir.open_repository()
1018
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
1020
self.assertEqual(network_name, repo._format.network_name())
1023
class TestBzrDirFormatInitializeEx(TestRemote):
1025
def test_success(self):
1026
"""Simple test for typical successful call."""
1027
fmt = RemoteBzrDirFormat()
1028
default_format_name = BzrDirFormat.get_default_format().network_name()
1029
transport = self.get_transport()
1030
client = FakeClient(transport.base)
1031
client.add_expected_call(
1032
'BzrDirFormat.initialize_ex_1.16',
1033
(default_format_name, 'path', 'False', 'False', 'False', '',
1034
'', '', '', 'False'),
1036
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
1037
'bzrdir fmt', 'False', '', '', 'repo lock token'))
1038
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1039
# it's currently hard to test that without supplying a real remote
1040
# transport connected to a real server.
1041
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1042
transport, False, False, False, None, None, None, None, False)
1043
self.assertFinished(client)
1045
def test_error(self):
1046
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1047
corresponding error from the client.
1049
fmt = RemoteBzrDirFormat()
1050
default_format_name = BzrDirFormat.get_default_format().network_name()
1051
transport = self.get_transport()
1052
client = FakeClient(transport.base)
1053
client.add_expected_call(
1054
'BzrDirFormat.initialize_ex_1.16',
1055
(default_format_name, 'path', 'False', 'False', 'False', '',
1056
'', '', '', 'False'),
1058
('PermissionDenied', 'path', 'extra info'))
1059
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1060
# it's currently hard to test that without supplying a real remote
1061
# transport connected to a real server.
1062
err = self.assertRaises(errors.PermissionDenied,
1063
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1064
False, False, False, None, None, None, None, False)
1065
self.assertEqual('path', err.path)
1066
self.assertEqual(': extra info', err.extra)
1067
self.assertFinished(client)
1069
def test_error_from_real_server(self):
1070
"""Integration test for error translation."""
1071
transport = self.make_smart_server('foo')
1072
transport = transport.clone('no-such-path')
1073
fmt = RemoteBzrDirFormat()
1074
err = self.assertRaises(errors.NoSuchFile,
1075
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1078
class OldSmartClient(object):
1079
"""A fake smart client for test_old_version that just returns a version one
1080
response to the 'hello' (query version) command.
1083
def get_request(self):
1084
input_file = BytesIO(b'ok\x011\n')
1085
output_file = BytesIO()
1086
client_medium = medium.SmartSimplePipesClientMedium(
1087
input_file, output_file)
1088
return medium.SmartClientStreamMediumRequest(client_medium)
1090
def protocol_version(self):
1094
class OldServerTransport(object):
1095
"""A fake transport for test_old_server that reports it's smart server
1096
protocol version as version one.
1102
def get_smart_client(self):
1103
return OldSmartClient()
1106
class RemoteBzrDirTestCase(TestRemote):
1108
def make_remote_bzrdir(self, transport, client):
1109
"""Make a RemotebzrDir using 'client' as the _client."""
1110
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1114
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1116
def lock_remote_branch(self, branch):
1117
"""Trick a RemoteBranch into thinking it is locked."""
1118
branch._lock_mode = 'w'
1119
branch._lock_count = 2
1120
branch._lock_token = 'branch token'
1121
branch._repo_lock_token = 'repo token'
1122
branch.repository._lock_mode = 'w'
1123
branch.repository._lock_count = 2
1124
branch.repository._lock_token = 'repo token'
1126
def make_remote_branch(self, transport, client):
1127
"""Make a RemoteBranch using 'client' as its _SmartClient.
1129
A RemoteBzrDir and RemoteRepository will also be created to fill out
1130
the RemoteBranch, albeit with stub values for some of their attributes.
1132
# we do not want bzrdir to make any remote calls, so use False as its
1133
# _client. If it tries to make a remote call, this will fail
1135
bzrdir = self.make_remote_bzrdir(transport, False)
1136
repo = RemoteRepository(bzrdir, None, _client=client)
1137
branch_format = self.get_branch_format()
1138
format = RemoteBranchFormat(network_name=branch_format.network_name())
1139
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1142
class TestBranchBreakLock(RemoteBranchTestCase):
1144
def test_break_lock(self):
1145
transport_path = 'quack'
1146
transport = MemoryTransport()
1147
client = FakeClient(transport.base)
1148
client.add_expected_call(
1149
'Branch.get_stacked_on_url', ('quack/',),
1150
'error', ('NotStacked',))
1151
client.add_expected_call(
1152
'Branch.break_lock', ('quack/',),
1154
transport.mkdir('quack')
1155
transport = transport.clone('quack')
1156
branch = self.make_remote_branch(transport, client)
1158
self.assertFinished(client)
1161
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1163
def test_get_physical_lock_status_yes(self):
1164
transport = MemoryTransport()
1165
client = FakeClient(transport.base)
1166
client.add_expected_call(
1167
'Branch.get_stacked_on_url', ('quack/',),
1168
'error', ('NotStacked',))
1169
client.add_expected_call(
1170
'Branch.get_physical_lock_status', ('quack/',),
1171
'success', ('yes',))
1172
transport.mkdir('quack')
1173
transport = transport.clone('quack')
1174
branch = self.make_remote_branch(transport, client)
1175
result = branch.get_physical_lock_status()
1176
self.assertFinished(client)
1177
self.assertEqual(True, result)
1179
def test_get_physical_lock_status_no(self):
1180
transport = MemoryTransport()
1181
client = FakeClient(transport.base)
1182
client.add_expected_call(
1183
'Branch.get_stacked_on_url', ('quack/',),
1184
'error', ('NotStacked',))
1185
client.add_expected_call(
1186
'Branch.get_physical_lock_status', ('quack/',),
1188
transport.mkdir('quack')
1189
transport = transport.clone('quack')
1190
branch = self.make_remote_branch(transport, client)
1191
result = branch.get_physical_lock_status()
1192
self.assertFinished(client)
1193
self.assertEqual(False, result)
1196
class TestBranchGetParent(RemoteBranchTestCase):
1198
def test_no_parent(self):
1199
# in an empty branch we decode the response properly
1200
transport = MemoryTransport()
1201
client = FakeClient(transport.base)
1202
client.add_expected_call(
1203
'Branch.get_stacked_on_url', ('quack/',),
1204
'error', ('NotStacked',))
1205
client.add_expected_call(
1206
'Branch.get_parent', ('quack/',),
1208
transport.mkdir('quack')
1209
transport = transport.clone('quack')
1210
branch = self.make_remote_branch(transport, client)
1211
result = branch.get_parent()
1212
self.assertFinished(client)
1213
self.assertEqual(None, result)
1215
def test_parent_relative(self):
1216
transport = MemoryTransport()
1217
client = FakeClient(transport.base)
1218
client.add_expected_call(
1219
'Branch.get_stacked_on_url', ('kwaak/',),
1220
'error', ('NotStacked',))
1221
client.add_expected_call(
1222
'Branch.get_parent', ('kwaak/',),
1223
'success', ('../foo/',))
1224
transport.mkdir('kwaak')
1225
transport = transport.clone('kwaak')
1226
branch = self.make_remote_branch(transport, client)
1227
result = branch.get_parent()
1228
self.assertEqual(transport.clone('../foo').base, result)
1230
def test_parent_absolute(self):
1231
transport = MemoryTransport()
1232
client = FakeClient(transport.base)
1233
client.add_expected_call(
1234
'Branch.get_stacked_on_url', ('kwaak/',),
1235
'error', ('NotStacked',))
1236
client.add_expected_call(
1237
'Branch.get_parent', ('kwaak/',),
1238
'success', ('http://foo/',))
1239
transport.mkdir('kwaak')
1240
transport = transport.clone('kwaak')
1241
branch = self.make_remote_branch(transport, client)
1242
result = branch.get_parent()
1243
self.assertEqual('http://foo/', result)
1244
self.assertFinished(client)
1247
class TestBranchSetParentLocation(RemoteBranchTestCase):
1249
def test_no_parent(self):
1250
# We call the verb when setting parent to None
1251
transport = MemoryTransport()
1252
client = FakeClient(transport.base)
1253
client.add_expected_call(
1254
'Branch.get_stacked_on_url', ('quack/',),
1255
'error', ('NotStacked',))
1256
client.add_expected_call(
1257
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1259
transport.mkdir('quack')
1260
transport = transport.clone('quack')
1261
branch = self.make_remote_branch(transport, client)
1262
branch._lock_token = 'b'
1263
branch._repo_lock_token = 'r'
1264
branch._set_parent_location(None)
1265
self.assertFinished(client)
1267
def test_parent(self):
1268
transport = MemoryTransport()
1269
client = FakeClient(transport.base)
1270
client.add_expected_call(
1271
'Branch.get_stacked_on_url', ('kwaak/',),
1272
'error', ('NotStacked',))
1273
client.add_expected_call(
1274
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1276
transport.mkdir('kwaak')
1277
transport = transport.clone('kwaak')
1278
branch = self.make_remote_branch(transport, client)
1279
branch._lock_token = 'b'
1280
branch._repo_lock_token = 'r'
1281
branch._set_parent_location('foo')
1282
self.assertFinished(client)
1284
def test_backwards_compat(self):
1285
self.setup_smart_server_with_call_log()
1286
branch = self.make_branch('.')
1287
self.reset_smart_call_log()
1288
verb = 'Branch.set_parent_location'
1289
self.disable_verb(verb)
1290
branch.set_parent('http://foo/')
1291
self.assertLength(14, self.hpss_calls)
1294
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1296
def test_backwards_compat(self):
1297
self.setup_smart_server_with_call_log()
1298
branch = self.make_branch('.')
1299
self.reset_smart_call_log()
1300
verb = 'Branch.get_tags_bytes'
1301
self.disable_verb(verb)
1302
branch.tags.get_tag_dict()
1303
call_count = len([call for call in self.hpss_calls if
1304
call.call.method == verb])
1305
self.assertEqual(1, call_count)
1307
def test_trivial(self):
1308
transport = MemoryTransport()
1309
client = FakeClient(transport.base)
1310
client.add_expected_call(
1311
'Branch.get_stacked_on_url', ('quack/',),
1312
'error', ('NotStacked',))
1313
client.add_expected_call(
1314
'Branch.get_tags_bytes', ('quack/',),
1316
transport.mkdir('quack')
1317
transport = transport.clone('quack')
1318
branch = self.make_remote_branch(transport, client)
1319
result = branch.tags.get_tag_dict()
1320
self.assertFinished(client)
1321
self.assertEqual({}, result)
1324
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1326
def test_trivial(self):
1327
transport = MemoryTransport()
1328
client = FakeClient(transport.base)
1329
client.add_expected_call(
1330
'Branch.get_stacked_on_url', ('quack/',),
1331
'error', ('NotStacked',))
1332
client.add_expected_call(
1333
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1335
transport.mkdir('quack')
1336
transport = transport.clone('quack')
1337
branch = self.make_remote_branch(transport, client)
1338
self.lock_remote_branch(branch)
1339
branch._set_tags_bytes('tags bytes')
1340
self.assertFinished(client)
1341
self.assertEqual('tags bytes', client._calls[-1][-1])
1343
def test_backwards_compatible(self):
1344
transport = MemoryTransport()
1345
client = FakeClient(transport.base)
1346
client.add_expected_call(
1347
'Branch.get_stacked_on_url', ('quack/',),
1348
'error', ('NotStacked',))
1349
client.add_expected_call(
1350
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1351
'unknown', ('Branch.set_tags_bytes',))
1352
transport.mkdir('quack')
1353
transport = transport.clone('quack')
1354
branch = self.make_remote_branch(transport, client)
1355
self.lock_remote_branch(branch)
1356
class StubRealBranch(object):
1359
def _set_tags_bytes(self, bytes):
1360
self.calls.append(('set_tags_bytes', bytes))
1361
real_branch = StubRealBranch()
1362
branch._real_branch = real_branch
1363
branch._set_tags_bytes('tags bytes')
1364
# Call a second time, to exercise the 'remote version already inferred'
1366
branch._set_tags_bytes('tags bytes')
1367
self.assertFinished(client)
1369
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1372
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1374
def test_uses_last_revision_info_and_tags_by_default(self):
1375
transport = MemoryTransport()
1376
client = FakeClient(transport.base)
1377
client.add_expected_call(
1378
'Branch.get_stacked_on_url', ('quack/',),
1379
'error', ('NotStacked',))
1380
client.add_expected_call(
1381
'Branch.last_revision_info', ('quack/',),
1382
'success', ('ok', '1', 'rev-tip'))
1383
client.add_expected_call(
1384
'Branch.get_config_file', ('quack/',),
1385
'success', ('ok',), '')
1386
transport.mkdir('quack')
1387
transport = transport.clone('quack')
1388
branch = self.make_remote_branch(transport, client)
1389
result = branch.heads_to_fetch()
1390
self.assertFinished(client)
1391
self.assertEqual(({'rev-tip'}, set()), result)
1393
def test_uses_last_revision_info_and_tags_when_set(self):
1394
transport = MemoryTransport()
1395
client = FakeClient(transport.base)
1396
client.add_expected_call(
1397
'Branch.get_stacked_on_url', ('quack/',),
1398
'error', ('NotStacked',))
1399
client.add_expected_call(
1400
'Branch.last_revision_info', ('quack/',),
1401
'success', ('ok', '1', 'rev-tip'))
1402
client.add_expected_call(
1403
'Branch.get_config_file', ('quack/',),
1404
'success', ('ok',), 'branch.fetch_tags = True')
1405
# XXX: this will break if the default format's serialization of tags
1406
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1407
client.add_expected_call(
1408
'Branch.get_tags_bytes', ('quack/',),
1409
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1410
transport.mkdir('quack')
1411
transport = transport.clone('quack')
1412
branch = self.make_remote_branch(transport, client)
1413
result = branch.heads_to_fetch()
1414
self.assertFinished(client)
1416
({'rev-tip'}, {'rev-foo', 'rev-bar'}), result)
1418
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1419
transport = MemoryTransport()
1420
client = FakeClient(transport.base)
1421
client.add_expected_call(
1422
'Branch.get_stacked_on_url', ('quack/',),
1423
'error', ('NotStacked',))
1424
client.add_expected_call(
1425
'Branch.heads_to_fetch', ('quack/',),
1426
'success', (['tip'], ['tagged-1', 'tagged-2']))
1427
transport.mkdir('quack')
1428
transport = transport.clone('quack')
1429
branch = self.make_remote_branch(transport, client)
1430
branch._format._use_default_local_heads_to_fetch = lambda: False
1431
result = branch.heads_to_fetch()
1432
self.assertFinished(client)
1433
self.assertEqual(({'tip'}, {'tagged-1', 'tagged-2'}), result)
1435
def make_branch_with_tags(self):
1436
self.setup_smart_server_with_call_log()
1437
# Make a branch with a single revision.
1438
builder = self.make_branch_builder('foo')
1439
builder.start_series()
1440
builder.build_snapshot('tip', None, [
1441
('add', ('', 'root-id', 'directory', ''))])
1442
builder.finish_series()
1443
branch = builder.get_branch()
1444
# Add two tags to that branch
1445
branch.tags.set_tag('tag-1', 'rev-1')
1446
branch.tags.set_tag('tag-2', 'rev-2')
1449
def test_backwards_compatible(self):
1450
br = self.make_branch_with_tags()
1451
br.get_config_stack().set('branch.fetch_tags', True)
1452
self.addCleanup(br.lock_read().unlock)
1453
# Disable the heads_to_fetch verb
1454
verb = 'Branch.heads_to_fetch'
1455
self.disable_verb(verb)
1456
self.reset_smart_call_log()
1457
result = br.heads_to_fetch()
1458
self.assertEqual(({'tip'}, {'rev-1', 'rev-2'}), result)
1460
['Branch.last_revision_info', 'Branch.get_tags_bytes'],
1461
[call.call.method for call in self.hpss_calls])
1463
def test_backwards_compatible_no_tags(self):
1464
br = self.make_branch_with_tags()
1465
br.get_config_stack().set('branch.fetch_tags', False)
1466
self.addCleanup(br.lock_read().unlock)
1467
# Disable the heads_to_fetch verb
1468
verb = 'Branch.heads_to_fetch'
1469
self.disable_verb(verb)
1470
self.reset_smart_call_log()
1471
result = br.heads_to_fetch()
1472
self.assertEqual(({'tip'}, set()), result)
1474
['Branch.last_revision_info'],
1475
[call.call.method for call in self.hpss_calls])
1478
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1480
def test_empty_branch(self):
1481
# in an empty branch we decode the response properly
1482
transport = MemoryTransport()
1483
client = FakeClient(transport.base)
1484
client.add_expected_call(
1485
'Branch.get_stacked_on_url', ('quack/',),
1486
'error', ('NotStacked',))
1487
client.add_expected_call(
1488
'Branch.last_revision_info', ('quack/',),
1489
'success', ('ok', '0', 'null:'))
1490
transport.mkdir('quack')
1491
transport = transport.clone('quack')
1492
branch = self.make_remote_branch(transport, client)
1493
result = branch.last_revision_info()
1494
self.assertFinished(client)
1495
self.assertEqual((0, NULL_REVISION), result)
1497
def test_non_empty_branch(self):
1498
# in a non-empty branch we also decode the response properly
1499
revid = u'\xc8'.encode('utf8')
1500
transport = MemoryTransport()
1501
client = FakeClient(transport.base)
1502
client.add_expected_call(
1503
'Branch.get_stacked_on_url', ('kwaak/',),
1504
'error', ('NotStacked',))
1505
client.add_expected_call(
1506
'Branch.last_revision_info', ('kwaak/',),
1507
'success', ('ok', '2', revid))
1508
transport.mkdir('kwaak')
1509
transport = transport.clone('kwaak')
1510
branch = self.make_remote_branch(transport, client)
1511
result = branch.last_revision_info()
1512
self.assertEqual((2, revid), result)
1515
class TestBranch_get_stacked_on_url(TestRemote):
1516
"""Test Branch._get_stacked_on_url rpc"""
1518
def test_get_stacked_on_invalid_url(self):
1519
# test that asking for a stacked on url the server can't access works.
1520
# This isn't perfect, but then as we're in the same process there
1521
# really isn't anything we can do to be 100% sure that the server
1522
# doesn't just open in - this test probably needs to be rewritten using
1523
# a spawn()ed server.
1524
stacked_branch = self.make_branch('stacked', format='1.9')
1525
memory_branch = self.make_branch('base', format='1.9')
1526
vfs_url = self.get_vfs_only_url('base')
1527
stacked_branch.set_stacked_on_url(vfs_url)
1528
transport = stacked_branch.bzrdir.root_transport
1529
client = FakeClient(transport.base)
1530
client.add_expected_call(
1531
'Branch.get_stacked_on_url', ('stacked/',),
1532
'success', ('ok', vfs_url))
1533
# XXX: Multiple calls are bad, this second call documents what is
1535
client.add_expected_call(
1536
'Branch.get_stacked_on_url', ('stacked/',),
1537
'success', ('ok', vfs_url))
1538
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1540
repo_fmt = remote.RemoteRepositoryFormat()
1541
repo_fmt._custom_format = stacked_branch.repository._format
1542
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1544
result = branch.get_stacked_on_url()
1545
self.assertEqual(vfs_url, result)
1547
def test_backwards_compatible(self):
1548
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1549
base_branch = self.make_branch('base', format='1.6')
1550
stacked_branch = self.make_branch('stacked', format='1.6')
1551
stacked_branch.set_stacked_on_url('../base')
1552
client = FakeClient(self.get_url())
1553
branch_network_name = self.get_branch_format().network_name()
1554
client.add_expected_call(
1555
'BzrDir.open_branchV3', ('stacked/',),
1556
'success', ('branch', branch_network_name))
1557
client.add_expected_call(
1558
'BzrDir.find_repositoryV3', ('stacked/',),
1559
'success', ('ok', '', 'no', 'no', 'yes',
1560
stacked_branch.repository._format.network_name()))
1561
# called twice, once from constructor and then again by us
1562
client.add_expected_call(
1563
'Branch.get_stacked_on_url', ('stacked/',),
1564
'unknown', ('Branch.get_stacked_on_url',))
1565
client.add_expected_call(
1566
'Branch.get_stacked_on_url', ('stacked/',),
1567
'unknown', ('Branch.get_stacked_on_url',))
1568
# this will also do vfs access, but that goes direct to the transport
1569
# and isn't seen by the FakeClient.
1570
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1571
RemoteBzrDirFormat(), _client=client)
1572
branch = bzrdir.open_branch()
1573
result = branch.get_stacked_on_url()
1574
self.assertEqual('../base', result)
1575
self.assertFinished(client)
1576
# it's in the fallback list both for the RemoteRepository and its vfs
1578
self.assertEqual(1, len(branch.repository._fallback_repositories))
1580
len(branch.repository._real_repository._fallback_repositories))
1582
def test_get_stacked_on_real_branch(self):
1583
base_branch = self.make_branch('base')
1584
stacked_branch = self.make_branch('stacked')
1585
stacked_branch.set_stacked_on_url('../base')
1586
reference_format = self.get_repo_format()
1587
network_name = reference_format.network_name()
1588
client = FakeClient(self.get_url())
1589
branch_network_name = self.get_branch_format().network_name()
1590
client.add_expected_call(
1591
'BzrDir.open_branchV3', ('stacked/',),
1592
'success', ('branch', branch_network_name))
1593
client.add_expected_call(
1594
'BzrDir.find_repositoryV3', ('stacked/',),
1595
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1596
# called twice, once from constructor and then again by us
1597
client.add_expected_call(
1598
'Branch.get_stacked_on_url', ('stacked/',),
1599
'success', ('ok', '../base'))
1600
client.add_expected_call(
1601
'Branch.get_stacked_on_url', ('stacked/',),
1602
'success', ('ok', '../base'))
1603
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1604
RemoteBzrDirFormat(), _client=client)
1605
branch = bzrdir.open_branch()
1606
result = branch.get_stacked_on_url()
1607
self.assertEqual('../base', result)
1608
self.assertFinished(client)
1609
# it's in the fallback list both for the RemoteRepository.
1610
self.assertEqual(1, len(branch.repository._fallback_repositories))
1611
# And we haven't had to construct a real repository.
1612
self.assertEqual(None, branch.repository._real_repository)
1615
class TestBranchSetLastRevision(RemoteBranchTestCase):
1617
def test_set_empty(self):
1618
# _set_last_revision_info('null:') is translated to calling
1619
# Branch.set_last_revision(path, '') on the wire.
1620
transport = MemoryTransport()
1621
transport.mkdir('branch')
1622
transport = transport.clone('branch')
1624
client = FakeClient(transport.base)
1625
client.add_expected_call(
1626
'Branch.get_stacked_on_url', ('branch/',),
1627
'error', ('NotStacked',))
1628
client.add_expected_call(
1629
'Branch.lock_write', ('branch/', '', ''),
1630
'success', ('ok', 'branch token', 'repo token'))
1631
client.add_expected_call(
1632
'Branch.last_revision_info',
1634
'success', ('ok', '0', 'null:'))
1635
client.add_expected_call(
1636
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1638
client.add_expected_call(
1639
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1641
branch = self.make_remote_branch(transport, client)
1643
result = branch._set_last_revision(NULL_REVISION)
1645
self.assertEqual(None, result)
1646
self.assertFinished(client)
1648
def test_set_nonempty(self):
1649
# set_last_revision_info(N, rev-idN) is translated to calling
1650
# Branch.set_last_revision(path, rev-idN) 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
'Branch.get_stacked_on_url', ('branch/',),
1658
'error', ('NotStacked',))
1659
client.add_expected_call(
1660
'Branch.lock_write', ('branch/', '', ''),
1661
'success', ('ok', 'branch token', 'repo token'))
1662
client.add_expected_call(
1663
'Branch.last_revision_info',
1665
'success', ('ok', '0', 'null:'))
1667
encoded_body = bz2.compress('\n'.join(lines))
1668
client.add_success_response_with_body(encoded_body, 'ok')
1669
client.add_expected_call(
1670
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1672
client.add_expected_call(
1673
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1675
branch = self.make_remote_branch(transport, client)
1676
# Lock the branch, reset the record of remote calls.
1678
result = branch._set_last_revision('rev-id2')
1680
self.assertEqual(None, result)
1681
self.assertFinished(client)
1683
def test_no_such_revision(self):
1684
transport = MemoryTransport()
1685
transport.mkdir('branch')
1686
transport = transport.clone('branch')
1687
# A response of 'NoSuchRevision' is translated into an exception.
1688
client = FakeClient(transport.base)
1689
client.add_expected_call(
1690
'Branch.get_stacked_on_url', ('branch/',),
1691
'error', ('NotStacked',))
1692
client.add_expected_call(
1693
'Branch.lock_write', ('branch/', '', ''),
1694
'success', ('ok', 'branch token', 'repo token'))
1695
client.add_expected_call(
1696
'Branch.last_revision_info',
1698
'success', ('ok', '0', 'null:'))
1699
# get_graph calls to construct the revision history, for the set_rh
1702
encoded_body = bz2.compress('\n'.join(lines))
1703
client.add_success_response_with_body(encoded_body, 'ok')
1704
client.add_expected_call(
1705
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1706
'error', ('NoSuchRevision', 'rev-id'))
1707
client.add_expected_call(
1708
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1711
branch = self.make_remote_branch(transport, client)
1714
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1716
self.assertFinished(client)
1718
def test_tip_change_rejected(self):
1719
"""TipChangeRejected responses cause a TipChangeRejected exception to
1722
transport = MemoryTransport()
1723
transport.mkdir('branch')
1724
transport = transport.clone('branch')
1725
client = FakeClient(transport.base)
1726
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1727
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1728
client.add_expected_call(
1729
'Branch.get_stacked_on_url', ('branch/',),
1730
'error', ('NotStacked',))
1731
client.add_expected_call(
1732
'Branch.lock_write', ('branch/', '', ''),
1733
'success', ('ok', 'branch token', 'repo token'))
1734
client.add_expected_call(
1735
'Branch.last_revision_info',
1737
'success', ('ok', '0', 'null:'))
1739
encoded_body = bz2.compress('\n'.join(lines))
1740
client.add_success_response_with_body(encoded_body, 'ok')
1741
client.add_expected_call(
1742
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1743
'error', ('TipChangeRejected', rejection_msg_utf8))
1744
client.add_expected_call(
1745
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1747
branch = self.make_remote_branch(transport, client)
1749
# The 'TipChangeRejected' error response triggered by calling
1750
# set_last_revision_info causes a TipChangeRejected exception.
1751
err = self.assertRaises(
1752
errors.TipChangeRejected,
1753
branch._set_last_revision, 'rev-id')
1754
# The UTF-8 message from the response has been decoded into a unicode
1756
self.assertIsInstance(err.msg, unicode)
1757
self.assertEqual(rejection_msg_unicode, err.msg)
1759
self.assertFinished(client)
1762
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1764
def test_set_last_revision_info(self):
1765
# set_last_revision_info(num, 'rev-id') is translated to calling
1766
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1767
transport = MemoryTransport()
1768
transport.mkdir('branch')
1769
transport = transport.clone('branch')
1770
client = FakeClient(transport.base)
1771
# get_stacked_on_url
1772
client.add_error_response('NotStacked')
1774
client.add_success_response('ok', 'branch token', 'repo token')
1775
# query the current revision
1776
client.add_success_response('ok', '0', 'null:')
1778
client.add_success_response('ok')
1780
client.add_success_response('ok')
1782
branch = self.make_remote_branch(transport, client)
1783
# Lock the branch, reset the record of remote calls.
1786
result = branch.set_last_revision_info(1234, 'a-revision-id')
1788
[('call', 'Branch.last_revision_info', ('branch/',)),
1789
('call', 'Branch.set_last_revision_info',
1790
('branch/', 'branch token', 'repo token',
1791
'1234', 'a-revision-id'))],
1793
self.assertEqual(None, result)
1795
def test_no_such_revision(self):
1796
# A response of 'NoSuchRevision' is translated into an exception.
1797
transport = MemoryTransport()
1798
transport.mkdir('branch')
1799
transport = transport.clone('branch')
1800
client = FakeClient(transport.base)
1801
# get_stacked_on_url
1802
client.add_error_response('NotStacked')
1804
client.add_success_response('ok', 'branch token', 'repo token')
1806
client.add_error_response('NoSuchRevision', 'revid')
1808
client.add_success_response('ok')
1810
branch = self.make_remote_branch(transport, client)
1811
# Lock the branch, reset the record of remote calls.
1816
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1819
def test_backwards_compatibility(self):
1820
"""If the server does not support the Branch.set_last_revision_info
1821
verb (which is new in 1.4), then the client falls back to VFS methods.
1823
# This test is a little messy. Unlike most tests in this file, it
1824
# doesn't purely test what a Remote* object sends over the wire, and
1825
# how it reacts to responses from the wire. It instead relies partly
1826
# on asserting that the RemoteBranch will call
1827
# self._real_branch.set_last_revision_info(...).
1829
# First, set up our RemoteBranch with a FakeClient that raises
1830
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1831
transport = MemoryTransport()
1832
transport.mkdir('branch')
1833
transport = transport.clone('branch')
1834
client = FakeClient(transport.base)
1835
client.add_expected_call(
1836
'Branch.get_stacked_on_url', ('branch/',),
1837
'error', ('NotStacked',))
1838
client.add_expected_call(
1839
'Branch.last_revision_info',
1841
'success', ('ok', '0', 'null:'))
1842
client.add_expected_call(
1843
'Branch.set_last_revision_info',
1844
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1845
'unknown', 'Branch.set_last_revision_info')
1847
branch = self.make_remote_branch(transport, client)
1848
class StubRealBranch(object):
1851
def set_last_revision_info(self, revno, revision_id):
1853
('set_last_revision_info', revno, revision_id))
1854
def _clear_cached_state(self):
1856
real_branch = StubRealBranch()
1857
branch._real_branch = real_branch
1858
self.lock_remote_branch(branch)
1860
# Call set_last_revision_info, and verify it behaved as expected.
1861
result = branch.set_last_revision_info(1234, 'a-revision-id')
1863
[('set_last_revision_info', 1234, 'a-revision-id')],
1865
self.assertFinished(client)
1867
def test_unexpected_error(self):
1868
# If the server sends an error the client doesn't understand, it gets
1869
# turned into an UnknownErrorFromSmartServer, which is presented as a
1870
# non-internal error to the user.
1871
transport = MemoryTransport()
1872
transport.mkdir('branch')
1873
transport = transport.clone('branch')
1874
client = FakeClient(transport.base)
1875
# get_stacked_on_url
1876
client.add_error_response('NotStacked')
1878
client.add_success_response('ok', 'branch token', 'repo token')
1880
client.add_error_response('UnexpectedError')
1882
client.add_success_response('ok')
1884
branch = self.make_remote_branch(transport, client)
1885
# Lock the branch, reset the record of remote calls.
1889
err = self.assertRaises(
1890
errors.UnknownErrorFromSmartServer,
1891
branch.set_last_revision_info, 123, 'revid')
1892
self.assertEqual(('UnexpectedError',), err.error_tuple)
1895
def test_tip_change_rejected(self):
1896
"""TipChangeRejected responses cause a TipChangeRejected exception to
1899
transport = MemoryTransport()
1900
transport.mkdir('branch')
1901
transport = transport.clone('branch')
1902
client = FakeClient(transport.base)
1903
# get_stacked_on_url
1904
client.add_error_response('NotStacked')
1906
client.add_success_response('ok', 'branch token', 'repo token')
1908
client.add_error_response('TipChangeRejected', 'rejection message')
1910
client.add_success_response('ok')
1912
branch = self.make_remote_branch(transport, client)
1913
# Lock the branch, reset the record of remote calls.
1915
self.addCleanup(branch.unlock)
1918
# The 'TipChangeRejected' error response triggered by calling
1919
# set_last_revision_info causes a TipChangeRejected exception.
1920
err = self.assertRaises(
1921
errors.TipChangeRejected,
1922
branch.set_last_revision_info, 123, 'revid')
1923
self.assertEqual('rejection message', err.msg)
1926
class TestBranchGetSetConfig(RemoteBranchTestCase):
1928
def test_get_branch_conf(self):
1929
# in an empty branch we decode the response properly
1930
client = FakeClient()
1931
client.add_expected_call(
1932
'Branch.get_stacked_on_url', ('memory:///',),
1933
'error', ('NotStacked',),)
1934
client.add_success_response_with_body('# config file body', 'ok')
1935
transport = MemoryTransport()
1936
branch = self.make_remote_branch(transport, client)
1937
config = branch.get_config()
1938
config.has_explicit_nickname()
1940
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1941
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1944
def test_get_multi_line_branch_conf(self):
1945
# Make sure that multiple-line branch.conf files are supported
1947
# https://bugs.launchpad.net/bzr/+bug/354075
1948
client = FakeClient()
1949
client.add_expected_call(
1950
'Branch.get_stacked_on_url', ('memory:///',),
1951
'error', ('NotStacked',),)
1952
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1953
transport = MemoryTransport()
1954
branch = self.make_remote_branch(transport, client)
1955
config = branch.get_config()
1956
self.assertEqual(u'2', config.get_user_option('b'))
1958
def test_set_option(self):
1959
client = FakeClient()
1960
client.add_expected_call(
1961
'Branch.get_stacked_on_url', ('memory:///',),
1962
'error', ('NotStacked',),)
1963
client.add_expected_call(
1964
'Branch.lock_write', ('memory:///', '', ''),
1965
'success', ('ok', 'branch token', 'repo token'))
1966
client.add_expected_call(
1967
'Branch.set_config_option', ('memory:///', 'branch token',
1968
'repo token', 'foo', 'bar', ''),
1970
client.add_expected_call(
1971
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1973
transport = MemoryTransport()
1974
branch = self.make_remote_branch(transport, client)
1976
config = branch._get_config()
1977
config.set_option('foo', 'bar')
1979
self.assertFinished(client)
1981
def test_set_option_with_dict(self):
1982
client = FakeClient()
1983
client.add_expected_call(
1984
'Branch.get_stacked_on_url', ('memory:///',),
1985
'error', ('NotStacked',),)
1986
client.add_expected_call(
1987
'Branch.lock_write', ('memory:///', '', ''),
1988
'success', ('ok', 'branch token', 'repo token'))
1989
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1990
client.add_expected_call(
1991
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1992
'repo token', encoded_dict_value, 'foo', ''),
1994
client.add_expected_call(
1995
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1997
transport = MemoryTransport()
1998
branch = self.make_remote_branch(transport, client)
2000
config = branch._get_config()
2002
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2005
self.assertFinished(client)
2007
def test_backwards_compat_set_option(self):
2008
self.setup_smart_server_with_call_log()
2009
branch = self.make_branch('.')
2010
verb = 'Branch.set_config_option'
2011
self.disable_verb(verb)
2013
self.addCleanup(branch.unlock)
2014
self.reset_smart_call_log()
2015
branch._get_config().set_option('value', 'name')
2016
self.assertLength(11, self.hpss_calls)
2017
self.assertEqual('value', branch._get_config().get_option('name'))
2019
def test_backwards_compat_set_option_with_dict(self):
2020
self.setup_smart_server_with_call_log()
2021
branch = self.make_branch('.')
2022
verb = 'Branch.set_config_option_dict'
2023
self.disable_verb(verb)
2025
self.addCleanup(branch.unlock)
2026
self.reset_smart_call_log()
2027
config = branch._get_config()
2028
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2029
config.set_option(value_dict, 'name')
2030
self.assertLength(11, self.hpss_calls)
2031
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2034
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2036
def test_get_branch_conf(self):
2037
# in an empty branch we decode the response properly
2038
client = FakeClient()
2039
client.add_expected_call(
2040
'Branch.get_stacked_on_url', ('memory:///',),
2041
'error', ('NotStacked',),)
2042
client.add_success_response_with_body('# config file body', 'ok')
2043
transport = MemoryTransport()
2044
branch = self.make_remote_branch(transport, client)
2045
config = branch.get_config_stack()
2047
config.get("log_format")
2049
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2050
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2053
def test_set_branch_conf(self):
2054
client = FakeClient()
2055
client.add_expected_call(
2056
'Branch.get_stacked_on_url', ('memory:///',),
2057
'error', ('NotStacked',),)
2058
client.add_expected_call(
2059
'Branch.lock_write', ('memory:///', '', ''),
2060
'success', ('ok', 'branch token', 'repo token'))
2061
client.add_expected_call(
2062
'Branch.get_config_file', ('memory:///', ),
2063
'success', ('ok', ), "# line 1\n")
2064
client.add_expected_call(
2065
'Branch.get_config_file', ('memory:///', ),
2066
'success', ('ok', ), "# line 1\n")
2067
client.add_expected_call(
2068
'Branch.put_config_file', ('memory:///', 'branch token',
2071
client.add_expected_call(
2072
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2074
transport = MemoryTransport()
2075
branch = self.make_remote_branch(transport, client)
2077
config = branch.get_config_stack()
2078
config.set('email', 'The Dude <lebowski@example.com>')
2080
self.assertFinished(client)
2082
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2083
('call', 'Branch.lock_write', ('memory:///', '', '')),
2084
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2085
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2086
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2087
('memory:///', 'branch token', 'repo token'),
2088
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2089
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2093
class TestBranchLockWrite(RemoteBranchTestCase):
2095
def test_lock_write_unlockable(self):
2096
transport = MemoryTransport()
2097
client = FakeClient(transport.base)
2098
client.add_expected_call(
2099
'Branch.get_stacked_on_url', ('quack/',),
2100
'error', ('NotStacked',),)
2101
client.add_expected_call(
2102
'Branch.lock_write', ('quack/', '', ''),
2103
'error', ('UnlockableTransport',))
2104
transport.mkdir('quack')
2105
transport = transport.clone('quack')
2106
branch = self.make_remote_branch(transport, client)
2107
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2108
self.assertFinished(client)
2111
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2113
def test_simple(self):
2114
transport = MemoryTransport()
2115
client = FakeClient(transport.base)
2116
client.add_expected_call(
2117
'Branch.get_stacked_on_url', ('quack/',),
2118
'error', ('NotStacked',),)
2119
client.add_expected_call(
2120
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2121
'success', ('ok', '0',),)
2122
client.add_expected_call(
2123
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2124
'error', ('NoSuchRevision', 'unknown',),)
2125
transport.mkdir('quack')
2126
transport = transport.clone('quack')
2127
branch = self.make_remote_branch(transport, client)
2128
self.assertEqual(0, branch.revision_id_to_revno('null:'))
2129
self.assertRaises(errors.NoSuchRevision,
2130
branch.revision_id_to_revno, 'unknown')
2131
self.assertFinished(client)
2133
def test_dotted(self):
2134
transport = MemoryTransport()
2135
client = FakeClient(transport.base)
2136
client.add_expected_call(
2137
'Branch.get_stacked_on_url', ('quack/',),
2138
'error', ('NotStacked',),)
2139
client.add_expected_call(
2140
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2141
'success', ('ok', '0',),)
2142
client.add_expected_call(
2143
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2144
'error', ('NoSuchRevision', 'unknown',),)
2145
transport.mkdir('quack')
2146
transport = transport.clone('quack')
2147
branch = self.make_remote_branch(transport, client)
2148
self.assertEqual((0, ), branch.revision_id_to_dotted_revno('null:'))
2149
self.assertRaises(errors.NoSuchRevision,
2150
branch.revision_id_to_dotted_revno, 'unknown')
2151
self.assertFinished(client)
2153
def test_dotted_no_smart_verb(self):
2154
self.setup_smart_server_with_call_log()
2155
branch = self.make_branch('.')
2156
self.disable_verb('Branch.revision_id_to_revno')
2157
self.reset_smart_call_log()
2158
self.assertEqual((0, ),
2159
branch.revision_id_to_dotted_revno('null:'))
2160
self.assertLength(8, self.hpss_calls)
2163
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2165
def test__get_config(self):
2166
client = FakeClient()
2167
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2168
transport = MemoryTransport()
2169
bzrdir = self.make_remote_bzrdir(transport, client)
2170
config = bzrdir.get_config()
2171
self.assertEqual('/', config.get_default_stack_on())
2173
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2176
def test_set_option_uses_vfs(self):
2177
self.setup_smart_server_with_call_log()
2178
bzrdir = self.make_bzrdir('.')
2179
self.reset_smart_call_log()
2180
config = bzrdir.get_config()
2181
config.set_default_stack_on('/')
2182
self.assertLength(4, self.hpss_calls)
2184
def test_backwards_compat_get_option(self):
2185
self.setup_smart_server_with_call_log()
2186
bzrdir = self.make_bzrdir('.')
2187
verb = 'BzrDir.get_config_file'
2188
self.disable_verb(verb)
2189
self.reset_smart_call_log()
2190
self.assertEqual(None,
2191
bzrdir._get_config().get_option('default_stack_on'))
2192
self.assertLength(4, self.hpss_calls)
2195
class TestTransportIsReadonly(tests.TestCase):
2197
def test_true(self):
2198
client = FakeClient()
2199
client.add_success_response('yes')
2200
transport = RemoteTransport('bzr://example.com/', medium=False,
2202
self.assertEqual(True, transport.is_readonly())
2204
[('call', 'Transport.is_readonly', ())],
2207
def test_false(self):
2208
client = FakeClient()
2209
client.add_success_response('no')
2210
transport = RemoteTransport('bzr://example.com/', medium=False,
2212
self.assertEqual(False, transport.is_readonly())
2214
[('call', 'Transport.is_readonly', ())],
2217
def test_error_from_old_server(self):
2218
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2220
Clients should treat it as a "no" response, because is_readonly is only
2221
advisory anyway (a transport could be read-write, but then the
2222
underlying filesystem could be readonly anyway).
2224
client = FakeClient()
2225
client.add_unknown_method_response('Transport.is_readonly')
2226
transport = RemoteTransport('bzr://example.com/', medium=False,
2228
self.assertEqual(False, transport.is_readonly())
2230
[('call', 'Transport.is_readonly', ())],
2234
class TestTransportMkdir(tests.TestCase):
2236
def test_permissiondenied(self):
2237
client = FakeClient()
2238
client.add_error_response('PermissionDenied', 'remote path', 'extra')
2239
transport = RemoteTransport('bzr://example.com/', medium=False,
2241
exc = self.assertRaises(
2242
errors.PermissionDenied, transport.mkdir, 'client path')
2243
expected_error = errors.PermissionDenied('/client path', 'extra')
2244
self.assertEqual(expected_error, exc)
2247
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2249
def test_defaults_to_none(self):
2250
t = RemoteSSHTransport('bzr+ssh://example.com')
2251
self.assertIs(None, t._get_credentials()[0])
2253
def test_uses_authentication_config(self):
2254
conf = config.AuthenticationConfig()
2255
conf._get_config().update(
2256
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2259
t = RemoteSSHTransport('bzr+ssh://example.com')
2260
self.assertEqual('bar', t._get_credentials()[0])
2263
class TestRemoteRepository(TestRemote):
2264
"""Base for testing RemoteRepository protocol usage.
2266
These tests contain frozen requests and responses. We want any changes to
2267
what is sent or expected to be require a thoughtful update to these tests
2268
because they might break compatibility with different-versioned servers.
2271
def setup_fake_client_and_repository(self, transport_path):
2272
"""Create the fake client and repository for testing with.
2274
There's no real server here; we just have canned responses sent
2277
:param transport_path: Path below the root of the MemoryTransport
2278
where the repository will be created.
2280
transport = MemoryTransport()
2281
transport.mkdir(transport_path)
2282
client = FakeClient(transport.base)
2283
transport = transport.clone(transport_path)
2284
# we do not want bzrdir to make any remote calls
2285
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2287
repo = RemoteRepository(bzrdir, None, _client=client)
2291
def remoted_description(format):
2292
return 'Remote: ' + format.get_format_description()
2295
class TestBranchFormat(tests.TestCase):
2297
def test_get_format_description(self):
2298
remote_format = RemoteBranchFormat()
2299
real_format = branch.format_registry.get_default()
2300
remote_format._network_name = real_format.network_name()
2301
self.assertEqual(remoted_description(real_format),
2302
remote_format.get_format_description())
2305
class TestRepositoryFormat(TestRemoteRepository):
2307
def test_fast_delta(self):
2308
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2309
true_format = RemoteRepositoryFormat()
2310
true_format._network_name = true_name
2311
self.assertEqual(True, true_format.fast_deltas)
2312
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2313
false_format = RemoteRepositoryFormat()
2314
false_format._network_name = false_name
2315
self.assertEqual(False, false_format.fast_deltas)
2317
def test_get_format_description(self):
2318
remote_repo_format = RemoteRepositoryFormat()
2319
real_format = repository.format_registry.get_default()
2320
remote_repo_format._network_name = real_format.network_name()
2321
self.assertEqual(remoted_description(real_format),
2322
remote_repo_format.get_format_description())
2325
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2327
def test_empty(self):
2328
transport_path = 'quack'
2329
repo, client = self.setup_fake_client_and_repository(transport_path)
2330
client.add_success_response_with_body('', 'ok')
2331
self.assertEqual([], repo.all_revision_ids())
2333
[('call_expecting_body', 'Repository.all_revision_ids',
2337
def test_with_some_content(self):
2338
transport_path = 'quack'
2339
repo, client = self.setup_fake_client_and_repository(transport_path)
2340
client.add_success_response_with_body(
2341
'rev1\nrev2\nanotherrev\n', 'ok')
2342
self.assertEqual(["rev1", "rev2", "anotherrev"],
2343
repo.all_revision_ids())
2345
[('call_expecting_body', 'Repository.all_revision_ids',
2350
class TestRepositoryGatherStats(TestRemoteRepository):
2352
def test_revid_none(self):
2353
# ('ok',), body with revisions and size
2354
transport_path = 'quack'
2355
repo, client = self.setup_fake_client_and_repository(transport_path)
2356
client.add_success_response_with_body(
2357
'revisions: 2\nsize: 18\n', 'ok')
2358
result = repo.gather_stats(None)
2360
[('call_expecting_body', 'Repository.gather_stats',
2361
('quack/','','no'))],
2363
self.assertEqual({'revisions': 2, 'size': 18}, result)
2365
def test_revid_no_committers(self):
2366
# ('ok',), body without committers
2367
body = ('firstrev: 123456.300 3600\n'
2368
'latestrev: 654231.400 0\n'
2371
transport_path = 'quick'
2372
revid = u'\xc8'.encode('utf8')
2373
repo, client = self.setup_fake_client_and_repository(transport_path)
2374
client.add_success_response_with_body(body, 'ok')
2375
result = repo.gather_stats(revid)
2377
[('call_expecting_body', 'Repository.gather_stats',
2378
('quick/', revid, 'no'))],
2380
self.assertEqual({'revisions': 2, 'size': 18,
2381
'firstrev': (123456.300, 3600),
2382
'latestrev': (654231.400, 0),},
2385
def test_revid_with_committers(self):
2386
# ('ok',), body with committers
2387
body = ('committers: 128\n'
2388
'firstrev: 123456.300 3600\n'
2389
'latestrev: 654231.400 0\n'
2392
transport_path = 'buick'
2393
revid = u'\xc8'.encode('utf8')
2394
repo, client = self.setup_fake_client_and_repository(transport_path)
2395
client.add_success_response_with_body(body, 'ok')
2396
result = repo.gather_stats(revid, True)
2398
[('call_expecting_body', 'Repository.gather_stats',
2399
('buick/', revid, 'yes'))],
2401
self.assertEqual({'revisions': 2, 'size': 18,
2403
'firstrev': (123456.300, 3600),
2404
'latestrev': (654231.400, 0),},
2408
class TestRepositoryBreakLock(TestRemoteRepository):
2410
def test_break_lock(self):
2411
transport_path = 'quack'
2412
repo, client = self.setup_fake_client_and_repository(transport_path)
2413
client.add_success_response('ok')
2416
[('call', 'Repository.break_lock', ('quack/',))],
2420
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2422
def test_get_serializer_format(self):
2423
transport_path = 'hill'
2424
repo, client = self.setup_fake_client_and_repository(transport_path)
2425
client.add_success_response('ok', '7')
2426
self.assertEqual('7', repo.get_serializer_format())
2428
[('call', 'VersionedFileRepository.get_serializer_format',
2433
class TestRepositoryReconcile(TestRemoteRepository):
2435
def test_reconcile(self):
2436
transport_path = 'hill'
2437
repo, client = self.setup_fake_client_and_repository(transport_path)
2438
body = ("garbage_inventories: 2\n"
2439
"inconsistent_parents: 3\n")
2440
client.add_expected_call(
2441
'Repository.lock_write', ('hill/', ''),
2442
'success', ('ok', 'a token'))
2443
client.add_success_response_with_body(body, 'ok')
2444
reconciler = repo.reconcile()
2446
[('call', 'Repository.lock_write', ('hill/', '')),
2447
('call_expecting_body', 'Repository.reconcile',
2448
('hill/', 'a token'))],
2450
self.assertEqual(2, reconciler.garbage_inventories)
2451
self.assertEqual(3, reconciler.inconsistent_parents)
2454
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2456
def test_text(self):
2457
# ('ok',), body with signature text
2458
transport_path = 'quack'
2459
repo, client = self.setup_fake_client_and_repository(transport_path)
2460
client.add_success_response_with_body(
2462
self.assertEqual("THETEXT", repo.get_signature_text("revid"))
2464
[('call_expecting_body', 'Repository.get_revision_signature_text',
2465
('quack/', 'revid'))],
2468
def test_no_signature(self):
2469
transport_path = 'quick'
2470
repo, client = self.setup_fake_client_and_repository(transport_path)
2471
client.add_error_response('nosuchrevision', 'unknown')
2472
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2475
[('call_expecting_body', 'Repository.get_revision_signature_text',
2476
('quick/', 'unknown'))],
2480
class TestRepositoryGetGraph(TestRemoteRepository):
2482
def test_get_graph(self):
2483
# get_graph returns a graph with a custom parents provider.
2484
transport_path = 'quack'
2485
repo, client = self.setup_fake_client_and_repository(transport_path)
2486
graph = repo.get_graph()
2487
self.assertNotEqual(graph._parents_provider, repo)
2490
class TestRepositoryAddSignatureText(TestRemoteRepository):
2492
def test_add_signature_text(self):
2493
transport_path = 'quack'
2494
repo, client = self.setup_fake_client_and_repository(transport_path)
2495
client.add_expected_call(
2496
'Repository.lock_write', ('quack/', ''),
2497
'success', ('ok', 'a token'))
2498
client.add_expected_call(
2499
'Repository.start_write_group', ('quack/', 'a token'),
2500
'success', ('ok', ('token1', )))
2501
client.add_expected_call(
2502
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2504
'success', ('ok', ), None)
2506
repo.start_write_group()
2508
repo.add_signature_text("rev1", "every bloody emperor"))
2510
('call_with_body_bytes_expecting_body',
2511
'Repository.add_signature_text',
2512
('quack/', 'a token', 'rev1', 'token1'),
2513
'every bloody emperor'),
2517
class TestRepositoryGetParentMap(TestRemoteRepository):
2519
def test_get_parent_map_caching(self):
2520
# get_parent_map returns from cache until unlock()
2521
# setup a reponse with two revisions
2522
r1 = u'\u0e33'.encode('utf8')
2523
r2 = u'\u0dab'.encode('utf8')
2524
lines = [' '.join([r2, r1]), r1]
2525
encoded_body = bz2.compress('\n'.join(lines))
2527
transport_path = 'quack'
2528
repo, client = self.setup_fake_client_and_repository(transport_path)
2529
client.add_success_response_with_body(encoded_body, 'ok')
2530
client.add_success_response_with_body(encoded_body, 'ok')
2532
graph = repo.get_graph()
2533
parents = graph.get_parent_map([r2])
2534
self.assertEqual({r2: (r1,)}, parents)
2535
# locking and unlocking deeper should not reset
2538
parents = graph.get_parent_map([r1])
2539
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2541
[('call_with_body_bytes_expecting_body',
2542
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2546
# now we call again, and it should use the second response.
2548
graph = repo.get_graph()
2549
parents = graph.get_parent_map([r1])
2550
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2552
[('call_with_body_bytes_expecting_body',
2553
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2555
('call_with_body_bytes_expecting_body',
2556
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2562
def test_get_parent_map_reconnects_if_unknown_method(self):
2563
transport_path = 'quack'
2564
rev_id = 'revision-id'
2565
repo, client = self.setup_fake_client_and_repository(transport_path)
2566
client.add_unknown_method_response('Repository.get_parent_map')
2567
client.add_success_response_with_body(rev_id, 'ok')
2568
self.assertFalse(client._medium._is_remote_before((1, 2)))
2569
parents = repo.get_parent_map([rev_id])
2571
[('call_with_body_bytes_expecting_body',
2572
'Repository.get_parent_map',
2573
('quack/', 'include-missing:', rev_id), '\n\n0'),
2574
('disconnect medium',),
2575
('call_expecting_body', 'Repository.get_revision_graph',
2578
# The medium is now marked as being connected to an older server
2579
self.assertTrue(client._medium._is_remote_before((1, 2)))
2580
self.assertEqual({rev_id: ('null:',)}, parents)
2582
def test_get_parent_map_fallback_parentless_node(self):
2583
"""get_parent_map falls back to get_revision_graph on old servers. The
2584
results from get_revision_graph are tweaked to match the get_parent_map
2587
Specifically, a {key: ()} result from get_revision_graph means "no
2588
parents" for that key, which in get_parent_map results should be
2589
represented as {key: ('null:',)}.
2591
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2593
rev_id = 'revision-id'
2594
transport_path = 'quack'
2595
repo, client = self.setup_fake_client_and_repository(transport_path)
2596
client.add_success_response_with_body(rev_id, 'ok')
2597
client._medium._remember_remote_is_before((1, 2))
2598
parents = repo.get_parent_map([rev_id])
2600
[('call_expecting_body', 'Repository.get_revision_graph',
2603
self.assertEqual({rev_id: ('null:',)}, parents)
2605
def test_get_parent_map_unexpected_response(self):
2606
repo, client = self.setup_fake_client_and_repository('path')
2607
client.add_success_response('something unexpected!')
2609
errors.UnexpectedSmartServerResponse,
2610
repo.get_parent_map, ['a-revision-id'])
2612
def test_get_parent_map_negative_caches_missing_keys(self):
2613
self.setup_smart_server_with_call_log()
2614
repo = self.make_repository('foo')
2615
self.assertIsInstance(repo, RemoteRepository)
2617
self.addCleanup(repo.unlock)
2618
self.reset_smart_call_log()
2619
graph = repo.get_graph()
2620
self.assertEqual({},
2621
graph.get_parent_map(['some-missing', 'other-missing']))
2622
self.assertLength(1, self.hpss_calls)
2623
# No call if we repeat this
2624
self.reset_smart_call_log()
2625
graph = repo.get_graph()
2626
self.assertEqual({},
2627
graph.get_parent_map(['some-missing', 'other-missing']))
2628
self.assertLength(0, self.hpss_calls)
2629
# Asking for more unknown keys makes a request.
2630
self.reset_smart_call_log()
2631
graph = repo.get_graph()
2632
self.assertEqual({},
2633
graph.get_parent_map(['some-missing', 'other-missing',
2635
self.assertLength(1, self.hpss_calls)
2637
def disableExtraResults(self):
2638
self.overrideAttr(SmartServerRepositoryGetParentMap,
2639
'no_extra_results', True)
2641
def test_null_cached_missing_and_stop_key(self):
2642
self.setup_smart_server_with_call_log()
2643
# Make a branch with a single revision.
2644
builder = self.make_branch_builder('foo')
2645
builder.start_series()
2646
builder.build_snapshot('first', None, [
2647
('add', ('', 'root-id', 'directory', ''))])
2648
builder.finish_series()
2649
branch = builder.get_branch()
2650
repo = branch.repository
2651
self.assertIsInstance(repo, RemoteRepository)
2652
# Stop the server from sending extra results.
2653
self.disableExtraResults()
2655
self.addCleanup(repo.unlock)
2656
self.reset_smart_call_log()
2657
graph = repo.get_graph()
2658
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2659
# 'first' it will be a candidate for the stop_keys of subsequent
2660
# requests, and because 'null:' was queried but not returned it will be
2661
# cached as missing.
2662
self.assertEqual({'first': ('null:',)},
2663
graph.get_parent_map(['first', 'null:']))
2664
# Now query for another key. This request will pass along a recipe of
2665
# start and stop keys describing the already cached results, and this
2666
# recipe's revision count must be correct (or else it will trigger an
2667
# error from the server).
2668
self.assertEqual({}, graph.get_parent_map(['another-key']))
2669
# This assertion guards against disableExtraResults silently failing to
2670
# work, thus invalidating the test.
2671
self.assertLength(2, self.hpss_calls)
2673
def test_get_parent_map_gets_ghosts_from_result(self):
2674
# asking for a revision should negatively cache close ghosts in its
2676
self.setup_smart_server_with_call_log()
2677
tree = self.make_branch_and_memory_tree('foo')
2680
builder = treebuilder.TreeBuilder()
2681
builder.start_tree(tree)
2683
builder.finish_tree()
2684
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2685
rev_id = tree.commit('')
2689
self.addCleanup(tree.unlock)
2690
repo = tree.branch.repository
2691
self.assertIsInstance(repo, RemoteRepository)
2693
repo.get_parent_map([rev_id])
2694
self.reset_smart_call_log()
2695
# Now asking for rev_id's ghost parent should not make calls
2696
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2697
self.assertLength(0, self.hpss_calls)
2699
def test_exposes_get_cached_parent_map(self):
2700
"""RemoteRepository exposes get_cached_parent_map from
2703
r1 = u'\u0e33'.encode('utf8')
2704
r2 = u'\u0dab'.encode('utf8')
2705
lines = [' '.join([r2, r1]), r1]
2706
encoded_body = bz2.compress('\n'.join(lines))
2708
transport_path = 'quack'
2709
repo, client = self.setup_fake_client_and_repository(transport_path)
2710
client.add_success_response_with_body(encoded_body, 'ok')
2712
# get_cached_parent_map should *not* trigger an RPC
2713
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2714
self.assertEqual([], client._calls)
2715
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2716
self.assertEqual({r1: (NULL_REVISION,)},
2717
repo.get_cached_parent_map([r1]))
2719
[('call_with_body_bytes_expecting_body',
2720
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2726
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2728
def test_allows_new_revisions(self):
2729
"""get_parent_map's results can be updated by commit."""
2730
smart_server = test_server.SmartTCPServer_for_testing()
2731
self.start_server(smart_server)
2732
self.make_branch('branch')
2733
branch = Branch.open(smart_server.get_url() + '/branch')
2734
tree = branch.create_checkout('tree', lightweight=True)
2736
self.addCleanup(tree.unlock)
2737
graph = tree.branch.repository.get_graph()
2738
# This provides an opportunity for the missing rev-id to be cached.
2739
self.assertEqual({}, graph.get_parent_map(['rev1']))
2740
tree.commit('message', rev_id='rev1')
2741
graph = tree.branch.repository.get_graph()
2742
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2745
class TestRepositoryGetRevisions(TestRemoteRepository):
2747
def test_hpss_missing_revision(self):
2748
transport_path = 'quack'
2749
repo, client = self.setup_fake_client_and_repository(transport_path)
2750
client.add_success_response_with_body(
2752
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2753
['somerev1', 'anotherrev2'])
2755
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2756
('quack/', ), "somerev1\nanotherrev2")],
2759
def test_hpss_get_single_revision(self):
2760
transport_path = 'quack'
2761
repo, client = self.setup_fake_client_and_repository(transport_path)
2762
somerev1 = Revision("somerev1")
2763
somerev1.committer = "Joe Committer <joe@example.com>"
2764
somerev1.timestamp = 1321828927
2765
somerev1.timezone = -60
2766
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2767
somerev1.message = "Message"
2768
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2770
# Split up body into two bits to make sure the zlib compression object
2771
# gets data fed twice.
2772
client.add_success_response_with_body(
2773
[body[:10], body[10:]], 'ok', '10')
2774
revs = repo.get_revisions(['somerev1'])
2775
self.assertEqual(revs, [somerev1])
2777
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2778
('quack/', ), "somerev1")],
2782
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2784
def test_null_revision(self):
2785
# a null revision has the predictable result {}, we should have no wire
2786
# traffic when calling it with this argument
2787
transport_path = 'empty'
2788
repo, client = self.setup_fake_client_and_repository(transport_path)
2789
client.add_success_response('notused')
2790
# actual RemoteRepository.get_revision_graph is gone, but there's an
2791
# equivalent private method for testing
2792
result = repo._get_revision_graph(NULL_REVISION)
2793
self.assertEqual([], client._calls)
2794
self.assertEqual({}, result)
2796
def test_none_revision(self):
2797
# with none we want the entire graph
2798
r1 = u'\u0e33'.encode('utf8')
2799
r2 = u'\u0dab'.encode('utf8')
2800
lines = [' '.join([r2, r1]), r1]
2801
encoded_body = '\n'.join(lines)
2803
transport_path = 'sinhala'
2804
repo, client = self.setup_fake_client_and_repository(transport_path)
2805
client.add_success_response_with_body(encoded_body, 'ok')
2806
# actual RemoteRepository.get_revision_graph is gone, but there's an
2807
# equivalent private method for testing
2808
result = repo._get_revision_graph(None)
2810
[('call_expecting_body', 'Repository.get_revision_graph',
2813
self.assertEqual({r1: (), r2: (r1, )}, result)
2815
def test_specific_revision(self):
2816
# with a specific revision we want the graph for that
2817
# with none we want the entire graph
2818
r11 = u'\u0e33'.encode('utf8')
2819
r12 = u'\xc9'.encode('utf8')
2820
r2 = u'\u0dab'.encode('utf8')
2821
lines = [' '.join([r2, r11, r12]), r11, r12]
2822
encoded_body = '\n'.join(lines)
2824
transport_path = 'sinhala'
2825
repo, client = self.setup_fake_client_and_repository(transport_path)
2826
client.add_success_response_with_body(encoded_body, 'ok')
2827
result = repo._get_revision_graph(r2)
2829
[('call_expecting_body', 'Repository.get_revision_graph',
2832
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2834
def test_no_such_revision(self):
2836
transport_path = 'sinhala'
2837
repo, client = self.setup_fake_client_and_repository(transport_path)
2838
client.add_error_response('nosuchrevision', revid)
2839
# also check that the right revision is reported in the error
2840
self.assertRaises(errors.NoSuchRevision,
2841
repo._get_revision_graph, revid)
2843
[('call_expecting_body', 'Repository.get_revision_graph',
2844
('sinhala/', revid))],
2847
def test_unexpected_error(self):
2849
transport_path = 'sinhala'
2850
repo, client = self.setup_fake_client_and_repository(transport_path)
2851
client.add_error_response('AnUnexpectedError')
2852
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2853
repo._get_revision_graph, revid)
2854
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2857
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2860
repo, client = self.setup_fake_client_and_repository('quack')
2861
client.add_expected_call(
2862
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2863
'success', ('ok', 'rev-five'))
2864
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2865
self.assertEqual((True, 'rev-five'), result)
2866
self.assertFinished(client)
2868
def test_history_incomplete(self):
2869
repo, client = self.setup_fake_client_and_repository('quack')
2870
client.add_expected_call(
2871
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2872
'success', ('history-incomplete', 10, 'rev-ten'))
2873
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2874
self.assertEqual((False, (10, 'rev-ten')), result)
2875
self.assertFinished(client)
2877
def test_history_incomplete_with_fallback(self):
2878
"""A 'history-incomplete' response causes the fallback repository to be
2879
queried too, if one is set.
2881
# Make a repo with a fallback repo, both using a FakeClient.
2882
format = remote.response_tuple_to_repo_format(
2883
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2884
repo, client = self.setup_fake_client_and_repository('quack')
2885
repo._format = format
2886
fallback_repo, ignored = self.setup_fake_client_and_repository(
2888
fallback_repo._client = client
2889
fallback_repo._format = format
2890
repo.add_fallback_repository(fallback_repo)
2891
# First the client should ask the primary repo
2892
client.add_expected_call(
2893
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2894
'success', ('history-incomplete', 2, 'rev-two'))
2895
# Then it should ask the fallback, using revno/revid from the
2896
# history-incomplete response as the known revno/revid.
2897
client.add_expected_call(
2898
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2899
'success', ('ok', 'rev-one'))
2900
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2901
self.assertEqual((True, 'rev-one'), result)
2902
self.assertFinished(client)
2904
def test_nosuchrevision(self):
2905
# 'nosuchrevision' is returned when the known-revid is not found in the
2906
# remote repo. The client translates that response to NoSuchRevision.
2907
repo, client = self.setup_fake_client_and_repository('quack')
2908
client.add_expected_call(
2909
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2910
'error', ('nosuchrevision', 'rev-foo'))
2912
errors.NoSuchRevision,
2913
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2914
self.assertFinished(client)
2916
def test_branch_fallback_locking(self):
2917
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2918
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2919
will be invoked, which will fail if the repo is unlocked.
2921
self.setup_smart_server_with_call_log()
2922
tree = self.make_branch_and_memory_tree('.')
2925
rev1 = tree.commit('First')
2926
rev2 = tree.commit('Second')
2928
branch = tree.branch
2929
self.assertFalse(branch.is_locked())
2930
self.reset_smart_call_log()
2931
verb = 'Repository.get_rev_id_for_revno'
2932
self.disable_verb(verb)
2933
self.assertEqual(rev1, branch.get_rev_id(1))
2934
self.assertLength(1, [call for call in self.hpss_calls if
2935
call.call.method == verb])
2938
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2940
def test_has_signature_for_revision_id(self):
2941
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2942
transport_path = 'quack'
2943
repo, client = self.setup_fake_client_and_repository(transport_path)
2944
client.add_success_response('yes')
2945
result = repo.has_signature_for_revision_id('A')
2947
[('call', 'Repository.has_signature_for_revision_id',
2950
self.assertEqual(True, result)
2952
def test_is_not_shared(self):
2953
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2954
transport_path = 'qwack'
2955
repo, client = self.setup_fake_client_and_repository(transport_path)
2956
client.add_success_response('no')
2957
result = repo.has_signature_for_revision_id('A')
2959
[('call', 'Repository.has_signature_for_revision_id',
2962
self.assertEqual(False, result)
2965
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2967
def test_get_physical_lock_status_yes(self):
2968
transport_path = 'qwack'
2969
repo, client = self.setup_fake_client_and_repository(transport_path)
2970
client.add_success_response('yes')
2971
result = repo.get_physical_lock_status()
2973
[('call', 'Repository.get_physical_lock_status',
2976
self.assertEqual(True, result)
2978
def test_get_physical_lock_status_no(self):
2979
transport_path = 'qwack'
2980
repo, client = self.setup_fake_client_and_repository(transport_path)
2981
client.add_success_response('no')
2982
result = repo.get_physical_lock_status()
2984
[('call', 'Repository.get_physical_lock_status',
2987
self.assertEqual(False, result)
2990
class TestRepositoryIsShared(TestRemoteRepository):
2992
def test_is_shared(self):
2993
# ('yes', ) for Repository.is_shared -> 'True'.
2994
transport_path = 'quack'
2995
repo, client = self.setup_fake_client_and_repository(transport_path)
2996
client.add_success_response('yes')
2997
result = repo.is_shared()
2999
[('call', 'Repository.is_shared', ('quack/',))],
3001
self.assertEqual(True, result)
3003
def test_is_not_shared(self):
3004
# ('no', ) for Repository.is_shared -> 'False'.
3005
transport_path = 'qwack'
3006
repo, client = self.setup_fake_client_and_repository(transport_path)
3007
client.add_success_response('no')
3008
result = repo.is_shared()
3010
[('call', 'Repository.is_shared', ('qwack/',))],
3012
self.assertEqual(False, result)
3015
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3017
def test_make_working_trees(self):
3018
# ('yes', ) for Repository.make_working_trees -> 'True'.
3019
transport_path = 'quack'
3020
repo, client = self.setup_fake_client_and_repository(transport_path)
3021
client.add_success_response('yes')
3022
result = repo.make_working_trees()
3024
[('call', 'Repository.make_working_trees', ('quack/',))],
3026
self.assertEqual(True, result)
3028
def test_no_working_trees(self):
3029
# ('no', ) for Repository.make_working_trees -> 'False'.
3030
transport_path = 'qwack'
3031
repo, client = self.setup_fake_client_and_repository(transport_path)
3032
client.add_success_response('no')
3033
result = repo.make_working_trees()
3035
[('call', 'Repository.make_working_trees', ('qwack/',))],
3037
self.assertEqual(False, result)
3040
class TestRepositoryLockWrite(TestRemoteRepository):
3042
def test_lock_write(self):
3043
transport_path = 'quack'
3044
repo, client = self.setup_fake_client_and_repository(transport_path)
3045
client.add_success_response('ok', 'a token')
3046
token = repo.lock_write().repository_token
3048
[('call', 'Repository.lock_write', ('quack/', ''))],
3050
self.assertEqual('a token', token)
3052
def test_lock_write_already_locked(self):
3053
transport_path = 'quack'
3054
repo, client = self.setup_fake_client_and_repository(transport_path)
3055
client.add_error_response('LockContention')
3056
self.assertRaises(errors.LockContention, repo.lock_write)
3058
[('call', 'Repository.lock_write', ('quack/', ''))],
3061
def test_lock_write_unlockable(self):
3062
transport_path = 'quack'
3063
repo, client = self.setup_fake_client_and_repository(transport_path)
3064
client.add_error_response('UnlockableTransport')
3065
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3067
[('call', 'Repository.lock_write', ('quack/', ''))],
3071
class TestRepositoryWriteGroups(TestRemoteRepository):
3073
def test_start_write_group(self):
3074
transport_path = 'quack'
3075
repo, client = self.setup_fake_client_and_repository(transport_path)
3076
client.add_expected_call(
3077
'Repository.lock_write', ('quack/', ''),
3078
'success', ('ok', 'a token'))
3079
client.add_expected_call(
3080
'Repository.start_write_group', ('quack/', 'a token'),
3081
'success', ('ok', ('token1', )))
3083
repo.start_write_group()
3085
def test_start_write_group_unsuspendable(self):
3086
# Some repositories do not support suspending write
3087
# groups. For those, fall back to the "real" repository.
3088
transport_path = 'quack'
3089
repo, client = self.setup_fake_client_and_repository(transport_path)
3090
def stub_ensure_real():
3091
client._calls.append(('_ensure_real',))
3092
repo._real_repository = _StubRealPackRepository(client._calls)
3093
repo._ensure_real = stub_ensure_real
3094
client.add_expected_call(
3095
'Repository.lock_write', ('quack/', ''),
3096
'success', ('ok', 'a token'))
3097
client.add_expected_call(
3098
'Repository.start_write_group', ('quack/', 'a token'),
3099
'error', ('UnsuspendableWriteGroup',))
3101
repo.start_write_group()
3102
self.assertEqual(client._calls[-2:], [
3104
('start_write_group',)])
3106
def test_commit_write_group(self):
3107
transport_path = 'quack'
3108
repo, client = self.setup_fake_client_and_repository(transport_path)
3109
client.add_expected_call(
3110
'Repository.lock_write', ('quack/', ''),
3111
'success', ('ok', 'a token'))
3112
client.add_expected_call(
3113
'Repository.start_write_group', ('quack/', 'a token'),
3114
'success', ('ok', ['token1']))
3115
client.add_expected_call(
3116
'Repository.commit_write_group', ('quack/', 'a token', ['token1']),
3119
repo.start_write_group()
3120
repo.commit_write_group()
3122
def test_abort_write_group(self):
3123
transport_path = 'quack'
3124
repo, client = self.setup_fake_client_and_repository(transport_path)
3125
client.add_expected_call(
3126
'Repository.lock_write', ('quack/', ''),
3127
'success', ('ok', 'a token'))
3128
client.add_expected_call(
3129
'Repository.start_write_group', ('quack/', 'a token'),
3130
'success', ('ok', ['token1']))
3131
client.add_expected_call(
3132
'Repository.abort_write_group', ('quack/', 'a token', ['token1']),
3135
repo.start_write_group()
3136
repo.abort_write_group(False)
3138
def test_suspend_write_group(self):
3139
transport_path = 'quack'
3140
repo, client = self.setup_fake_client_and_repository(transport_path)
3141
self.assertEqual([], repo.suspend_write_group())
3143
def test_resume_write_group(self):
3144
transport_path = 'quack'
3145
repo, client = self.setup_fake_client_and_repository(transport_path)
3146
client.add_expected_call(
3147
'Repository.lock_write', ('quack/', ''),
3148
'success', ('ok', 'a token'))
3149
client.add_expected_call(
3150
'Repository.check_write_group', ('quack/', 'a token', ['token1']),
3153
repo.resume_write_group(['token1'])
3156
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3158
def test_backwards_compat(self):
3159
self.setup_smart_server_with_call_log()
3160
repo = self.make_repository('.')
3161
self.reset_smart_call_log()
3162
verb = 'Repository.set_make_working_trees'
3163
self.disable_verb(verb)
3164
repo.set_make_working_trees(True)
3165
call_count = len([call for call in self.hpss_calls if
3166
call.call.method == verb])
3167
self.assertEqual(1, call_count)
3169
def test_current(self):
3170
transport_path = 'quack'
3171
repo, client = self.setup_fake_client_and_repository(transport_path)
3172
client.add_expected_call(
3173
'Repository.set_make_working_trees', ('quack/', 'True'),
3175
client.add_expected_call(
3176
'Repository.set_make_working_trees', ('quack/', 'False'),
3178
repo.set_make_working_trees(True)
3179
repo.set_make_working_trees(False)
3182
class TestRepositoryUnlock(TestRemoteRepository):
3184
def test_unlock(self):
3185
transport_path = 'quack'
3186
repo, client = self.setup_fake_client_and_repository(transport_path)
3187
client.add_success_response('ok', 'a token')
3188
client.add_success_response('ok')
3192
[('call', 'Repository.lock_write', ('quack/', '')),
3193
('call', 'Repository.unlock', ('quack/', 'a token'))],
3196
def test_unlock_wrong_token(self):
3197
# If somehow the token is wrong, unlock will raise TokenMismatch.
3198
transport_path = 'quack'
3199
repo, client = self.setup_fake_client_and_repository(transport_path)
3200
client.add_success_response('ok', 'a token')
3201
client.add_error_response('TokenMismatch')
3203
self.assertRaises(errors.TokenMismatch, repo.unlock)
3206
class TestRepositoryHasRevision(TestRemoteRepository):
3208
def test_none(self):
3209
# repo.has_revision(None) should not cause any traffic.
3210
transport_path = 'quack'
3211
repo, client = self.setup_fake_client_and_repository(transport_path)
3213
# The null revision is always there, so has_revision(None) == True.
3214
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3216
# The remote repo shouldn't be accessed.
3217
self.assertEqual([], client._calls)
3220
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3221
"""Test Repository.iter_file_bytes."""
3223
def test_single(self):
3224
transport_path = 'quack'
3225
repo, client = self.setup_fake_client_and_repository(transport_path)
3226
client.add_expected_call(
3227
'Repository.iter_files_bytes', ('quack/', ),
3228
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3229
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3230
"somerev", "myid")]):
3231
self.assertEqual("myid", identifier)
3232
self.assertEqual("".join(byte_stream), "mydata" * 10)
3234
def test_missing(self):
3235
transport_path = 'quack'
3236
repo, client = self.setup_fake_client_and_repository(transport_path)
3237
client.add_expected_call(
3238
'Repository.iter_files_bytes',
3240
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3241
iter(["absent\0somefile\0somerev\n"]))
3242
self.assertRaises(errors.RevisionNotPresent, list,
3243
repo.iter_files_bytes(
3244
[("somefile", "somerev", "myid")]))
3247
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3248
"""Base class for Repository.insert_stream and .insert_stream_1.19
3252
def checkInsertEmptyStream(self, repo, client):
3253
"""Insert an empty stream, checking the result.
3255
This checks that there are no resume_tokens or missing_keys, and that
3256
the client is finished.
3258
sink = repo._get_sink()
3259
fmt = repository.format_registry.get_default()
3260
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3261
self.assertEqual([], resume_tokens)
3262
self.assertEqual(set(), missing_keys)
3263
self.assertFinished(client)
3266
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3267
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3270
This test case is very similar to TestRepositoryInsertStream_1_19.
3274
super(TestRepositoryInsertStream, self).setUp()
3275
self.disable_verb('Repository.insert_stream_1.19')
3277
def test_unlocked_repo(self):
3278
transport_path = 'quack'
3279
repo, client = self.setup_fake_client_and_repository(transport_path)
3280
client.add_expected_call(
3281
'Repository.insert_stream_1.19', ('quack/', ''),
3282
'unknown', ('Repository.insert_stream_1.19',))
3283
client.add_expected_call(
3284
'Repository.insert_stream', ('quack/', ''),
3286
client.add_expected_call(
3287
'Repository.insert_stream', ('quack/', ''),
3289
self.checkInsertEmptyStream(repo, client)
3291
def test_locked_repo_with_no_lock_token(self):
3292
transport_path = 'quack'
3293
repo, client = self.setup_fake_client_and_repository(transport_path)
3294
client.add_expected_call(
3295
'Repository.lock_write', ('quack/', ''),
3296
'success', ('ok', ''))
3297
client.add_expected_call(
3298
'Repository.insert_stream_1.19', ('quack/', ''),
3299
'unknown', ('Repository.insert_stream_1.19',))
3300
client.add_expected_call(
3301
'Repository.insert_stream', ('quack/', ''),
3303
client.add_expected_call(
3304
'Repository.insert_stream', ('quack/', ''),
3307
self.checkInsertEmptyStream(repo, client)
3309
def test_locked_repo_with_lock_token(self):
3310
transport_path = 'quack'
3311
repo, client = self.setup_fake_client_and_repository(transport_path)
3312
client.add_expected_call(
3313
'Repository.lock_write', ('quack/', ''),
3314
'success', ('ok', 'a token'))
3315
client.add_expected_call(
3316
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3317
'unknown', ('Repository.insert_stream_1.19',))
3318
client.add_expected_call(
3319
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3321
client.add_expected_call(
3322
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3325
self.checkInsertEmptyStream(repo, client)
3327
def test_stream_with_inventory_deltas(self):
3328
"""'inventory-deltas' substreams cannot be sent to the
3329
Repository.insert_stream verb, because not all servers that implement
3330
that verb will accept them. So when one is encountered the RemoteSink
3331
immediately stops using that verb and falls back to VFS insert_stream.
3333
transport_path = 'quack'
3334
repo, client = self.setup_fake_client_and_repository(transport_path)
3335
client.add_expected_call(
3336
'Repository.insert_stream_1.19', ('quack/', ''),
3337
'unknown', ('Repository.insert_stream_1.19',))
3338
client.add_expected_call(
3339
'Repository.insert_stream', ('quack/', ''),
3341
client.add_expected_call(
3342
'Repository.insert_stream', ('quack/', ''),
3344
# Create a fake real repository for insert_stream to fall back on, so
3345
# that we can directly see the records the RemoteSink passes to the
3350
def insert_stream(self, stream, src_format, resume_tokens):
3351
for substream_kind, substream in stream:
3352
self.records.append(
3353
(substream_kind, [record.key for record in substream]))
3354
return ['fake tokens'], ['fake missing keys']
3355
fake_real_sink = FakeRealSink()
3356
class FakeRealRepository:
3357
def _get_sink(self):
3358
return fake_real_sink
3359
def is_in_write_group(self):
3361
def refresh_data(self):
3363
repo._real_repository = FakeRealRepository()
3364
sink = repo._get_sink()
3365
fmt = repository.format_registry.get_default()
3366
stream = self.make_stream_with_inv_deltas(fmt)
3367
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3368
# Every record from the first inventory delta should have been sent to
3370
expected_records = [
3371
('inventory-deltas', [('rev2',), ('rev3',)]),
3372
('texts', [('some-rev', 'some-file')])]
3373
self.assertEqual(expected_records, fake_real_sink.records)
3374
# The return values from the real sink's insert_stream are propagated
3375
# back to the original caller.
3376
self.assertEqual(['fake tokens'], resume_tokens)
3377
self.assertEqual(['fake missing keys'], missing_keys)
3378
self.assertFinished(client)
3380
def make_stream_with_inv_deltas(self, fmt):
3381
"""Make a simple stream with an inventory delta followed by more
3382
records and more substreams to test that all records and substreams
3383
from that point on are used.
3385
This sends, in order:
3386
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3388
* texts substream: (some-rev, some-file)
3390
# Define a stream using generators so that it isn't rewindable.
3391
inv = inventory.Inventory(revision_id='rev1')
3392
inv.root.revision = 'rev1'
3393
def stream_with_inv_delta():
3394
yield ('inventories', inventories_substream())
3395
yield ('inventory-deltas', inventory_delta_substream())
3397
versionedfile.FulltextContentFactory(
3398
('some-rev', 'some-file'), (), None, 'content')])
3399
def inventories_substream():
3400
# An empty inventory fulltext. This will be streamed normally.
3401
text = fmt._serializer.write_inventory_to_string(inv)
3402
yield versionedfile.FulltextContentFactory(
3403
('rev1',), (), None, text)
3404
def inventory_delta_substream():
3405
# An inventory delta. This can't be streamed via this verb, so it
3406
# will trigger a fallback to VFS insert_stream.
3407
entry = inv.make_entry(
3408
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3409
entry.revision = 'ghost'
3410
delta = [(None, 'newdir', 'newdir-id', entry)]
3411
serializer = inventory_delta.InventoryDeltaSerializer(
3412
versioned_root=True, tree_references=False)
3413
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3414
yield versionedfile.ChunkedContentFactory(
3415
('rev2',), (('rev1',)), None, lines)
3417
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3418
yield versionedfile.ChunkedContentFactory(
3419
('rev3',), (('rev1',)), None, lines)
3420
return stream_with_inv_delta()
3423
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3425
def test_unlocked_repo(self):
3426
transport_path = 'quack'
3427
repo, client = self.setup_fake_client_and_repository(transport_path)
3428
client.add_expected_call(
3429
'Repository.insert_stream_1.19', ('quack/', ''),
3431
client.add_expected_call(
3432
'Repository.insert_stream_1.19', ('quack/', ''),
3434
self.checkInsertEmptyStream(repo, client)
3436
def test_locked_repo_with_no_lock_token(self):
3437
transport_path = 'quack'
3438
repo, client = self.setup_fake_client_and_repository(transport_path)
3439
client.add_expected_call(
3440
'Repository.lock_write', ('quack/', ''),
3441
'success', ('ok', ''))
3442
client.add_expected_call(
3443
'Repository.insert_stream_1.19', ('quack/', ''),
3445
client.add_expected_call(
3446
'Repository.insert_stream_1.19', ('quack/', ''),
3449
self.checkInsertEmptyStream(repo, client)
3451
def test_locked_repo_with_lock_token(self):
3452
transport_path = 'quack'
3453
repo, client = self.setup_fake_client_and_repository(transport_path)
3454
client.add_expected_call(
3455
'Repository.lock_write', ('quack/', ''),
3456
'success', ('ok', 'a token'))
3457
client.add_expected_call(
3458
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3460
client.add_expected_call(
3461
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3464
self.checkInsertEmptyStream(repo, client)
3467
class TestRepositoryTarball(TestRemoteRepository):
3469
# This is a canned tarball reponse we can validate against
3471
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3472
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3473
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3474
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3475
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3476
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3477
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3478
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3479
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3480
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3481
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3482
'nWQ7QH/F3JFOFCQ0aSPfA='
3485
def test_repository_tarball(self):
3486
# Test that Repository.tarball generates the right operations
3487
transport_path = 'repo'
3488
expected_calls = [('call_expecting_body', 'Repository.tarball',
3489
('repo/', 'bz2',),),
3491
repo, client = self.setup_fake_client_and_repository(transport_path)
3492
client.add_success_response_with_body(self.tarball_content, 'ok')
3493
# Now actually ask for the tarball
3494
tarball_file = repo._get_tarball('bz2')
3496
self.assertEqual(expected_calls, client._calls)
3497
self.assertEqual(self.tarball_content, tarball_file.read())
3499
tarball_file.close()
3502
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3503
"""RemoteRepository.copy_content_into optimizations"""
3505
def test_copy_content_remote_to_local(self):
3506
self.transport_server = test_server.SmartTCPServer_for_testing
3507
src_repo = self.make_repository('repo1')
3508
src_repo = repository.Repository.open(self.get_url('repo1'))
3509
# At the moment the tarball-based copy_content_into can't write back
3510
# into a smart server. It would be good if it could upload the
3511
# tarball; once that works we'd have to create repositories of
3512
# different formats. -- mbp 20070410
3513
dest_url = self.get_vfs_only_url('repo2')
3514
dest_bzrdir = BzrDir.create(dest_url)
3515
dest_repo = dest_bzrdir.create_repository()
3516
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3517
self.assertTrue(isinstance(src_repo, RemoteRepository))
3518
src_repo.copy_content_into(dest_repo)
3521
class _StubRealPackRepository(object):
3523
def __init__(self, calls):
3525
self._pack_collection = _StubPackCollection(calls)
3527
def start_write_group(self):
3528
self.calls.append(('start_write_group',))
3530
def is_in_write_group(self):
3533
def refresh_data(self):
3534
self.calls.append(('pack collection reload_pack_names',))
3537
class _StubPackCollection(object):
3539
def __init__(self, calls):
3543
self.calls.append(('pack collection autopack',))
3546
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3547
"""Tests for RemoteRepository.autopack implementation."""
3550
"""When the server returns 'ok' and there's no _real_repository, then
3551
nothing else happens: the autopack method is done.
3553
transport_path = 'quack'
3554
repo, client = self.setup_fake_client_and_repository(transport_path)
3555
client.add_expected_call(
3556
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3558
self.assertFinished(client)
3560
def test_ok_with_real_repo(self):
3561
"""When the server returns 'ok' and there is a _real_repository, then
3562
the _real_repository's reload_pack_name's method will be called.
3564
transport_path = 'quack'
3565
repo, client = self.setup_fake_client_and_repository(transport_path)
3566
client.add_expected_call(
3567
'PackRepository.autopack', ('quack/',),
3569
repo._real_repository = _StubRealPackRepository(client._calls)
3572
[('call', 'PackRepository.autopack', ('quack/',)),
3573
('pack collection reload_pack_names',)],
3576
def test_backwards_compatibility(self):
3577
"""If the server does not recognise the PackRepository.autopack verb,
3578
fallback to the real_repository's implementation.
3580
transport_path = 'quack'
3581
repo, client = self.setup_fake_client_and_repository(transport_path)
3582
client.add_unknown_method_response('PackRepository.autopack')
3583
def stub_ensure_real():
3584
client._calls.append(('_ensure_real',))
3585
repo._real_repository = _StubRealPackRepository(client._calls)
3586
repo._ensure_real = stub_ensure_real
3589
[('call', 'PackRepository.autopack', ('quack/',)),
3591
('pack collection autopack',)],
3594
def test_oom_error_reporting(self):
3595
"""An out-of-memory condition on the server is reported clearly"""
3596
transport_path = 'quack'
3597
repo, client = self.setup_fake_client_and_repository(transport_path)
3598
client.add_expected_call(
3599
'PackRepository.autopack', ('quack/',),
3600
'error', ('MemoryError',))
3601
err = self.assertRaises(errors.BzrError, repo.autopack)
3602
self.assertContainsRe(str(err), "^remote server out of mem")
3605
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3606
"""Base class for unit tests for breezy.remote._translate_error."""
3608
def translateTuple(self, error_tuple, **context):
3609
"""Call _translate_error with an ErrorFromSmartServer built from the
3612
:param error_tuple: A tuple of a smart server response, as would be
3613
passed to an ErrorFromSmartServer.
3614
:kwargs context: context items to call _translate_error with.
3616
:returns: The error raised by _translate_error.
3618
# Raise the ErrorFromSmartServer before passing it as an argument,
3619
# because _translate_error may need to re-raise it with a bare 'raise'
3621
server_error = errors.ErrorFromSmartServer(error_tuple)
3622
translated_error = self.translateErrorFromSmartServer(
3623
server_error, **context)
3624
return translated_error
3626
def translateErrorFromSmartServer(self, error_object, **context):
3627
"""Like translateTuple, but takes an already constructed
3628
ErrorFromSmartServer rather than a tuple.
3632
except errors.ErrorFromSmartServer as server_error:
3633
translated_error = self.assertRaises(
3634
errors.BzrError, remote._translate_error, server_error,
3636
return translated_error
3639
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3640
"""Unit tests for breezy.remote._translate_error.
3642
Given an ErrorFromSmartServer (which has an error tuple from a smart
3643
server) and some context, _translate_error raises more specific errors from
3646
This test case covers the cases where _translate_error succeeds in
3647
translating an ErrorFromSmartServer to something better. See
3648
TestErrorTranslationRobustness for other cases.
3651
def test_NoSuchRevision(self):
3652
branch = self.make_branch('')
3654
translated_error = self.translateTuple(
3655
('NoSuchRevision', revid), branch=branch)
3656
expected_error = errors.NoSuchRevision(branch, revid)
3657
self.assertEqual(expected_error, translated_error)
3659
def test_nosuchrevision(self):
3660
repository = self.make_repository('')
3662
translated_error = self.translateTuple(
3663
('nosuchrevision', revid), repository=repository)
3664
expected_error = errors.NoSuchRevision(repository, revid)
3665
self.assertEqual(expected_error, translated_error)
3667
def test_nobranch(self):
3668
bzrdir = self.make_bzrdir('')
3669
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3670
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3671
self.assertEqual(expected_error, translated_error)
3673
def test_nobranch_one_arg(self):
3674
bzrdir = self.make_bzrdir('')
3675
translated_error = self.translateTuple(
3676
('nobranch', 'extra detail'), bzrdir=bzrdir)
3677
expected_error = errors.NotBranchError(
3678
path=bzrdir.root_transport.base,
3679
detail='extra detail')
3680
self.assertEqual(expected_error, translated_error)
3682
def test_norepository(self):
3683
bzrdir = self.make_bzrdir('')
3684
translated_error = self.translateTuple(('norepository',),
3686
expected_error = errors.NoRepositoryPresent(bzrdir)
3687
self.assertEqual(expected_error, translated_error)
3689
def test_LockContention(self):
3690
translated_error = self.translateTuple(('LockContention',))
3691
expected_error = errors.LockContention('(remote lock)')
3692
self.assertEqual(expected_error, translated_error)
3694
def test_UnlockableTransport(self):
3695
bzrdir = self.make_bzrdir('')
3696
translated_error = self.translateTuple(
3697
('UnlockableTransport',), bzrdir=bzrdir)
3698
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3699
self.assertEqual(expected_error, translated_error)
3701
def test_LockFailed(self):
3702
lock = 'str() of a server lock'
3703
why = 'str() of why'
3704
translated_error = self.translateTuple(('LockFailed', lock, why))
3705
expected_error = errors.LockFailed(lock, why)
3706
self.assertEqual(expected_error, translated_error)
3708
def test_TokenMismatch(self):
3709
token = 'a lock token'
3710
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3711
expected_error = errors.TokenMismatch(token, '(remote token)')
3712
self.assertEqual(expected_error, translated_error)
3714
def test_Diverged(self):
3715
branch = self.make_branch('a')
3716
other_branch = self.make_branch('b')
3717
translated_error = self.translateTuple(
3718
('Diverged',), branch=branch, other_branch=other_branch)
3719
expected_error = errors.DivergedBranches(branch, other_branch)
3720
self.assertEqual(expected_error, translated_error)
3722
def test_NotStacked(self):
3723
branch = self.make_branch('')
3724
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3725
expected_error = errors.NotStacked(branch)
3726
self.assertEqual(expected_error, translated_error)
3728
def test_ReadError_no_args(self):
3730
translated_error = self.translateTuple(('ReadError',), path=path)
3731
expected_error = errors.ReadError(path)
3732
self.assertEqual(expected_error, translated_error)
3734
def test_ReadError(self):
3736
translated_error = self.translateTuple(('ReadError', path))
3737
expected_error = errors.ReadError(path)
3738
self.assertEqual(expected_error, translated_error)
3740
def test_IncompatibleRepositories(self):
3741
translated_error = self.translateTuple(('IncompatibleRepositories',
3742
"repo1", "repo2", "details here"))
3743
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3745
self.assertEqual(expected_error, translated_error)
3747
def test_PermissionDenied_no_args(self):
3749
translated_error = self.translateTuple(('PermissionDenied',),
3751
expected_error = errors.PermissionDenied(path)
3752
self.assertEqual(expected_error, translated_error)
3754
def test_PermissionDenied_one_arg(self):
3756
translated_error = self.translateTuple(('PermissionDenied', path))
3757
expected_error = errors.PermissionDenied(path)
3758
self.assertEqual(expected_error, translated_error)
3760
def test_PermissionDenied_one_arg_and_context(self):
3761
"""Given a choice between a path from the local context and a path on
3762
the wire, _translate_error prefers the path from the local context.
3764
local_path = 'local path'
3765
remote_path = 'remote path'
3766
translated_error = self.translateTuple(
3767
('PermissionDenied', remote_path), path=local_path)
3768
expected_error = errors.PermissionDenied(local_path)
3769
self.assertEqual(expected_error, translated_error)
3771
def test_PermissionDenied_two_args(self):
3773
extra = 'a string with extra info'
3774
translated_error = self.translateTuple(
3775
('PermissionDenied', path, extra))
3776
expected_error = errors.PermissionDenied(path, extra)
3777
self.assertEqual(expected_error, translated_error)
3779
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3781
def test_NoSuchFile_context_path(self):
3782
local_path = "local path"
3783
translated_error = self.translateTuple(('ReadError', "remote path"),
3785
expected_error = errors.ReadError(local_path)
3786
self.assertEqual(expected_error, translated_error)
3788
def test_NoSuchFile_without_context(self):
3789
remote_path = "remote path"
3790
translated_error = self.translateTuple(('ReadError', remote_path))
3791
expected_error = errors.ReadError(remote_path)
3792
self.assertEqual(expected_error, translated_error)
3794
def test_ReadOnlyError(self):
3795
translated_error = self.translateTuple(('ReadOnlyError',))
3796
expected_error = errors.TransportNotPossible("readonly transport")
3797
self.assertEqual(expected_error, translated_error)
3799
def test_MemoryError(self):
3800
translated_error = self.translateTuple(('MemoryError',))
3801
self.assertStartsWith(str(translated_error),
3802
"remote server out of memory")
3804
def test_generic_IndexError_no_classname(self):
3805
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3806
translated_error = self.translateErrorFromSmartServer(err)
3807
expected_error = errors.UnknownErrorFromSmartServer(err)
3808
self.assertEqual(expected_error, translated_error)
3810
# GZ 2011-03-02: TODO test generic non-ascii error string
3812
def test_generic_KeyError(self):
3813
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3814
translated_error = self.translateErrorFromSmartServer(err)
3815
expected_error = errors.UnknownErrorFromSmartServer(err)
3816
self.assertEqual(expected_error, translated_error)
3819
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3820
"""Unit tests for breezy.remote._translate_error's robustness.
3822
TestErrorTranslationSuccess is for cases where _translate_error can
3823
translate successfully. This class about how _translate_err behaves when
3824
it fails to translate: it re-raises the original error.
3827
def test_unrecognised_server_error(self):
3828
"""If the error code from the server is not recognised, the original
3829
ErrorFromSmartServer is propagated unmodified.
3831
error_tuple = ('An unknown error tuple',)
3832
server_error = errors.ErrorFromSmartServer(error_tuple)
3833
translated_error = self.translateErrorFromSmartServer(server_error)
3834
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3835
self.assertEqual(expected_error, translated_error)
3837
def test_context_missing_a_key(self):
3838
"""In case of a bug in the client, or perhaps an unexpected response
3839
from a server, _translate_error returns the original error tuple from
3840
the server and mutters a warning.
3842
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3843
# in the context dict. So let's give it an empty context dict instead
3844
# to exercise its error recovery.
3846
error_tuple = ('NoSuchRevision', 'revid')
3847
server_error = errors.ErrorFromSmartServer(error_tuple)
3848
translated_error = self.translateErrorFromSmartServer(server_error)
3849
self.assertEqual(server_error, translated_error)
3850
# In addition to re-raising ErrorFromSmartServer, some debug info has
3851
# been muttered to the log file for developer to look at.
3852
self.assertContainsRe(
3854
"Missing key 'branch' in context")
3856
def test_path_missing(self):
3857
"""Some translations (PermissionDenied, ReadError) can determine the
3858
'path' variable from either the wire or the local context. If neither
3859
has it, then an error is raised.
3861
error_tuple = ('ReadError',)
3862
server_error = errors.ErrorFromSmartServer(error_tuple)
3863
translated_error = self.translateErrorFromSmartServer(server_error)
3864
self.assertEqual(server_error, translated_error)
3865
# In addition to re-raising ErrorFromSmartServer, some debug info has
3866
# been muttered to the log file for developer to look at.
3867
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3870
class TestStacking(tests.TestCaseWithTransport):
3871
"""Tests for operations on stacked remote repositories.
3873
The underlying format type must support stacking.
3876
def test_access_stacked_remote(self):
3877
# based on <http://launchpad.net/bugs/261315>
3878
# make a branch stacked on another repository containing an empty
3879
# revision, then open it over hpss - we should be able to see that
3881
base_transport = self.get_transport()
3882
base_builder = self.make_branch_builder('base', format='1.9')
3883
base_builder.start_series()
3884
base_revid = base_builder.build_snapshot('rev-id', None,
3885
[('add', ('', None, 'directory', None))],
3887
base_builder.finish_series()
3888
stacked_branch = self.make_branch('stacked', format='1.9')
3889
stacked_branch.set_stacked_on_url('../base')
3890
# start a server looking at this
3891
smart_server = test_server.SmartTCPServer_for_testing()
3892
self.start_server(smart_server)
3893
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3894
# can get its branch and repository
3895
remote_branch = remote_bzrdir.open_branch()
3896
remote_repo = remote_branch.repository
3897
remote_repo.lock_read()
3899
# it should have an appropriate fallback repository, which should also
3900
# be a RemoteRepository
3901
self.assertLength(1, remote_repo._fallback_repositories)
3902
self.assertIsInstance(remote_repo._fallback_repositories[0],
3904
# and it has the revision committed to the underlying repository;
3905
# these have varying implementations so we try several of them
3906
self.assertTrue(remote_repo.has_revisions([base_revid]))
3907
self.assertTrue(remote_repo.has_revision(base_revid))
3908
self.assertEqual(remote_repo.get_revision(base_revid).message,
3911
remote_repo.unlock()
3913
def prepare_stacked_remote_branch(self):
3914
"""Get stacked_upon and stacked branches with content in each."""
3915
self.setup_smart_server_with_call_log()
3916
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3917
tree1.commit('rev1', rev_id='rev1')
3918
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3919
).open_workingtree()
3920
local_tree = tree2.branch.create_checkout('local')
3921
local_tree.commit('local changes make me feel good.')
3922
branch2 = Branch.open(self.get_url('tree2'))
3924
self.addCleanup(branch2.unlock)
3925
return tree1.branch, branch2
3927
def test_stacked_get_parent_map(self):
3928
# the public implementation of get_parent_map obeys stacking
3929
_, branch = self.prepare_stacked_remote_branch()
3930
repo = branch.repository
3931
self.assertEqual({'rev1'}, set(repo.get_parent_map(['rev1'])))
3933
def test_unstacked_get_parent_map(self):
3934
# _unstacked_provider.get_parent_map ignores stacking
3935
_, branch = self.prepare_stacked_remote_branch()
3936
provider = branch.repository._unstacked_provider
3937
self.assertEqual(set(), set(provider.get_parent_map(['rev1'])))
3939
def fetch_stream_to_rev_order(self, stream):
3941
for kind, substream in stream:
3942
if not kind == 'revisions':
3945
for content in substream:
3946
result.append(content.key[-1])
3949
def get_ordered_revs(self, format, order, branch_factory=None):
3950
"""Get a list of the revisions in a stream to format format.
3952
:param format: The format of the target.
3953
:param order: the order that target should have requested.
3954
:param branch_factory: A callable to create a trunk and stacked branch
3955
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3956
:result: The revision ids in the stream, in the order seen,
3957
the topological order of revisions in the source.
3959
unordered_format = controldir.format_registry.get(format)()
3960
target_repository_format = unordered_format.repository_format
3962
self.assertEqual(order, target_repository_format._fetch_order)
3963
if branch_factory is None:
3964
branch_factory = self.prepare_stacked_remote_branch
3965
_, stacked = branch_factory()
3966
source = stacked.repository._get_source(target_repository_format)
3967
tip = stacked.last_revision()
3968
stacked.repository._ensure_real()
3969
graph = stacked.repository.get_graph()
3970
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3971
if r != NULL_REVISION]
3973
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3974
self.reset_smart_call_log()
3975
stream = source.get_stream(search)
3976
# We trust that if a revision is in the stream the rest of the new
3977
# content for it is too, as per our main fetch tests; here we are
3978
# checking that the revisions are actually included at all, and their
3980
return self.fetch_stream_to_rev_order(stream), revs
3982
def test_stacked_get_stream_unordered(self):
3983
# Repository._get_source.get_stream() from a stacked repository with
3984
# unordered yields the full data from both stacked and stacked upon
3986
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3987
self.assertEqual(set(expected_revs), set(rev_ord))
3988
# Getting unordered results should have made a streaming data request
3989
# from the server, then one from the backing branch.
3990
self.assertLength(2, self.hpss_calls)
3992
def test_stacked_on_stacked_get_stream_unordered(self):
3993
# Repository._get_source.get_stream() from a stacked repository which
3994
# is itself stacked yields the full data from all three sources.
3995
def make_stacked_stacked():
3996
_, stacked = self.prepare_stacked_remote_branch()
3997
tree = stacked.bzrdir.sprout('tree3', stacked=True
3998
).open_workingtree()
3999
local_tree = tree.branch.create_checkout('local-tree3')
4000
local_tree.commit('more local changes are better')
4001
branch = Branch.open(self.get_url('tree3'))
4003
self.addCleanup(branch.unlock)
4005
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
4006
branch_factory=make_stacked_stacked)
4007
self.assertEqual(set(expected_revs), set(rev_ord))
4008
# Getting unordered results should have made a streaming data request
4009
# from the server, and one from each backing repo
4010
self.assertLength(3, self.hpss_calls)
4012
def test_stacked_get_stream_topological(self):
4013
# Repository._get_source.get_stream() from a stacked repository with
4014
# topological sorting yields the full data from both stacked and
4015
# stacked upon sources in topological order.
4016
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4017
self.assertEqual(expected_revs, rev_ord)
4018
# Getting topological sort requires VFS calls still - one of which is
4019
# pushing up from the bound branch.
4020
self.assertLength(14, self.hpss_calls)
4022
def test_stacked_get_stream_groupcompress(self):
4023
# Repository._get_source.get_stream() from a stacked repository with
4024
# groupcompress sorting yields the full data from both stacked and
4025
# stacked upon sources in groupcompress order.
4026
raise tests.TestSkipped('No groupcompress ordered format available')
4027
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4028
self.assertEqual(expected_revs, reversed(rev_ord))
4029
# Getting unordered results should have made a streaming data request
4030
# from the backing branch, and one from the stacked on branch.
4031
self.assertLength(2, self.hpss_calls)
4033
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4034
# When pulling some fixed amount of content that is more than the
4035
# source has (because some is coming from a fallback branch, no error
4036
# should be received. This was reported as bug 360791.
4037
# Need three branches: a trunk, a stacked branch, and a preexisting
4038
# branch pulling content from stacked and trunk.
4039
self.setup_smart_server_with_call_log()
4040
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4041
r1 = trunk.commit('start')
4042
stacked_branch = trunk.branch.create_clone_on_transport(
4043
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4044
local = self.make_branch('local', format='1.9-rich-root')
4045
local.repository.fetch(stacked_branch.repository,
4046
stacked_branch.last_revision())
4049
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4052
super(TestRemoteBranchEffort, self).setUp()
4053
# Create a smart server that publishes whatever the backing VFS server
4055
self.smart_server = test_server.SmartTCPServer_for_testing()
4056
self.start_server(self.smart_server, self.get_server())
4057
# Log all HPSS calls into self.hpss_calls.
4058
_SmartClient.hooks.install_named_hook(
4059
'call', self.capture_hpss_call, None)
4060
self.hpss_calls = []
4062
def capture_hpss_call(self, params):
4063
self.hpss_calls.append(params.method)
4065
def test_copy_content_into_avoids_revision_history(self):
4066
local = self.make_branch('local')
4067
builder = self.make_branch_builder('remote')
4068
builder.build_commit(message="Commit.")
4069
remote_branch_url = self.smart_server.get_url() + 'remote'
4070
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4071
local.repository.fetch(remote_branch.repository)
4072
self.hpss_calls = []
4073
remote_branch.copy_content_into(local)
4074
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4076
def test_fetch_everything_needs_just_one_call(self):
4077
local = self.make_branch('local')
4078
builder = self.make_branch_builder('remote')
4079
builder.build_commit(message="Commit.")
4080
remote_branch_url = self.smart_server.get_url() + 'remote'
4081
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4082
self.hpss_calls = []
4083
local.repository.fetch(
4084
remote_branch.repository,
4085
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4086
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4088
def override_verb(self, verb_name, verb):
4089
request_handlers = request.request_handlers
4090
orig_verb = request_handlers.get(verb_name)
4091
orig_info = request_handlers.get_info(verb_name)
4092
request_handlers.register(verb_name, verb, override_existing=True)
4093
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4094
override_existing=True, info=orig_info)
4096
def test_fetch_everything_backwards_compat(self):
4097
"""Can fetch with EverythingResult even with pre 2.4 servers.
4099
Pre-2.4 do not support 'everything' searches with the
4100
Repository.get_stream_1.19 verb.
4103
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4104
"""A version of the Repository.get_stream_1.19 verb patched to
4105
reject 'everything' searches the way 2.3 and earlier do.
4107
def recreate_search(self, repository, search_bytes,
4108
discard_excess=False):
4109
verb_log.append(search_bytes.split('\n', 1)[0])
4110
if search_bytes == 'everything':
4112
request.FailedSmartServerResponse(('BadSearch',)))
4113
return super(OldGetStreamVerb,
4114
self).recreate_search(repository, search_bytes,
4115
discard_excess=discard_excess)
4116
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4117
local = self.make_branch('local')
4118
builder = self.make_branch_builder('remote')
4119
builder.build_commit(message="Commit.")
4120
remote_branch_url = self.smart_server.get_url() + 'remote'
4121
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4122
self.hpss_calls = []
4123
local.repository.fetch(
4124
remote_branch.repository,
4125
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4126
# make sure the overridden verb was used
4127
self.assertLength(1, verb_log)
4128
# more than one HPSS call is needed, but because it's a VFS callback
4129
# its hard to predict exactly how many.
4130
self.assertTrue(len(self.hpss_calls) > 1)
4133
class TestUpdateBoundBranchWithModifiedBoundLocation(
4134
tests.TestCaseWithTransport):
4135
"""Ensure correct handling of bound_location modifications.
4137
This is tested against a smart server as http://pad.lv/786980 was about a
4138
ReadOnlyError (write attempt during a read-only transaction) which can only
4139
happen in this context.
4143
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4144
self.transport_server = test_server.SmartTCPServer_for_testing
4146
def make_master_and_checkout(self, master_name, checkout_name):
4147
# Create the master branch and its associated checkout
4148
self.master = self.make_branch_and_tree(master_name)
4149
self.checkout = self.master.branch.create_checkout(checkout_name)
4150
# Modify the master branch so there is something to update
4151
self.master.commit('add stuff')
4152
self.last_revid = self.master.commit('even more stuff')
4153
self.bound_location = self.checkout.branch.get_bound_location()
4155
def assertUpdateSucceeds(self, new_location):
4156
self.checkout.branch.set_bound_location(new_location)
4157
self.checkout.update()
4158
self.assertEqual(self.last_revid, self.checkout.last_revision())
4160
def test_without_final_slash(self):
4161
self.make_master_and_checkout('master', 'checkout')
4162
# For unclear reasons some users have a bound_location without a final
4163
# '/', simulate that by forcing such a value
4164
self.assertEndsWith(self.bound_location, '/')
4165
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4167
def test_plus_sign(self):
4168
self.make_master_and_checkout('+master', 'checkout')
4169
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4171
def test_tilda(self):
4172
# Embed ~ in the middle of the path just to avoid any $HOME
4174
self.make_master_and_checkout('mas~ter', 'checkout')
4175
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4178
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4180
def test_no_context(self):
4181
class OutOfCoffee(errors.BzrError):
4182
"""A dummy exception for testing."""
4184
def __init__(self, urgency):
4185
self.urgency = urgency
4186
remote.no_context_error_translators.register("OutOfCoffee",
4187
lambda err: OutOfCoffee(err.error_args[0]))
4188
transport = MemoryTransport()
4189
client = FakeClient(transport.base)
4190
client.add_expected_call(
4191
'Branch.get_stacked_on_url', ('quack/',),
4192
'error', ('NotStacked',))
4193
client.add_expected_call(
4194
'Branch.last_revision_info',
4196
'error', ('OutOfCoffee', 'low'))
4197
transport.mkdir('quack')
4198
transport = transport.clone('quack')
4199
branch = self.make_remote_branch(transport, client)
4200
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4201
self.assertFinished(client)
4203
def test_with_context(self):
4204
class OutOfTea(errors.BzrError):
4205
def __init__(self, branch, urgency):
4206
self.branch = branch
4207
self.urgency = urgency
4208
remote.error_translators.register("OutOfTea",
4209
lambda err, find, path: OutOfTea(err.error_args[0],
4211
transport = MemoryTransport()
4212
client = FakeClient(transport.base)
4213
client.add_expected_call(
4214
'Branch.get_stacked_on_url', ('quack/',),
4215
'error', ('NotStacked',))
4216
client.add_expected_call(
4217
'Branch.last_revision_info',
4219
'error', ('OutOfTea', 'low'))
4220
transport.mkdir('quack')
4221
transport = transport.clone('quack')
4222
branch = self.make_remote_branch(transport, client)
4223
self.assertRaises(OutOfTea, branch.last_revision_info)
4224
self.assertFinished(client)
4227
class TestRepositoryPack(TestRemoteRepository):
4229
def test_pack(self):
4230
transport_path = 'quack'
4231
repo, client = self.setup_fake_client_and_repository(transport_path)
4232
client.add_expected_call(
4233
'Repository.lock_write', ('quack/', ''),
4234
'success', ('ok', 'token'))
4235
client.add_expected_call(
4236
'Repository.pack', ('quack/', 'token', 'False'),
4237
'success', ('ok',), )
4238
client.add_expected_call(
4239
'Repository.unlock', ('quack/', 'token'),
4240
'success', ('ok', ))
4243
def test_pack_with_hint(self):
4244
transport_path = 'quack'
4245
repo, client = self.setup_fake_client_and_repository(transport_path)
4246
client.add_expected_call(
4247
'Repository.lock_write', ('quack/', ''),
4248
'success', ('ok', 'token'))
4249
client.add_expected_call(
4250
'Repository.pack', ('quack/', 'token', 'False'),
4251
'success', ('ok',), )
4252
client.add_expected_call(
4253
'Repository.unlock', ('quack/', 'token', 'False'),
4254
'success', ('ok', ))
4255
repo.pack(['hinta', 'hintb'])
4258
class TestRepositoryIterInventories(TestRemoteRepository):
4259
"""Test Repository.iter_inventories."""
4261
def _serialize_inv_delta(self, old_name, new_name, delta):
4262
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4263
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4265
def test_single_empty(self):
4266
transport_path = 'quack'
4267
repo, client = self.setup_fake_client_and_repository(transport_path)
4268
fmt = controldir.format_registry.get('2a')().repository_format
4270
stream = [('inventory-deltas', [
4271
versionedfile.FulltextContentFactory('somerevid', None, None,
4272
self._serialize_inv_delta('null:', 'somerevid', []))])]
4273
client.add_expected_call(
4274
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4275
'success', ('ok', ),
4276
_stream_to_byte_stream(stream, fmt))
4277
ret = list(repo.iter_inventories(["somerevid"]))
4278
self.assertLength(1, ret)
4280
self.assertEqual("somerevid", inv.revision_id)
4282
def test_empty(self):
4283
transport_path = 'quack'
4284
repo, client = self.setup_fake_client_and_repository(transport_path)
4285
ret = list(repo.iter_inventories([]))
4286
self.assertEqual(ret, [])
4288
def test_missing(self):
4289
transport_path = 'quack'
4290
repo, client = self.setup_fake_client_and_repository(transport_path)
4291
client.add_expected_call(
4292
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4293
'success', ('ok', ), iter([]))
4294
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(