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.
40
from ..branch import Branch
49
from ..bzr.bzrdir import (
56
from ..bzr.chk_serializer import chk_bencode_serializer
57
from ..bzr.remote import (
63
RemoteRepositoryFormat,
65
from ..bzr import groupcompress_repo, knitpack_repo
66
from ..revision import (
70
from ..sixish import (
73
from ..bzr.smart import medium, request
74
from ..bzr.smart.client import _SmartClient
75
from ..bzr.smart.repository import (
76
SmartServerRepositoryGetParentMap,
77
SmartServerRepositoryGetStream_1_19,
78
_stream_to_byte_stream,
83
from .scenarios import load_tests_apply_scenarios
84
from ..transport.memory import MemoryTransport
85
from ..transport.remote import (
92
load_tests = load_tests_apply_scenarios
95
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
99
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
101
{'transport_server': test_server.SmartTCPServer_for_testing})]
105
super(BasicRemoteObjectTests, self).setUp()
106
self.transport = self.get_transport()
107
# make a branch that can be opened over the smart transport
108
self.local_wt = BzrDir.create_standalone_workingtree('.')
109
self.addCleanup(self.transport.disconnect)
111
def test_create_remote_bzrdir(self):
112
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
113
self.assertIsInstance(b, BzrDir)
115
def test_open_remote_branch(self):
116
# open a standalone branch in the working directory
117
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
118
branch = b.open_branch()
119
self.assertIsInstance(branch, Branch)
121
def test_remote_repository(self):
122
b = BzrDir.open_from_transport(self.transport)
123
repo = b.open_repository()
124
revid = u'\xc823123123'.encode('utf8')
125
self.assertFalse(repo.has_revision(revid))
126
self.local_wt.commit(message='test commit', rev_id=revid)
127
self.assertTrue(repo.has_revision(revid))
129
def test_find_correct_format(self):
130
"""Should open a RemoteBzrDir over a RemoteTransport"""
131
fmt = BzrDirFormat.find_format(self.transport)
132
self.assertTrue(RemoteBzrProber
133
in controldir.ControlDirFormat._server_probers)
134
self.assertIsInstance(fmt, RemoteBzrDirFormat)
136
def test_open_detected_smart_format(self):
137
fmt = BzrDirFormat.find_format(self.transport)
138
d = fmt.open(self.transport)
139
self.assertIsInstance(d, BzrDir)
141
def test_remote_branch_repr(self):
142
b = BzrDir.open_from_transport(self.transport).open_branch()
143
self.assertStartsWith(str(b), 'RemoteBranch(')
145
def test_remote_bzrdir_repr(self):
146
b = BzrDir.open_from_transport(self.transport)
147
self.assertStartsWith(str(b), 'RemoteBzrDir(')
149
def test_remote_branch_format_supports_stacking(self):
151
self.make_branch('unstackable', format='pack-0.92')
152
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
153
self.assertFalse(b._format.supports_stacking())
154
self.make_branch('stackable', format='1.9')
155
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
156
self.assertTrue(b._format.supports_stacking())
158
def test_remote_repo_format_supports_external_references(self):
160
bd = self.make_controldir('unstackable', format='pack-0.92')
161
r = bd.create_repository()
162
self.assertFalse(r._format.supports_external_lookups)
163
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
164
self.assertFalse(r._format.supports_external_lookups)
165
bd = self.make_controldir('stackable', format='1.9')
166
r = bd.create_repository()
167
self.assertTrue(r._format.supports_external_lookups)
168
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
169
self.assertTrue(r._format.supports_external_lookups)
171
def test_remote_branch_set_append_revisions_only(self):
172
# Make a format 1.9 branch, which supports append_revisions_only
173
branch = self.make_branch('branch', format='1.9')
174
branch.set_append_revisions_only(True)
175
config = branch.get_config_stack()
177
True, config.get('append_revisions_only'))
178
branch.set_append_revisions_only(False)
179
config = branch.get_config_stack()
181
False, config.get('append_revisions_only'))
183
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
184
branch = self.make_branch('branch', format='knit')
186
errors.UpgradeRequired, branch.set_append_revisions_only, True)
189
class FakeProtocol(object):
190
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
192
def __init__(self, body, fake_client):
194
self._body_buffer = None
195
self._fake_client = fake_client
197
def read_body_bytes(self, count=-1):
198
if self._body_buffer is None:
199
self._body_buffer = BytesIO(self.body)
200
bytes = self._body_buffer.read(count)
201
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
202
self._fake_client.expecting_body = False
205
def cancel_read_body(self):
206
self._fake_client.expecting_body = False
208
def read_streamed_body(self):
212
class FakeClient(_SmartClient):
213
"""Lookalike for _SmartClient allowing testing."""
215
def __init__(self, fake_medium_base='fake base'):
216
"""Create a FakeClient."""
219
self.expecting_body = False
220
# if non-None, this is the list of expected calls, with only the
221
# method name and arguments included. the body might be hard to
222
# compute so is not included. If a call is None, that call can
224
self._expected_calls = None
225
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
227
def add_expected_call(self, call_name, call_args, response_type,
228
response_args, response_body=None):
229
if self._expected_calls is None:
230
self._expected_calls = []
231
self._expected_calls.append((call_name, call_args))
232
self.responses.append((response_type, response_args, response_body))
234
def add_success_response(self, *args):
235
self.responses.append(('success', args, None))
237
def add_success_response_with_body(self, body, *args):
238
self.responses.append(('success', args, body))
239
if self._expected_calls is not None:
240
self._expected_calls.append(None)
242
def add_error_response(self, *args):
243
self.responses.append(('error', args))
245
def add_unknown_method_response(self, verb):
246
self.responses.append(('unknown', verb))
248
def finished_test(self):
249
if self._expected_calls:
250
raise AssertionError("%r finished but was still expecting %r"
251
% (self, self._expected_calls[0]))
253
def _get_next_response(self):
255
response_tuple = self.responses.pop(0)
256
except IndexError as e:
257
raise AssertionError("%r didn't expect any more calls"
259
if response_tuple[0] == 'unknown':
260
raise errors.UnknownSmartMethod(response_tuple[1])
261
elif response_tuple[0] == 'error':
262
raise errors.ErrorFromSmartServer(response_tuple[1])
263
return response_tuple
265
def _check_call(self, method, args):
266
if self._expected_calls is None:
267
# the test should be updated to say what it expects
270
next_call = self._expected_calls.pop(0)
272
raise AssertionError("%r didn't expect any more calls "
274
% (self, method, args,))
275
if next_call is None:
277
if method != next_call[0] or args != next_call[1]:
278
raise AssertionError("%r expected %r%r "
280
% (self, next_call[0], next_call[1], method, args,))
282
def call(self, method, *args):
283
self._check_call(method, args)
284
self._calls.append(('call', method, args))
285
return self._get_next_response()[1]
287
def call_expecting_body(self, method, *args):
288
self._check_call(method, args)
289
self._calls.append(('call_expecting_body', method, args))
290
result = self._get_next_response()
291
self.expecting_body = True
292
return result[1], FakeProtocol(result[2], self)
294
def call_with_body_bytes(self, method, args, body):
295
self._check_call(method, args)
296
self._calls.append(('call_with_body_bytes', method, args, body))
297
result = self._get_next_response()
298
return result[1], FakeProtocol(result[2], self)
300
def call_with_body_bytes_expecting_body(self, method, args, body):
301
self._check_call(method, args)
302
self._calls.append(('call_with_body_bytes_expecting_body', method,
304
result = self._get_next_response()
305
self.expecting_body = True
306
return result[1], FakeProtocol(result[2], self)
308
def call_with_body_stream(self, args, stream):
309
# Explicitly consume the stream before checking for an error, because
310
# that's what happens a real medium.
311
stream = list(stream)
312
self._check_call(args[0], args[1:])
313
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
314
result = self._get_next_response()
315
# The second value returned from call_with_body_stream is supposed to
316
# be a response_handler object, but so far no tests depend on that.
317
response_handler = None
318
return result[1], response_handler
321
class FakeMedium(medium.SmartClientMedium):
323
def __init__(self, client_calls, base):
324
medium.SmartClientMedium.__init__(self, base)
325
self._client_calls = client_calls
327
def disconnect(self):
328
self._client_calls.append(('disconnect medium',))
331
class TestVfsHas(tests.TestCase):
333
def test_unicode_path(self):
334
client = FakeClient('/')
335
client.add_success_response('yes',)
336
transport = RemoteTransport('bzr://localhost/', _client=client)
337
filename = u'/hell\u00d8'.encode('utf8')
338
result = transport.has(filename)
340
[('call', 'has', (filename,))],
342
self.assertTrue(result)
345
class TestRemote(tests.TestCaseWithMemoryTransport):
347
def get_branch_format(self):
348
reference_bzrdir_format = controldir.format_registry.get('default')()
349
return reference_bzrdir_format.get_branch_format()
351
def get_repo_format(self):
352
reference_bzrdir_format = controldir.format_registry.get('default')()
353
return reference_bzrdir_format.repository_format
355
def assertFinished(self, fake_client):
356
"""Assert that all of a FakeClient's expected calls have occurred."""
357
fake_client.finished_test()
360
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
361
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
363
def assertRemotePath(self, expected, client_base, transport_base):
364
"""Assert that the result of
365
SmartClientMedium.remote_path_from_transport is the expected value for
366
a given client_base and transport_base.
368
client_medium = medium.SmartClientMedium(client_base)
369
t = transport.get_transport(transport_base)
370
result = client_medium.remote_path_from_transport(t)
371
self.assertEqual(expected, result)
373
def test_remote_path_from_transport(self):
374
"""SmartClientMedium.remote_path_from_transport calculates a URL for
375
the given transport relative to the root of the client base URL.
377
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
378
self.assertRemotePath(
379
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
381
def assertRemotePathHTTP(self, expected, transport_base, relpath):
382
"""Assert that the result of
383
HttpTransportBase.remote_path_from_transport is the expected value for
384
a given transport_base and relpath of that transport. (Note that
385
HttpTransportBase is a subclass of SmartClientMedium)
387
base_transport = transport.get_transport(transport_base)
388
client_medium = base_transport.get_smart_medium()
389
cloned_transport = base_transport.clone(relpath)
390
result = client_medium.remote_path_from_transport(cloned_transport)
391
self.assertEqual(expected, result)
393
def test_remote_path_from_transport_http(self):
394
"""Remote paths for HTTP transports are calculated differently to other
395
transports. They are just relative to the client base, not the root
396
directory of the host.
398
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
399
self.assertRemotePathHTTP(
400
'../xyz/', scheme + '//host/path', '../xyz/')
401
self.assertRemotePathHTTP(
402
'xyz/', scheme + '//host/path', 'xyz/')
405
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
406
"""Tests for the behaviour of client_medium.remote_is_at_least."""
408
def test_initially_unlimited(self):
409
"""A fresh medium assumes that the remote side supports all
412
client_medium = medium.SmartClientMedium('dummy base')
413
self.assertFalse(client_medium._is_remote_before((99, 99)))
415
def test__remember_remote_is_before(self):
416
"""Calling _remember_remote_is_before ratchets down the known remote
419
client_medium = medium.SmartClientMedium('dummy base')
420
# Mark the remote side as being less than 1.6. The remote side may
422
client_medium._remember_remote_is_before((1, 6))
423
self.assertTrue(client_medium._is_remote_before((1, 6)))
424
self.assertFalse(client_medium._is_remote_before((1, 5)))
425
# Calling _remember_remote_is_before again with a lower value works.
426
client_medium._remember_remote_is_before((1, 5))
427
self.assertTrue(client_medium._is_remote_before((1, 5)))
428
# If you call _remember_remote_is_before with a higher value it logs a
429
# warning, and continues to remember the lower value.
430
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
431
client_medium._remember_remote_is_before((1, 9))
432
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
433
self.assertTrue(client_medium._is_remote_before((1, 5)))
436
class TestBzrDirCloningMetaDir(TestRemote):
438
def test_backwards_compat(self):
439
self.setup_smart_server_with_call_log()
440
a_dir = self.make_controldir('.')
441
self.reset_smart_call_log()
442
verb = 'BzrDir.cloning_metadir'
443
self.disable_verb(verb)
444
format = a_dir.cloning_metadir()
445
call_count = len([call for call in self.hpss_calls if
446
call.call.method == verb])
447
self.assertEqual(1, call_count)
449
def test_branch_reference(self):
450
transport = self.get_transport('quack')
451
referenced = self.make_branch('referenced')
452
expected = referenced.controldir.cloning_metadir()
453
client = FakeClient(transport.base)
454
client.add_expected_call(
455
'BzrDir.cloning_metadir', ('quack/', 'False'),
456
'error', ('BranchReference',)),
457
client.add_expected_call(
458
'BzrDir.open_branchV3', ('quack/',),
459
'success', ('ref', self.get_url('referenced'))),
460
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
462
result = a_controldir.cloning_metadir()
463
# We should have got a control dir matching the referenced branch.
464
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
465
self.assertEqual(expected._repository_format, result._repository_format)
466
self.assertEqual(expected._branch_format, result._branch_format)
467
self.assertFinished(client)
469
def test_current_server(self):
470
transport = self.get_transport('.')
471
transport = transport.clone('quack')
472
self.make_controldir('quack')
473
client = FakeClient(transport.base)
474
reference_bzrdir_format = controldir.format_registry.get('default')()
475
control_name = reference_bzrdir_format.network_name()
476
client.add_expected_call(
477
'BzrDir.cloning_metadir', ('quack/', 'False'),
478
'success', (control_name, '', ('branch', ''))),
479
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
481
result = a_controldir.cloning_metadir()
482
# We should have got a reference control dir with default branch and
483
# repository formats.
484
# This pokes a little, just to be sure.
485
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
486
self.assertEqual(None, result._repository_format)
487
self.assertEqual(None, result._branch_format)
488
self.assertFinished(client)
490
def test_unknown(self):
491
transport = self.get_transport('quack')
492
referenced = self.make_branch('referenced')
493
expected = referenced.controldir.cloning_metadir()
494
client = FakeClient(transport.base)
495
client.add_expected_call(
496
'BzrDir.cloning_metadir', ('quack/', 'False'),
497
'success', ('unknown', 'unknown', ('branch', ''))),
498
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
500
self.assertRaises(errors.UnknownFormatError, a_controldir.cloning_metadir)
503
class TestBzrDirCheckoutMetaDir(TestRemote):
505
def test__get_checkout_format(self):
506
transport = MemoryTransport()
507
client = FakeClient(transport.base)
508
reference_bzrdir_format = controldir.format_registry.get('default')()
509
control_name = reference_bzrdir_format.network_name()
510
client.add_expected_call(
511
'BzrDir.checkout_metadir', ('quack/', ),
512
'success', (control_name, '', ''))
513
transport.mkdir('quack')
514
transport = transport.clone('quack')
515
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
517
result = a_controldir.checkout_metadir()
518
# We should have got a reference control dir with default branch and
519
# repository formats.
520
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
521
self.assertEqual(None, result._repository_format)
522
self.assertEqual(None, result._branch_format)
523
self.assertFinished(client)
525
def test_unknown_format(self):
526
transport = MemoryTransport()
527
client = FakeClient(transport.base)
528
client.add_expected_call(
529
'BzrDir.checkout_metadir', ('quack/',),
530
'success', ('dontknow', '', ''))
531
transport.mkdir('quack')
532
transport = transport.clone('quack')
533
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
535
self.assertRaises(errors.UnknownFormatError,
536
a_controldir.checkout_metadir)
537
self.assertFinished(client)
540
class TestBzrDirGetBranches(TestRemote):
542
def test_get_branches(self):
543
transport = MemoryTransport()
544
client = FakeClient(transport.base)
545
reference_bzrdir_format = controldir.format_registry.get('default')()
546
branch_name = reference_bzrdir_format.get_branch_format().network_name()
547
client.add_success_response_with_body(
549
"foo": ("branch", branch_name),
550
"": ("branch", branch_name)}), "success")
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
client.add_success_response(
556
'ok', '', 'no', 'no', 'no',
557
reference_bzrdir_format.repository_format.network_name())
558
client.add_error_response('NotStacked')
559
transport.mkdir('quack')
560
transport = transport.clone('quack')
561
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
563
result = a_controldir.get_branches()
564
self.assertEqual({"", "foo"}, set(result.keys()))
566
[('call_expecting_body', 'BzrDir.get_branches', ('quack/',)),
567
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
568
('call', 'Branch.get_stacked_on_url', ('quack/', )),
569
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
570
('call', 'Branch.get_stacked_on_url', ('quack/', ))],
574
class TestBzrDirDestroyBranch(TestRemote):
576
def test_destroy_default(self):
577
transport = self.get_transport('quack')
578
referenced = self.make_branch('referenced')
579
client = FakeClient(transport.base)
580
client.add_expected_call(
581
'BzrDir.destroy_branch', ('quack/', ),
583
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
585
a_controldir.destroy_branch()
586
self.assertFinished(client)
589
class TestBzrDirHasWorkingTree(TestRemote):
591
def test_has_workingtree(self):
592
transport = self.get_transport('quack')
593
client = FakeClient(transport.base)
594
client.add_expected_call(
595
'BzrDir.has_workingtree', ('quack/',),
596
'success', ('yes',)),
597
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
599
self.assertTrue(a_controldir.has_workingtree())
600
self.assertFinished(client)
602
def test_no_workingtree(self):
603
transport = self.get_transport('quack')
604
client = FakeClient(transport.base)
605
client.add_expected_call(
606
'BzrDir.has_workingtree', ('quack/',),
608
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
610
self.assertFalse(a_controldir.has_workingtree())
611
self.assertFinished(client)
614
class TestBzrDirDestroyRepository(TestRemote):
616
def test_destroy_repository(self):
617
transport = self.get_transport('quack')
618
client = FakeClient(transport.base)
619
client.add_expected_call(
620
'BzrDir.destroy_repository', ('quack/',),
622
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
624
a_controldir.destroy_repository()
625
self.assertFinished(client)
628
class TestBzrDirOpen(TestRemote):
630
def make_fake_client_and_transport(self, path='quack'):
631
transport = MemoryTransport()
632
transport.mkdir(path)
633
transport = transport.clone(path)
634
client = FakeClient(transport.base)
635
return client, transport
637
def test_absent(self):
638
client, transport = self.make_fake_client_and_transport()
639
client.add_expected_call(
640
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
641
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
642
RemoteBzrDirFormat(), _client=client, _force_probe=True)
643
self.assertFinished(client)
645
def test_present_without_workingtree(self):
646
client, transport = self.make_fake_client_and_transport()
647
client.add_expected_call(
648
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
649
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
650
_client=client, _force_probe=True)
651
self.assertIsInstance(bd, RemoteBzrDir)
652
self.assertFalse(bd.has_workingtree())
653
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
654
self.assertFinished(client)
656
def test_present_with_workingtree(self):
657
client, transport = self.make_fake_client_and_transport()
658
client.add_expected_call(
659
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
660
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
661
_client=client, _force_probe=True)
662
self.assertIsInstance(bd, RemoteBzrDir)
663
self.assertTrue(bd.has_workingtree())
664
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
665
self.assertFinished(client)
667
def test_backwards_compat(self):
668
client, transport = self.make_fake_client_and_transport()
669
client.add_expected_call(
670
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
671
client.add_expected_call(
672
'BzrDir.open', ('quack/',), 'success', ('yes',))
673
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
674
_client=client, _force_probe=True)
675
self.assertIsInstance(bd, RemoteBzrDir)
676
self.assertFinished(client)
678
def test_backwards_compat_hpss_v2(self):
679
client, transport = self.make_fake_client_and_transport()
680
# Monkey-patch fake client to simulate real-world behaviour with v2
681
# server: upon first RPC call detect the protocol version, and because
682
# the version is 2 also do _remember_remote_is_before((1, 6)) before
683
# continuing with the RPC.
684
orig_check_call = client._check_call
685
def check_call(method, args):
686
client._medium._protocol_version = 2
687
client._medium._remember_remote_is_before((1, 6))
688
client._check_call = orig_check_call
689
client._check_call(method, args)
690
client._check_call = check_call
691
client.add_expected_call(
692
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
693
client.add_expected_call(
694
'BzrDir.open', ('quack/',), 'success', ('yes',))
695
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
696
_client=client, _force_probe=True)
697
self.assertIsInstance(bd, RemoteBzrDir)
698
self.assertFinished(client)
701
class TestBzrDirOpenBranch(TestRemote):
703
def test_backwards_compat(self):
704
self.setup_smart_server_with_call_log()
705
self.make_branch('.')
706
a_dir = BzrDir.open(self.get_url('.'))
707
self.reset_smart_call_log()
708
verb = 'BzrDir.open_branchV3'
709
self.disable_verb(verb)
710
format = a_dir.open_branch()
711
call_count = len([call for call in self.hpss_calls if
712
call.call.method == verb])
713
self.assertEqual(1, call_count)
715
def test_branch_present(self):
716
reference_format = self.get_repo_format()
717
network_name = reference_format.network_name()
718
branch_network_name = self.get_branch_format().network_name()
719
transport = MemoryTransport()
720
transport.mkdir('quack')
721
transport = transport.clone('quack')
722
client = FakeClient(transport.base)
723
client.add_expected_call(
724
'BzrDir.open_branchV3', ('quack/',),
725
'success', ('branch', branch_network_name))
726
client.add_expected_call(
727
'BzrDir.find_repositoryV3', ('quack/',),
728
'success', ('ok', '', 'no', 'no', 'no', network_name))
729
client.add_expected_call(
730
'Branch.get_stacked_on_url', ('quack/',),
731
'error', ('NotStacked',))
732
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
734
result = bzrdir.open_branch()
735
self.assertIsInstance(result, RemoteBranch)
736
self.assertEqual(bzrdir, result.controldir)
737
self.assertFinished(client)
739
def test_branch_missing(self):
740
transport = MemoryTransport()
741
transport.mkdir('quack')
742
transport = transport.clone('quack')
743
client = FakeClient(transport.base)
744
client.add_error_response('nobranch')
745
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
747
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
749
[('call', 'BzrDir.open_branchV3', ('quack/',))],
752
def test__get_tree_branch(self):
753
# _get_tree_branch is a form of open_branch, but it should only ask for
754
# branch opening, not any other network requests.
756
def open_branch(name=None, possible_transports=None):
757
calls.append("Called")
759
transport = MemoryTransport()
760
# no requests on the network - catches other api calls being made.
761
client = FakeClient(transport.base)
762
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
764
# patch the open_branch call to record that it was called.
765
bzrdir.open_branch = open_branch
766
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
767
self.assertEqual(["Called"], calls)
768
self.assertEqual([], client._calls)
770
def test_url_quoting_of_path(self):
771
# Relpaths on the wire should not be URL-escaped. So "~" should be
772
# transmitted as "~", not "%7E".
773
transport = RemoteTCPTransport('bzr://localhost/~hello/')
774
client = FakeClient(transport.base)
775
reference_format = self.get_repo_format()
776
network_name = reference_format.network_name()
777
branch_network_name = self.get_branch_format().network_name()
778
client.add_expected_call(
779
'BzrDir.open_branchV3', ('~hello/',),
780
'success', ('branch', branch_network_name))
781
client.add_expected_call(
782
'BzrDir.find_repositoryV3', ('~hello/',),
783
'success', ('ok', '', 'no', 'no', 'no', network_name))
784
client.add_expected_call(
785
'Branch.get_stacked_on_url', ('~hello/',),
786
'error', ('NotStacked',))
787
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
789
result = bzrdir.open_branch()
790
self.assertFinished(client)
792
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
793
reference_format = self.get_repo_format()
794
network_name = reference_format.network_name()
795
transport = MemoryTransport()
796
transport.mkdir('quack')
797
transport = transport.clone('quack')
799
rich_response = 'yes'
803
subtree_response = 'yes'
805
subtree_response = 'no'
806
client = FakeClient(transport.base)
807
client.add_success_response(
808
'ok', '', rich_response, subtree_response, external_lookup,
810
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
812
result = bzrdir.open_repository()
814
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
816
self.assertIsInstance(result, RemoteRepository)
817
self.assertEqual(bzrdir, result.controldir)
818
self.assertEqual(rich_root, result._format.rich_root_data)
819
self.assertEqual(subtrees, result._format.supports_tree_reference)
821
def test_open_repository_sets_format_attributes(self):
822
self.check_open_repository(True, True)
823
self.check_open_repository(False, True)
824
self.check_open_repository(True, False)
825
self.check_open_repository(False, False)
826
self.check_open_repository(False, False, 'yes')
828
def test_old_server(self):
829
"""RemoteBzrDirFormat should fail to probe if the server version is too
832
self.assertRaises(errors.NotBranchError,
833
RemoteBzrProber.probe_transport, OldServerTransport())
836
class TestBzrDirCreateBranch(TestRemote):
838
def test_backwards_compat(self):
839
self.setup_smart_server_with_call_log()
840
repo = self.make_repository('.')
841
self.reset_smart_call_log()
842
self.disable_verb('BzrDir.create_branch')
843
branch = repo.controldir.create_branch()
844
create_branch_call_count = len([call for call in self.hpss_calls if
845
call.call.method == 'BzrDir.create_branch'])
846
self.assertEqual(1, create_branch_call_count)
848
def test_current_server(self):
849
transport = self.get_transport('.')
850
transport = transport.clone('quack')
851
self.make_repository('quack')
852
client = FakeClient(transport.base)
853
reference_bzrdir_format = controldir.format_registry.get('default')()
854
reference_format = reference_bzrdir_format.get_branch_format()
855
network_name = reference_format.network_name()
856
reference_repo_fmt = reference_bzrdir_format.repository_format
857
reference_repo_name = reference_repo_fmt.network_name()
858
client.add_expected_call(
859
'BzrDir.create_branch', ('quack/', network_name),
860
'success', ('ok', network_name, '', 'no', 'no', 'yes',
861
reference_repo_name))
862
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
864
branch = a_controldir.create_branch()
865
# We should have got a remote branch
866
self.assertIsInstance(branch, remote.RemoteBranch)
867
# its format should have the settings from the response
868
format = branch._format
869
self.assertEqual(network_name, format.network_name())
871
def test_already_open_repo_and_reused_medium(self):
872
"""Bug 726584: create_branch(..., repository=repo) should work
873
regardless of what the smart medium's base URL is.
875
self.transport_server = test_server.SmartTCPServer_for_testing
876
transport = self.get_transport('.')
877
repo = self.make_repository('quack')
878
# Client's medium rooted a transport root (not at the bzrdir)
879
client = FakeClient(transport.base)
880
transport = transport.clone('quack')
881
reference_bzrdir_format = controldir.format_registry.get('default')()
882
reference_format = reference_bzrdir_format.get_branch_format()
883
network_name = reference_format.network_name()
884
reference_repo_fmt = reference_bzrdir_format.repository_format
885
reference_repo_name = reference_repo_fmt.network_name()
886
client.add_expected_call(
887
'BzrDir.create_branch', ('extra/quack/', network_name),
888
'success', ('ok', network_name, '', 'no', 'no', 'yes',
889
reference_repo_name))
890
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
892
branch = a_controldir.create_branch(repository=repo)
893
# We should have got a remote branch
894
self.assertIsInstance(branch, remote.RemoteBranch)
895
# its format should have the settings from the response
896
format = branch._format
897
self.assertEqual(network_name, format.network_name())
900
class TestBzrDirCreateRepository(TestRemote):
902
def test_backwards_compat(self):
903
self.setup_smart_server_with_call_log()
904
bzrdir = self.make_controldir('.')
905
self.reset_smart_call_log()
906
self.disable_verb('BzrDir.create_repository')
907
repo = bzrdir.create_repository()
908
create_repo_call_count = len([call for call in self.hpss_calls if
909
call.call.method == 'BzrDir.create_repository'])
910
self.assertEqual(1, create_repo_call_count)
912
def test_current_server(self):
913
transport = self.get_transport('.')
914
transport = transport.clone('quack')
915
self.make_controldir('quack')
916
client = FakeClient(transport.base)
917
reference_bzrdir_format = controldir.format_registry.get('default')()
918
reference_format = reference_bzrdir_format.repository_format
919
network_name = reference_format.network_name()
920
client.add_expected_call(
921
'BzrDir.create_repository', ('quack/',
922
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
924
'success', ('ok', 'yes', 'yes', 'yes', network_name))
925
a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
927
repo = a_controldir.create_repository()
928
# We should have got a remote repository
929
self.assertIsInstance(repo, remote.RemoteRepository)
930
# its format should have the settings from the response
931
format = repo._format
932
self.assertTrue(format.rich_root_data)
933
self.assertTrue(format.supports_tree_reference)
934
self.assertTrue(format.supports_external_lookups)
935
self.assertEqual(network_name, format.network_name())
938
class TestBzrDirOpenRepository(TestRemote):
940
def test_backwards_compat_1_2_3(self):
941
# fallback all the way to the first version.
942
reference_format = self.get_repo_format()
943
network_name = reference_format.network_name()
944
server_url = 'bzr://example.com/'
945
self.permit_url(server_url)
946
client = FakeClient(server_url)
947
client.add_unknown_method_response('BzrDir.find_repositoryV3')
948
client.add_unknown_method_response('BzrDir.find_repositoryV2')
949
client.add_success_response('ok', '', 'no', 'no')
950
# A real repository instance will be created to determine the network
952
client.add_success_response_with_body(
953
"Bazaar-NG meta directory, format 1\n", 'ok')
954
client.add_success_response('stat', '0', '65535')
955
client.add_success_response_with_body(
956
reference_format.get_format_string(), 'ok')
957
# PackRepository wants to do a stat
958
client.add_success_response('stat', '0', '65535')
959
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
961
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
963
repo = bzrdir.open_repository()
965
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
966
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
967
('call', 'BzrDir.find_repository', ('quack/',)),
968
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
969
('call', 'stat', ('/quack/.bzr',)),
970
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
971
('call', 'stat', ('/quack/.bzr/repository',)),
974
self.assertEqual(network_name, repo._format.network_name())
976
def test_backwards_compat_2(self):
977
# fallback to find_repositoryV2
978
reference_format = self.get_repo_format()
979
network_name = reference_format.network_name()
980
server_url = 'bzr://example.com/'
981
self.permit_url(server_url)
982
client = FakeClient(server_url)
983
client.add_unknown_method_response('BzrDir.find_repositoryV3')
984
client.add_success_response('ok', '', 'no', 'no', 'no')
985
# A real repository instance will be created to determine the network
987
client.add_success_response_with_body(
988
"Bazaar-NG meta directory, format 1\n", 'ok')
989
client.add_success_response('stat', '0', '65535')
990
client.add_success_response_with_body(
991
reference_format.get_format_string(), 'ok')
992
# PackRepository wants to do a stat
993
client.add_success_response('stat', '0', '65535')
994
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
996
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
998
repo = bzrdir.open_repository()
1000
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
1001
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
1002
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
1003
('call', 'stat', ('/quack/.bzr',)),
1004
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
1005
('call', 'stat', ('/quack/.bzr/repository',)),
1008
self.assertEqual(network_name, repo._format.network_name())
1010
def test_current_server(self):
1011
reference_format = self.get_repo_format()
1012
network_name = reference_format.network_name()
1013
transport = MemoryTransport()
1014
transport.mkdir('quack')
1015
transport = transport.clone('quack')
1016
client = FakeClient(transport.base)
1017
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
1018
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1020
repo = bzrdir.open_repository()
1022
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
1024
self.assertEqual(network_name, repo._format.network_name())
1027
class TestBzrDirFormatInitializeEx(TestRemote):
1029
def test_success(self):
1030
"""Simple test for typical successful call."""
1031
fmt = RemoteBzrDirFormat()
1032
default_format_name = BzrDirFormat.get_default_format().network_name()
1033
transport = self.get_transport()
1034
client = FakeClient(transport.base)
1035
client.add_expected_call(
1036
'BzrDirFormat.initialize_ex_1.16',
1037
(default_format_name, 'path', 'False', 'False', 'False', '',
1038
'', '', '', 'False'),
1040
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
1041
'bzrdir fmt', 'False', '', '', 'repo lock token'))
1042
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1043
# it's currently hard to test that without supplying a real remote
1044
# transport connected to a real server.
1045
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1046
transport, False, False, False, None, None, None, None, False)
1047
self.assertFinished(client)
1049
def test_error(self):
1050
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1051
corresponding error from the client.
1053
fmt = RemoteBzrDirFormat()
1054
default_format_name = BzrDirFormat.get_default_format().network_name()
1055
transport = self.get_transport()
1056
client = FakeClient(transport.base)
1057
client.add_expected_call(
1058
'BzrDirFormat.initialize_ex_1.16',
1059
(default_format_name, 'path', 'False', 'False', 'False', '',
1060
'', '', '', 'False'),
1062
('PermissionDenied', 'path', 'extra info'))
1063
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1064
# it's currently hard to test that without supplying a real remote
1065
# transport connected to a real server.
1066
err = self.assertRaises(errors.PermissionDenied,
1067
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1068
False, False, False, None, None, None, None, False)
1069
self.assertEqual('path', err.path)
1070
self.assertEqual(': extra info', err.extra)
1071
self.assertFinished(client)
1073
def test_error_from_real_server(self):
1074
"""Integration test for error translation."""
1075
transport = self.make_smart_server('foo')
1076
transport = transport.clone('no-such-path')
1077
fmt = RemoteBzrDirFormat()
1078
err = self.assertRaises(errors.NoSuchFile,
1079
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1082
class OldSmartClient(object):
1083
"""A fake smart client for test_old_version that just returns a version one
1084
response to the 'hello' (query version) command.
1087
def get_request(self):
1088
input_file = BytesIO(b'ok\x011\n')
1089
output_file = BytesIO()
1090
client_medium = medium.SmartSimplePipesClientMedium(
1091
input_file, output_file)
1092
return medium.SmartClientStreamMediumRequest(client_medium)
1094
def protocol_version(self):
1098
class OldServerTransport(object):
1099
"""A fake transport for test_old_server that reports it's smart server
1100
protocol version as version one.
1106
def get_smart_client(self):
1107
return OldSmartClient()
1110
class RemoteBzrDirTestCase(TestRemote):
1112
def make_remote_bzrdir(self, transport, client):
1113
"""Make a RemotebzrDir using 'client' as the _client."""
1114
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1118
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1120
def lock_remote_branch(self, branch):
1121
"""Trick a RemoteBranch into thinking it is locked."""
1122
branch._lock_mode = 'w'
1123
branch._lock_count = 2
1124
branch._lock_token = 'branch token'
1125
branch._repo_lock_token = 'repo token'
1126
branch.repository._lock_mode = 'w'
1127
branch.repository._lock_count = 2
1128
branch.repository._lock_token = 'repo token'
1130
def make_remote_branch(self, transport, client):
1131
"""Make a RemoteBranch using 'client' as its _SmartClient.
1133
A RemoteBzrDir and RemoteRepository will also be created to fill out
1134
the RemoteBranch, albeit with stub values for some of their attributes.
1136
# we do not want bzrdir to make any remote calls, so use False as its
1137
# _client. If it tries to make a remote call, this will fail
1139
bzrdir = self.make_remote_bzrdir(transport, False)
1140
repo = RemoteRepository(bzrdir, None, _client=client)
1141
branch_format = self.get_branch_format()
1142
format = RemoteBranchFormat(network_name=branch_format.network_name())
1143
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1146
class TestBranchBreakLock(RemoteBranchTestCase):
1148
def test_break_lock(self):
1149
transport_path = 'quack'
1150
transport = MemoryTransport()
1151
client = FakeClient(transport.base)
1152
client.add_expected_call(
1153
'Branch.get_stacked_on_url', ('quack/',),
1154
'error', ('NotStacked',))
1155
client.add_expected_call(
1156
'Branch.break_lock', ('quack/',),
1158
transport.mkdir('quack')
1159
transport = transport.clone('quack')
1160
branch = self.make_remote_branch(transport, client)
1162
self.assertFinished(client)
1165
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1167
def test_get_physical_lock_status_yes(self):
1168
transport = MemoryTransport()
1169
client = FakeClient(transport.base)
1170
client.add_expected_call(
1171
'Branch.get_stacked_on_url', ('quack/',),
1172
'error', ('NotStacked',))
1173
client.add_expected_call(
1174
'Branch.get_physical_lock_status', ('quack/',),
1175
'success', ('yes',))
1176
transport.mkdir('quack')
1177
transport = transport.clone('quack')
1178
branch = self.make_remote_branch(transport, client)
1179
result = branch.get_physical_lock_status()
1180
self.assertFinished(client)
1181
self.assertEqual(True, result)
1183
def test_get_physical_lock_status_no(self):
1184
transport = MemoryTransport()
1185
client = FakeClient(transport.base)
1186
client.add_expected_call(
1187
'Branch.get_stacked_on_url', ('quack/',),
1188
'error', ('NotStacked',))
1189
client.add_expected_call(
1190
'Branch.get_physical_lock_status', ('quack/',),
1192
transport.mkdir('quack')
1193
transport = transport.clone('quack')
1194
branch = self.make_remote_branch(transport, client)
1195
result = branch.get_physical_lock_status()
1196
self.assertFinished(client)
1197
self.assertEqual(False, result)
1200
class TestBranchGetParent(RemoteBranchTestCase):
1202
def test_no_parent(self):
1203
# in an empty branch we decode the response properly
1204
transport = MemoryTransport()
1205
client = FakeClient(transport.base)
1206
client.add_expected_call(
1207
'Branch.get_stacked_on_url', ('quack/',),
1208
'error', ('NotStacked',))
1209
client.add_expected_call(
1210
'Branch.get_parent', ('quack/',),
1212
transport.mkdir('quack')
1213
transport = transport.clone('quack')
1214
branch = self.make_remote_branch(transport, client)
1215
result = branch.get_parent()
1216
self.assertFinished(client)
1217
self.assertEqual(None, result)
1219
def test_parent_relative(self):
1220
transport = MemoryTransport()
1221
client = FakeClient(transport.base)
1222
client.add_expected_call(
1223
'Branch.get_stacked_on_url', ('kwaak/',),
1224
'error', ('NotStacked',))
1225
client.add_expected_call(
1226
'Branch.get_parent', ('kwaak/',),
1227
'success', ('../foo/',))
1228
transport.mkdir('kwaak')
1229
transport = transport.clone('kwaak')
1230
branch = self.make_remote_branch(transport, client)
1231
result = branch.get_parent()
1232
self.assertEqual(transport.clone('../foo').base, result)
1234
def test_parent_absolute(self):
1235
transport = MemoryTransport()
1236
client = FakeClient(transport.base)
1237
client.add_expected_call(
1238
'Branch.get_stacked_on_url', ('kwaak/',),
1239
'error', ('NotStacked',))
1240
client.add_expected_call(
1241
'Branch.get_parent', ('kwaak/',),
1242
'success', ('http://foo/',))
1243
transport.mkdir('kwaak')
1244
transport = transport.clone('kwaak')
1245
branch = self.make_remote_branch(transport, client)
1246
result = branch.get_parent()
1247
self.assertEqual('http://foo/', result)
1248
self.assertFinished(client)
1251
class TestBranchSetParentLocation(RemoteBranchTestCase):
1253
def test_no_parent(self):
1254
# We call the verb when setting parent to None
1255
transport = MemoryTransport()
1256
client = FakeClient(transport.base)
1257
client.add_expected_call(
1258
'Branch.get_stacked_on_url', ('quack/',),
1259
'error', ('NotStacked',))
1260
client.add_expected_call(
1261
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1263
transport.mkdir('quack')
1264
transport = transport.clone('quack')
1265
branch = self.make_remote_branch(transport, client)
1266
branch._lock_token = 'b'
1267
branch._repo_lock_token = 'r'
1268
branch._set_parent_location(None)
1269
self.assertFinished(client)
1271
def test_parent(self):
1272
transport = MemoryTransport()
1273
client = FakeClient(transport.base)
1274
client.add_expected_call(
1275
'Branch.get_stacked_on_url', ('kwaak/',),
1276
'error', ('NotStacked',))
1277
client.add_expected_call(
1278
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1280
transport.mkdir('kwaak')
1281
transport = transport.clone('kwaak')
1282
branch = self.make_remote_branch(transport, client)
1283
branch._lock_token = 'b'
1284
branch._repo_lock_token = 'r'
1285
branch._set_parent_location('foo')
1286
self.assertFinished(client)
1288
def test_backwards_compat(self):
1289
self.setup_smart_server_with_call_log()
1290
branch = self.make_branch('.')
1291
self.reset_smart_call_log()
1292
verb = 'Branch.set_parent_location'
1293
self.disable_verb(verb)
1294
branch.set_parent('http://foo/')
1295
self.assertLength(14, self.hpss_calls)
1298
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1300
def test_backwards_compat(self):
1301
self.setup_smart_server_with_call_log()
1302
branch = self.make_branch('.')
1303
self.reset_smart_call_log()
1304
verb = 'Branch.get_tags_bytes'
1305
self.disable_verb(verb)
1306
branch.tags.get_tag_dict()
1307
call_count = len([call for call in self.hpss_calls if
1308
call.call.method == verb])
1309
self.assertEqual(1, call_count)
1311
def test_trivial(self):
1312
transport = MemoryTransport()
1313
client = FakeClient(transport.base)
1314
client.add_expected_call(
1315
'Branch.get_stacked_on_url', ('quack/',),
1316
'error', ('NotStacked',))
1317
client.add_expected_call(
1318
'Branch.get_tags_bytes', ('quack/',),
1320
transport.mkdir('quack')
1321
transport = transport.clone('quack')
1322
branch = self.make_remote_branch(transport, client)
1323
result = branch.tags.get_tag_dict()
1324
self.assertFinished(client)
1325
self.assertEqual({}, result)
1328
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1330
def test_trivial(self):
1331
transport = MemoryTransport()
1332
client = FakeClient(transport.base)
1333
client.add_expected_call(
1334
'Branch.get_stacked_on_url', ('quack/',),
1335
'error', ('NotStacked',))
1336
client.add_expected_call(
1337
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1339
transport.mkdir('quack')
1340
transport = transport.clone('quack')
1341
branch = self.make_remote_branch(transport, client)
1342
self.lock_remote_branch(branch)
1343
branch._set_tags_bytes('tags bytes')
1344
self.assertFinished(client)
1345
self.assertEqual('tags bytes', client._calls[-1][-1])
1347
def test_backwards_compatible(self):
1348
transport = MemoryTransport()
1349
client = FakeClient(transport.base)
1350
client.add_expected_call(
1351
'Branch.get_stacked_on_url', ('quack/',),
1352
'error', ('NotStacked',))
1353
client.add_expected_call(
1354
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1355
'unknown', ('Branch.set_tags_bytes',))
1356
transport.mkdir('quack')
1357
transport = transport.clone('quack')
1358
branch = self.make_remote_branch(transport, client)
1359
self.lock_remote_branch(branch)
1360
class StubRealBranch(object):
1363
def _set_tags_bytes(self, bytes):
1364
self.calls.append(('set_tags_bytes', bytes))
1365
real_branch = StubRealBranch()
1366
branch._real_branch = real_branch
1367
branch._set_tags_bytes('tags bytes')
1368
# Call a second time, to exercise the 'remote version already inferred'
1370
branch._set_tags_bytes('tags bytes')
1371
self.assertFinished(client)
1373
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1376
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1378
def test_uses_last_revision_info_and_tags_by_default(self):
1379
transport = MemoryTransport()
1380
client = FakeClient(transport.base)
1381
client.add_expected_call(
1382
'Branch.get_stacked_on_url', ('quack/',),
1383
'error', ('NotStacked',))
1384
client.add_expected_call(
1385
'Branch.last_revision_info', ('quack/',),
1386
'success', ('ok', '1', 'rev-tip'))
1387
client.add_expected_call(
1388
'Branch.get_config_file', ('quack/',),
1389
'success', ('ok',), '')
1390
transport.mkdir('quack')
1391
transport = transport.clone('quack')
1392
branch = self.make_remote_branch(transport, client)
1393
result = branch.heads_to_fetch()
1394
self.assertFinished(client)
1395
self.assertEqual(({'rev-tip'}, set()), result)
1397
def test_uses_last_revision_info_and_tags_when_set(self):
1398
transport = MemoryTransport()
1399
client = FakeClient(transport.base)
1400
client.add_expected_call(
1401
'Branch.get_stacked_on_url', ('quack/',),
1402
'error', ('NotStacked',))
1403
client.add_expected_call(
1404
'Branch.last_revision_info', ('quack/',),
1405
'success', ('ok', '1', 'rev-tip'))
1406
client.add_expected_call(
1407
'Branch.get_config_file', ('quack/',),
1408
'success', ('ok',), 'branch.fetch_tags = True')
1409
# XXX: this will break if the default format's serialization of tags
1410
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1411
client.add_expected_call(
1412
'Branch.get_tags_bytes', ('quack/',),
1413
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1414
transport.mkdir('quack')
1415
transport = transport.clone('quack')
1416
branch = self.make_remote_branch(transport, client)
1417
result = branch.heads_to_fetch()
1418
self.assertFinished(client)
1420
({'rev-tip'}, {'rev-foo', 'rev-bar'}), result)
1422
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1423
transport = MemoryTransport()
1424
client = FakeClient(transport.base)
1425
client.add_expected_call(
1426
'Branch.get_stacked_on_url', ('quack/',),
1427
'error', ('NotStacked',))
1428
client.add_expected_call(
1429
'Branch.heads_to_fetch', ('quack/',),
1430
'success', (['tip'], ['tagged-1', 'tagged-2']))
1431
transport.mkdir('quack')
1432
transport = transport.clone('quack')
1433
branch = self.make_remote_branch(transport, client)
1434
branch._format._use_default_local_heads_to_fetch = lambda: False
1435
result = branch.heads_to_fetch()
1436
self.assertFinished(client)
1437
self.assertEqual(({'tip'}, {'tagged-1', 'tagged-2'}), result)
1439
def make_branch_with_tags(self):
1440
self.setup_smart_server_with_call_log()
1441
# Make a branch with a single revision.
1442
builder = self.make_branch_builder('foo')
1443
builder.start_series()
1444
builder.build_snapshot('tip', None, [
1445
('add', ('', 'root-id', 'directory', ''))])
1446
builder.finish_series()
1447
branch = builder.get_branch()
1448
# Add two tags to that branch
1449
branch.tags.set_tag('tag-1', 'rev-1')
1450
branch.tags.set_tag('tag-2', 'rev-2')
1453
def test_backwards_compatible(self):
1454
br = self.make_branch_with_tags()
1455
br.get_config_stack().set('branch.fetch_tags', True)
1456
self.addCleanup(br.lock_read().unlock)
1457
# Disable the heads_to_fetch verb
1458
verb = 'Branch.heads_to_fetch'
1459
self.disable_verb(verb)
1460
self.reset_smart_call_log()
1461
result = br.heads_to_fetch()
1462
self.assertEqual(({'tip'}, {'rev-1', 'rev-2'}), result)
1464
['Branch.last_revision_info', 'Branch.get_tags_bytes'],
1465
[call.call.method for call in self.hpss_calls])
1467
def test_backwards_compatible_no_tags(self):
1468
br = self.make_branch_with_tags()
1469
br.get_config_stack().set('branch.fetch_tags', False)
1470
self.addCleanup(br.lock_read().unlock)
1471
# Disable the heads_to_fetch verb
1472
verb = 'Branch.heads_to_fetch'
1473
self.disable_verb(verb)
1474
self.reset_smart_call_log()
1475
result = br.heads_to_fetch()
1476
self.assertEqual(({'tip'}, set()), result)
1478
['Branch.last_revision_info'],
1479
[call.call.method for call in self.hpss_calls])
1482
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1484
def test_empty_branch(self):
1485
# in an empty branch we decode the response properly
1486
transport = MemoryTransport()
1487
client = FakeClient(transport.base)
1488
client.add_expected_call(
1489
'Branch.get_stacked_on_url', ('quack/',),
1490
'error', ('NotStacked',))
1491
client.add_expected_call(
1492
'Branch.last_revision_info', ('quack/',),
1493
'success', ('ok', '0', 'null:'))
1494
transport.mkdir('quack')
1495
transport = transport.clone('quack')
1496
branch = self.make_remote_branch(transport, client)
1497
result = branch.last_revision_info()
1498
self.assertFinished(client)
1499
self.assertEqual((0, NULL_REVISION), result)
1501
def test_non_empty_branch(self):
1502
# in a non-empty branch we also decode the response properly
1503
revid = u'\xc8'.encode('utf8')
1504
transport = MemoryTransport()
1505
client = FakeClient(transport.base)
1506
client.add_expected_call(
1507
'Branch.get_stacked_on_url', ('kwaak/',),
1508
'error', ('NotStacked',))
1509
client.add_expected_call(
1510
'Branch.last_revision_info', ('kwaak/',),
1511
'success', ('ok', '2', revid))
1512
transport.mkdir('kwaak')
1513
transport = transport.clone('kwaak')
1514
branch = self.make_remote_branch(transport, client)
1515
result = branch.last_revision_info()
1516
self.assertEqual((2, revid), result)
1519
class TestBranch_get_stacked_on_url(TestRemote):
1520
"""Test Branch._get_stacked_on_url rpc"""
1522
def test_get_stacked_on_invalid_url(self):
1523
# test that asking for a stacked on url the server can't access works.
1524
# This isn't perfect, but then as we're in the same process there
1525
# really isn't anything we can do to be 100% sure that the server
1526
# doesn't just open in - this test probably needs to be rewritten using
1527
# a spawn()ed server.
1528
stacked_branch = self.make_branch('stacked', format='1.9')
1529
memory_branch = self.make_branch('base', format='1.9')
1530
vfs_url = self.get_vfs_only_url('base')
1531
stacked_branch.set_stacked_on_url(vfs_url)
1532
transport = stacked_branch.controldir.root_transport
1533
client = FakeClient(transport.base)
1534
client.add_expected_call(
1535
'Branch.get_stacked_on_url', ('stacked/',),
1536
'success', ('ok', vfs_url))
1537
# XXX: Multiple calls are bad, this second call documents what is
1539
client.add_expected_call(
1540
'Branch.get_stacked_on_url', ('stacked/',),
1541
'success', ('ok', vfs_url))
1542
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1544
repo_fmt = remote.RemoteRepositoryFormat()
1545
repo_fmt._custom_format = stacked_branch.repository._format
1546
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1548
result = branch.get_stacked_on_url()
1549
self.assertEqual(vfs_url, result)
1551
def test_backwards_compatible(self):
1552
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1553
base_branch = self.make_branch('base', format='1.6')
1554
stacked_branch = self.make_branch('stacked', format='1.6')
1555
stacked_branch.set_stacked_on_url('../base')
1556
client = FakeClient(self.get_url())
1557
branch_network_name = self.get_branch_format().network_name()
1558
client.add_expected_call(
1559
'BzrDir.open_branchV3', ('stacked/',),
1560
'success', ('branch', branch_network_name))
1561
client.add_expected_call(
1562
'BzrDir.find_repositoryV3', ('stacked/',),
1563
'success', ('ok', '', 'no', 'no', 'yes',
1564
stacked_branch.repository._format.network_name()))
1565
# called twice, once from constructor and then again by us
1566
client.add_expected_call(
1567
'Branch.get_stacked_on_url', ('stacked/',),
1568
'unknown', ('Branch.get_stacked_on_url',))
1569
client.add_expected_call(
1570
'Branch.get_stacked_on_url', ('stacked/',),
1571
'unknown', ('Branch.get_stacked_on_url',))
1572
# this will also do vfs access, but that goes direct to the transport
1573
# and isn't seen by the FakeClient.
1574
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1575
RemoteBzrDirFormat(), _client=client)
1576
branch = bzrdir.open_branch()
1577
result = branch.get_stacked_on_url()
1578
self.assertEqual('../base', result)
1579
self.assertFinished(client)
1580
# it's in the fallback list both for the RemoteRepository and its vfs
1582
self.assertEqual(1, len(branch.repository._fallback_repositories))
1584
len(branch.repository._real_repository._fallback_repositories))
1586
def test_get_stacked_on_real_branch(self):
1587
base_branch = self.make_branch('base')
1588
stacked_branch = self.make_branch('stacked')
1589
stacked_branch.set_stacked_on_url('../base')
1590
reference_format = self.get_repo_format()
1591
network_name = reference_format.network_name()
1592
client = FakeClient(self.get_url())
1593
branch_network_name = self.get_branch_format().network_name()
1594
client.add_expected_call(
1595
'BzrDir.open_branchV3', ('stacked/',),
1596
'success', ('branch', branch_network_name))
1597
client.add_expected_call(
1598
'BzrDir.find_repositoryV3', ('stacked/',),
1599
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1600
# called twice, once from constructor and then again by us
1601
client.add_expected_call(
1602
'Branch.get_stacked_on_url', ('stacked/',),
1603
'success', ('ok', '../base'))
1604
client.add_expected_call(
1605
'Branch.get_stacked_on_url', ('stacked/',),
1606
'success', ('ok', '../base'))
1607
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1608
RemoteBzrDirFormat(), _client=client)
1609
branch = bzrdir.open_branch()
1610
result = branch.get_stacked_on_url()
1611
self.assertEqual('../base', result)
1612
self.assertFinished(client)
1613
# it's in the fallback list both for the RemoteRepository.
1614
self.assertEqual(1, len(branch.repository._fallback_repositories))
1615
# And we haven't had to construct a real repository.
1616
self.assertEqual(None, branch.repository._real_repository)
1619
class TestBranchSetLastRevision(RemoteBranchTestCase):
1621
def test_set_empty(self):
1622
# _set_last_revision_info('null:') is translated to calling
1623
# Branch.set_last_revision(path, '') on the wire.
1624
transport = MemoryTransport()
1625
transport.mkdir('branch')
1626
transport = transport.clone('branch')
1628
client = FakeClient(transport.base)
1629
client.add_expected_call(
1630
'Branch.get_stacked_on_url', ('branch/',),
1631
'error', ('NotStacked',))
1632
client.add_expected_call(
1633
'Branch.lock_write', ('branch/', '', ''),
1634
'success', ('ok', 'branch token', 'repo token'))
1635
client.add_expected_call(
1636
'Branch.last_revision_info',
1638
'success', ('ok', '0', 'null:'))
1639
client.add_expected_call(
1640
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1642
client.add_expected_call(
1643
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1645
branch = self.make_remote_branch(transport, client)
1647
result = branch._set_last_revision(NULL_REVISION)
1649
self.assertEqual(None, result)
1650
self.assertFinished(client)
1652
def test_set_nonempty(self):
1653
# set_last_revision_info(N, rev-idN) is translated to calling
1654
# Branch.set_last_revision(path, rev-idN) on the wire.
1655
transport = MemoryTransport()
1656
transport.mkdir('branch')
1657
transport = transport.clone('branch')
1659
client = FakeClient(transport.base)
1660
client.add_expected_call(
1661
'Branch.get_stacked_on_url', ('branch/',),
1662
'error', ('NotStacked',))
1663
client.add_expected_call(
1664
'Branch.lock_write', ('branch/', '', ''),
1665
'success', ('ok', 'branch token', 'repo token'))
1666
client.add_expected_call(
1667
'Branch.last_revision_info',
1669
'success', ('ok', '0', 'null:'))
1671
encoded_body = bz2.compress('\n'.join(lines))
1672
client.add_success_response_with_body(encoded_body, 'ok')
1673
client.add_expected_call(
1674
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1676
client.add_expected_call(
1677
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1679
branch = self.make_remote_branch(transport, client)
1680
# Lock the branch, reset the record of remote calls.
1682
result = branch._set_last_revision('rev-id2')
1684
self.assertEqual(None, result)
1685
self.assertFinished(client)
1687
def test_no_such_revision(self):
1688
transport = MemoryTransport()
1689
transport.mkdir('branch')
1690
transport = transport.clone('branch')
1691
# A response of 'NoSuchRevision' is translated into an exception.
1692
client = FakeClient(transport.base)
1693
client.add_expected_call(
1694
'Branch.get_stacked_on_url', ('branch/',),
1695
'error', ('NotStacked',))
1696
client.add_expected_call(
1697
'Branch.lock_write', ('branch/', '', ''),
1698
'success', ('ok', 'branch token', 'repo token'))
1699
client.add_expected_call(
1700
'Branch.last_revision_info',
1702
'success', ('ok', '0', 'null:'))
1703
# get_graph calls to construct the revision history, for the set_rh
1706
encoded_body = bz2.compress('\n'.join(lines))
1707
client.add_success_response_with_body(encoded_body, 'ok')
1708
client.add_expected_call(
1709
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1710
'error', ('NoSuchRevision', 'rev-id'))
1711
client.add_expected_call(
1712
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1715
branch = self.make_remote_branch(transport, client)
1718
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1720
self.assertFinished(client)
1722
def test_tip_change_rejected(self):
1723
"""TipChangeRejected responses cause a TipChangeRejected exception to
1726
transport = MemoryTransport()
1727
transport.mkdir('branch')
1728
transport = transport.clone('branch')
1729
client = FakeClient(transport.base)
1730
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1731
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1732
client.add_expected_call(
1733
'Branch.get_stacked_on_url', ('branch/',),
1734
'error', ('NotStacked',))
1735
client.add_expected_call(
1736
'Branch.lock_write', ('branch/', '', ''),
1737
'success', ('ok', 'branch token', 'repo token'))
1738
client.add_expected_call(
1739
'Branch.last_revision_info',
1741
'success', ('ok', '0', 'null:'))
1743
encoded_body = bz2.compress('\n'.join(lines))
1744
client.add_success_response_with_body(encoded_body, 'ok')
1745
client.add_expected_call(
1746
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1747
'error', ('TipChangeRejected', rejection_msg_utf8))
1748
client.add_expected_call(
1749
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1751
branch = self.make_remote_branch(transport, client)
1753
# The 'TipChangeRejected' error response triggered by calling
1754
# set_last_revision_info causes a TipChangeRejected exception.
1755
err = self.assertRaises(
1756
errors.TipChangeRejected,
1757
branch._set_last_revision, 'rev-id')
1758
# The UTF-8 message from the response has been decoded into a unicode
1760
self.assertIsInstance(err.msg, unicode)
1761
self.assertEqual(rejection_msg_unicode, err.msg)
1763
self.assertFinished(client)
1766
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1768
def test_set_last_revision_info(self):
1769
# set_last_revision_info(num, 'rev-id') is translated to calling
1770
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1771
transport = MemoryTransport()
1772
transport.mkdir('branch')
1773
transport = transport.clone('branch')
1774
client = FakeClient(transport.base)
1775
# get_stacked_on_url
1776
client.add_error_response('NotStacked')
1778
client.add_success_response('ok', 'branch token', 'repo token')
1779
# query the current revision
1780
client.add_success_response('ok', '0', 'null:')
1782
client.add_success_response('ok')
1784
client.add_success_response('ok')
1786
branch = self.make_remote_branch(transport, client)
1787
# Lock the branch, reset the record of remote calls.
1790
result = branch.set_last_revision_info(1234, 'a-revision-id')
1792
[('call', 'Branch.last_revision_info', ('branch/',)),
1793
('call', 'Branch.set_last_revision_info',
1794
('branch/', 'branch token', 'repo token',
1795
'1234', 'a-revision-id'))],
1797
self.assertEqual(None, result)
1799
def test_no_such_revision(self):
1800
# A response of 'NoSuchRevision' is translated into an exception.
1801
transport = MemoryTransport()
1802
transport.mkdir('branch')
1803
transport = transport.clone('branch')
1804
client = FakeClient(transport.base)
1805
# get_stacked_on_url
1806
client.add_error_response('NotStacked')
1808
client.add_success_response('ok', 'branch token', 'repo token')
1810
client.add_error_response('NoSuchRevision', 'revid')
1812
client.add_success_response('ok')
1814
branch = self.make_remote_branch(transport, client)
1815
# Lock the branch, reset the record of remote calls.
1820
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1823
def test_backwards_compatibility(self):
1824
"""If the server does not support the Branch.set_last_revision_info
1825
verb (which is new in 1.4), then the client falls back to VFS methods.
1827
# This test is a little messy. Unlike most tests in this file, it
1828
# doesn't purely test what a Remote* object sends over the wire, and
1829
# how it reacts to responses from the wire. It instead relies partly
1830
# on asserting that the RemoteBranch will call
1831
# self._real_branch.set_last_revision_info(...).
1833
# First, set up our RemoteBranch with a FakeClient that raises
1834
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1835
transport = MemoryTransport()
1836
transport.mkdir('branch')
1837
transport = transport.clone('branch')
1838
client = FakeClient(transport.base)
1839
client.add_expected_call(
1840
'Branch.get_stacked_on_url', ('branch/',),
1841
'error', ('NotStacked',))
1842
client.add_expected_call(
1843
'Branch.last_revision_info',
1845
'success', ('ok', '0', 'null:'))
1846
client.add_expected_call(
1847
'Branch.set_last_revision_info',
1848
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1849
'unknown', 'Branch.set_last_revision_info')
1851
branch = self.make_remote_branch(transport, client)
1852
class StubRealBranch(object):
1855
def set_last_revision_info(self, revno, revision_id):
1857
('set_last_revision_info', revno, revision_id))
1858
def _clear_cached_state(self):
1860
real_branch = StubRealBranch()
1861
branch._real_branch = real_branch
1862
self.lock_remote_branch(branch)
1864
# Call set_last_revision_info, and verify it behaved as expected.
1865
result = branch.set_last_revision_info(1234, 'a-revision-id')
1867
[('set_last_revision_info', 1234, 'a-revision-id')],
1869
self.assertFinished(client)
1871
def test_unexpected_error(self):
1872
# If the server sends an error the client doesn't understand, it gets
1873
# turned into an UnknownErrorFromSmartServer, which is presented as a
1874
# non-internal error to the user.
1875
transport = MemoryTransport()
1876
transport.mkdir('branch')
1877
transport = transport.clone('branch')
1878
client = FakeClient(transport.base)
1879
# get_stacked_on_url
1880
client.add_error_response('NotStacked')
1882
client.add_success_response('ok', 'branch token', 'repo token')
1884
client.add_error_response('UnexpectedError')
1886
client.add_success_response('ok')
1888
branch = self.make_remote_branch(transport, client)
1889
# Lock the branch, reset the record of remote calls.
1893
err = self.assertRaises(
1894
errors.UnknownErrorFromSmartServer,
1895
branch.set_last_revision_info, 123, 'revid')
1896
self.assertEqual(('UnexpectedError',), err.error_tuple)
1899
def test_tip_change_rejected(self):
1900
"""TipChangeRejected responses cause a TipChangeRejected exception to
1903
transport = MemoryTransport()
1904
transport.mkdir('branch')
1905
transport = transport.clone('branch')
1906
client = FakeClient(transport.base)
1907
# get_stacked_on_url
1908
client.add_error_response('NotStacked')
1910
client.add_success_response('ok', 'branch token', 'repo token')
1912
client.add_error_response('TipChangeRejected', 'rejection message')
1914
client.add_success_response('ok')
1916
branch = self.make_remote_branch(transport, client)
1917
# Lock the branch, reset the record of remote calls.
1919
self.addCleanup(branch.unlock)
1922
# The 'TipChangeRejected' error response triggered by calling
1923
# set_last_revision_info causes a TipChangeRejected exception.
1924
err = self.assertRaises(
1925
errors.TipChangeRejected,
1926
branch.set_last_revision_info, 123, 'revid')
1927
self.assertEqual('rejection message', err.msg)
1930
class TestBranchGetSetConfig(RemoteBranchTestCase):
1932
def test_get_branch_conf(self):
1933
# in an empty branch we decode the response properly
1934
client = FakeClient()
1935
client.add_expected_call(
1936
'Branch.get_stacked_on_url', ('memory:///',),
1937
'error', ('NotStacked',),)
1938
client.add_success_response_with_body('# config file body', 'ok')
1939
transport = MemoryTransport()
1940
branch = self.make_remote_branch(transport, client)
1941
config = branch.get_config()
1942
config.has_explicit_nickname()
1944
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1945
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1948
def test_get_multi_line_branch_conf(self):
1949
# Make sure that multiple-line branch.conf files are supported
1951
# https://bugs.launchpad.net/bzr/+bug/354075
1952
client = FakeClient()
1953
client.add_expected_call(
1954
'Branch.get_stacked_on_url', ('memory:///',),
1955
'error', ('NotStacked',),)
1956
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1957
transport = MemoryTransport()
1958
branch = self.make_remote_branch(transport, client)
1959
config = branch.get_config()
1960
self.assertEqual(u'2', config.get_user_option('b'))
1962
def test_set_option(self):
1963
client = FakeClient()
1964
client.add_expected_call(
1965
'Branch.get_stacked_on_url', ('memory:///',),
1966
'error', ('NotStacked',),)
1967
client.add_expected_call(
1968
'Branch.lock_write', ('memory:///', '', ''),
1969
'success', ('ok', 'branch token', 'repo token'))
1970
client.add_expected_call(
1971
'Branch.set_config_option', ('memory:///', 'branch token',
1972
'repo token', 'foo', 'bar', ''),
1974
client.add_expected_call(
1975
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1977
transport = MemoryTransport()
1978
branch = self.make_remote_branch(transport, client)
1980
config = branch._get_config()
1981
config.set_option('foo', 'bar')
1983
self.assertFinished(client)
1985
def test_set_option_with_dict(self):
1986
client = FakeClient()
1987
client.add_expected_call(
1988
'Branch.get_stacked_on_url', ('memory:///',),
1989
'error', ('NotStacked',),)
1990
client.add_expected_call(
1991
'Branch.lock_write', ('memory:///', '', ''),
1992
'success', ('ok', 'branch token', 'repo token'))
1993
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1994
client.add_expected_call(
1995
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1996
'repo token', encoded_dict_value, 'foo', ''),
1998
client.add_expected_call(
1999
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2001
transport = MemoryTransport()
2002
branch = self.make_remote_branch(transport, client)
2004
config = branch._get_config()
2006
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2009
self.assertFinished(client)
2011
def test_backwards_compat_set_option(self):
2012
self.setup_smart_server_with_call_log()
2013
branch = self.make_branch('.')
2014
verb = 'Branch.set_config_option'
2015
self.disable_verb(verb)
2017
self.addCleanup(branch.unlock)
2018
self.reset_smart_call_log()
2019
branch._get_config().set_option('value', 'name')
2020
self.assertLength(11, self.hpss_calls)
2021
self.assertEqual('value', branch._get_config().get_option('name'))
2023
def test_backwards_compat_set_option_with_dict(self):
2024
self.setup_smart_server_with_call_log()
2025
branch = self.make_branch('.')
2026
verb = 'Branch.set_config_option_dict'
2027
self.disable_verb(verb)
2029
self.addCleanup(branch.unlock)
2030
self.reset_smart_call_log()
2031
config = branch._get_config()
2032
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2033
config.set_option(value_dict, 'name')
2034
self.assertLength(11, self.hpss_calls)
2035
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2038
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2040
def test_get_branch_conf(self):
2041
# in an empty branch we decode the response properly
2042
client = FakeClient()
2043
client.add_expected_call(
2044
'Branch.get_stacked_on_url', ('memory:///',),
2045
'error', ('NotStacked',),)
2046
client.add_success_response_with_body('# config file body', 'ok')
2047
transport = MemoryTransport()
2048
branch = self.make_remote_branch(transport, client)
2049
config = branch.get_config_stack()
2051
config.get("log_format")
2053
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2054
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2057
def test_set_branch_conf(self):
2058
client = FakeClient()
2059
client.add_expected_call(
2060
'Branch.get_stacked_on_url', ('memory:///',),
2061
'error', ('NotStacked',),)
2062
client.add_expected_call(
2063
'Branch.lock_write', ('memory:///', '', ''),
2064
'success', ('ok', 'branch token', 'repo token'))
2065
client.add_expected_call(
2066
'Branch.get_config_file', ('memory:///', ),
2067
'success', ('ok', ), "# line 1\n")
2068
client.add_expected_call(
2069
'Branch.get_config_file', ('memory:///', ),
2070
'success', ('ok', ), "# line 1\n")
2071
client.add_expected_call(
2072
'Branch.put_config_file', ('memory:///', 'branch token',
2075
client.add_expected_call(
2076
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2078
transport = MemoryTransport()
2079
branch = self.make_remote_branch(transport, client)
2081
config = branch.get_config_stack()
2082
config.set('email', 'The Dude <lebowski@example.com>')
2084
self.assertFinished(client)
2086
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2087
('call', 'Branch.lock_write', ('memory:///', '', '')),
2088
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2089
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2090
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2091
('memory:///', 'branch token', 'repo token'),
2092
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2093
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2097
class TestBranchLockWrite(RemoteBranchTestCase):
2099
def test_lock_write_unlockable(self):
2100
transport = MemoryTransport()
2101
client = FakeClient(transport.base)
2102
client.add_expected_call(
2103
'Branch.get_stacked_on_url', ('quack/',),
2104
'error', ('NotStacked',),)
2105
client.add_expected_call(
2106
'Branch.lock_write', ('quack/', '', ''),
2107
'error', ('UnlockableTransport',))
2108
transport.mkdir('quack')
2109
transport = transport.clone('quack')
2110
branch = self.make_remote_branch(transport, client)
2111
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2112
self.assertFinished(client)
2115
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2117
def test_simple(self):
2118
transport = MemoryTransport()
2119
client = FakeClient(transport.base)
2120
client.add_expected_call(
2121
'Branch.get_stacked_on_url', ('quack/',),
2122
'error', ('NotStacked',),)
2123
client.add_expected_call(
2124
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2125
'success', ('ok', '0',),)
2126
client.add_expected_call(
2127
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2128
'error', ('NoSuchRevision', 'unknown',),)
2129
transport.mkdir('quack')
2130
transport = transport.clone('quack')
2131
branch = self.make_remote_branch(transport, client)
2132
self.assertEqual(0, branch.revision_id_to_revno('null:'))
2133
self.assertRaises(errors.NoSuchRevision,
2134
branch.revision_id_to_revno, 'unknown')
2135
self.assertFinished(client)
2137
def test_dotted(self):
2138
transport = MemoryTransport()
2139
client = FakeClient(transport.base)
2140
client.add_expected_call(
2141
'Branch.get_stacked_on_url', ('quack/',),
2142
'error', ('NotStacked',),)
2143
client.add_expected_call(
2144
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2145
'success', ('ok', '0',),)
2146
client.add_expected_call(
2147
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2148
'error', ('NoSuchRevision', 'unknown',),)
2149
transport.mkdir('quack')
2150
transport = transport.clone('quack')
2151
branch = self.make_remote_branch(transport, client)
2152
self.assertEqual((0, ), branch.revision_id_to_dotted_revno('null:'))
2153
self.assertRaises(errors.NoSuchRevision,
2154
branch.revision_id_to_dotted_revno, 'unknown')
2155
self.assertFinished(client)
2157
def test_dotted_no_smart_verb(self):
2158
self.setup_smart_server_with_call_log()
2159
branch = self.make_branch('.')
2160
self.disable_verb('Branch.revision_id_to_revno')
2161
self.reset_smart_call_log()
2162
self.assertEqual((0, ),
2163
branch.revision_id_to_dotted_revno('null:'))
2164
self.assertLength(8, self.hpss_calls)
2167
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2169
def test__get_config(self):
2170
client = FakeClient()
2171
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2172
transport = MemoryTransport()
2173
bzrdir = self.make_remote_bzrdir(transport, client)
2174
config = bzrdir.get_config()
2175
self.assertEqual('/', config.get_default_stack_on())
2177
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2180
def test_set_option_uses_vfs(self):
2181
self.setup_smart_server_with_call_log()
2182
bzrdir = self.make_controldir('.')
2183
self.reset_smart_call_log()
2184
config = bzrdir.get_config()
2185
config.set_default_stack_on('/')
2186
self.assertLength(4, self.hpss_calls)
2188
def test_backwards_compat_get_option(self):
2189
self.setup_smart_server_with_call_log()
2190
bzrdir = self.make_controldir('.')
2191
verb = 'BzrDir.get_config_file'
2192
self.disable_verb(verb)
2193
self.reset_smart_call_log()
2194
self.assertEqual(None,
2195
bzrdir._get_config().get_option('default_stack_on'))
2196
self.assertLength(4, self.hpss_calls)
2199
class TestTransportIsReadonly(tests.TestCase):
2201
def test_true(self):
2202
client = FakeClient()
2203
client.add_success_response('yes')
2204
transport = RemoteTransport('bzr://example.com/', medium=False,
2206
self.assertEqual(True, transport.is_readonly())
2208
[('call', 'Transport.is_readonly', ())],
2211
def test_false(self):
2212
client = FakeClient()
2213
client.add_success_response('no')
2214
transport = RemoteTransport('bzr://example.com/', medium=False,
2216
self.assertEqual(False, transport.is_readonly())
2218
[('call', 'Transport.is_readonly', ())],
2221
def test_error_from_old_server(self):
2222
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2224
Clients should treat it as a "no" response, because is_readonly is only
2225
advisory anyway (a transport could be read-write, but then the
2226
underlying filesystem could be readonly anyway).
2228
client = FakeClient()
2229
client.add_unknown_method_response('Transport.is_readonly')
2230
transport = RemoteTransport('bzr://example.com/', medium=False,
2232
self.assertEqual(False, transport.is_readonly())
2234
[('call', 'Transport.is_readonly', ())],
2238
class TestTransportMkdir(tests.TestCase):
2240
def test_permissiondenied(self):
2241
client = FakeClient()
2242
client.add_error_response('PermissionDenied', 'remote path', 'extra')
2243
transport = RemoteTransport('bzr://example.com/', medium=False,
2245
exc = self.assertRaises(
2246
errors.PermissionDenied, transport.mkdir, 'client path')
2247
expected_error = errors.PermissionDenied('/client path', 'extra')
2248
self.assertEqual(expected_error, exc)
2251
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2253
def test_defaults_to_none(self):
2254
t = RemoteSSHTransport('bzr+ssh://example.com')
2255
self.assertIs(None, t._get_credentials()[0])
2257
def test_uses_authentication_config(self):
2258
conf = config.AuthenticationConfig()
2259
conf._get_config().update(
2260
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2263
t = RemoteSSHTransport('bzr+ssh://example.com')
2264
self.assertEqual('bar', t._get_credentials()[0])
2267
class TestRemoteRepository(TestRemote):
2268
"""Base for testing RemoteRepository protocol usage.
2270
These tests contain frozen requests and responses. We want any changes to
2271
what is sent or expected to be require a thoughtful update to these tests
2272
because they might break compatibility with different-versioned servers.
2275
def setup_fake_client_and_repository(self, transport_path):
2276
"""Create the fake client and repository for testing with.
2278
There's no real server here; we just have canned responses sent
2281
:param transport_path: Path below the root of the MemoryTransport
2282
where the repository will be created.
2284
transport = MemoryTransport()
2285
transport.mkdir(transport_path)
2286
client = FakeClient(transport.base)
2287
transport = transport.clone(transport_path)
2288
# we do not want bzrdir to make any remote calls
2289
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2291
repo = RemoteRepository(bzrdir, None, _client=client)
2295
def remoted_description(format):
2296
return 'Remote: ' + format.get_format_description()
2299
class TestBranchFormat(tests.TestCase):
2301
def test_get_format_description(self):
2302
remote_format = RemoteBranchFormat()
2303
real_format = branch.format_registry.get_default()
2304
remote_format._network_name = real_format.network_name()
2305
self.assertEqual(remoted_description(real_format),
2306
remote_format.get_format_description())
2309
class TestRepositoryFormat(TestRemoteRepository):
2311
def test_fast_delta(self):
2312
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2313
true_format = RemoteRepositoryFormat()
2314
true_format._network_name = true_name
2315
self.assertEqual(True, true_format.fast_deltas)
2316
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2317
false_format = RemoteRepositoryFormat()
2318
false_format._network_name = false_name
2319
self.assertEqual(False, false_format.fast_deltas)
2321
def test_get_format_description(self):
2322
remote_repo_format = RemoteRepositoryFormat()
2323
real_format = repository.format_registry.get_default()
2324
remote_repo_format._network_name = real_format.network_name()
2325
self.assertEqual(remoted_description(real_format),
2326
remote_repo_format.get_format_description())
2329
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2331
def test_empty(self):
2332
transport_path = 'quack'
2333
repo, client = self.setup_fake_client_and_repository(transport_path)
2334
client.add_success_response_with_body('', 'ok')
2335
self.assertEqual([], repo.all_revision_ids())
2337
[('call_expecting_body', 'Repository.all_revision_ids',
2341
def test_with_some_content(self):
2342
transport_path = 'quack'
2343
repo, client = self.setup_fake_client_and_repository(transport_path)
2344
client.add_success_response_with_body(
2345
'rev1\nrev2\nanotherrev\n', 'ok')
2346
self.assertEqual(["rev1", "rev2", "anotherrev"],
2347
repo.all_revision_ids())
2349
[('call_expecting_body', 'Repository.all_revision_ids',
2354
class TestRepositoryGatherStats(TestRemoteRepository):
2356
def test_revid_none(self):
2357
# ('ok',), body with revisions and size
2358
transport_path = 'quack'
2359
repo, client = self.setup_fake_client_and_repository(transport_path)
2360
client.add_success_response_with_body(
2361
'revisions: 2\nsize: 18\n', 'ok')
2362
result = repo.gather_stats(None)
2364
[('call_expecting_body', 'Repository.gather_stats',
2365
('quack/','','no'))],
2367
self.assertEqual({'revisions': 2, 'size': 18}, result)
2369
def test_revid_no_committers(self):
2370
# ('ok',), body without committers
2371
body = ('firstrev: 123456.300 3600\n'
2372
'latestrev: 654231.400 0\n'
2375
transport_path = 'quick'
2376
revid = u'\xc8'.encode('utf8')
2377
repo, client = self.setup_fake_client_and_repository(transport_path)
2378
client.add_success_response_with_body(body, 'ok')
2379
result = repo.gather_stats(revid)
2381
[('call_expecting_body', 'Repository.gather_stats',
2382
('quick/', revid, 'no'))],
2384
self.assertEqual({'revisions': 2, 'size': 18,
2385
'firstrev': (123456.300, 3600),
2386
'latestrev': (654231.400, 0),},
2389
def test_revid_with_committers(self):
2390
# ('ok',), body with committers
2391
body = ('committers: 128\n'
2392
'firstrev: 123456.300 3600\n'
2393
'latestrev: 654231.400 0\n'
2396
transport_path = 'buick'
2397
revid = u'\xc8'.encode('utf8')
2398
repo, client = self.setup_fake_client_and_repository(transport_path)
2399
client.add_success_response_with_body(body, 'ok')
2400
result = repo.gather_stats(revid, True)
2402
[('call_expecting_body', 'Repository.gather_stats',
2403
('buick/', revid, 'yes'))],
2405
self.assertEqual({'revisions': 2, 'size': 18,
2407
'firstrev': (123456.300, 3600),
2408
'latestrev': (654231.400, 0),},
2412
class TestRepositoryBreakLock(TestRemoteRepository):
2414
def test_break_lock(self):
2415
transport_path = 'quack'
2416
repo, client = self.setup_fake_client_and_repository(transport_path)
2417
client.add_success_response('ok')
2420
[('call', 'Repository.break_lock', ('quack/',))],
2424
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2426
def test_get_serializer_format(self):
2427
transport_path = 'hill'
2428
repo, client = self.setup_fake_client_and_repository(transport_path)
2429
client.add_success_response('ok', '7')
2430
self.assertEqual('7', repo.get_serializer_format())
2432
[('call', 'VersionedFileRepository.get_serializer_format',
2437
class TestRepositoryReconcile(TestRemoteRepository):
2439
def test_reconcile(self):
2440
transport_path = 'hill'
2441
repo, client = self.setup_fake_client_and_repository(transport_path)
2442
body = ("garbage_inventories: 2\n"
2443
"inconsistent_parents: 3\n")
2444
client.add_expected_call(
2445
'Repository.lock_write', ('hill/', ''),
2446
'success', ('ok', 'a token'))
2447
client.add_success_response_with_body(body, 'ok')
2448
reconciler = repo.reconcile()
2450
[('call', 'Repository.lock_write', ('hill/', '')),
2451
('call_expecting_body', 'Repository.reconcile',
2452
('hill/', 'a token'))],
2454
self.assertEqual(2, reconciler.garbage_inventories)
2455
self.assertEqual(3, reconciler.inconsistent_parents)
2458
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2460
def test_text(self):
2461
# ('ok',), body with signature text
2462
transport_path = 'quack'
2463
repo, client = self.setup_fake_client_and_repository(transport_path)
2464
client.add_success_response_with_body(
2466
self.assertEqual("THETEXT", repo.get_signature_text("revid"))
2468
[('call_expecting_body', 'Repository.get_revision_signature_text',
2469
('quack/', 'revid'))],
2472
def test_no_signature(self):
2473
transport_path = 'quick'
2474
repo, client = self.setup_fake_client_and_repository(transport_path)
2475
client.add_error_response('nosuchrevision', 'unknown')
2476
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2479
[('call_expecting_body', 'Repository.get_revision_signature_text',
2480
('quick/', 'unknown'))],
2484
class TestRepositoryGetGraph(TestRemoteRepository):
2486
def test_get_graph(self):
2487
# get_graph returns a graph with a custom parents provider.
2488
transport_path = 'quack'
2489
repo, client = self.setup_fake_client_and_repository(transport_path)
2490
graph = repo.get_graph()
2491
self.assertNotEqual(graph._parents_provider, repo)
2494
class TestRepositoryAddSignatureText(TestRemoteRepository):
2496
def test_add_signature_text(self):
2497
transport_path = 'quack'
2498
repo, client = self.setup_fake_client_and_repository(transport_path)
2499
client.add_expected_call(
2500
'Repository.lock_write', ('quack/', ''),
2501
'success', ('ok', 'a token'))
2502
client.add_expected_call(
2503
'Repository.start_write_group', ('quack/', 'a token'),
2504
'success', ('ok', ('token1', )))
2505
client.add_expected_call(
2506
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2508
'success', ('ok', ), None)
2510
repo.start_write_group()
2512
repo.add_signature_text("rev1", "every bloody emperor"))
2514
('call_with_body_bytes_expecting_body',
2515
'Repository.add_signature_text',
2516
('quack/', 'a token', 'rev1', 'token1'),
2517
'every bloody emperor'),
2521
class TestRepositoryGetParentMap(TestRemoteRepository):
2523
def test_get_parent_map_caching(self):
2524
# get_parent_map returns from cache until unlock()
2525
# setup a reponse with two revisions
2526
r1 = u'\u0e33'.encode('utf8')
2527
r2 = u'\u0dab'.encode('utf8')
2528
lines = [' '.join([r2, r1]), r1]
2529
encoded_body = bz2.compress('\n'.join(lines))
2531
transport_path = 'quack'
2532
repo, client = self.setup_fake_client_and_repository(transport_path)
2533
client.add_success_response_with_body(encoded_body, 'ok')
2534
client.add_success_response_with_body(encoded_body, 'ok')
2536
graph = repo.get_graph()
2537
parents = graph.get_parent_map([r2])
2538
self.assertEqual({r2: (r1,)}, parents)
2539
# locking and unlocking deeper should not reset
2542
parents = graph.get_parent_map([r1])
2543
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2545
[('call_with_body_bytes_expecting_body',
2546
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2550
# now we call again, and it should use the second response.
2552
graph = repo.get_graph()
2553
parents = graph.get_parent_map([r1])
2554
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2556
[('call_with_body_bytes_expecting_body',
2557
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2559
('call_with_body_bytes_expecting_body',
2560
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2566
def test_get_parent_map_reconnects_if_unknown_method(self):
2567
transport_path = 'quack'
2568
rev_id = 'revision-id'
2569
repo, client = self.setup_fake_client_and_repository(transport_path)
2570
client.add_unknown_method_response('Repository.get_parent_map')
2571
client.add_success_response_with_body(rev_id, 'ok')
2572
self.assertFalse(client._medium._is_remote_before((1, 2)))
2573
parents = repo.get_parent_map([rev_id])
2575
[('call_with_body_bytes_expecting_body',
2576
'Repository.get_parent_map',
2577
('quack/', 'include-missing:', rev_id), '\n\n0'),
2578
('disconnect medium',),
2579
('call_expecting_body', 'Repository.get_revision_graph',
2582
# The medium is now marked as being connected to an older server
2583
self.assertTrue(client._medium._is_remote_before((1, 2)))
2584
self.assertEqual({rev_id: ('null:',)}, parents)
2586
def test_get_parent_map_fallback_parentless_node(self):
2587
"""get_parent_map falls back to get_revision_graph on old servers. The
2588
results from get_revision_graph are tweaked to match the get_parent_map
2591
Specifically, a {key: ()} result from get_revision_graph means "no
2592
parents" for that key, which in get_parent_map results should be
2593
represented as {key: ('null:',)}.
2595
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2597
rev_id = 'revision-id'
2598
transport_path = 'quack'
2599
repo, client = self.setup_fake_client_and_repository(transport_path)
2600
client.add_success_response_with_body(rev_id, 'ok')
2601
client._medium._remember_remote_is_before((1, 2))
2602
parents = repo.get_parent_map([rev_id])
2604
[('call_expecting_body', 'Repository.get_revision_graph',
2607
self.assertEqual({rev_id: ('null:',)}, parents)
2609
def test_get_parent_map_unexpected_response(self):
2610
repo, client = self.setup_fake_client_and_repository('path')
2611
client.add_success_response('something unexpected!')
2613
errors.UnexpectedSmartServerResponse,
2614
repo.get_parent_map, ['a-revision-id'])
2616
def test_get_parent_map_negative_caches_missing_keys(self):
2617
self.setup_smart_server_with_call_log()
2618
repo = self.make_repository('foo')
2619
self.assertIsInstance(repo, RemoteRepository)
2621
self.addCleanup(repo.unlock)
2622
self.reset_smart_call_log()
2623
graph = repo.get_graph()
2624
self.assertEqual({},
2625
graph.get_parent_map(['some-missing', 'other-missing']))
2626
self.assertLength(1, self.hpss_calls)
2627
# No call if we repeat this
2628
self.reset_smart_call_log()
2629
graph = repo.get_graph()
2630
self.assertEqual({},
2631
graph.get_parent_map(['some-missing', 'other-missing']))
2632
self.assertLength(0, self.hpss_calls)
2633
# Asking for more unknown keys makes a request.
2634
self.reset_smart_call_log()
2635
graph = repo.get_graph()
2636
self.assertEqual({},
2637
graph.get_parent_map(['some-missing', 'other-missing',
2639
self.assertLength(1, self.hpss_calls)
2641
def disableExtraResults(self):
2642
self.overrideAttr(SmartServerRepositoryGetParentMap,
2643
'no_extra_results', True)
2645
def test_null_cached_missing_and_stop_key(self):
2646
self.setup_smart_server_with_call_log()
2647
# Make a branch with a single revision.
2648
builder = self.make_branch_builder('foo')
2649
builder.start_series()
2650
builder.build_snapshot('first', None, [
2651
('add', ('', 'root-id', 'directory', ''))])
2652
builder.finish_series()
2653
branch = builder.get_branch()
2654
repo = branch.repository
2655
self.assertIsInstance(repo, RemoteRepository)
2656
# Stop the server from sending extra results.
2657
self.disableExtraResults()
2659
self.addCleanup(repo.unlock)
2660
self.reset_smart_call_log()
2661
graph = repo.get_graph()
2662
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2663
# 'first' it will be a candidate for the stop_keys of subsequent
2664
# requests, and because 'null:' was queried but not returned it will be
2665
# cached as missing.
2666
self.assertEqual({'first': ('null:',)},
2667
graph.get_parent_map(['first', 'null:']))
2668
# Now query for another key. This request will pass along a recipe of
2669
# start and stop keys describing the already cached results, and this
2670
# recipe's revision count must be correct (or else it will trigger an
2671
# error from the server).
2672
self.assertEqual({}, graph.get_parent_map(['another-key']))
2673
# This assertion guards against disableExtraResults silently failing to
2674
# work, thus invalidating the test.
2675
self.assertLength(2, self.hpss_calls)
2677
def test_get_parent_map_gets_ghosts_from_result(self):
2678
# asking for a revision should negatively cache close ghosts in its
2680
self.setup_smart_server_with_call_log()
2681
tree = self.make_branch_and_memory_tree('foo')
2684
builder = treebuilder.TreeBuilder()
2685
builder.start_tree(tree)
2687
builder.finish_tree()
2688
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2689
rev_id = tree.commit('')
2693
self.addCleanup(tree.unlock)
2694
repo = tree.branch.repository
2695
self.assertIsInstance(repo, RemoteRepository)
2697
repo.get_parent_map([rev_id])
2698
self.reset_smart_call_log()
2699
# Now asking for rev_id's ghost parent should not make calls
2700
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2701
self.assertLength(0, self.hpss_calls)
2703
def test_exposes_get_cached_parent_map(self):
2704
"""RemoteRepository exposes get_cached_parent_map from
2707
r1 = u'\u0e33'.encode('utf8')
2708
r2 = u'\u0dab'.encode('utf8')
2709
lines = [' '.join([r2, r1]), r1]
2710
encoded_body = bz2.compress('\n'.join(lines))
2712
transport_path = 'quack'
2713
repo, client = self.setup_fake_client_and_repository(transport_path)
2714
client.add_success_response_with_body(encoded_body, 'ok')
2716
# get_cached_parent_map should *not* trigger an RPC
2717
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2718
self.assertEqual([], client._calls)
2719
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2720
self.assertEqual({r1: (NULL_REVISION,)},
2721
repo.get_cached_parent_map([r1]))
2723
[('call_with_body_bytes_expecting_body',
2724
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2730
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2732
def test_allows_new_revisions(self):
2733
"""get_parent_map's results can be updated by commit."""
2734
smart_server = test_server.SmartTCPServer_for_testing()
2735
self.start_server(smart_server)
2736
self.make_branch('branch')
2737
branch = Branch.open(smart_server.get_url() + '/branch')
2738
tree = branch.create_checkout('tree', lightweight=True)
2740
self.addCleanup(tree.unlock)
2741
graph = tree.branch.repository.get_graph()
2742
# This provides an opportunity for the missing rev-id to be cached.
2743
self.assertEqual({}, graph.get_parent_map(['rev1']))
2744
tree.commit('message', rev_id='rev1')
2745
graph = tree.branch.repository.get_graph()
2746
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2749
class TestRepositoryGetRevisions(TestRemoteRepository):
2751
def test_hpss_missing_revision(self):
2752
transport_path = 'quack'
2753
repo, client = self.setup_fake_client_and_repository(transport_path)
2754
client.add_success_response_with_body(
2756
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2757
['somerev1', 'anotherrev2'])
2759
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2760
('quack/', ), "somerev1\nanotherrev2")],
2763
def test_hpss_get_single_revision(self):
2764
transport_path = 'quack'
2765
repo, client = self.setup_fake_client_and_repository(transport_path)
2766
somerev1 = Revision("somerev1")
2767
somerev1.committer = "Joe Committer <joe@example.com>"
2768
somerev1.timestamp = 1321828927
2769
somerev1.timezone = -60
2770
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2771
somerev1.message = "Message"
2772
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2774
# Split up body into two bits to make sure the zlib compression object
2775
# gets data fed twice.
2776
client.add_success_response_with_body(
2777
[body[:10], body[10:]], 'ok', '10')
2778
revs = repo.get_revisions(['somerev1'])
2779
self.assertEqual(revs, [somerev1])
2781
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2782
('quack/', ), "somerev1")],
2786
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2788
def test_null_revision(self):
2789
# a null revision has the predictable result {}, we should have no wire
2790
# traffic when calling it with this argument
2791
transport_path = 'empty'
2792
repo, client = self.setup_fake_client_and_repository(transport_path)
2793
client.add_success_response('notused')
2794
# actual RemoteRepository.get_revision_graph is gone, but there's an
2795
# equivalent private method for testing
2796
result = repo._get_revision_graph(NULL_REVISION)
2797
self.assertEqual([], client._calls)
2798
self.assertEqual({}, result)
2800
def test_none_revision(self):
2801
# with none we want the entire graph
2802
r1 = u'\u0e33'.encode('utf8')
2803
r2 = u'\u0dab'.encode('utf8')
2804
lines = [' '.join([r2, r1]), r1]
2805
encoded_body = '\n'.join(lines)
2807
transport_path = 'sinhala'
2808
repo, client = self.setup_fake_client_and_repository(transport_path)
2809
client.add_success_response_with_body(encoded_body, 'ok')
2810
# actual RemoteRepository.get_revision_graph is gone, but there's an
2811
# equivalent private method for testing
2812
result = repo._get_revision_graph(None)
2814
[('call_expecting_body', 'Repository.get_revision_graph',
2817
self.assertEqual({r1: (), r2: (r1, )}, result)
2819
def test_specific_revision(self):
2820
# with a specific revision we want the graph for that
2821
# with none we want the entire graph
2822
r11 = u'\u0e33'.encode('utf8')
2823
r12 = u'\xc9'.encode('utf8')
2824
r2 = u'\u0dab'.encode('utf8')
2825
lines = [' '.join([r2, r11, r12]), r11, r12]
2826
encoded_body = '\n'.join(lines)
2828
transport_path = 'sinhala'
2829
repo, client = self.setup_fake_client_and_repository(transport_path)
2830
client.add_success_response_with_body(encoded_body, 'ok')
2831
result = repo._get_revision_graph(r2)
2833
[('call_expecting_body', 'Repository.get_revision_graph',
2836
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2838
def test_no_such_revision(self):
2840
transport_path = 'sinhala'
2841
repo, client = self.setup_fake_client_and_repository(transport_path)
2842
client.add_error_response('nosuchrevision', revid)
2843
# also check that the right revision is reported in the error
2844
self.assertRaises(errors.NoSuchRevision,
2845
repo._get_revision_graph, revid)
2847
[('call_expecting_body', 'Repository.get_revision_graph',
2848
('sinhala/', revid))],
2851
def test_unexpected_error(self):
2853
transport_path = 'sinhala'
2854
repo, client = self.setup_fake_client_and_repository(transport_path)
2855
client.add_error_response('AnUnexpectedError')
2856
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2857
repo._get_revision_graph, revid)
2858
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2861
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2864
repo, client = self.setup_fake_client_and_repository('quack')
2865
client.add_expected_call(
2866
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2867
'success', ('ok', 'rev-five'))
2868
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2869
self.assertEqual((True, 'rev-five'), result)
2870
self.assertFinished(client)
2872
def test_history_incomplete(self):
2873
repo, client = self.setup_fake_client_and_repository('quack')
2874
client.add_expected_call(
2875
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2876
'success', ('history-incomplete', 10, 'rev-ten'))
2877
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2878
self.assertEqual((False, (10, 'rev-ten')), result)
2879
self.assertFinished(client)
2881
def test_history_incomplete_with_fallback(self):
2882
"""A 'history-incomplete' response causes the fallback repository to be
2883
queried too, if one is set.
2885
# Make a repo with a fallback repo, both using a FakeClient.
2886
format = remote.response_tuple_to_repo_format(
2887
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2888
repo, client = self.setup_fake_client_and_repository('quack')
2889
repo._format = format
2890
fallback_repo, ignored = self.setup_fake_client_and_repository(
2892
fallback_repo._client = client
2893
fallback_repo._format = format
2894
repo.add_fallback_repository(fallback_repo)
2895
# First the client should ask the primary repo
2896
client.add_expected_call(
2897
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2898
'success', ('history-incomplete', 2, 'rev-two'))
2899
# Then it should ask the fallback, using revno/revid from the
2900
# history-incomplete response as the known revno/revid.
2901
client.add_expected_call(
2902
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2903
'success', ('ok', 'rev-one'))
2904
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2905
self.assertEqual((True, 'rev-one'), result)
2906
self.assertFinished(client)
2908
def test_nosuchrevision(self):
2909
# 'nosuchrevision' is returned when the known-revid is not found in the
2910
# remote repo. The client translates that response to NoSuchRevision.
2911
repo, client = self.setup_fake_client_and_repository('quack')
2912
client.add_expected_call(
2913
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2914
'error', ('nosuchrevision', 'rev-foo'))
2916
errors.NoSuchRevision,
2917
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2918
self.assertFinished(client)
2920
def test_branch_fallback_locking(self):
2921
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2922
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2923
will be invoked, which will fail if the repo is unlocked.
2925
self.setup_smart_server_with_call_log()
2926
tree = self.make_branch_and_memory_tree('.')
2929
rev1 = tree.commit('First')
2930
rev2 = tree.commit('Second')
2932
branch = tree.branch
2933
self.assertFalse(branch.is_locked())
2934
self.reset_smart_call_log()
2935
verb = 'Repository.get_rev_id_for_revno'
2936
self.disable_verb(verb)
2937
self.assertEqual(rev1, branch.get_rev_id(1))
2938
self.assertLength(1, [call for call in self.hpss_calls if
2939
call.call.method == verb])
2942
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2944
def test_has_signature_for_revision_id(self):
2945
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2946
transport_path = 'quack'
2947
repo, client = self.setup_fake_client_and_repository(transport_path)
2948
client.add_success_response('yes')
2949
result = repo.has_signature_for_revision_id('A')
2951
[('call', 'Repository.has_signature_for_revision_id',
2954
self.assertEqual(True, result)
2956
def test_is_not_shared(self):
2957
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2958
transport_path = 'qwack'
2959
repo, client = self.setup_fake_client_and_repository(transport_path)
2960
client.add_success_response('no')
2961
result = repo.has_signature_for_revision_id('A')
2963
[('call', 'Repository.has_signature_for_revision_id',
2966
self.assertEqual(False, result)
2969
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2971
def test_get_physical_lock_status_yes(self):
2972
transport_path = 'qwack'
2973
repo, client = self.setup_fake_client_and_repository(transport_path)
2974
client.add_success_response('yes')
2975
result = repo.get_physical_lock_status()
2977
[('call', 'Repository.get_physical_lock_status',
2980
self.assertEqual(True, result)
2982
def test_get_physical_lock_status_no(self):
2983
transport_path = 'qwack'
2984
repo, client = self.setup_fake_client_and_repository(transport_path)
2985
client.add_success_response('no')
2986
result = repo.get_physical_lock_status()
2988
[('call', 'Repository.get_physical_lock_status',
2991
self.assertEqual(False, result)
2994
class TestRepositoryIsShared(TestRemoteRepository):
2996
def test_is_shared(self):
2997
# ('yes', ) for Repository.is_shared -> 'True'.
2998
transport_path = 'quack'
2999
repo, client = self.setup_fake_client_and_repository(transport_path)
3000
client.add_success_response('yes')
3001
result = repo.is_shared()
3003
[('call', 'Repository.is_shared', ('quack/',))],
3005
self.assertEqual(True, result)
3007
def test_is_not_shared(self):
3008
# ('no', ) for Repository.is_shared -> 'False'.
3009
transport_path = 'qwack'
3010
repo, client = self.setup_fake_client_and_repository(transport_path)
3011
client.add_success_response('no')
3012
result = repo.is_shared()
3014
[('call', 'Repository.is_shared', ('qwack/',))],
3016
self.assertEqual(False, result)
3019
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3021
def test_make_working_trees(self):
3022
# ('yes', ) for Repository.make_working_trees -> 'True'.
3023
transport_path = 'quack'
3024
repo, client = self.setup_fake_client_and_repository(transport_path)
3025
client.add_success_response('yes')
3026
result = repo.make_working_trees()
3028
[('call', 'Repository.make_working_trees', ('quack/',))],
3030
self.assertEqual(True, result)
3032
def test_no_working_trees(self):
3033
# ('no', ) for Repository.make_working_trees -> 'False'.
3034
transport_path = 'qwack'
3035
repo, client = self.setup_fake_client_and_repository(transport_path)
3036
client.add_success_response('no')
3037
result = repo.make_working_trees()
3039
[('call', 'Repository.make_working_trees', ('qwack/',))],
3041
self.assertEqual(False, result)
3044
class TestRepositoryLockWrite(TestRemoteRepository):
3046
def test_lock_write(self):
3047
transport_path = 'quack'
3048
repo, client = self.setup_fake_client_and_repository(transport_path)
3049
client.add_success_response('ok', 'a token')
3050
token = repo.lock_write().repository_token
3052
[('call', 'Repository.lock_write', ('quack/', ''))],
3054
self.assertEqual('a token', token)
3056
def test_lock_write_already_locked(self):
3057
transport_path = 'quack'
3058
repo, client = self.setup_fake_client_and_repository(transport_path)
3059
client.add_error_response('LockContention')
3060
self.assertRaises(errors.LockContention, repo.lock_write)
3062
[('call', 'Repository.lock_write', ('quack/', ''))],
3065
def test_lock_write_unlockable(self):
3066
transport_path = 'quack'
3067
repo, client = self.setup_fake_client_and_repository(transport_path)
3068
client.add_error_response('UnlockableTransport')
3069
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3071
[('call', 'Repository.lock_write', ('quack/', ''))],
3075
class TestRepositoryWriteGroups(TestRemoteRepository):
3077
def test_start_write_group(self):
3078
transport_path = 'quack'
3079
repo, client = self.setup_fake_client_and_repository(transport_path)
3080
client.add_expected_call(
3081
'Repository.lock_write', ('quack/', ''),
3082
'success', ('ok', 'a token'))
3083
client.add_expected_call(
3084
'Repository.start_write_group', ('quack/', 'a token'),
3085
'success', ('ok', ('token1', )))
3087
repo.start_write_group()
3089
def test_start_write_group_unsuspendable(self):
3090
# Some repositories do not support suspending write
3091
# groups. For those, fall back to the "real" repository.
3092
transport_path = 'quack'
3093
repo, client = self.setup_fake_client_and_repository(transport_path)
3094
def stub_ensure_real():
3095
client._calls.append(('_ensure_real',))
3096
repo._real_repository = _StubRealPackRepository(client._calls)
3097
repo._ensure_real = stub_ensure_real
3098
client.add_expected_call(
3099
'Repository.lock_write', ('quack/', ''),
3100
'success', ('ok', 'a token'))
3101
client.add_expected_call(
3102
'Repository.start_write_group', ('quack/', 'a token'),
3103
'error', ('UnsuspendableWriteGroup',))
3105
repo.start_write_group()
3106
self.assertEqual(client._calls[-2:], [
3108
('start_write_group',)])
3110
def test_commit_write_group(self):
3111
transport_path = 'quack'
3112
repo, client = self.setup_fake_client_and_repository(transport_path)
3113
client.add_expected_call(
3114
'Repository.lock_write', ('quack/', ''),
3115
'success', ('ok', 'a token'))
3116
client.add_expected_call(
3117
'Repository.start_write_group', ('quack/', 'a token'),
3118
'success', ('ok', ['token1']))
3119
client.add_expected_call(
3120
'Repository.commit_write_group', ('quack/', 'a token', ['token1']),
3123
repo.start_write_group()
3124
repo.commit_write_group()
3126
def test_abort_write_group(self):
3127
transport_path = 'quack'
3128
repo, client = self.setup_fake_client_and_repository(transport_path)
3129
client.add_expected_call(
3130
'Repository.lock_write', ('quack/', ''),
3131
'success', ('ok', 'a token'))
3132
client.add_expected_call(
3133
'Repository.start_write_group', ('quack/', 'a token'),
3134
'success', ('ok', ['token1']))
3135
client.add_expected_call(
3136
'Repository.abort_write_group', ('quack/', 'a token', ['token1']),
3139
repo.start_write_group()
3140
repo.abort_write_group(False)
3142
def test_suspend_write_group(self):
3143
transport_path = 'quack'
3144
repo, client = self.setup_fake_client_and_repository(transport_path)
3145
self.assertEqual([], repo.suspend_write_group())
3147
def test_resume_write_group(self):
3148
transport_path = 'quack'
3149
repo, client = self.setup_fake_client_and_repository(transport_path)
3150
client.add_expected_call(
3151
'Repository.lock_write', ('quack/', ''),
3152
'success', ('ok', 'a token'))
3153
client.add_expected_call(
3154
'Repository.check_write_group', ('quack/', 'a token', ['token1']),
3157
repo.resume_write_group(['token1'])
3160
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3162
def test_backwards_compat(self):
3163
self.setup_smart_server_with_call_log()
3164
repo = self.make_repository('.')
3165
self.reset_smart_call_log()
3166
verb = 'Repository.set_make_working_trees'
3167
self.disable_verb(verb)
3168
repo.set_make_working_trees(True)
3169
call_count = len([call for call in self.hpss_calls if
3170
call.call.method == verb])
3171
self.assertEqual(1, call_count)
3173
def test_current(self):
3174
transport_path = 'quack'
3175
repo, client = self.setup_fake_client_and_repository(transport_path)
3176
client.add_expected_call(
3177
'Repository.set_make_working_trees', ('quack/', 'True'),
3179
client.add_expected_call(
3180
'Repository.set_make_working_trees', ('quack/', 'False'),
3182
repo.set_make_working_trees(True)
3183
repo.set_make_working_trees(False)
3186
class TestRepositoryUnlock(TestRemoteRepository):
3188
def test_unlock(self):
3189
transport_path = 'quack'
3190
repo, client = self.setup_fake_client_and_repository(transport_path)
3191
client.add_success_response('ok', 'a token')
3192
client.add_success_response('ok')
3196
[('call', 'Repository.lock_write', ('quack/', '')),
3197
('call', 'Repository.unlock', ('quack/', 'a token'))],
3200
def test_unlock_wrong_token(self):
3201
# If somehow the token is wrong, unlock will raise TokenMismatch.
3202
transport_path = 'quack'
3203
repo, client = self.setup_fake_client_and_repository(transport_path)
3204
client.add_success_response('ok', 'a token')
3205
client.add_error_response('TokenMismatch')
3207
self.assertRaises(errors.TokenMismatch, repo.unlock)
3210
class TestRepositoryHasRevision(TestRemoteRepository):
3212
def test_none(self):
3213
# repo.has_revision(None) should not cause any traffic.
3214
transport_path = 'quack'
3215
repo, client = self.setup_fake_client_and_repository(transport_path)
3217
# The null revision is always there, so has_revision(None) == True.
3218
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3220
# The remote repo shouldn't be accessed.
3221
self.assertEqual([], client._calls)
3224
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3225
"""Test Repository.iter_file_bytes."""
3227
def test_single(self):
3228
transport_path = 'quack'
3229
repo, client = self.setup_fake_client_and_repository(transport_path)
3230
client.add_expected_call(
3231
'Repository.iter_files_bytes', ('quack/', ),
3232
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3233
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3234
"somerev", "myid")]):
3235
self.assertEqual("myid", identifier)
3236
self.assertEqual("".join(byte_stream), "mydata" * 10)
3238
def test_missing(self):
3239
transport_path = 'quack'
3240
repo, client = self.setup_fake_client_and_repository(transport_path)
3241
client.add_expected_call(
3242
'Repository.iter_files_bytes',
3244
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3245
iter(["absent\0somefile\0somerev\n"]))
3246
self.assertRaises(errors.RevisionNotPresent, list,
3247
repo.iter_files_bytes(
3248
[("somefile", "somerev", "myid")]))
3251
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3252
"""Base class for Repository.insert_stream and .insert_stream_1.19
3256
def checkInsertEmptyStream(self, repo, client):
3257
"""Insert an empty stream, checking the result.
3259
This checks that there are no resume_tokens or missing_keys, and that
3260
the client is finished.
3262
sink = repo._get_sink()
3263
fmt = repository.format_registry.get_default()
3264
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3265
self.assertEqual([], resume_tokens)
3266
self.assertEqual(set(), missing_keys)
3267
self.assertFinished(client)
3270
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3271
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3274
This test case is very similar to TestRepositoryInsertStream_1_19.
3278
super(TestRepositoryInsertStream, self).setUp()
3279
self.disable_verb('Repository.insert_stream_1.19')
3281
def test_unlocked_repo(self):
3282
transport_path = 'quack'
3283
repo, client = self.setup_fake_client_and_repository(transport_path)
3284
client.add_expected_call(
3285
'Repository.insert_stream_1.19', ('quack/', ''),
3286
'unknown', ('Repository.insert_stream_1.19',))
3287
client.add_expected_call(
3288
'Repository.insert_stream', ('quack/', ''),
3290
client.add_expected_call(
3291
'Repository.insert_stream', ('quack/', ''),
3293
self.checkInsertEmptyStream(repo, client)
3295
def test_locked_repo_with_no_lock_token(self):
3296
transport_path = 'quack'
3297
repo, client = self.setup_fake_client_and_repository(transport_path)
3298
client.add_expected_call(
3299
'Repository.lock_write', ('quack/', ''),
3300
'success', ('ok', ''))
3301
client.add_expected_call(
3302
'Repository.insert_stream_1.19', ('quack/', ''),
3303
'unknown', ('Repository.insert_stream_1.19',))
3304
client.add_expected_call(
3305
'Repository.insert_stream', ('quack/', ''),
3307
client.add_expected_call(
3308
'Repository.insert_stream', ('quack/', ''),
3311
self.checkInsertEmptyStream(repo, client)
3313
def test_locked_repo_with_lock_token(self):
3314
transport_path = 'quack'
3315
repo, client = self.setup_fake_client_and_repository(transport_path)
3316
client.add_expected_call(
3317
'Repository.lock_write', ('quack/', ''),
3318
'success', ('ok', 'a token'))
3319
client.add_expected_call(
3320
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3321
'unknown', ('Repository.insert_stream_1.19',))
3322
client.add_expected_call(
3323
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3325
client.add_expected_call(
3326
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3329
self.checkInsertEmptyStream(repo, client)
3331
def test_stream_with_inventory_deltas(self):
3332
"""'inventory-deltas' substreams cannot be sent to the
3333
Repository.insert_stream verb, because not all servers that implement
3334
that verb will accept them. So when one is encountered the RemoteSink
3335
immediately stops using that verb and falls back to VFS insert_stream.
3337
transport_path = 'quack'
3338
repo, client = self.setup_fake_client_and_repository(transport_path)
3339
client.add_expected_call(
3340
'Repository.insert_stream_1.19', ('quack/', ''),
3341
'unknown', ('Repository.insert_stream_1.19',))
3342
client.add_expected_call(
3343
'Repository.insert_stream', ('quack/', ''),
3345
client.add_expected_call(
3346
'Repository.insert_stream', ('quack/', ''),
3348
# Create a fake real repository for insert_stream to fall back on, so
3349
# that we can directly see the records the RemoteSink passes to the
3354
def insert_stream(self, stream, src_format, resume_tokens):
3355
for substream_kind, substream in stream:
3356
self.records.append(
3357
(substream_kind, [record.key for record in substream]))
3358
return ['fake tokens'], ['fake missing keys']
3359
fake_real_sink = FakeRealSink()
3360
class FakeRealRepository:
3361
def _get_sink(self):
3362
return fake_real_sink
3363
def is_in_write_group(self):
3365
def refresh_data(self):
3367
repo._real_repository = FakeRealRepository()
3368
sink = repo._get_sink()
3369
fmt = repository.format_registry.get_default()
3370
stream = self.make_stream_with_inv_deltas(fmt)
3371
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3372
# Every record from the first inventory delta should have been sent to
3374
expected_records = [
3375
('inventory-deltas', [('rev2',), ('rev3',)]),
3376
('texts', [('some-rev', 'some-file')])]
3377
self.assertEqual(expected_records, fake_real_sink.records)
3378
# The return values from the real sink's insert_stream are propagated
3379
# back to the original caller.
3380
self.assertEqual(['fake tokens'], resume_tokens)
3381
self.assertEqual(['fake missing keys'], missing_keys)
3382
self.assertFinished(client)
3384
def make_stream_with_inv_deltas(self, fmt):
3385
"""Make a simple stream with an inventory delta followed by more
3386
records and more substreams to test that all records and substreams
3387
from that point on are used.
3389
This sends, in order:
3390
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3392
* texts substream: (some-rev, some-file)
3394
# Define a stream using generators so that it isn't rewindable.
3395
inv = inventory.Inventory(revision_id='rev1')
3396
inv.root.revision = 'rev1'
3397
def stream_with_inv_delta():
3398
yield ('inventories', inventories_substream())
3399
yield ('inventory-deltas', inventory_delta_substream())
3401
versionedfile.FulltextContentFactory(
3402
('some-rev', 'some-file'), (), None, 'content')])
3403
def inventories_substream():
3404
# An empty inventory fulltext. This will be streamed normally.
3405
text = fmt._serializer.write_inventory_to_string(inv)
3406
yield versionedfile.FulltextContentFactory(
3407
('rev1',), (), None, text)
3408
def inventory_delta_substream():
3409
# An inventory delta. This can't be streamed via this verb, so it
3410
# will trigger a fallback to VFS insert_stream.
3411
entry = inv.make_entry(
3412
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3413
entry.revision = 'ghost'
3414
delta = [(None, 'newdir', 'newdir-id', entry)]
3415
serializer = inventory_delta.InventoryDeltaSerializer(
3416
versioned_root=True, tree_references=False)
3417
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3418
yield versionedfile.ChunkedContentFactory(
3419
('rev2',), (('rev1',)), None, lines)
3421
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3422
yield versionedfile.ChunkedContentFactory(
3423
('rev3',), (('rev1',)), None, lines)
3424
return stream_with_inv_delta()
3427
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3429
def test_unlocked_repo(self):
3430
transport_path = 'quack'
3431
repo, client = self.setup_fake_client_and_repository(transport_path)
3432
client.add_expected_call(
3433
'Repository.insert_stream_1.19', ('quack/', ''),
3435
client.add_expected_call(
3436
'Repository.insert_stream_1.19', ('quack/', ''),
3438
self.checkInsertEmptyStream(repo, client)
3440
def test_locked_repo_with_no_lock_token(self):
3441
transport_path = 'quack'
3442
repo, client = self.setup_fake_client_and_repository(transport_path)
3443
client.add_expected_call(
3444
'Repository.lock_write', ('quack/', ''),
3445
'success', ('ok', ''))
3446
client.add_expected_call(
3447
'Repository.insert_stream_1.19', ('quack/', ''),
3449
client.add_expected_call(
3450
'Repository.insert_stream_1.19', ('quack/', ''),
3453
self.checkInsertEmptyStream(repo, client)
3455
def test_locked_repo_with_lock_token(self):
3456
transport_path = 'quack'
3457
repo, client = self.setup_fake_client_and_repository(transport_path)
3458
client.add_expected_call(
3459
'Repository.lock_write', ('quack/', ''),
3460
'success', ('ok', 'a token'))
3461
client.add_expected_call(
3462
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3464
client.add_expected_call(
3465
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3468
self.checkInsertEmptyStream(repo, client)
3471
class TestRepositoryTarball(TestRemoteRepository):
3473
# This is a canned tarball reponse we can validate against
3475
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3476
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3477
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3478
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3479
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3480
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3481
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3482
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3483
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3484
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3485
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3486
'nWQ7QH/F3JFOFCQ0aSPfA='
3489
def test_repository_tarball(self):
3490
# Test that Repository.tarball generates the right operations
3491
transport_path = 'repo'
3492
expected_calls = [('call_expecting_body', 'Repository.tarball',
3493
('repo/', 'bz2',),),
3495
repo, client = self.setup_fake_client_and_repository(transport_path)
3496
client.add_success_response_with_body(self.tarball_content, 'ok')
3497
# Now actually ask for the tarball
3498
tarball_file = repo._get_tarball('bz2')
3500
self.assertEqual(expected_calls, client._calls)
3501
self.assertEqual(self.tarball_content, tarball_file.read())
3503
tarball_file.close()
3506
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3507
"""RemoteRepository.copy_content_into optimizations"""
3509
def test_copy_content_remote_to_local(self):
3510
self.transport_server = test_server.SmartTCPServer_for_testing
3511
src_repo = self.make_repository('repo1')
3512
src_repo = repository.Repository.open(self.get_url('repo1'))
3513
# At the moment the tarball-based copy_content_into can't write back
3514
# into a smart server. It would be good if it could upload the
3515
# tarball; once that works we'd have to create repositories of
3516
# different formats. -- mbp 20070410
3517
dest_url = self.get_vfs_only_url('repo2')
3518
dest_bzrdir = BzrDir.create(dest_url)
3519
dest_repo = dest_bzrdir.create_repository()
3520
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3521
self.assertTrue(isinstance(src_repo, RemoteRepository))
3522
src_repo.copy_content_into(dest_repo)
3525
class _StubRealPackRepository(object):
3527
def __init__(self, calls):
3529
self._pack_collection = _StubPackCollection(calls)
3531
def start_write_group(self):
3532
self.calls.append(('start_write_group',))
3534
def is_in_write_group(self):
3537
def refresh_data(self):
3538
self.calls.append(('pack collection reload_pack_names',))
3541
class _StubPackCollection(object):
3543
def __init__(self, calls):
3547
self.calls.append(('pack collection autopack',))
3550
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3551
"""Tests for RemoteRepository.autopack implementation."""
3554
"""When the server returns 'ok' and there's no _real_repository, then
3555
nothing else happens: the autopack method is done.
3557
transport_path = 'quack'
3558
repo, client = self.setup_fake_client_and_repository(transport_path)
3559
client.add_expected_call(
3560
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3562
self.assertFinished(client)
3564
def test_ok_with_real_repo(self):
3565
"""When the server returns 'ok' and there is a _real_repository, then
3566
the _real_repository's reload_pack_name's method will be called.
3568
transport_path = 'quack'
3569
repo, client = self.setup_fake_client_and_repository(transport_path)
3570
client.add_expected_call(
3571
'PackRepository.autopack', ('quack/',),
3573
repo._real_repository = _StubRealPackRepository(client._calls)
3576
[('call', 'PackRepository.autopack', ('quack/',)),
3577
('pack collection reload_pack_names',)],
3580
def test_backwards_compatibility(self):
3581
"""If the server does not recognise the PackRepository.autopack verb,
3582
fallback to the real_repository's implementation.
3584
transport_path = 'quack'
3585
repo, client = self.setup_fake_client_and_repository(transport_path)
3586
client.add_unknown_method_response('PackRepository.autopack')
3587
def stub_ensure_real():
3588
client._calls.append(('_ensure_real',))
3589
repo._real_repository = _StubRealPackRepository(client._calls)
3590
repo._ensure_real = stub_ensure_real
3593
[('call', 'PackRepository.autopack', ('quack/',)),
3595
('pack collection autopack',)],
3598
def test_oom_error_reporting(self):
3599
"""An out-of-memory condition on the server is reported clearly"""
3600
transport_path = 'quack'
3601
repo, client = self.setup_fake_client_and_repository(transport_path)
3602
client.add_expected_call(
3603
'PackRepository.autopack', ('quack/',),
3604
'error', ('MemoryError',))
3605
err = self.assertRaises(errors.BzrError, repo.autopack)
3606
self.assertContainsRe(str(err), "^remote server out of mem")
3609
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3610
"""Base class for unit tests for breezy.bzr.remote._translate_error."""
3612
def translateTuple(self, error_tuple, **context):
3613
"""Call _translate_error with an ErrorFromSmartServer built from the
3616
:param error_tuple: A tuple of a smart server response, as would be
3617
passed to an ErrorFromSmartServer.
3618
:kwargs context: context items to call _translate_error with.
3620
:returns: The error raised by _translate_error.
3622
# Raise the ErrorFromSmartServer before passing it as an argument,
3623
# because _translate_error may need to re-raise it with a bare 'raise'
3625
server_error = errors.ErrorFromSmartServer(error_tuple)
3626
translated_error = self.translateErrorFromSmartServer(
3627
server_error, **context)
3628
return translated_error
3630
def translateErrorFromSmartServer(self, error_object, **context):
3631
"""Like translateTuple, but takes an already constructed
3632
ErrorFromSmartServer rather than a tuple.
3636
except errors.ErrorFromSmartServer as server_error:
3637
translated_error = self.assertRaises(
3638
errors.BzrError, remote._translate_error, server_error,
3640
return translated_error
3643
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3644
"""Unit tests for breezy.bzr.remote._translate_error.
3646
Given an ErrorFromSmartServer (which has an error tuple from a smart
3647
server) and some context, _translate_error raises more specific errors from
3650
This test case covers the cases where _translate_error succeeds in
3651
translating an ErrorFromSmartServer to something better. See
3652
TestErrorTranslationRobustness for other cases.
3655
def test_NoSuchRevision(self):
3656
branch = self.make_branch('')
3658
translated_error = self.translateTuple(
3659
('NoSuchRevision', revid), branch=branch)
3660
expected_error = errors.NoSuchRevision(branch, revid)
3661
self.assertEqual(expected_error, translated_error)
3663
def test_nosuchrevision(self):
3664
repository = self.make_repository('')
3666
translated_error = self.translateTuple(
3667
('nosuchrevision', revid), repository=repository)
3668
expected_error = errors.NoSuchRevision(repository, revid)
3669
self.assertEqual(expected_error, translated_error)
3671
def test_nobranch(self):
3672
bzrdir = self.make_controldir('')
3673
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3674
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3675
self.assertEqual(expected_error, translated_error)
3677
def test_nobranch_one_arg(self):
3678
bzrdir = self.make_controldir('')
3679
translated_error = self.translateTuple(
3680
('nobranch', 'extra detail'), bzrdir=bzrdir)
3681
expected_error = errors.NotBranchError(
3682
path=bzrdir.root_transport.base,
3683
detail='extra detail')
3684
self.assertEqual(expected_error, translated_error)
3686
def test_norepository(self):
3687
bzrdir = self.make_controldir('')
3688
translated_error = self.translateTuple(('norepository',),
3690
expected_error = errors.NoRepositoryPresent(bzrdir)
3691
self.assertEqual(expected_error, translated_error)
3693
def test_LockContention(self):
3694
translated_error = self.translateTuple(('LockContention',))
3695
expected_error = errors.LockContention('(remote lock)')
3696
self.assertEqual(expected_error, translated_error)
3698
def test_UnlockableTransport(self):
3699
bzrdir = self.make_controldir('')
3700
translated_error = self.translateTuple(
3701
('UnlockableTransport',), bzrdir=bzrdir)
3702
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3703
self.assertEqual(expected_error, translated_error)
3705
def test_LockFailed(self):
3706
lock = 'str() of a server lock'
3707
why = 'str() of why'
3708
translated_error = self.translateTuple(('LockFailed', lock, why))
3709
expected_error = errors.LockFailed(lock, why)
3710
self.assertEqual(expected_error, translated_error)
3712
def test_TokenMismatch(self):
3713
token = 'a lock token'
3714
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3715
expected_error = errors.TokenMismatch(token, '(remote token)')
3716
self.assertEqual(expected_error, translated_error)
3718
def test_Diverged(self):
3719
branch = self.make_branch('a')
3720
other_branch = self.make_branch('b')
3721
translated_error = self.translateTuple(
3722
('Diverged',), branch=branch, other_branch=other_branch)
3723
expected_error = errors.DivergedBranches(branch, other_branch)
3724
self.assertEqual(expected_error, translated_error)
3726
def test_NotStacked(self):
3727
branch = self.make_branch('')
3728
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3729
expected_error = errors.NotStacked(branch)
3730
self.assertEqual(expected_error, translated_error)
3732
def test_ReadError_no_args(self):
3734
translated_error = self.translateTuple(('ReadError',), path=path)
3735
expected_error = errors.ReadError(path)
3736
self.assertEqual(expected_error, translated_error)
3738
def test_ReadError(self):
3740
translated_error = self.translateTuple(('ReadError', path))
3741
expected_error = errors.ReadError(path)
3742
self.assertEqual(expected_error, translated_error)
3744
def test_IncompatibleRepositories(self):
3745
translated_error = self.translateTuple(('IncompatibleRepositories',
3746
"repo1", "repo2", "details here"))
3747
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3749
self.assertEqual(expected_error, translated_error)
3751
def test_PermissionDenied_no_args(self):
3753
translated_error = self.translateTuple(('PermissionDenied',),
3755
expected_error = errors.PermissionDenied(path)
3756
self.assertEqual(expected_error, translated_error)
3758
def test_PermissionDenied_one_arg(self):
3760
translated_error = self.translateTuple(('PermissionDenied', path))
3761
expected_error = errors.PermissionDenied(path)
3762
self.assertEqual(expected_error, translated_error)
3764
def test_PermissionDenied_one_arg_and_context(self):
3765
"""Given a choice between a path from the local context and a path on
3766
the wire, _translate_error prefers the path from the local context.
3768
local_path = 'local path'
3769
remote_path = 'remote path'
3770
translated_error = self.translateTuple(
3771
('PermissionDenied', remote_path), path=local_path)
3772
expected_error = errors.PermissionDenied(local_path)
3773
self.assertEqual(expected_error, translated_error)
3775
def test_PermissionDenied_two_args(self):
3777
extra = 'a string with extra info'
3778
translated_error = self.translateTuple(
3779
('PermissionDenied', path, extra))
3780
expected_error = errors.PermissionDenied(path, extra)
3781
self.assertEqual(expected_error, translated_error)
3783
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3785
def test_NoSuchFile_context_path(self):
3786
local_path = "local path"
3787
translated_error = self.translateTuple(('ReadError', "remote path"),
3789
expected_error = errors.ReadError(local_path)
3790
self.assertEqual(expected_error, translated_error)
3792
def test_NoSuchFile_without_context(self):
3793
remote_path = "remote path"
3794
translated_error = self.translateTuple(('ReadError', remote_path))
3795
expected_error = errors.ReadError(remote_path)
3796
self.assertEqual(expected_error, translated_error)
3798
def test_ReadOnlyError(self):
3799
translated_error = self.translateTuple(('ReadOnlyError',))
3800
expected_error = errors.TransportNotPossible("readonly transport")
3801
self.assertEqual(expected_error, translated_error)
3803
def test_MemoryError(self):
3804
translated_error = self.translateTuple(('MemoryError',))
3805
self.assertStartsWith(str(translated_error),
3806
"remote server out of memory")
3808
def test_generic_IndexError_no_classname(self):
3809
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3810
translated_error = self.translateErrorFromSmartServer(err)
3811
expected_error = errors.UnknownErrorFromSmartServer(err)
3812
self.assertEqual(expected_error, translated_error)
3814
# GZ 2011-03-02: TODO test generic non-ascii error string
3816
def test_generic_KeyError(self):
3817
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3818
translated_error = self.translateErrorFromSmartServer(err)
3819
expected_error = errors.UnknownErrorFromSmartServer(err)
3820
self.assertEqual(expected_error, translated_error)
3823
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3824
"""Unit tests for breezy.bzr.remote._translate_error's robustness.
3826
TestErrorTranslationSuccess is for cases where _translate_error can
3827
translate successfully. This class about how _translate_err behaves when
3828
it fails to translate: it re-raises the original error.
3831
def test_unrecognised_server_error(self):
3832
"""If the error code from the server is not recognised, the original
3833
ErrorFromSmartServer is propagated unmodified.
3835
error_tuple = ('An unknown error tuple',)
3836
server_error = errors.ErrorFromSmartServer(error_tuple)
3837
translated_error = self.translateErrorFromSmartServer(server_error)
3838
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3839
self.assertEqual(expected_error, translated_error)
3841
def test_context_missing_a_key(self):
3842
"""In case of a bug in the client, or perhaps an unexpected response
3843
from a server, _translate_error returns the original error tuple from
3844
the server and mutters a warning.
3846
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3847
# in the context dict. So let's give it an empty context dict instead
3848
# to exercise its error recovery.
3850
error_tuple = ('NoSuchRevision', 'revid')
3851
server_error = errors.ErrorFromSmartServer(error_tuple)
3852
translated_error = self.translateErrorFromSmartServer(server_error)
3853
self.assertEqual(server_error, translated_error)
3854
# In addition to re-raising ErrorFromSmartServer, some debug info has
3855
# been muttered to the log file for developer to look at.
3856
self.assertContainsRe(
3858
"Missing key 'branch' in context")
3860
def test_path_missing(self):
3861
"""Some translations (PermissionDenied, ReadError) can determine the
3862
'path' variable from either the wire or the local context. If neither
3863
has it, then an error is raised.
3865
error_tuple = ('ReadError',)
3866
server_error = errors.ErrorFromSmartServer(error_tuple)
3867
translated_error = self.translateErrorFromSmartServer(server_error)
3868
self.assertEqual(server_error, translated_error)
3869
# In addition to re-raising ErrorFromSmartServer, some debug info has
3870
# been muttered to the log file for developer to look at.
3871
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3874
class TestStacking(tests.TestCaseWithTransport):
3875
"""Tests for operations on stacked remote repositories.
3877
The underlying format type must support stacking.
3880
def test_access_stacked_remote(self):
3881
# based on <http://launchpad.net/bugs/261315>
3882
# make a branch stacked on another repository containing an empty
3883
# revision, then open it over hpss - we should be able to see that
3885
base_transport = self.get_transport()
3886
base_builder = self.make_branch_builder('base', format='1.9')
3887
base_builder.start_series()
3888
base_revid = base_builder.build_snapshot('rev-id', None,
3889
[('add', ('', None, 'directory', None))],
3891
base_builder.finish_series()
3892
stacked_branch = self.make_branch('stacked', format='1.9')
3893
stacked_branch.set_stacked_on_url('../base')
3894
# start a server looking at this
3895
smart_server = test_server.SmartTCPServer_for_testing()
3896
self.start_server(smart_server)
3897
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3898
# can get its branch and repository
3899
remote_branch = remote_bzrdir.open_branch()
3900
remote_repo = remote_branch.repository
3901
remote_repo.lock_read()
3903
# it should have an appropriate fallback repository, which should also
3904
# be a RemoteRepository
3905
self.assertLength(1, remote_repo._fallback_repositories)
3906
self.assertIsInstance(remote_repo._fallback_repositories[0],
3908
# and it has the revision committed to the underlying repository;
3909
# these have varying implementations so we try several of them
3910
self.assertTrue(remote_repo.has_revisions([base_revid]))
3911
self.assertTrue(remote_repo.has_revision(base_revid))
3912
self.assertEqual(remote_repo.get_revision(base_revid).message,
3915
remote_repo.unlock()
3917
def prepare_stacked_remote_branch(self):
3918
"""Get stacked_upon and stacked branches with content in each."""
3919
self.setup_smart_server_with_call_log()
3920
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3921
tree1.commit('rev1', rev_id='rev1')
3922
tree2 = tree1.branch.controldir.sprout('tree2', stacked=True
3923
).open_workingtree()
3924
local_tree = tree2.branch.create_checkout('local')
3925
local_tree.commit('local changes make me feel good.')
3926
branch2 = Branch.open(self.get_url('tree2'))
3928
self.addCleanup(branch2.unlock)
3929
return tree1.branch, branch2
3931
def test_stacked_get_parent_map(self):
3932
# the public implementation of get_parent_map obeys stacking
3933
_, branch = self.prepare_stacked_remote_branch()
3934
repo = branch.repository
3935
self.assertEqual({'rev1'}, set(repo.get_parent_map(['rev1'])))
3937
def test_unstacked_get_parent_map(self):
3938
# _unstacked_provider.get_parent_map ignores stacking
3939
_, branch = self.prepare_stacked_remote_branch()
3940
provider = branch.repository._unstacked_provider
3941
self.assertEqual(set(), set(provider.get_parent_map(['rev1'])))
3943
def fetch_stream_to_rev_order(self, stream):
3945
for kind, substream in stream:
3946
if not kind == 'revisions':
3949
for content in substream:
3950
result.append(content.key[-1])
3953
def get_ordered_revs(self, format, order, branch_factory=None):
3954
"""Get a list of the revisions in a stream to format format.
3956
:param format: The format of the target.
3957
:param order: the order that target should have requested.
3958
:param branch_factory: A callable to create a trunk and stacked branch
3959
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3960
:result: The revision ids in the stream, in the order seen,
3961
the topological order of revisions in the source.
3963
unordered_format = controldir.format_registry.get(format)()
3964
target_repository_format = unordered_format.repository_format
3966
self.assertEqual(order, target_repository_format._fetch_order)
3967
if branch_factory is None:
3968
branch_factory = self.prepare_stacked_remote_branch
3969
_, stacked = branch_factory()
3970
source = stacked.repository._get_source(target_repository_format)
3971
tip = stacked.last_revision()
3972
stacked.repository._ensure_real()
3973
graph = stacked.repository.get_graph()
3974
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3975
if r != NULL_REVISION]
3977
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3978
self.reset_smart_call_log()
3979
stream = source.get_stream(search)
3980
# We trust that if a revision is in the stream the rest of the new
3981
# content for it is too, as per our main fetch tests; here we are
3982
# checking that the revisions are actually included at all, and their
3984
return self.fetch_stream_to_rev_order(stream), revs
3986
def test_stacked_get_stream_unordered(self):
3987
# Repository._get_source.get_stream() from a stacked repository with
3988
# unordered yields the full data from both stacked and stacked upon
3990
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3991
self.assertEqual(set(expected_revs), set(rev_ord))
3992
# Getting unordered results should have made a streaming data request
3993
# from the server, then one from the backing branch.
3994
self.assertLength(2, self.hpss_calls)
3996
def test_stacked_on_stacked_get_stream_unordered(self):
3997
# Repository._get_source.get_stream() from a stacked repository which
3998
# is itself stacked yields the full data from all three sources.
3999
def make_stacked_stacked():
4000
_, stacked = self.prepare_stacked_remote_branch()
4001
tree = stacked.controldir.sprout('tree3', stacked=True
4002
).open_workingtree()
4003
local_tree = tree.branch.create_checkout('local-tree3')
4004
local_tree.commit('more local changes are better')
4005
branch = Branch.open(self.get_url('tree3'))
4007
self.addCleanup(branch.unlock)
4009
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
4010
branch_factory=make_stacked_stacked)
4011
self.assertEqual(set(expected_revs), set(rev_ord))
4012
# Getting unordered results should have made a streaming data request
4013
# from the server, and one from each backing repo
4014
self.assertLength(3, self.hpss_calls)
4016
def test_stacked_get_stream_topological(self):
4017
# Repository._get_source.get_stream() from a stacked repository with
4018
# topological sorting yields the full data from both stacked and
4019
# stacked upon sources in topological order.
4020
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4021
self.assertEqual(expected_revs, rev_ord)
4022
# Getting topological sort requires VFS calls still - one of which is
4023
# pushing up from the bound branch.
4024
self.assertLength(14, self.hpss_calls)
4026
def test_stacked_get_stream_groupcompress(self):
4027
# Repository._get_source.get_stream() from a stacked repository with
4028
# groupcompress sorting yields the full data from both stacked and
4029
# stacked upon sources in groupcompress order.
4030
raise tests.TestSkipped('No groupcompress ordered format available')
4031
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4032
self.assertEqual(expected_revs, reversed(rev_ord))
4033
# Getting unordered results should have made a streaming data request
4034
# from the backing branch, and one from the stacked on branch.
4035
self.assertLength(2, self.hpss_calls)
4037
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4038
# When pulling some fixed amount of content that is more than the
4039
# source has (because some is coming from a fallback branch, no error
4040
# should be received. This was reported as bug 360791.
4041
# Need three branches: a trunk, a stacked branch, and a preexisting
4042
# branch pulling content from stacked and trunk.
4043
self.setup_smart_server_with_call_log()
4044
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4045
r1 = trunk.commit('start')
4046
stacked_branch = trunk.branch.create_clone_on_transport(
4047
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4048
local = self.make_branch('local', format='1.9-rich-root')
4049
local.repository.fetch(stacked_branch.repository,
4050
stacked_branch.last_revision())
4053
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4056
super(TestRemoteBranchEffort, self).setUp()
4057
# Create a smart server that publishes whatever the backing VFS server
4059
self.smart_server = test_server.SmartTCPServer_for_testing()
4060
self.start_server(self.smart_server, self.get_server())
4061
# Log all HPSS calls into self.hpss_calls.
4062
_SmartClient.hooks.install_named_hook(
4063
'call', self.capture_hpss_call, None)
4064
self.hpss_calls = []
4066
def capture_hpss_call(self, params):
4067
self.hpss_calls.append(params.method)
4069
def test_copy_content_into_avoids_revision_history(self):
4070
local = self.make_branch('local')
4071
builder = self.make_branch_builder('remote')
4072
builder.build_commit(message="Commit.")
4073
remote_branch_url = self.smart_server.get_url() + 'remote'
4074
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4075
local.repository.fetch(remote_branch.repository)
4076
self.hpss_calls = []
4077
remote_branch.copy_content_into(local)
4078
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4080
def test_fetch_everything_needs_just_one_call(self):
4081
local = self.make_branch('local')
4082
builder = self.make_branch_builder('remote')
4083
builder.build_commit(message="Commit.")
4084
remote_branch_url = self.smart_server.get_url() + 'remote'
4085
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4086
self.hpss_calls = []
4087
local.repository.fetch(
4088
remote_branch.repository,
4089
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4090
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4092
def override_verb(self, verb_name, verb):
4093
request_handlers = request.request_handlers
4094
orig_verb = request_handlers.get(verb_name)
4095
orig_info = request_handlers.get_info(verb_name)
4096
request_handlers.register(verb_name, verb, override_existing=True)
4097
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4098
override_existing=True, info=orig_info)
4100
def test_fetch_everything_backwards_compat(self):
4101
"""Can fetch with EverythingResult even with pre 2.4 servers.
4103
Pre-2.4 do not support 'everything' searches with the
4104
Repository.get_stream_1.19 verb.
4107
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4108
"""A version of the Repository.get_stream_1.19 verb patched to
4109
reject 'everything' searches the way 2.3 and earlier do.
4111
def recreate_search(self, repository, search_bytes,
4112
discard_excess=False):
4113
verb_log.append(search_bytes.split('\n', 1)[0])
4114
if search_bytes == 'everything':
4116
request.FailedSmartServerResponse(('BadSearch',)))
4117
return super(OldGetStreamVerb,
4118
self).recreate_search(repository, search_bytes,
4119
discard_excess=discard_excess)
4120
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4121
local = self.make_branch('local')
4122
builder = self.make_branch_builder('remote')
4123
builder.build_commit(message="Commit.")
4124
remote_branch_url = self.smart_server.get_url() + 'remote'
4125
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4126
self.hpss_calls = []
4127
local.repository.fetch(
4128
remote_branch.repository,
4129
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4130
# make sure the overridden verb was used
4131
self.assertLength(1, verb_log)
4132
# more than one HPSS call is needed, but because it's a VFS callback
4133
# its hard to predict exactly how many.
4134
self.assertTrue(len(self.hpss_calls) > 1)
4137
class TestUpdateBoundBranchWithModifiedBoundLocation(
4138
tests.TestCaseWithTransport):
4139
"""Ensure correct handling of bound_location modifications.
4141
This is tested against a smart server as http://pad.lv/786980 was about a
4142
ReadOnlyError (write attempt during a read-only transaction) which can only
4143
happen in this context.
4147
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4148
self.transport_server = test_server.SmartTCPServer_for_testing
4150
def make_master_and_checkout(self, master_name, checkout_name):
4151
# Create the master branch and its associated checkout
4152
self.master = self.make_branch_and_tree(master_name)
4153
self.checkout = self.master.branch.create_checkout(checkout_name)
4154
# Modify the master branch so there is something to update
4155
self.master.commit('add stuff')
4156
self.last_revid = self.master.commit('even more stuff')
4157
self.bound_location = self.checkout.branch.get_bound_location()
4159
def assertUpdateSucceeds(self, new_location):
4160
self.checkout.branch.set_bound_location(new_location)
4161
self.checkout.update()
4162
self.assertEqual(self.last_revid, self.checkout.last_revision())
4164
def test_without_final_slash(self):
4165
self.make_master_and_checkout('master', 'checkout')
4166
# For unclear reasons some users have a bound_location without a final
4167
# '/', simulate that by forcing such a value
4168
self.assertEndsWith(self.bound_location, '/')
4169
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4171
def test_plus_sign(self):
4172
self.make_master_and_checkout('+master', 'checkout')
4173
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4175
def test_tilda(self):
4176
# Embed ~ in the middle of the path just to avoid any $HOME
4178
self.make_master_and_checkout('mas~ter', 'checkout')
4179
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4182
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4184
def test_no_context(self):
4185
class OutOfCoffee(errors.BzrError):
4186
"""A dummy exception for testing."""
4188
def __init__(self, urgency):
4189
self.urgency = urgency
4190
remote.no_context_error_translators.register("OutOfCoffee",
4191
lambda err: OutOfCoffee(err.error_args[0]))
4192
transport = MemoryTransport()
4193
client = FakeClient(transport.base)
4194
client.add_expected_call(
4195
'Branch.get_stacked_on_url', ('quack/',),
4196
'error', ('NotStacked',))
4197
client.add_expected_call(
4198
'Branch.last_revision_info',
4200
'error', ('OutOfCoffee', 'low'))
4201
transport.mkdir('quack')
4202
transport = transport.clone('quack')
4203
branch = self.make_remote_branch(transport, client)
4204
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4205
self.assertFinished(client)
4207
def test_with_context(self):
4208
class OutOfTea(errors.BzrError):
4209
def __init__(self, branch, urgency):
4210
self.branch = branch
4211
self.urgency = urgency
4212
remote.error_translators.register("OutOfTea",
4213
lambda err, find, path: OutOfTea(err.error_args[0],
4215
transport = MemoryTransport()
4216
client = FakeClient(transport.base)
4217
client.add_expected_call(
4218
'Branch.get_stacked_on_url', ('quack/',),
4219
'error', ('NotStacked',))
4220
client.add_expected_call(
4221
'Branch.last_revision_info',
4223
'error', ('OutOfTea', 'low'))
4224
transport.mkdir('quack')
4225
transport = transport.clone('quack')
4226
branch = self.make_remote_branch(transport, client)
4227
self.assertRaises(OutOfTea, branch.last_revision_info)
4228
self.assertFinished(client)
4231
class TestRepositoryPack(TestRemoteRepository):
4233
def test_pack(self):
4234
transport_path = 'quack'
4235
repo, client = self.setup_fake_client_and_repository(transport_path)
4236
client.add_expected_call(
4237
'Repository.lock_write', ('quack/', ''),
4238
'success', ('ok', 'token'))
4239
client.add_expected_call(
4240
'Repository.pack', ('quack/', 'token', 'False'),
4241
'success', ('ok',), )
4242
client.add_expected_call(
4243
'Repository.unlock', ('quack/', 'token'),
4244
'success', ('ok', ))
4247
def test_pack_with_hint(self):
4248
transport_path = 'quack'
4249
repo, client = self.setup_fake_client_and_repository(transport_path)
4250
client.add_expected_call(
4251
'Repository.lock_write', ('quack/', ''),
4252
'success', ('ok', 'token'))
4253
client.add_expected_call(
4254
'Repository.pack', ('quack/', 'token', 'False'),
4255
'success', ('ok',), )
4256
client.add_expected_call(
4257
'Repository.unlock', ('quack/', 'token', 'False'),
4258
'success', ('ok', ))
4259
repo.pack(['hinta', 'hintb'])
4262
class TestRepositoryIterInventories(TestRemoteRepository):
4263
"""Test Repository.iter_inventories."""
4265
def _serialize_inv_delta(self, old_name, new_name, delta):
4266
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4267
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4269
def test_single_empty(self):
4270
transport_path = 'quack'
4271
repo, client = self.setup_fake_client_and_repository(transport_path)
4272
fmt = controldir.format_registry.get('2a')().repository_format
4274
stream = [('inventory-deltas', [
4275
versionedfile.FulltextContentFactory('somerevid', None, None,
4276
self._serialize_inv_delta('null:', 'somerevid', []))])]
4277
client.add_expected_call(
4278
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4279
'success', ('ok', ),
4280
_stream_to_byte_stream(stream, fmt))
4281
ret = list(repo.iter_inventories(["somerevid"]))
4282
self.assertLength(1, ret)
4284
self.assertEqual("somerevid", inv.revision_id)
4286
def test_empty(self):
4287
transport_path = 'quack'
4288
repo, client = self.setup_fake_client_and_repository(transport_path)
4289
ret = list(repo.iter_inventories([]))
4290
self.assertEqual(ret, [])
4292
def test_missing(self):
4293
transport_path = 'quack'
4294
repo, client = self.setup_fake_client_and_repository(transport_path)
4295
client.add_expected_call(
4296
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4297
'success', ('ok', ), iter([]))
4298
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(