1
# Copyright (C) 2006-2012 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.
27
from cStringIO import StringIO
47
from bzrlib.branch import Branch
48
from bzrlib.bzrdir import (
53
from bzrlib.chk_serializer import chk_bencode_serializer
54
from bzrlib.remote import (
60
RemoteRepositoryFormat,
62
from bzrlib.repofmt import groupcompress_repo, knitpack_repo
63
from bzrlib.revision import (
67
from bzrlib.smart import medium, request
68
from bzrlib.smart.client import _SmartClient
69
from bzrlib.smart.repository import (
70
SmartServerRepositoryGetParentMap,
71
SmartServerRepositoryGetStream_1_19,
72
_stream_to_byte_stream,
74
from bzrlib.symbol_versioning import deprecated_in
75
from bzrlib.tests import (
78
from bzrlib.tests.scenarios import load_tests_apply_scenarios
79
from bzrlib.transport.memory import MemoryTransport
80
from bzrlib.transport.remote import (
87
load_tests = load_tests_apply_scenarios
90
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
94
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
96
{'transport_server': test_server.SmartTCPServer_for_testing})]
100
super(BasicRemoteObjectTests, self).setUp()
101
self.transport = self.get_transport()
102
# make a branch that can be opened over the smart transport
103
self.local_wt = BzrDir.create_standalone_workingtree('.')
104
self.addCleanup(self.transport.disconnect)
106
def test_create_remote_bzrdir(self):
107
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
108
self.assertIsInstance(b, BzrDir)
110
def test_open_remote_branch(self):
111
# open a standalone branch in the working directory
112
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
113
branch = b.open_branch()
114
self.assertIsInstance(branch, Branch)
116
def test_remote_repository(self):
117
b = BzrDir.open_from_transport(self.transport)
118
repo = b.open_repository()
119
revid = u'\xc823123123'.encode('utf8')
120
self.assertFalse(repo.has_revision(revid))
121
self.local_wt.commit(message='test commit', rev_id=revid)
122
self.assertTrue(repo.has_revision(revid))
124
def test_remote_branch_revision_history(self):
125
b = BzrDir.open_from_transport(self.transport).open_branch()
127
self.applyDeprecated(deprecated_in((2, 5, 0)), b.revision_history))
128
r1 = self.local_wt.commit('1st commit')
129
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
130
self.assertEqual([r1, r2],
131
self.applyDeprecated(deprecated_in((2, 5, 0)), b.revision_history))
133
def test_find_correct_format(self):
134
"""Should open a RemoteBzrDir over a RemoteTransport"""
135
fmt = BzrDirFormat.find_format(self.transport)
136
self.assertTrue(bzrdir.RemoteBzrProber
137
in controldir.ControlDirFormat._server_probers)
138
self.assertIsInstance(fmt, RemoteBzrDirFormat)
140
def test_open_detected_smart_format(self):
141
fmt = BzrDirFormat.find_format(self.transport)
142
d = fmt.open(self.transport)
143
self.assertIsInstance(d, BzrDir)
145
def test_remote_branch_repr(self):
146
b = BzrDir.open_from_transport(self.transport).open_branch()
147
self.assertStartsWith(str(b), 'RemoteBranch(')
149
def test_remote_bzrdir_repr(self):
150
b = BzrDir.open_from_transport(self.transport)
151
self.assertStartsWith(str(b), 'RemoteBzrDir(')
153
def test_remote_branch_format_supports_stacking(self):
155
self.make_branch('unstackable', format='pack-0.92')
156
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
157
self.assertFalse(b._format.supports_stacking())
158
self.make_branch('stackable', format='1.9')
159
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
160
self.assertTrue(b._format.supports_stacking())
162
def test_remote_repo_format_supports_external_references(self):
164
bd = self.make_bzrdir('unstackable', format='pack-0.92')
165
r = bd.create_repository()
166
self.assertFalse(r._format.supports_external_lookups)
167
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
168
self.assertFalse(r._format.supports_external_lookups)
169
bd = self.make_bzrdir('stackable', format='1.9')
170
r = bd.create_repository()
171
self.assertTrue(r._format.supports_external_lookups)
172
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
173
self.assertTrue(r._format.supports_external_lookups)
175
def test_remote_branch_set_append_revisions_only(self):
176
# Make a format 1.9 branch, which supports append_revisions_only
177
branch = self.make_branch('branch', format='1.9')
178
branch.set_append_revisions_only(True)
179
config = branch.get_config_stack()
181
True, config.get('append_revisions_only'))
182
branch.set_append_revisions_only(False)
183
config = branch.get_config_stack()
185
False, config.get('append_revisions_only'))
187
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
188
branch = self.make_branch('branch', format='knit')
190
errors.UpgradeRequired, branch.set_append_revisions_only, True)
193
class FakeProtocol(object):
194
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
196
def __init__(self, body, fake_client):
198
self._body_buffer = None
199
self._fake_client = fake_client
201
def read_body_bytes(self, count=-1):
202
if self._body_buffer is None:
203
self._body_buffer = StringIO(self.body)
204
bytes = self._body_buffer.read(count)
205
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
206
self._fake_client.expecting_body = False
209
def cancel_read_body(self):
210
self._fake_client.expecting_body = False
212
def read_streamed_body(self):
216
class FakeClient(_SmartClient):
217
"""Lookalike for _SmartClient allowing testing."""
219
def __init__(self, fake_medium_base='fake base'):
220
"""Create a FakeClient."""
223
self.expecting_body = False
224
# if non-None, this is the list of expected calls, with only the
225
# method name and arguments included. the body might be hard to
226
# compute so is not included. If a call is None, that call can
228
self._expected_calls = None
229
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
231
def add_expected_call(self, call_name, call_args, response_type,
232
response_args, response_body=None):
233
if self._expected_calls is None:
234
self._expected_calls = []
235
self._expected_calls.append((call_name, call_args))
236
self.responses.append((response_type, response_args, response_body))
238
def add_success_response(self, *args):
239
self.responses.append(('success', args, None))
241
def add_success_response_with_body(self, body, *args):
242
self.responses.append(('success', args, body))
243
if self._expected_calls is not None:
244
self._expected_calls.append(None)
246
def add_error_response(self, *args):
247
self.responses.append(('error', args))
249
def add_unknown_method_response(self, verb):
250
self.responses.append(('unknown', verb))
252
def finished_test(self):
253
if self._expected_calls:
254
raise AssertionError("%r finished but was still expecting %r"
255
% (self, self._expected_calls[0]))
257
def _get_next_response(self):
259
response_tuple = self.responses.pop(0)
260
except IndexError, e:
261
raise AssertionError("%r didn't expect any more calls"
263
if response_tuple[0] == 'unknown':
264
raise errors.UnknownSmartMethod(response_tuple[1])
265
elif response_tuple[0] == 'error':
266
raise errors.ErrorFromSmartServer(response_tuple[1])
267
return response_tuple
269
def _check_call(self, method, args):
270
if self._expected_calls is None:
271
# the test should be updated to say what it expects
274
next_call = self._expected_calls.pop(0)
276
raise AssertionError("%r didn't expect any more calls "
278
% (self, method, args,))
279
if next_call is None:
281
if method != next_call[0] or args != next_call[1]:
282
raise AssertionError("%r expected %r%r "
284
% (self, next_call[0], next_call[1], method, args,))
286
def call(self, method, *args):
287
self._check_call(method, args)
288
self._calls.append(('call', method, args))
289
return self._get_next_response()[1]
291
def call_expecting_body(self, method, *args):
292
self._check_call(method, args)
293
self._calls.append(('call_expecting_body', method, args))
294
result = self._get_next_response()
295
self.expecting_body = True
296
return result[1], FakeProtocol(result[2], self)
298
def call_with_body_bytes(self, method, args, body):
299
self._check_call(method, args)
300
self._calls.append(('call_with_body_bytes', method, args, body))
301
result = self._get_next_response()
302
return result[1], FakeProtocol(result[2], self)
304
def call_with_body_bytes_expecting_body(self, method, args, body):
305
self._check_call(method, args)
306
self._calls.append(('call_with_body_bytes_expecting_body', method,
308
result = self._get_next_response()
309
self.expecting_body = True
310
return result[1], FakeProtocol(result[2], self)
312
def call_with_body_stream(self, args, stream):
313
# Explicitly consume the stream before checking for an error, because
314
# that's what happens a real medium.
315
stream = list(stream)
316
self._check_call(args[0], args[1:])
317
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
318
result = self._get_next_response()
319
# The second value returned from call_with_body_stream is supposed to
320
# be a response_handler object, but so far no tests depend on that.
321
response_handler = None
322
return result[1], response_handler
325
class FakeMedium(medium.SmartClientMedium):
327
def __init__(self, client_calls, base):
328
medium.SmartClientMedium.__init__(self, base)
329
self._client_calls = client_calls
331
def disconnect(self):
332
self._client_calls.append(('disconnect medium',))
335
class TestVfsHas(tests.TestCase):
337
def test_unicode_path(self):
338
client = FakeClient('/')
339
client.add_success_response('yes',)
340
transport = RemoteTransport('bzr://localhost/', _client=client)
341
filename = u'/hell\u00d8'.encode('utf8')
342
result = transport.has(filename)
344
[('call', 'has', (filename,))],
346
self.assertTrue(result)
349
class TestRemote(tests.TestCaseWithMemoryTransport):
351
def get_branch_format(self):
352
reference_bzrdir_format = bzrdir.format_registry.get('default')()
353
return reference_bzrdir_format.get_branch_format()
355
def get_repo_format(self):
356
reference_bzrdir_format = bzrdir.format_registry.get('default')()
357
return reference_bzrdir_format.repository_format
359
def assertFinished(self, fake_client):
360
"""Assert that all of a FakeClient's expected calls have occurred."""
361
fake_client.finished_test()
364
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
365
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
367
def assertRemotePath(self, expected, client_base, transport_base):
368
"""Assert that the result of
369
SmartClientMedium.remote_path_from_transport is the expected value for
370
a given client_base and transport_base.
372
client_medium = medium.SmartClientMedium(client_base)
373
t = transport.get_transport(transport_base)
374
result = client_medium.remote_path_from_transport(t)
375
self.assertEqual(expected, result)
377
def test_remote_path_from_transport(self):
378
"""SmartClientMedium.remote_path_from_transport calculates a URL for
379
the given transport relative to the root of the client base URL.
381
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
382
self.assertRemotePath(
383
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
385
def assertRemotePathHTTP(self, expected, transport_base, relpath):
386
"""Assert that the result of
387
HttpTransportBase.remote_path_from_transport is the expected value for
388
a given transport_base and relpath of that transport. (Note that
389
HttpTransportBase is a subclass of SmartClientMedium)
391
base_transport = transport.get_transport(transport_base)
392
client_medium = base_transport.get_smart_medium()
393
cloned_transport = base_transport.clone(relpath)
394
result = client_medium.remote_path_from_transport(cloned_transport)
395
self.assertEqual(expected, result)
397
def test_remote_path_from_transport_http(self):
398
"""Remote paths for HTTP transports are calculated differently to other
399
transports. They are just relative to the client base, not the root
400
directory of the host.
402
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
403
self.assertRemotePathHTTP(
404
'../xyz/', scheme + '//host/path', '../xyz/')
405
self.assertRemotePathHTTP(
406
'xyz/', scheme + '//host/path', 'xyz/')
409
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
410
"""Tests for the behaviour of client_medium.remote_is_at_least."""
412
def test_initially_unlimited(self):
413
"""A fresh medium assumes that the remote side supports all
416
client_medium = medium.SmartClientMedium('dummy base')
417
self.assertFalse(client_medium._is_remote_before((99, 99)))
419
def test__remember_remote_is_before(self):
420
"""Calling _remember_remote_is_before ratchets down the known remote
423
client_medium = medium.SmartClientMedium('dummy base')
424
# Mark the remote side as being less than 1.6. The remote side may
426
client_medium._remember_remote_is_before((1, 6))
427
self.assertTrue(client_medium._is_remote_before((1, 6)))
428
self.assertFalse(client_medium._is_remote_before((1, 5)))
429
# Calling _remember_remote_is_before again with a lower value works.
430
client_medium._remember_remote_is_before((1, 5))
431
self.assertTrue(client_medium._is_remote_before((1, 5)))
432
# If you call _remember_remote_is_before with a higher value it logs a
433
# warning, and continues to remember the lower value.
434
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
435
client_medium._remember_remote_is_before((1, 9))
436
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
437
self.assertTrue(client_medium._is_remote_before((1, 5)))
440
class TestBzrDirCloningMetaDir(TestRemote):
442
def test_backwards_compat(self):
443
self.setup_smart_server_with_call_log()
444
a_dir = self.make_bzrdir('.')
445
self.reset_smart_call_log()
446
verb = 'BzrDir.cloning_metadir'
447
self.disable_verb(verb)
448
format = a_dir.cloning_metadir()
449
call_count = len([call for call in self.hpss_calls if
450
call.call.method == verb])
451
self.assertEqual(1, call_count)
453
def test_branch_reference(self):
454
transport = self.get_transport('quack')
455
referenced = self.make_branch('referenced')
456
expected = referenced.bzrdir.cloning_metadir()
457
client = FakeClient(transport.base)
458
client.add_expected_call(
459
'BzrDir.cloning_metadir', ('quack/', 'False'),
460
'error', ('BranchReference',)),
461
client.add_expected_call(
462
'BzrDir.open_branchV3', ('quack/',),
463
'success', ('ref', self.get_url('referenced'))),
464
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
466
result = a_bzrdir.cloning_metadir()
467
# We should have got a control dir matching the referenced branch.
468
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
469
self.assertEqual(expected._repository_format, result._repository_format)
470
self.assertEqual(expected._branch_format, result._branch_format)
471
self.assertFinished(client)
473
def test_current_server(self):
474
transport = self.get_transport('.')
475
transport = transport.clone('quack')
476
self.make_bzrdir('quack')
477
client = FakeClient(transport.base)
478
reference_bzrdir_format = bzrdir.format_registry.get('default')()
479
control_name = reference_bzrdir_format.network_name()
480
client.add_expected_call(
481
'BzrDir.cloning_metadir', ('quack/', 'False'),
482
'success', (control_name, '', ('branch', ''))),
483
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
485
result = a_bzrdir.cloning_metadir()
486
# We should have got a reference control dir with default branch and
487
# repository formats.
488
# This pokes a little, just to be sure.
489
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
490
self.assertEqual(None, result._repository_format)
491
self.assertEqual(None, result._branch_format)
492
self.assertFinished(client)
494
def test_unknown(self):
495
transport = self.get_transport('quack')
496
referenced = self.make_branch('referenced')
497
expected = referenced.bzrdir.cloning_metadir()
498
client = FakeClient(transport.base)
499
client.add_expected_call(
500
'BzrDir.cloning_metadir', ('quack/', 'False'),
501
'success', ('unknown', 'unknown', ('branch', ''))),
502
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
504
self.assertRaises(errors.UnknownFormatError, a_bzrdir.cloning_metadir)
507
class TestBzrDirCheckoutMetaDir(TestRemote):
509
def test__get_checkout_format(self):
510
transport = MemoryTransport()
511
client = FakeClient(transport.base)
512
reference_bzrdir_format = bzrdir.format_registry.get('default')()
513
control_name = reference_bzrdir_format.network_name()
514
client.add_expected_call(
515
'BzrDir.checkout_metadir', ('quack/', ),
516
'success', (control_name, '', ''))
517
transport.mkdir('quack')
518
transport = transport.clone('quack')
519
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
521
result = a_bzrdir.checkout_metadir()
522
# We should have got a reference control dir with default branch and
523
# repository formats.
524
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
525
self.assertEqual(None, result._repository_format)
526
self.assertEqual(None, result._branch_format)
527
self.assertFinished(client)
529
def test_unknown_format(self):
530
transport = MemoryTransport()
531
client = FakeClient(transport.base)
532
client.add_expected_call(
533
'BzrDir.checkout_metadir', ('quack/',),
534
'success', ('dontknow', '', ''))
535
transport.mkdir('quack')
536
transport = transport.clone('quack')
537
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
539
self.assertRaises(errors.UnknownFormatError,
540
a_bzrdir.checkout_metadir)
541
self.assertFinished(client)
544
class TestBzrDirDestroyBranch(TestRemote):
546
def test_destroy_default(self):
547
transport = self.get_transport('quack')
548
referenced = self.make_branch('referenced')
549
client = FakeClient(transport.base)
550
client.add_expected_call(
551
'BzrDir.destroy_branch', ('quack/', ),
553
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
555
a_bzrdir.destroy_branch()
556
self.assertFinished(client)
558
def test_destroy_named(self):
559
transport = self.get_transport('quack')
560
referenced = self.make_branch('referenced')
561
client = FakeClient(transport.base)
562
client.add_expected_call(
563
'BzrDir.destroy_branch', ('quack/', "foo"),
565
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
567
a_bzrdir.destroy_branch("foo")
568
self.assertFinished(client)
571
class TestBzrDirHasWorkingTree(TestRemote):
573
def test_has_workingtree(self):
574
transport = self.get_transport('quack')
575
client = FakeClient(transport.base)
576
client.add_expected_call(
577
'BzrDir.has_workingtree', ('quack/',),
578
'success', ('yes',)),
579
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
581
self.assertTrue(a_bzrdir.has_workingtree())
582
self.assertFinished(client)
584
def test_no_workingtree(self):
585
transport = self.get_transport('quack')
586
client = FakeClient(transport.base)
587
client.add_expected_call(
588
'BzrDir.has_workingtree', ('quack/',),
590
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
592
self.assertFalse(a_bzrdir.has_workingtree())
593
self.assertFinished(client)
596
class TestBzrDirDestroyRepository(TestRemote):
598
def test_destroy_repository(self):
599
transport = self.get_transport('quack')
600
client = FakeClient(transport.base)
601
client.add_expected_call(
602
'BzrDir.destroy_repository', ('quack/',),
604
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
606
a_bzrdir.destroy_repository()
607
self.assertFinished(client)
610
class TestBzrDirOpen(TestRemote):
612
def make_fake_client_and_transport(self, path='quack'):
613
transport = MemoryTransport()
614
transport.mkdir(path)
615
transport = transport.clone(path)
616
client = FakeClient(transport.base)
617
return client, transport
619
def test_absent(self):
620
client, transport = self.make_fake_client_and_transport()
621
client.add_expected_call(
622
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
623
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
624
RemoteBzrDirFormat(), _client=client, _force_probe=True)
625
self.assertFinished(client)
627
def test_present_without_workingtree(self):
628
client, transport = self.make_fake_client_and_transport()
629
client.add_expected_call(
630
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
631
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
632
_client=client, _force_probe=True)
633
self.assertIsInstance(bd, RemoteBzrDir)
634
self.assertFalse(bd.has_workingtree())
635
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
636
self.assertFinished(client)
638
def test_present_with_workingtree(self):
639
client, transport = self.make_fake_client_and_transport()
640
client.add_expected_call(
641
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
642
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
643
_client=client, _force_probe=True)
644
self.assertIsInstance(bd, RemoteBzrDir)
645
self.assertTrue(bd.has_workingtree())
646
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
647
self.assertFinished(client)
649
def test_backwards_compat(self):
650
client, transport = self.make_fake_client_and_transport()
651
client.add_expected_call(
652
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
653
client.add_expected_call(
654
'BzrDir.open', ('quack/',), 'success', ('yes',))
655
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
656
_client=client, _force_probe=True)
657
self.assertIsInstance(bd, RemoteBzrDir)
658
self.assertFinished(client)
660
def test_backwards_compat_hpss_v2(self):
661
client, transport = self.make_fake_client_and_transport()
662
# Monkey-patch fake client to simulate real-world behaviour with v2
663
# server: upon first RPC call detect the protocol version, and because
664
# the version is 2 also do _remember_remote_is_before((1, 6)) before
665
# continuing with the RPC.
666
orig_check_call = client._check_call
667
def check_call(method, args):
668
client._medium._protocol_version = 2
669
client._medium._remember_remote_is_before((1, 6))
670
client._check_call = orig_check_call
671
client._check_call(method, args)
672
client._check_call = check_call
673
client.add_expected_call(
674
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
675
client.add_expected_call(
676
'BzrDir.open', ('quack/',), 'success', ('yes',))
677
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
678
_client=client, _force_probe=True)
679
self.assertIsInstance(bd, RemoteBzrDir)
680
self.assertFinished(client)
683
class TestBzrDirOpenBranch(TestRemote):
685
def test_backwards_compat(self):
686
self.setup_smart_server_with_call_log()
687
self.make_branch('.')
688
a_dir = BzrDir.open(self.get_url('.'))
689
self.reset_smart_call_log()
690
verb = 'BzrDir.open_branchV3'
691
self.disable_verb(verb)
692
format = a_dir.open_branch()
693
call_count = len([call for call in self.hpss_calls if
694
call.call.method == verb])
695
self.assertEqual(1, call_count)
697
def test_branch_present(self):
698
reference_format = self.get_repo_format()
699
network_name = reference_format.network_name()
700
branch_network_name = self.get_branch_format().network_name()
701
transport = MemoryTransport()
702
transport.mkdir('quack')
703
transport = transport.clone('quack')
704
client = FakeClient(transport.base)
705
client.add_expected_call(
706
'BzrDir.open_branchV3', ('quack/',),
707
'success', ('branch', branch_network_name))
708
client.add_expected_call(
709
'BzrDir.find_repositoryV3', ('quack/',),
710
'success', ('ok', '', 'no', 'no', 'no', network_name))
711
client.add_expected_call(
712
'Branch.get_stacked_on_url', ('quack/',),
713
'error', ('NotStacked',))
714
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
716
result = bzrdir.open_branch()
717
self.assertIsInstance(result, RemoteBranch)
718
self.assertEqual(bzrdir, result.bzrdir)
719
self.assertFinished(client)
721
def test_branch_missing(self):
722
transport = MemoryTransport()
723
transport.mkdir('quack')
724
transport = transport.clone('quack')
725
client = FakeClient(transport.base)
726
client.add_error_response('nobranch')
727
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
729
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
731
[('call', 'BzrDir.open_branchV3', ('quack/',))],
734
def test__get_tree_branch(self):
735
# _get_tree_branch is a form of open_branch, but it should only ask for
736
# branch opening, not any other network requests.
738
def open_branch(name=None, possible_transports=None):
739
calls.append("Called")
741
transport = MemoryTransport()
742
# no requests on the network - catches other api calls being made.
743
client = FakeClient(transport.base)
744
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
746
# patch the open_branch call to record that it was called.
747
bzrdir.open_branch = open_branch
748
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
749
self.assertEqual(["Called"], calls)
750
self.assertEqual([], client._calls)
752
def test_url_quoting_of_path(self):
753
# Relpaths on the wire should not be URL-escaped. So "~" should be
754
# transmitted as "~", not "%7E".
755
transport = RemoteTCPTransport('bzr://localhost/~hello/')
756
client = FakeClient(transport.base)
757
reference_format = self.get_repo_format()
758
network_name = reference_format.network_name()
759
branch_network_name = self.get_branch_format().network_name()
760
client.add_expected_call(
761
'BzrDir.open_branchV3', ('~hello/',),
762
'success', ('branch', branch_network_name))
763
client.add_expected_call(
764
'BzrDir.find_repositoryV3', ('~hello/',),
765
'success', ('ok', '', 'no', 'no', 'no', network_name))
766
client.add_expected_call(
767
'Branch.get_stacked_on_url', ('~hello/',),
768
'error', ('NotStacked',))
769
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
771
result = bzrdir.open_branch()
772
self.assertFinished(client)
774
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
775
reference_format = self.get_repo_format()
776
network_name = reference_format.network_name()
777
transport = MemoryTransport()
778
transport.mkdir('quack')
779
transport = transport.clone('quack')
781
rich_response = 'yes'
785
subtree_response = 'yes'
787
subtree_response = 'no'
788
client = FakeClient(transport.base)
789
client.add_success_response(
790
'ok', '', rich_response, subtree_response, external_lookup,
792
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
794
result = bzrdir.open_repository()
796
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
798
self.assertIsInstance(result, RemoteRepository)
799
self.assertEqual(bzrdir, result.bzrdir)
800
self.assertEqual(rich_root, result._format.rich_root_data)
801
self.assertEqual(subtrees, result._format.supports_tree_reference)
803
def test_open_repository_sets_format_attributes(self):
804
self.check_open_repository(True, True)
805
self.check_open_repository(False, True)
806
self.check_open_repository(True, False)
807
self.check_open_repository(False, False)
808
self.check_open_repository(False, False, 'yes')
810
def test_old_server(self):
811
"""RemoteBzrDirFormat should fail to probe if the server version is too
814
self.assertRaises(errors.NotBranchError,
815
RemoteBzrProber.probe_transport, OldServerTransport())
818
class TestBzrDirCreateBranch(TestRemote):
820
def test_backwards_compat(self):
821
self.setup_smart_server_with_call_log()
822
repo = self.make_repository('.')
823
self.reset_smart_call_log()
824
self.disable_verb('BzrDir.create_branch')
825
branch = repo.bzrdir.create_branch()
826
create_branch_call_count = len([call for call in self.hpss_calls if
827
call.call.method == 'BzrDir.create_branch'])
828
self.assertEqual(1, create_branch_call_count)
830
def test_current_server(self):
831
transport = self.get_transport('.')
832
transport = transport.clone('quack')
833
self.make_repository('quack')
834
client = FakeClient(transport.base)
835
reference_bzrdir_format = bzrdir.format_registry.get('default')()
836
reference_format = reference_bzrdir_format.get_branch_format()
837
network_name = reference_format.network_name()
838
reference_repo_fmt = reference_bzrdir_format.repository_format
839
reference_repo_name = reference_repo_fmt.network_name()
840
client.add_expected_call(
841
'BzrDir.create_branch', ('quack/', network_name),
842
'success', ('ok', network_name, '', 'no', 'no', 'yes',
843
reference_repo_name))
844
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
846
branch = a_bzrdir.create_branch()
847
# We should have got a remote branch
848
self.assertIsInstance(branch, remote.RemoteBranch)
849
# its format should have the settings from the response
850
format = branch._format
851
self.assertEqual(network_name, format.network_name())
853
def test_already_open_repo_and_reused_medium(self):
854
"""Bug 726584: create_branch(..., repository=repo) should work
855
regardless of what the smart medium's base URL is.
857
self.transport_server = test_server.SmartTCPServer_for_testing
858
transport = self.get_transport('.')
859
repo = self.make_repository('quack')
860
# Client's medium rooted a transport root (not at the bzrdir)
861
client = FakeClient(transport.base)
862
transport = transport.clone('quack')
863
reference_bzrdir_format = bzrdir.format_registry.get('default')()
864
reference_format = reference_bzrdir_format.get_branch_format()
865
network_name = reference_format.network_name()
866
reference_repo_fmt = reference_bzrdir_format.repository_format
867
reference_repo_name = reference_repo_fmt.network_name()
868
client.add_expected_call(
869
'BzrDir.create_branch', ('extra/quack/', network_name),
870
'success', ('ok', network_name, '', 'no', 'no', 'yes',
871
reference_repo_name))
872
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
874
branch = a_bzrdir.create_branch(repository=repo)
875
# We should have got a remote branch
876
self.assertIsInstance(branch, remote.RemoteBranch)
877
# its format should have the settings from the response
878
format = branch._format
879
self.assertEqual(network_name, format.network_name())
882
class TestBzrDirCreateRepository(TestRemote):
884
def test_backwards_compat(self):
885
self.setup_smart_server_with_call_log()
886
bzrdir = self.make_bzrdir('.')
887
self.reset_smart_call_log()
888
self.disable_verb('BzrDir.create_repository')
889
repo = bzrdir.create_repository()
890
create_repo_call_count = len([call for call in self.hpss_calls if
891
call.call.method == 'BzrDir.create_repository'])
892
self.assertEqual(1, create_repo_call_count)
894
def test_current_server(self):
895
transport = self.get_transport('.')
896
transport = transport.clone('quack')
897
self.make_bzrdir('quack')
898
client = FakeClient(transport.base)
899
reference_bzrdir_format = bzrdir.format_registry.get('default')()
900
reference_format = reference_bzrdir_format.repository_format
901
network_name = reference_format.network_name()
902
client.add_expected_call(
903
'BzrDir.create_repository', ('quack/',
904
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
906
'success', ('ok', 'yes', 'yes', 'yes', network_name))
907
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
909
repo = a_bzrdir.create_repository()
910
# We should have got a remote repository
911
self.assertIsInstance(repo, remote.RemoteRepository)
912
# its format should have the settings from the response
913
format = repo._format
914
self.assertTrue(format.rich_root_data)
915
self.assertTrue(format.supports_tree_reference)
916
self.assertTrue(format.supports_external_lookups)
917
self.assertEqual(network_name, format.network_name())
920
class TestBzrDirOpenRepository(TestRemote):
922
def test_backwards_compat_1_2_3(self):
923
# fallback all the way to the first version.
924
reference_format = self.get_repo_format()
925
network_name = reference_format.network_name()
926
server_url = 'bzr://example.com/'
927
self.permit_url(server_url)
928
client = FakeClient(server_url)
929
client.add_unknown_method_response('BzrDir.find_repositoryV3')
930
client.add_unknown_method_response('BzrDir.find_repositoryV2')
931
client.add_success_response('ok', '', 'no', 'no')
932
# A real repository instance will be created to determine the network
934
client.add_success_response_with_body(
935
"Bazaar-NG meta directory, format 1\n", 'ok')
936
client.add_success_response('stat', '0', '65535')
937
client.add_success_response_with_body(
938
reference_format.get_format_string(), 'ok')
939
# PackRepository wants to do a stat
940
client.add_success_response('stat', '0', '65535')
941
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
943
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
945
repo = bzrdir.open_repository()
947
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
948
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
949
('call', 'BzrDir.find_repository', ('quack/',)),
950
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
951
('call', 'stat', ('/quack/.bzr',)),
952
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
953
('call', 'stat', ('/quack/.bzr/repository',)),
956
self.assertEqual(network_name, repo._format.network_name())
958
def test_backwards_compat_2(self):
959
# fallback to find_repositoryV2
960
reference_format = self.get_repo_format()
961
network_name = reference_format.network_name()
962
server_url = 'bzr://example.com/'
963
self.permit_url(server_url)
964
client = FakeClient(server_url)
965
client.add_unknown_method_response('BzrDir.find_repositoryV3')
966
client.add_success_response('ok', '', 'no', 'no', 'no')
967
# A real repository instance will be created to determine the network
969
client.add_success_response_with_body(
970
"Bazaar-NG meta directory, format 1\n", 'ok')
971
client.add_success_response('stat', '0', '65535')
972
client.add_success_response_with_body(
973
reference_format.get_format_string(), 'ok')
974
# PackRepository wants to do a stat
975
client.add_success_response('stat', '0', '65535')
976
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
978
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
980
repo = bzrdir.open_repository()
982
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
983
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
984
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
985
('call', 'stat', ('/quack/.bzr',)),
986
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
987
('call', 'stat', ('/quack/.bzr/repository',)),
990
self.assertEqual(network_name, repo._format.network_name())
992
def test_current_server(self):
993
reference_format = self.get_repo_format()
994
network_name = reference_format.network_name()
995
transport = MemoryTransport()
996
transport.mkdir('quack')
997
transport = transport.clone('quack')
998
client = FakeClient(transport.base)
999
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
1000
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1002
repo = bzrdir.open_repository()
1004
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
1006
self.assertEqual(network_name, repo._format.network_name())
1009
class TestBzrDirFormatInitializeEx(TestRemote):
1011
def test_success(self):
1012
"""Simple test for typical successful call."""
1013
fmt = RemoteBzrDirFormat()
1014
default_format_name = BzrDirFormat.get_default_format().network_name()
1015
transport = self.get_transport()
1016
client = FakeClient(transport.base)
1017
client.add_expected_call(
1018
'BzrDirFormat.initialize_ex_1.16',
1019
(default_format_name, 'path', 'False', 'False', 'False', '',
1020
'', '', '', 'False'),
1022
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
1023
'bzrdir fmt', 'False', '', '', 'repo lock token'))
1024
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1025
# it's currently hard to test that without supplying a real remote
1026
# transport connected to a real server.
1027
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1028
transport, False, False, False, None, None, None, None, False)
1029
self.assertFinished(client)
1031
def test_error(self):
1032
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1033
corresponding error from the client.
1035
fmt = RemoteBzrDirFormat()
1036
default_format_name = BzrDirFormat.get_default_format().network_name()
1037
transport = self.get_transport()
1038
client = FakeClient(transport.base)
1039
client.add_expected_call(
1040
'BzrDirFormat.initialize_ex_1.16',
1041
(default_format_name, 'path', 'False', 'False', 'False', '',
1042
'', '', '', 'False'),
1044
('PermissionDenied', 'path', 'extra info'))
1045
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1046
# it's currently hard to test that without supplying a real remote
1047
# transport connected to a real server.
1048
err = self.assertRaises(errors.PermissionDenied,
1049
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1050
False, False, False, None, None, None, None, False)
1051
self.assertEqual('path', err.path)
1052
self.assertEqual(': extra info', err.extra)
1053
self.assertFinished(client)
1055
def test_error_from_real_server(self):
1056
"""Integration test for error translation."""
1057
transport = self.make_smart_server('foo')
1058
transport = transport.clone('no-such-path')
1059
fmt = RemoteBzrDirFormat()
1060
err = self.assertRaises(errors.NoSuchFile,
1061
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1064
class OldSmartClient(object):
1065
"""A fake smart client for test_old_version that just returns a version one
1066
response to the 'hello' (query version) command.
1069
def get_request(self):
1070
input_file = StringIO('ok\x011\n')
1071
output_file = StringIO()
1072
client_medium = medium.SmartSimplePipesClientMedium(
1073
input_file, output_file)
1074
return medium.SmartClientStreamMediumRequest(client_medium)
1076
def protocol_version(self):
1080
class OldServerTransport(object):
1081
"""A fake transport for test_old_server that reports it's smart server
1082
protocol version as version one.
1088
def get_smart_client(self):
1089
return OldSmartClient()
1092
class RemoteBzrDirTestCase(TestRemote):
1094
def make_remote_bzrdir(self, transport, client):
1095
"""Make a RemotebzrDir using 'client' as the _client."""
1096
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1100
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1102
def lock_remote_branch(self, branch):
1103
"""Trick a RemoteBranch into thinking it is locked."""
1104
branch._lock_mode = 'w'
1105
branch._lock_count = 2
1106
branch._lock_token = 'branch token'
1107
branch._repo_lock_token = 'repo token'
1108
branch.repository._lock_mode = 'w'
1109
branch.repository._lock_count = 2
1110
branch.repository._lock_token = 'repo token'
1112
def make_remote_branch(self, transport, client):
1113
"""Make a RemoteBranch using 'client' as its _SmartClient.
1115
A RemoteBzrDir and RemoteRepository will also be created to fill out
1116
the RemoteBranch, albeit with stub values for some of their attributes.
1118
# we do not want bzrdir to make any remote calls, so use False as its
1119
# _client. If it tries to make a remote call, this will fail
1121
bzrdir = self.make_remote_bzrdir(transport, False)
1122
repo = RemoteRepository(bzrdir, None, _client=client)
1123
branch_format = self.get_branch_format()
1124
format = RemoteBranchFormat(network_name=branch_format.network_name())
1125
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1128
class TestBranchBreakLock(RemoteBranchTestCase):
1130
def test_break_lock(self):
1131
transport_path = 'quack'
1132
transport = MemoryTransport()
1133
client = FakeClient(transport.base)
1134
client.add_expected_call(
1135
'Branch.get_stacked_on_url', ('quack/',),
1136
'error', ('NotStacked',))
1137
client.add_expected_call(
1138
'Branch.break_lock', ('quack/',),
1140
transport.mkdir('quack')
1141
transport = transport.clone('quack')
1142
branch = self.make_remote_branch(transport, client)
1144
self.assertFinished(client)
1147
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1149
def test_get_physical_lock_status_yes(self):
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.get_physical_lock_status', ('quack/',),
1157
'success', ('yes',))
1158
transport.mkdir('quack')
1159
transport = transport.clone('quack')
1160
branch = self.make_remote_branch(transport, client)
1161
result = branch.get_physical_lock_status()
1162
self.assertFinished(client)
1163
self.assertEqual(True, result)
1165
def test_get_physical_lock_status_no(self):
1166
transport = MemoryTransport()
1167
client = FakeClient(transport.base)
1168
client.add_expected_call(
1169
'Branch.get_stacked_on_url', ('quack/',),
1170
'error', ('NotStacked',))
1171
client.add_expected_call(
1172
'Branch.get_physical_lock_status', ('quack/',),
1174
transport.mkdir('quack')
1175
transport = transport.clone('quack')
1176
branch = self.make_remote_branch(transport, client)
1177
result = branch.get_physical_lock_status()
1178
self.assertFinished(client)
1179
self.assertEqual(False, result)
1182
class TestBranchGetParent(RemoteBranchTestCase):
1184
def test_no_parent(self):
1185
# in an empty branch we decode the response properly
1186
transport = MemoryTransport()
1187
client = FakeClient(transport.base)
1188
client.add_expected_call(
1189
'Branch.get_stacked_on_url', ('quack/',),
1190
'error', ('NotStacked',))
1191
client.add_expected_call(
1192
'Branch.get_parent', ('quack/',),
1194
transport.mkdir('quack')
1195
transport = transport.clone('quack')
1196
branch = self.make_remote_branch(transport, client)
1197
result = branch.get_parent()
1198
self.assertFinished(client)
1199
self.assertEqual(None, result)
1201
def test_parent_relative(self):
1202
transport = MemoryTransport()
1203
client = FakeClient(transport.base)
1204
client.add_expected_call(
1205
'Branch.get_stacked_on_url', ('kwaak/',),
1206
'error', ('NotStacked',))
1207
client.add_expected_call(
1208
'Branch.get_parent', ('kwaak/',),
1209
'success', ('../foo/',))
1210
transport.mkdir('kwaak')
1211
transport = transport.clone('kwaak')
1212
branch = self.make_remote_branch(transport, client)
1213
result = branch.get_parent()
1214
self.assertEqual(transport.clone('../foo').base, result)
1216
def test_parent_absolute(self):
1217
transport = MemoryTransport()
1218
client = FakeClient(transport.base)
1219
client.add_expected_call(
1220
'Branch.get_stacked_on_url', ('kwaak/',),
1221
'error', ('NotStacked',))
1222
client.add_expected_call(
1223
'Branch.get_parent', ('kwaak/',),
1224
'success', ('http://foo/',))
1225
transport.mkdir('kwaak')
1226
transport = transport.clone('kwaak')
1227
branch = self.make_remote_branch(transport, client)
1228
result = branch.get_parent()
1229
self.assertEqual('http://foo/', result)
1230
self.assertFinished(client)
1233
class TestBranchSetParentLocation(RemoteBranchTestCase):
1235
def test_no_parent(self):
1236
# We call the verb when setting parent to None
1237
transport = MemoryTransport()
1238
client = FakeClient(transport.base)
1239
client.add_expected_call(
1240
'Branch.get_stacked_on_url', ('quack/',),
1241
'error', ('NotStacked',))
1242
client.add_expected_call(
1243
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1245
transport.mkdir('quack')
1246
transport = transport.clone('quack')
1247
branch = self.make_remote_branch(transport, client)
1248
branch._lock_token = 'b'
1249
branch._repo_lock_token = 'r'
1250
branch._set_parent_location(None)
1251
self.assertFinished(client)
1253
def test_parent(self):
1254
transport = MemoryTransport()
1255
client = FakeClient(transport.base)
1256
client.add_expected_call(
1257
'Branch.get_stacked_on_url', ('kwaak/',),
1258
'error', ('NotStacked',))
1259
client.add_expected_call(
1260
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1262
transport.mkdir('kwaak')
1263
transport = transport.clone('kwaak')
1264
branch = self.make_remote_branch(transport, client)
1265
branch._lock_token = 'b'
1266
branch._repo_lock_token = 'r'
1267
branch._set_parent_location('foo')
1268
self.assertFinished(client)
1270
def test_backwards_compat(self):
1271
self.setup_smart_server_with_call_log()
1272
branch = self.make_branch('.')
1273
self.reset_smart_call_log()
1274
verb = 'Branch.set_parent_location'
1275
self.disable_verb(verb)
1276
branch.set_parent('http://foo/')
1277
self.assertLength(14, self.hpss_calls)
1280
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1282
def test_backwards_compat(self):
1283
self.setup_smart_server_with_call_log()
1284
branch = self.make_branch('.')
1285
self.reset_smart_call_log()
1286
verb = 'Branch.get_tags_bytes'
1287
self.disable_verb(verb)
1288
branch.tags.get_tag_dict()
1289
call_count = len([call for call in self.hpss_calls if
1290
call.call.method == verb])
1291
self.assertEqual(1, call_count)
1293
def test_trivial(self):
1294
transport = MemoryTransport()
1295
client = FakeClient(transport.base)
1296
client.add_expected_call(
1297
'Branch.get_stacked_on_url', ('quack/',),
1298
'error', ('NotStacked',))
1299
client.add_expected_call(
1300
'Branch.get_tags_bytes', ('quack/',),
1302
transport.mkdir('quack')
1303
transport = transport.clone('quack')
1304
branch = self.make_remote_branch(transport, client)
1305
result = branch.tags.get_tag_dict()
1306
self.assertFinished(client)
1307
self.assertEqual({}, result)
1310
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1312
def test_trivial(self):
1313
transport = MemoryTransport()
1314
client = FakeClient(transport.base)
1315
client.add_expected_call(
1316
'Branch.get_stacked_on_url', ('quack/',),
1317
'error', ('NotStacked',))
1318
client.add_expected_call(
1319
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1321
transport.mkdir('quack')
1322
transport = transport.clone('quack')
1323
branch = self.make_remote_branch(transport, client)
1324
self.lock_remote_branch(branch)
1325
branch._set_tags_bytes('tags bytes')
1326
self.assertFinished(client)
1327
self.assertEqual('tags bytes', client._calls[-1][-1])
1329
def test_backwards_compatible(self):
1330
transport = MemoryTransport()
1331
client = FakeClient(transport.base)
1332
client.add_expected_call(
1333
'Branch.get_stacked_on_url', ('quack/',),
1334
'error', ('NotStacked',))
1335
client.add_expected_call(
1336
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1337
'unknown', ('Branch.set_tags_bytes',))
1338
transport.mkdir('quack')
1339
transport = transport.clone('quack')
1340
branch = self.make_remote_branch(transport, client)
1341
self.lock_remote_branch(branch)
1342
class StubRealBranch(object):
1345
def _set_tags_bytes(self, bytes):
1346
self.calls.append(('set_tags_bytes', bytes))
1347
real_branch = StubRealBranch()
1348
branch._real_branch = real_branch
1349
branch._set_tags_bytes('tags bytes')
1350
# Call a second time, to exercise the 'remote version already inferred'
1352
branch._set_tags_bytes('tags bytes')
1353
self.assertFinished(client)
1355
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1358
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1360
def test_uses_last_revision_info_and_tags_by_default(self):
1361
transport = MemoryTransport()
1362
client = FakeClient(transport.base)
1363
client.add_expected_call(
1364
'Branch.get_stacked_on_url', ('quack/',),
1365
'error', ('NotStacked',))
1366
client.add_expected_call(
1367
'Branch.last_revision_info', ('quack/',),
1368
'success', ('ok', '1', 'rev-tip'))
1369
client.add_expected_call(
1370
'Branch.get_config_file', ('quack/',),
1371
'success', ('ok',), '')
1372
transport.mkdir('quack')
1373
transport = transport.clone('quack')
1374
branch = self.make_remote_branch(transport, client)
1375
result = branch.heads_to_fetch()
1376
self.assertFinished(client)
1377
self.assertEqual((set(['rev-tip']), set()), result)
1379
def test_uses_last_revision_info_and_tags_when_set(self):
1380
transport = MemoryTransport()
1381
client = FakeClient(transport.base)
1382
client.add_expected_call(
1383
'Branch.get_stacked_on_url', ('quack/',),
1384
'error', ('NotStacked',))
1385
client.add_expected_call(
1386
'Branch.last_revision_info', ('quack/',),
1387
'success', ('ok', '1', 'rev-tip'))
1388
client.add_expected_call(
1389
'Branch.get_config_file', ('quack/',),
1390
'success', ('ok',), 'branch.fetch_tags = True')
1391
# XXX: this will break if the default format's serialization of tags
1392
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1393
client.add_expected_call(
1394
'Branch.get_tags_bytes', ('quack/',),
1395
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1396
transport.mkdir('quack')
1397
transport = transport.clone('quack')
1398
branch = self.make_remote_branch(transport, client)
1399
result = branch.heads_to_fetch()
1400
self.assertFinished(client)
1402
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1404
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1405
transport = MemoryTransport()
1406
client = FakeClient(transport.base)
1407
client.add_expected_call(
1408
'Branch.get_stacked_on_url', ('quack/',),
1409
'error', ('NotStacked',))
1410
client.add_expected_call(
1411
'Branch.heads_to_fetch', ('quack/',),
1412
'success', (['tip'], ['tagged-1', 'tagged-2']))
1413
transport.mkdir('quack')
1414
transport = transport.clone('quack')
1415
branch = self.make_remote_branch(transport, client)
1416
branch._format._use_default_local_heads_to_fetch = lambda: False
1417
result = branch.heads_to_fetch()
1418
self.assertFinished(client)
1419
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1421
def make_branch_with_tags(self):
1422
self.setup_smart_server_with_call_log()
1423
# Make a branch with a single revision.
1424
builder = self.make_branch_builder('foo')
1425
builder.start_series()
1426
builder.build_snapshot('tip', None, [
1427
('add', ('', 'root-id', 'directory', ''))])
1428
builder.finish_series()
1429
branch = builder.get_branch()
1430
# Add two tags to that branch
1431
branch.tags.set_tag('tag-1', 'rev-1')
1432
branch.tags.set_tag('tag-2', 'rev-2')
1435
def test_backwards_compatible(self):
1436
br = self.make_branch_with_tags()
1437
br.get_config_stack().set('branch.fetch_tags', True)
1438
self.addCleanup(br.lock_read().unlock)
1439
# Disable the heads_to_fetch verb
1440
verb = 'Branch.heads_to_fetch'
1441
self.disable_verb(verb)
1442
self.reset_smart_call_log()
1443
result = br.heads_to_fetch()
1444
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1446
['Branch.last_revision_info', 'Branch.get_tags_bytes'],
1447
[call.call.method for call in self.hpss_calls])
1449
def test_backwards_compatible_no_tags(self):
1450
br = self.make_branch_with_tags()
1451
br.get_config_stack().set('branch.fetch_tags', False)
1452
self.addCleanup(br.lock_read().unlock)
1453
# Disable the heads_to_fetch verb
1454
verb = 'Branch.heads_to_fetch'
1455
self.disable_verb(verb)
1456
self.reset_smart_call_log()
1457
result = br.heads_to_fetch()
1458
self.assertEqual((set(['tip']), set()), result)
1460
['Branch.last_revision_info'],
1461
[call.call.method for call in self.hpss_calls])
1464
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1466
def test_empty_branch(self):
1467
# in an empty branch we decode the response properly
1468
transport = MemoryTransport()
1469
client = FakeClient(transport.base)
1470
client.add_expected_call(
1471
'Branch.get_stacked_on_url', ('quack/',),
1472
'error', ('NotStacked',))
1473
client.add_expected_call(
1474
'Branch.last_revision_info', ('quack/',),
1475
'success', ('ok', '0', 'null:'))
1476
transport.mkdir('quack')
1477
transport = transport.clone('quack')
1478
branch = self.make_remote_branch(transport, client)
1479
result = branch.last_revision_info()
1480
self.assertFinished(client)
1481
self.assertEqual((0, NULL_REVISION), result)
1483
def test_non_empty_branch(self):
1484
# in a non-empty branch we also decode the response properly
1485
revid = u'\xc8'.encode('utf8')
1486
transport = MemoryTransport()
1487
client = FakeClient(transport.base)
1488
client.add_expected_call(
1489
'Branch.get_stacked_on_url', ('kwaak/',),
1490
'error', ('NotStacked',))
1491
client.add_expected_call(
1492
'Branch.last_revision_info', ('kwaak/',),
1493
'success', ('ok', '2', revid))
1494
transport.mkdir('kwaak')
1495
transport = transport.clone('kwaak')
1496
branch = self.make_remote_branch(transport, client)
1497
result = branch.last_revision_info()
1498
self.assertEqual((2, revid), result)
1501
class TestBranch_get_stacked_on_url(TestRemote):
1502
"""Test Branch._get_stacked_on_url rpc"""
1504
def test_get_stacked_on_invalid_url(self):
1505
# test that asking for a stacked on url the server can't access works.
1506
# This isn't perfect, but then as we're in the same process there
1507
# really isn't anything we can do to be 100% sure that the server
1508
# doesn't just open in - this test probably needs to be rewritten using
1509
# a spawn()ed server.
1510
stacked_branch = self.make_branch('stacked', format='1.9')
1511
memory_branch = self.make_branch('base', format='1.9')
1512
vfs_url = self.get_vfs_only_url('base')
1513
stacked_branch.set_stacked_on_url(vfs_url)
1514
transport = stacked_branch.bzrdir.root_transport
1515
client = FakeClient(transport.base)
1516
client.add_expected_call(
1517
'Branch.get_stacked_on_url', ('stacked/',),
1518
'success', ('ok', vfs_url))
1519
# XXX: Multiple calls are bad, this second call documents what is
1521
client.add_expected_call(
1522
'Branch.get_stacked_on_url', ('stacked/',),
1523
'success', ('ok', vfs_url))
1524
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1526
repo_fmt = remote.RemoteRepositoryFormat()
1527
repo_fmt._custom_format = stacked_branch.repository._format
1528
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1530
result = branch.get_stacked_on_url()
1531
self.assertEqual(vfs_url, result)
1533
def test_backwards_compatible(self):
1534
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1535
base_branch = self.make_branch('base', format='1.6')
1536
stacked_branch = self.make_branch('stacked', format='1.6')
1537
stacked_branch.set_stacked_on_url('../base')
1538
client = FakeClient(self.get_url())
1539
branch_network_name = self.get_branch_format().network_name()
1540
client.add_expected_call(
1541
'BzrDir.open_branchV3', ('stacked/',),
1542
'success', ('branch', branch_network_name))
1543
client.add_expected_call(
1544
'BzrDir.find_repositoryV3', ('stacked/',),
1545
'success', ('ok', '', 'no', 'no', 'yes',
1546
stacked_branch.repository._format.network_name()))
1547
# called twice, once from constructor and then again by us
1548
client.add_expected_call(
1549
'Branch.get_stacked_on_url', ('stacked/',),
1550
'unknown', ('Branch.get_stacked_on_url',))
1551
client.add_expected_call(
1552
'Branch.get_stacked_on_url', ('stacked/',),
1553
'unknown', ('Branch.get_stacked_on_url',))
1554
# this will also do vfs access, but that goes direct to the transport
1555
# and isn't seen by the FakeClient.
1556
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1557
RemoteBzrDirFormat(), _client=client)
1558
branch = bzrdir.open_branch()
1559
result = branch.get_stacked_on_url()
1560
self.assertEqual('../base', result)
1561
self.assertFinished(client)
1562
# it's in the fallback list both for the RemoteRepository and its vfs
1564
self.assertEqual(1, len(branch.repository._fallback_repositories))
1566
len(branch.repository._real_repository._fallback_repositories))
1568
def test_get_stacked_on_real_branch(self):
1569
base_branch = self.make_branch('base')
1570
stacked_branch = self.make_branch('stacked')
1571
stacked_branch.set_stacked_on_url('../base')
1572
reference_format = self.get_repo_format()
1573
network_name = reference_format.network_name()
1574
client = FakeClient(self.get_url())
1575
branch_network_name = self.get_branch_format().network_name()
1576
client.add_expected_call(
1577
'BzrDir.open_branchV3', ('stacked/',),
1578
'success', ('branch', branch_network_name))
1579
client.add_expected_call(
1580
'BzrDir.find_repositoryV3', ('stacked/',),
1581
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1582
# called twice, once from constructor and then again by us
1583
client.add_expected_call(
1584
'Branch.get_stacked_on_url', ('stacked/',),
1585
'success', ('ok', '../base'))
1586
client.add_expected_call(
1587
'Branch.get_stacked_on_url', ('stacked/',),
1588
'success', ('ok', '../base'))
1589
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1590
RemoteBzrDirFormat(), _client=client)
1591
branch = bzrdir.open_branch()
1592
result = branch.get_stacked_on_url()
1593
self.assertEqual('../base', result)
1594
self.assertFinished(client)
1595
# it's in the fallback list both for the RemoteRepository.
1596
self.assertEqual(1, len(branch.repository._fallback_repositories))
1597
# And we haven't had to construct a real repository.
1598
self.assertEqual(None, branch.repository._real_repository)
1601
class TestBranchSetLastRevision(RemoteBranchTestCase):
1603
def test_set_empty(self):
1604
# _set_last_revision_info('null:') is translated to calling
1605
# Branch.set_last_revision(path, '') on the wire.
1606
transport = MemoryTransport()
1607
transport.mkdir('branch')
1608
transport = transport.clone('branch')
1610
client = FakeClient(transport.base)
1611
client.add_expected_call(
1612
'Branch.get_stacked_on_url', ('branch/',),
1613
'error', ('NotStacked',))
1614
client.add_expected_call(
1615
'Branch.lock_write', ('branch/', '', ''),
1616
'success', ('ok', 'branch token', 'repo token'))
1617
client.add_expected_call(
1618
'Branch.last_revision_info',
1620
'success', ('ok', '0', 'null:'))
1621
client.add_expected_call(
1622
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1624
client.add_expected_call(
1625
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1627
branch = self.make_remote_branch(transport, client)
1629
result = branch._set_last_revision(NULL_REVISION)
1631
self.assertEqual(None, result)
1632
self.assertFinished(client)
1634
def test_set_nonempty(self):
1635
# set_last_revision_info(N, rev-idN) is translated to calling
1636
# Branch.set_last_revision(path, rev-idN) on the wire.
1637
transport = MemoryTransport()
1638
transport.mkdir('branch')
1639
transport = transport.clone('branch')
1641
client = FakeClient(transport.base)
1642
client.add_expected_call(
1643
'Branch.get_stacked_on_url', ('branch/',),
1644
'error', ('NotStacked',))
1645
client.add_expected_call(
1646
'Branch.lock_write', ('branch/', '', ''),
1647
'success', ('ok', 'branch token', 'repo token'))
1648
client.add_expected_call(
1649
'Branch.last_revision_info',
1651
'success', ('ok', '0', 'null:'))
1653
encoded_body = bz2.compress('\n'.join(lines))
1654
client.add_success_response_with_body(encoded_body, 'ok')
1655
client.add_expected_call(
1656
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1658
client.add_expected_call(
1659
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1661
branch = self.make_remote_branch(transport, client)
1662
# Lock the branch, reset the record of remote calls.
1664
result = branch._set_last_revision('rev-id2')
1666
self.assertEqual(None, result)
1667
self.assertFinished(client)
1669
def test_no_such_revision(self):
1670
transport = MemoryTransport()
1671
transport.mkdir('branch')
1672
transport = transport.clone('branch')
1673
# A response of 'NoSuchRevision' is translated into an exception.
1674
client = FakeClient(transport.base)
1675
client.add_expected_call(
1676
'Branch.get_stacked_on_url', ('branch/',),
1677
'error', ('NotStacked',))
1678
client.add_expected_call(
1679
'Branch.lock_write', ('branch/', '', ''),
1680
'success', ('ok', 'branch token', 'repo token'))
1681
client.add_expected_call(
1682
'Branch.last_revision_info',
1684
'success', ('ok', '0', 'null:'))
1685
# get_graph calls to construct the revision history, for the set_rh
1688
encoded_body = bz2.compress('\n'.join(lines))
1689
client.add_success_response_with_body(encoded_body, 'ok')
1690
client.add_expected_call(
1691
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1692
'error', ('NoSuchRevision', 'rev-id'))
1693
client.add_expected_call(
1694
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1697
branch = self.make_remote_branch(transport, client)
1700
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1702
self.assertFinished(client)
1704
def test_tip_change_rejected(self):
1705
"""TipChangeRejected responses cause a TipChangeRejected exception to
1708
transport = MemoryTransport()
1709
transport.mkdir('branch')
1710
transport = transport.clone('branch')
1711
client = FakeClient(transport.base)
1712
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1713
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1714
client.add_expected_call(
1715
'Branch.get_stacked_on_url', ('branch/',),
1716
'error', ('NotStacked',))
1717
client.add_expected_call(
1718
'Branch.lock_write', ('branch/', '', ''),
1719
'success', ('ok', 'branch token', 'repo token'))
1720
client.add_expected_call(
1721
'Branch.last_revision_info',
1723
'success', ('ok', '0', 'null:'))
1725
encoded_body = bz2.compress('\n'.join(lines))
1726
client.add_success_response_with_body(encoded_body, 'ok')
1727
client.add_expected_call(
1728
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1729
'error', ('TipChangeRejected', rejection_msg_utf8))
1730
client.add_expected_call(
1731
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1733
branch = self.make_remote_branch(transport, client)
1735
# The 'TipChangeRejected' error response triggered by calling
1736
# set_last_revision_info causes a TipChangeRejected exception.
1737
err = self.assertRaises(
1738
errors.TipChangeRejected,
1739
branch._set_last_revision, 'rev-id')
1740
# The UTF-8 message from the response has been decoded into a unicode
1742
self.assertIsInstance(err.msg, unicode)
1743
self.assertEqual(rejection_msg_unicode, err.msg)
1745
self.assertFinished(client)
1748
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1750
def test_set_last_revision_info(self):
1751
# set_last_revision_info(num, 'rev-id') is translated to calling
1752
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1753
transport = MemoryTransport()
1754
transport.mkdir('branch')
1755
transport = transport.clone('branch')
1756
client = FakeClient(transport.base)
1757
# get_stacked_on_url
1758
client.add_error_response('NotStacked')
1760
client.add_success_response('ok', 'branch token', 'repo token')
1761
# query the current revision
1762
client.add_success_response('ok', '0', 'null:')
1764
client.add_success_response('ok')
1766
client.add_success_response('ok')
1768
branch = self.make_remote_branch(transport, client)
1769
# Lock the branch, reset the record of remote calls.
1772
result = branch.set_last_revision_info(1234, 'a-revision-id')
1774
[('call', 'Branch.last_revision_info', ('branch/',)),
1775
('call', 'Branch.set_last_revision_info',
1776
('branch/', 'branch token', 'repo token',
1777
'1234', 'a-revision-id'))],
1779
self.assertEqual(None, result)
1781
def test_no_such_revision(self):
1782
# A response of 'NoSuchRevision' is translated into an exception.
1783
transport = MemoryTransport()
1784
transport.mkdir('branch')
1785
transport = transport.clone('branch')
1786
client = FakeClient(transport.base)
1787
# get_stacked_on_url
1788
client.add_error_response('NotStacked')
1790
client.add_success_response('ok', 'branch token', 'repo token')
1792
client.add_error_response('NoSuchRevision', 'revid')
1794
client.add_success_response('ok')
1796
branch = self.make_remote_branch(transport, client)
1797
# Lock the branch, reset the record of remote calls.
1802
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1805
def test_backwards_compatibility(self):
1806
"""If the server does not support the Branch.set_last_revision_info
1807
verb (which is new in 1.4), then the client falls back to VFS methods.
1809
# This test is a little messy. Unlike most tests in this file, it
1810
# doesn't purely test what a Remote* object sends over the wire, and
1811
# how it reacts to responses from the wire. It instead relies partly
1812
# on asserting that the RemoteBranch will call
1813
# self._real_branch.set_last_revision_info(...).
1815
# First, set up our RemoteBranch with a FakeClient that raises
1816
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1817
transport = MemoryTransport()
1818
transport.mkdir('branch')
1819
transport = transport.clone('branch')
1820
client = FakeClient(transport.base)
1821
client.add_expected_call(
1822
'Branch.get_stacked_on_url', ('branch/',),
1823
'error', ('NotStacked',))
1824
client.add_expected_call(
1825
'Branch.last_revision_info',
1827
'success', ('ok', '0', 'null:'))
1828
client.add_expected_call(
1829
'Branch.set_last_revision_info',
1830
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1831
'unknown', 'Branch.set_last_revision_info')
1833
branch = self.make_remote_branch(transport, client)
1834
class StubRealBranch(object):
1837
def set_last_revision_info(self, revno, revision_id):
1839
('set_last_revision_info', revno, revision_id))
1840
def _clear_cached_state(self):
1842
real_branch = StubRealBranch()
1843
branch._real_branch = real_branch
1844
self.lock_remote_branch(branch)
1846
# Call set_last_revision_info, and verify it behaved as expected.
1847
result = branch.set_last_revision_info(1234, 'a-revision-id')
1849
[('set_last_revision_info', 1234, 'a-revision-id')],
1851
self.assertFinished(client)
1853
def test_unexpected_error(self):
1854
# If the server sends an error the client doesn't understand, it gets
1855
# turned into an UnknownErrorFromSmartServer, which is presented as a
1856
# non-internal error to the user.
1857
transport = MemoryTransport()
1858
transport.mkdir('branch')
1859
transport = transport.clone('branch')
1860
client = FakeClient(transport.base)
1861
# get_stacked_on_url
1862
client.add_error_response('NotStacked')
1864
client.add_success_response('ok', 'branch token', 'repo token')
1866
client.add_error_response('UnexpectedError')
1868
client.add_success_response('ok')
1870
branch = self.make_remote_branch(transport, client)
1871
# Lock the branch, reset the record of remote calls.
1875
err = self.assertRaises(
1876
errors.UnknownErrorFromSmartServer,
1877
branch.set_last_revision_info, 123, 'revid')
1878
self.assertEqual(('UnexpectedError',), err.error_tuple)
1881
def test_tip_change_rejected(self):
1882
"""TipChangeRejected responses cause a TipChangeRejected exception to
1885
transport = MemoryTransport()
1886
transport.mkdir('branch')
1887
transport = transport.clone('branch')
1888
client = FakeClient(transport.base)
1889
# get_stacked_on_url
1890
client.add_error_response('NotStacked')
1892
client.add_success_response('ok', 'branch token', 'repo token')
1894
client.add_error_response('TipChangeRejected', 'rejection message')
1896
client.add_success_response('ok')
1898
branch = self.make_remote_branch(transport, client)
1899
# Lock the branch, reset the record of remote calls.
1901
self.addCleanup(branch.unlock)
1904
# The 'TipChangeRejected' error response triggered by calling
1905
# set_last_revision_info causes a TipChangeRejected exception.
1906
err = self.assertRaises(
1907
errors.TipChangeRejected,
1908
branch.set_last_revision_info, 123, 'revid')
1909
self.assertEqual('rejection message', err.msg)
1912
class TestBranchGetSetConfig(RemoteBranchTestCase):
1914
def test_get_branch_conf(self):
1915
# in an empty branch we decode the response properly
1916
client = FakeClient()
1917
client.add_expected_call(
1918
'Branch.get_stacked_on_url', ('memory:///',),
1919
'error', ('NotStacked',),)
1920
client.add_success_response_with_body('# config file body', 'ok')
1921
transport = MemoryTransport()
1922
branch = self.make_remote_branch(transport, client)
1923
config = branch.get_config()
1924
config.has_explicit_nickname()
1926
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1927
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1930
def test_get_multi_line_branch_conf(self):
1931
# Make sure that multiple-line branch.conf files are supported
1933
# https://bugs.launchpad.net/bzr/+bug/354075
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('a = 1\nb = 2\nc = 3\n', 'ok')
1939
transport = MemoryTransport()
1940
branch = self.make_remote_branch(transport, client)
1941
config = branch.get_config()
1942
self.assertEqual(u'2', config.get_user_option('b'))
1944
def test_set_option(self):
1945
client = FakeClient()
1946
client.add_expected_call(
1947
'Branch.get_stacked_on_url', ('memory:///',),
1948
'error', ('NotStacked',),)
1949
client.add_expected_call(
1950
'Branch.lock_write', ('memory:///', '', ''),
1951
'success', ('ok', 'branch token', 'repo token'))
1952
client.add_expected_call(
1953
'Branch.set_config_option', ('memory:///', 'branch token',
1954
'repo token', 'foo', 'bar', ''),
1956
client.add_expected_call(
1957
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1959
transport = MemoryTransport()
1960
branch = self.make_remote_branch(transport, client)
1962
config = branch._get_config()
1963
config.set_option('foo', 'bar')
1965
self.assertFinished(client)
1967
def test_set_option_with_dict(self):
1968
client = FakeClient()
1969
client.add_expected_call(
1970
'Branch.get_stacked_on_url', ('memory:///',),
1971
'error', ('NotStacked',),)
1972
client.add_expected_call(
1973
'Branch.lock_write', ('memory:///', '', ''),
1974
'success', ('ok', 'branch token', 'repo token'))
1975
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1976
client.add_expected_call(
1977
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1978
'repo token', encoded_dict_value, 'foo', ''),
1980
client.add_expected_call(
1981
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1983
transport = MemoryTransport()
1984
branch = self.make_remote_branch(transport, client)
1986
config = branch._get_config()
1988
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1991
self.assertFinished(client)
1993
def test_backwards_compat_set_option(self):
1994
self.setup_smart_server_with_call_log()
1995
branch = self.make_branch('.')
1996
verb = 'Branch.set_config_option'
1997
self.disable_verb(verb)
1999
self.addCleanup(branch.unlock)
2000
self.reset_smart_call_log()
2001
branch._get_config().set_option('value', 'name')
2002
self.assertLength(11, self.hpss_calls)
2003
self.assertEqual('value', branch._get_config().get_option('name'))
2005
def test_backwards_compat_set_option_with_dict(self):
2006
self.setup_smart_server_with_call_log()
2007
branch = self.make_branch('.')
2008
verb = 'Branch.set_config_option_dict'
2009
self.disable_verb(verb)
2011
self.addCleanup(branch.unlock)
2012
self.reset_smart_call_log()
2013
config = branch._get_config()
2014
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2015
config.set_option(value_dict, 'name')
2016
self.assertLength(11, self.hpss_calls)
2017
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2020
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2022
def test_get_branch_conf(self):
2023
# in an empty branch we decode the response properly
2024
client = FakeClient()
2025
client.add_expected_call(
2026
'Branch.get_stacked_on_url', ('memory:///',),
2027
'error', ('NotStacked',),)
2028
client.add_success_response_with_body('# config file body', 'ok')
2029
transport = MemoryTransport()
2030
branch = self.make_remote_branch(transport, client)
2031
config = branch.get_config_stack()
2033
config.get("log_format")
2035
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2036
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2039
def test_set_branch_conf(self):
2040
client = FakeClient()
2041
client.add_expected_call(
2042
'Branch.get_stacked_on_url', ('memory:///',),
2043
'error', ('NotStacked',),)
2044
client.add_expected_call(
2045
'Branch.lock_write', ('memory:///', '', ''),
2046
'success', ('ok', 'branch token', 'repo token'))
2047
client.add_expected_call(
2048
'Branch.get_config_file', ('memory:///', ),
2049
'success', ('ok', ), "# line 1\n")
2050
client.add_expected_call(
2051
'Branch.get_config_file', ('memory:///', ),
2052
'success', ('ok', ), "# line 1\n")
2053
client.add_expected_call(
2054
'Branch.put_config_file', ('memory:///', 'branch token',
2057
client.add_expected_call(
2058
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2060
transport = MemoryTransport()
2061
branch = self.make_remote_branch(transport, client)
2063
config = branch.get_config_stack()
2064
config.set('email', 'The Dude <lebowski@example.com>')
2066
self.assertFinished(client)
2068
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2069
('call', 'Branch.lock_write', ('memory:///', '', '')),
2070
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2071
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2072
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2073
('memory:///', 'branch token', 'repo token'),
2074
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2075
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2079
class TestBranchLockWrite(RemoteBranchTestCase):
2081
def test_lock_write_unlockable(self):
2082
transport = MemoryTransport()
2083
client = FakeClient(transport.base)
2084
client.add_expected_call(
2085
'Branch.get_stacked_on_url', ('quack/',),
2086
'error', ('NotStacked',),)
2087
client.add_expected_call(
2088
'Branch.lock_write', ('quack/', '', ''),
2089
'error', ('UnlockableTransport',))
2090
transport.mkdir('quack')
2091
transport = transport.clone('quack')
2092
branch = self.make_remote_branch(transport, client)
2093
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2094
self.assertFinished(client)
2097
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2099
def test_simple(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.revision_id_to_revno', ('quack/', 'null:'),
2107
'success', ('ok', '0',),)
2108
client.add_expected_call(
2109
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2110
'error', ('NoSuchRevision', 'unknown',),)
2111
transport.mkdir('quack')
2112
transport = transport.clone('quack')
2113
branch = self.make_remote_branch(transport, client)
2114
self.assertEquals(0, branch.revision_id_to_revno('null:'))
2115
self.assertRaises(errors.NoSuchRevision,
2116
branch.revision_id_to_revno, 'unknown')
2117
self.assertFinished(client)
2119
def test_dotted(self):
2120
transport = MemoryTransport()
2121
client = FakeClient(transport.base)
2122
client.add_expected_call(
2123
'Branch.get_stacked_on_url', ('quack/',),
2124
'error', ('NotStacked',),)
2125
client.add_expected_call(
2126
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2127
'success', ('ok', '0',),)
2128
client.add_expected_call(
2129
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2130
'error', ('NoSuchRevision', 'unknown',),)
2131
transport.mkdir('quack')
2132
transport = transport.clone('quack')
2133
branch = self.make_remote_branch(transport, client)
2134
self.assertEquals((0, ), branch.revision_id_to_dotted_revno('null:'))
2135
self.assertRaises(errors.NoSuchRevision,
2136
branch.revision_id_to_dotted_revno, 'unknown')
2137
self.assertFinished(client)
2139
def test_dotted_no_smart_verb(self):
2140
self.setup_smart_server_with_call_log()
2141
branch = self.make_branch('.')
2142
self.disable_verb('Branch.revision_id_to_revno')
2143
self.reset_smart_call_log()
2144
self.assertEquals((0, ),
2145
branch.revision_id_to_dotted_revno('null:'))
2146
self.assertLength(8, self.hpss_calls)
2149
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2151
def test__get_config(self):
2152
client = FakeClient()
2153
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2154
transport = MemoryTransport()
2155
bzrdir = self.make_remote_bzrdir(transport, client)
2156
config = bzrdir.get_config()
2157
self.assertEqual('/', config.get_default_stack_on())
2159
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2162
def test_set_option_uses_vfs(self):
2163
self.setup_smart_server_with_call_log()
2164
bzrdir = self.make_bzrdir('.')
2165
self.reset_smart_call_log()
2166
config = bzrdir.get_config()
2167
config.set_default_stack_on('/')
2168
self.assertLength(4, self.hpss_calls)
2170
def test_backwards_compat_get_option(self):
2171
self.setup_smart_server_with_call_log()
2172
bzrdir = self.make_bzrdir('.')
2173
verb = 'BzrDir.get_config_file'
2174
self.disable_verb(verb)
2175
self.reset_smart_call_log()
2176
self.assertEqual(None,
2177
bzrdir._get_config().get_option('default_stack_on'))
2178
self.assertLength(4, self.hpss_calls)
2181
class TestTransportIsReadonly(tests.TestCase):
2183
def test_true(self):
2184
client = FakeClient()
2185
client.add_success_response('yes')
2186
transport = RemoteTransport('bzr://example.com/', medium=False,
2188
self.assertEqual(True, transport.is_readonly())
2190
[('call', 'Transport.is_readonly', ())],
2193
def test_false(self):
2194
client = FakeClient()
2195
client.add_success_response('no')
2196
transport = RemoteTransport('bzr://example.com/', medium=False,
2198
self.assertEqual(False, transport.is_readonly())
2200
[('call', 'Transport.is_readonly', ())],
2203
def test_error_from_old_server(self):
2204
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2206
Clients should treat it as a "no" response, because is_readonly is only
2207
advisory anyway (a transport could be read-write, but then the
2208
underlying filesystem could be readonly anyway).
2210
client = FakeClient()
2211
client.add_unknown_method_response('Transport.is_readonly')
2212
transport = RemoteTransport('bzr://example.com/', medium=False,
2214
self.assertEqual(False, transport.is_readonly())
2216
[('call', 'Transport.is_readonly', ())],
2220
class TestTransportMkdir(tests.TestCase):
2222
def test_permissiondenied(self):
2223
client = FakeClient()
2224
client.add_error_response('PermissionDenied', 'remote path', 'extra')
2225
transport = RemoteTransport('bzr://example.com/', medium=False,
2227
exc = self.assertRaises(
2228
errors.PermissionDenied, transport.mkdir, 'client path')
2229
expected_error = errors.PermissionDenied('/client path', 'extra')
2230
self.assertEqual(expected_error, exc)
2233
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2235
def test_defaults_to_none(self):
2236
t = RemoteSSHTransport('bzr+ssh://example.com')
2237
self.assertIs(None, t._get_credentials()[0])
2239
def test_uses_authentication_config(self):
2240
conf = config.AuthenticationConfig()
2241
conf._get_config().update(
2242
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2245
t = RemoteSSHTransport('bzr+ssh://example.com')
2246
self.assertEqual('bar', t._get_credentials()[0])
2249
class TestRemoteRepository(TestRemote):
2250
"""Base for testing RemoteRepository protocol usage.
2252
These tests contain frozen requests and responses. We want any changes to
2253
what is sent or expected to be require a thoughtful update to these tests
2254
because they might break compatibility with different-versioned servers.
2257
def setup_fake_client_and_repository(self, transport_path):
2258
"""Create the fake client and repository for testing with.
2260
There's no real server here; we just have canned responses sent
2263
:param transport_path: Path below the root of the MemoryTransport
2264
where the repository will be created.
2266
transport = MemoryTransport()
2267
transport.mkdir(transport_path)
2268
client = FakeClient(transport.base)
2269
transport = transport.clone(transport_path)
2270
# we do not want bzrdir to make any remote calls
2271
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2273
repo = RemoteRepository(bzrdir, None, _client=client)
2277
def remoted_description(format):
2278
return 'Remote: ' + format.get_format_description()
2281
class TestBranchFormat(tests.TestCase):
2283
def test_get_format_description(self):
2284
remote_format = RemoteBranchFormat()
2285
real_format = branch.format_registry.get_default()
2286
remote_format._network_name = real_format.network_name()
2287
self.assertEqual(remoted_description(real_format),
2288
remote_format.get_format_description())
2291
class TestRepositoryFormat(TestRemoteRepository):
2293
def test_fast_delta(self):
2294
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2295
true_format = RemoteRepositoryFormat()
2296
true_format._network_name = true_name
2297
self.assertEqual(True, true_format.fast_deltas)
2298
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2299
false_format = RemoteRepositoryFormat()
2300
false_format._network_name = false_name
2301
self.assertEqual(False, false_format.fast_deltas)
2303
def test_get_format_description(self):
2304
remote_repo_format = RemoteRepositoryFormat()
2305
real_format = repository.format_registry.get_default()
2306
remote_repo_format._network_name = real_format.network_name()
2307
self.assertEqual(remoted_description(real_format),
2308
remote_repo_format.get_format_description())
2311
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2313
def test_empty(self):
2314
transport_path = 'quack'
2315
repo, client = self.setup_fake_client_and_repository(transport_path)
2316
client.add_success_response_with_body('', 'ok')
2317
self.assertEquals([], repo.all_revision_ids())
2319
[('call_expecting_body', 'Repository.all_revision_ids',
2323
def test_with_some_content(self):
2324
transport_path = 'quack'
2325
repo, client = self.setup_fake_client_and_repository(transport_path)
2326
client.add_success_response_with_body(
2327
'rev1\nrev2\nanotherrev\n', 'ok')
2328
self.assertEquals(["rev1", "rev2", "anotherrev"],
2329
repo.all_revision_ids())
2331
[('call_expecting_body', 'Repository.all_revision_ids',
2336
class TestRepositoryGatherStats(TestRemoteRepository):
2338
def test_revid_none(self):
2339
# ('ok',), body with revisions and size
2340
transport_path = 'quack'
2341
repo, client = self.setup_fake_client_and_repository(transport_path)
2342
client.add_success_response_with_body(
2343
'revisions: 2\nsize: 18\n', 'ok')
2344
result = repo.gather_stats(None)
2346
[('call_expecting_body', 'Repository.gather_stats',
2347
('quack/','','no'))],
2349
self.assertEqual({'revisions': 2, 'size': 18}, result)
2351
def test_revid_no_committers(self):
2352
# ('ok',), body without committers
2353
body = ('firstrev: 123456.300 3600\n'
2354
'latestrev: 654231.400 0\n'
2357
transport_path = 'quick'
2358
revid = u'\xc8'.encode('utf8')
2359
repo, client = self.setup_fake_client_and_repository(transport_path)
2360
client.add_success_response_with_body(body, 'ok')
2361
result = repo.gather_stats(revid)
2363
[('call_expecting_body', 'Repository.gather_stats',
2364
('quick/', revid, 'no'))],
2366
self.assertEqual({'revisions': 2, 'size': 18,
2367
'firstrev': (123456.300, 3600),
2368
'latestrev': (654231.400, 0),},
2371
def test_revid_with_committers(self):
2372
# ('ok',), body with committers
2373
body = ('committers: 128\n'
2374
'firstrev: 123456.300 3600\n'
2375
'latestrev: 654231.400 0\n'
2378
transport_path = 'buick'
2379
revid = u'\xc8'.encode('utf8')
2380
repo, client = self.setup_fake_client_and_repository(transport_path)
2381
client.add_success_response_with_body(body, 'ok')
2382
result = repo.gather_stats(revid, True)
2384
[('call_expecting_body', 'Repository.gather_stats',
2385
('buick/', revid, 'yes'))],
2387
self.assertEqual({'revisions': 2, 'size': 18,
2389
'firstrev': (123456.300, 3600),
2390
'latestrev': (654231.400, 0),},
2394
class TestRepositoryBreakLock(TestRemoteRepository):
2396
def test_break_lock(self):
2397
transport_path = 'quack'
2398
repo, client = self.setup_fake_client_and_repository(transport_path)
2399
client.add_success_response('ok')
2402
[('call', 'Repository.break_lock', ('quack/',))],
2406
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2408
def test_get_serializer_format(self):
2409
transport_path = 'hill'
2410
repo, client = self.setup_fake_client_and_repository(transport_path)
2411
client.add_success_response('ok', '7')
2412
self.assertEquals('7', repo.get_serializer_format())
2414
[('call', 'VersionedFileRepository.get_serializer_format',
2419
class TestRepositoryReconcile(TestRemoteRepository):
2421
def test_reconcile(self):
2422
transport_path = 'hill'
2423
repo, client = self.setup_fake_client_and_repository(transport_path)
2424
body = ("garbage_inventories: 2\n"
2425
"inconsistent_parents: 3\n")
2426
client.add_expected_call(
2427
'Repository.lock_write', ('hill/', ''),
2428
'success', ('ok', 'a token'))
2429
client.add_success_response_with_body(body, 'ok')
2430
reconciler = repo.reconcile()
2432
[('call', 'Repository.lock_write', ('hill/', '')),
2433
('call_expecting_body', 'Repository.reconcile',
2434
('hill/', 'a token'))],
2436
self.assertEquals(2, reconciler.garbage_inventories)
2437
self.assertEquals(3, reconciler.inconsistent_parents)
2440
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2442
def test_text(self):
2443
# ('ok',), body with signature text
2444
transport_path = 'quack'
2445
repo, client = self.setup_fake_client_and_repository(transport_path)
2446
client.add_success_response_with_body(
2448
self.assertEquals("THETEXT", repo.get_signature_text("revid"))
2450
[('call_expecting_body', 'Repository.get_revision_signature_text',
2451
('quack/', 'revid'))],
2454
def test_no_signature(self):
2455
transport_path = 'quick'
2456
repo, client = self.setup_fake_client_and_repository(transport_path)
2457
client.add_error_response('nosuchrevision', 'unknown')
2458
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2461
[('call_expecting_body', 'Repository.get_revision_signature_text',
2462
('quick/', 'unknown'))],
2466
class TestRepositoryGetGraph(TestRemoteRepository):
2468
def test_get_graph(self):
2469
# get_graph returns a graph with a custom parents provider.
2470
transport_path = 'quack'
2471
repo, client = self.setup_fake_client_and_repository(transport_path)
2472
graph = repo.get_graph()
2473
self.assertNotEqual(graph._parents_provider, repo)
2476
class TestRepositoryAddSignatureText(TestRemoteRepository):
2478
def test_add_signature_text(self):
2479
transport_path = 'quack'
2480
repo, client = self.setup_fake_client_and_repository(transport_path)
2481
client.add_expected_call(
2482
'Repository.lock_write', ('quack/', ''),
2483
'success', ('ok', 'a token'))
2484
client.add_expected_call(
2485
'Repository.start_write_group', ('quack/', 'a token'),
2486
'success', ('ok', ('token1', )))
2487
client.add_expected_call(
2488
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2490
'success', ('ok', ), None)
2492
repo.start_write_group()
2494
repo.add_signature_text("rev1", "every bloody emperor"))
2496
('call_with_body_bytes_expecting_body',
2497
'Repository.add_signature_text',
2498
('quack/', 'a token', 'rev1', 'token1'),
2499
'every bloody emperor'),
2503
class TestRepositoryGetParentMap(TestRemoteRepository):
2505
def test_get_parent_map_caching(self):
2506
# get_parent_map returns from cache until unlock()
2507
# setup a reponse with two revisions
2508
r1 = u'\u0e33'.encode('utf8')
2509
r2 = u'\u0dab'.encode('utf8')
2510
lines = [' '.join([r2, r1]), r1]
2511
encoded_body = bz2.compress('\n'.join(lines))
2513
transport_path = 'quack'
2514
repo, client = self.setup_fake_client_and_repository(transport_path)
2515
client.add_success_response_with_body(encoded_body, 'ok')
2516
client.add_success_response_with_body(encoded_body, 'ok')
2518
graph = repo.get_graph()
2519
parents = graph.get_parent_map([r2])
2520
self.assertEqual({r2: (r1,)}, parents)
2521
# locking and unlocking deeper should not reset
2524
parents = graph.get_parent_map([r1])
2525
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2527
[('call_with_body_bytes_expecting_body',
2528
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2532
# now we call again, and it should use the second response.
2534
graph = repo.get_graph()
2535
parents = graph.get_parent_map([r1])
2536
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2538
[('call_with_body_bytes_expecting_body',
2539
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2541
('call_with_body_bytes_expecting_body',
2542
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2548
def test_get_parent_map_reconnects_if_unknown_method(self):
2549
transport_path = 'quack'
2550
rev_id = 'revision-id'
2551
repo, client = self.setup_fake_client_and_repository(transport_path)
2552
client.add_unknown_method_response('Repository.get_parent_map')
2553
client.add_success_response_with_body(rev_id, 'ok')
2554
self.assertFalse(client._medium._is_remote_before((1, 2)))
2555
parents = repo.get_parent_map([rev_id])
2557
[('call_with_body_bytes_expecting_body',
2558
'Repository.get_parent_map',
2559
('quack/', 'include-missing:', rev_id), '\n\n0'),
2560
('disconnect medium',),
2561
('call_expecting_body', 'Repository.get_revision_graph',
2564
# The medium is now marked as being connected to an older server
2565
self.assertTrue(client._medium._is_remote_before((1, 2)))
2566
self.assertEqual({rev_id: ('null:',)}, parents)
2568
def test_get_parent_map_fallback_parentless_node(self):
2569
"""get_parent_map falls back to get_revision_graph on old servers. The
2570
results from get_revision_graph are tweaked to match the get_parent_map
2573
Specifically, a {key: ()} result from get_revision_graph means "no
2574
parents" for that key, which in get_parent_map results should be
2575
represented as {key: ('null:',)}.
2577
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2579
rev_id = 'revision-id'
2580
transport_path = 'quack'
2581
repo, client = self.setup_fake_client_and_repository(transport_path)
2582
client.add_success_response_with_body(rev_id, 'ok')
2583
client._medium._remember_remote_is_before((1, 2))
2584
parents = repo.get_parent_map([rev_id])
2586
[('call_expecting_body', 'Repository.get_revision_graph',
2589
self.assertEqual({rev_id: ('null:',)}, parents)
2591
def test_get_parent_map_unexpected_response(self):
2592
repo, client = self.setup_fake_client_and_repository('path')
2593
client.add_success_response('something unexpected!')
2595
errors.UnexpectedSmartServerResponse,
2596
repo.get_parent_map, ['a-revision-id'])
2598
def test_get_parent_map_negative_caches_missing_keys(self):
2599
self.setup_smart_server_with_call_log()
2600
repo = self.make_repository('foo')
2601
self.assertIsInstance(repo, RemoteRepository)
2603
self.addCleanup(repo.unlock)
2604
self.reset_smart_call_log()
2605
graph = repo.get_graph()
2606
self.assertEqual({},
2607
graph.get_parent_map(['some-missing', 'other-missing']))
2608
self.assertLength(1, self.hpss_calls)
2609
# No call if we repeat this
2610
self.reset_smart_call_log()
2611
graph = repo.get_graph()
2612
self.assertEqual({},
2613
graph.get_parent_map(['some-missing', 'other-missing']))
2614
self.assertLength(0, self.hpss_calls)
2615
# Asking for more unknown keys makes a request.
2616
self.reset_smart_call_log()
2617
graph = repo.get_graph()
2618
self.assertEqual({},
2619
graph.get_parent_map(['some-missing', 'other-missing',
2621
self.assertLength(1, self.hpss_calls)
2623
def disableExtraResults(self):
2624
self.overrideAttr(SmartServerRepositoryGetParentMap,
2625
'no_extra_results', True)
2627
def test_null_cached_missing_and_stop_key(self):
2628
self.setup_smart_server_with_call_log()
2629
# Make a branch with a single revision.
2630
builder = self.make_branch_builder('foo')
2631
builder.start_series()
2632
builder.build_snapshot('first', None, [
2633
('add', ('', 'root-id', 'directory', ''))])
2634
builder.finish_series()
2635
branch = builder.get_branch()
2636
repo = branch.repository
2637
self.assertIsInstance(repo, RemoteRepository)
2638
# Stop the server from sending extra results.
2639
self.disableExtraResults()
2641
self.addCleanup(repo.unlock)
2642
self.reset_smart_call_log()
2643
graph = repo.get_graph()
2644
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2645
# 'first' it will be a candidate for the stop_keys of subsequent
2646
# requests, and because 'null:' was queried but not returned it will be
2647
# cached as missing.
2648
self.assertEqual({'first': ('null:',)},
2649
graph.get_parent_map(['first', 'null:']))
2650
# Now query for another key. This request will pass along a recipe of
2651
# start and stop keys describing the already cached results, and this
2652
# recipe's revision count must be correct (or else it will trigger an
2653
# error from the server).
2654
self.assertEqual({}, graph.get_parent_map(['another-key']))
2655
# This assertion guards against disableExtraResults silently failing to
2656
# work, thus invalidating the test.
2657
self.assertLength(2, self.hpss_calls)
2659
def test_get_parent_map_gets_ghosts_from_result(self):
2660
# asking for a revision should negatively cache close ghosts in its
2662
self.setup_smart_server_with_call_log()
2663
tree = self.make_branch_and_memory_tree('foo')
2666
builder = treebuilder.TreeBuilder()
2667
builder.start_tree(tree)
2669
builder.finish_tree()
2670
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2671
rev_id = tree.commit('')
2675
self.addCleanup(tree.unlock)
2676
repo = tree.branch.repository
2677
self.assertIsInstance(repo, RemoteRepository)
2679
repo.get_parent_map([rev_id])
2680
self.reset_smart_call_log()
2681
# Now asking for rev_id's ghost parent should not make calls
2682
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2683
self.assertLength(0, self.hpss_calls)
2685
def test_exposes_get_cached_parent_map(self):
2686
"""RemoteRepository exposes get_cached_parent_map from
2689
r1 = u'\u0e33'.encode('utf8')
2690
r2 = u'\u0dab'.encode('utf8')
2691
lines = [' '.join([r2, r1]), r1]
2692
encoded_body = bz2.compress('\n'.join(lines))
2694
transport_path = 'quack'
2695
repo, client = self.setup_fake_client_and_repository(transport_path)
2696
client.add_success_response_with_body(encoded_body, 'ok')
2698
# get_cached_parent_map should *not* trigger an RPC
2699
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2700
self.assertEqual([], client._calls)
2701
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2702
self.assertEqual({r1: (NULL_REVISION,)},
2703
repo.get_cached_parent_map([r1]))
2705
[('call_with_body_bytes_expecting_body',
2706
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2712
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2714
def test_allows_new_revisions(self):
2715
"""get_parent_map's results can be updated by commit."""
2716
smart_server = test_server.SmartTCPServer_for_testing()
2717
self.start_server(smart_server)
2718
self.make_branch('branch')
2719
branch = Branch.open(smart_server.get_url() + '/branch')
2720
tree = branch.create_checkout('tree', lightweight=True)
2722
self.addCleanup(tree.unlock)
2723
graph = tree.branch.repository.get_graph()
2724
# This provides an opportunity for the missing rev-id to be cached.
2725
self.assertEqual({}, graph.get_parent_map(['rev1']))
2726
tree.commit('message', rev_id='rev1')
2727
graph = tree.branch.repository.get_graph()
2728
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2731
class TestRepositoryGetRevisions(TestRemoteRepository):
2733
def test_hpss_missing_revision(self):
2734
transport_path = 'quack'
2735
repo, client = self.setup_fake_client_and_repository(transport_path)
2736
client.add_success_response_with_body(
2738
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2739
['somerev1', 'anotherrev2'])
2741
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2742
('quack/', ), "somerev1\nanotherrev2")],
2745
def test_hpss_get_single_revision(self):
2746
transport_path = 'quack'
2747
repo, client = self.setup_fake_client_and_repository(transport_path)
2748
somerev1 = Revision("somerev1")
2749
somerev1.committer = "Joe Committer <joe@example.com>"
2750
somerev1.timestamp = 1321828927
2751
somerev1.timezone = -60
2752
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2753
somerev1.message = "Message"
2754
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2756
# Split up body into two bits to make sure the zlib compression object
2757
# gets data fed twice.
2758
client.add_success_response_with_body(
2759
[body[:10], body[10:]], 'ok', '10')
2760
revs = repo.get_revisions(['somerev1'])
2761
self.assertEquals(revs, [somerev1])
2763
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2764
('quack/', ), "somerev1")],
2768
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2770
def test_null_revision(self):
2771
# a null revision has the predictable result {}, we should have no wire
2772
# traffic when calling it with this argument
2773
transport_path = 'empty'
2774
repo, client = self.setup_fake_client_and_repository(transport_path)
2775
client.add_success_response('notused')
2776
# actual RemoteRepository.get_revision_graph is gone, but there's an
2777
# equivalent private method for testing
2778
result = repo._get_revision_graph(NULL_REVISION)
2779
self.assertEqual([], client._calls)
2780
self.assertEqual({}, result)
2782
def test_none_revision(self):
2783
# with none we want the entire graph
2784
r1 = u'\u0e33'.encode('utf8')
2785
r2 = u'\u0dab'.encode('utf8')
2786
lines = [' '.join([r2, r1]), r1]
2787
encoded_body = '\n'.join(lines)
2789
transport_path = 'sinhala'
2790
repo, client = self.setup_fake_client_and_repository(transport_path)
2791
client.add_success_response_with_body(encoded_body, 'ok')
2792
# actual RemoteRepository.get_revision_graph is gone, but there's an
2793
# equivalent private method for testing
2794
result = repo._get_revision_graph(None)
2796
[('call_expecting_body', 'Repository.get_revision_graph',
2799
self.assertEqual({r1: (), r2: (r1, )}, result)
2801
def test_specific_revision(self):
2802
# with a specific revision we want the graph for that
2803
# with none we want the entire graph
2804
r11 = u'\u0e33'.encode('utf8')
2805
r12 = u'\xc9'.encode('utf8')
2806
r2 = u'\u0dab'.encode('utf8')
2807
lines = [' '.join([r2, r11, r12]), r11, r12]
2808
encoded_body = '\n'.join(lines)
2810
transport_path = 'sinhala'
2811
repo, client = self.setup_fake_client_and_repository(transport_path)
2812
client.add_success_response_with_body(encoded_body, 'ok')
2813
result = repo._get_revision_graph(r2)
2815
[('call_expecting_body', 'Repository.get_revision_graph',
2818
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2820
def test_no_such_revision(self):
2822
transport_path = 'sinhala'
2823
repo, client = self.setup_fake_client_and_repository(transport_path)
2824
client.add_error_response('nosuchrevision', revid)
2825
# also check that the right revision is reported in the error
2826
self.assertRaises(errors.NoSuchRevision,
2827
repo._get_revision_graph, revid)
2829
[('call_expecting_body', 'Repository.get_revision_graph',
2830
('sinhala/', revid))],
2833
def test_unexpected_error(self):
2835
transport_path = 'sinhala'
2836
repo, client = self.setup_fake_client_and_repository(transport_path)
2837
client.add_error_response('AnUnexpectedError')
2838
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2839
repo._get_revision_graph, revid)
2840
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2843
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2846
repo, client = self.setup_fake_client_and_repository('quack')
2847
client.add_expected_call(
2848
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2849
'success', ('ok', 'rev-five'))
2850
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2851
self.assertEqual((True, 'rev-five'), result)
2852
self.assertFinished(client)
2854
def test_history_incomplete(self):
2855
repo, client = self.setup_fake_client_and_repository('quack')
2856
client.add_expected_call(
2857
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2858
'success', ('history-incomplete', 10, 'rev-ten'))
2859
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2860
self.assertEqual((False, (10, 'rev-ten')), result)
2861
self.assertFinished(client)
2863
def test_history_incomplete_with_fallback(self):
2864
"""A 'history-incomplete' response causes the fallback repository to be
2865
queried too, if one is set.
2867
# Make a repo with a fallback repo, both using a FakeClient.
2868
format = remote.response_tuple_to_repo_format(
2869
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2870
repo, client = self.setup_fake_client_and_repository('quack')
2871
repo._format = format
2872
fallback_repo, ignored = self.setup_fake_client_and_repository(
2874
fallback_repo._client = client
2875
fallback_repo._format = format
2876
repo.add_fallback_repository(fallback_repo)
2877
# First the client should ask the primary repo
2878
client.add_expected_call(
2879
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2880
'success', ('history-incomplete', 2, 'rev-two'))
2881
# Then it should ask the fallback, using revno/revid from the
2882
# history-incomplete response as the known revno/revid.
2883
client.add_expected_call(
2884
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2885
'success', ('ok', 'rev-one'))
2886
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2887
self.assertEqual((True, 'rev-one'), result)
2888
self.assertFinished(client)
2890
def test_nosuchrevision(self):
2891
# 'nosuchrevision' is returned when the known-revid is not found in the
2892
# remote repo. The client translates that response to NoSuchRevision.
2893
repo, client = self.setup_fake_client_and_repository('quack')
2894
client.add_expected_call(
2895
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2896
'error', ('nosuchrevision', 'rev-foo'))
2898
errors.NoSuchRevision,
2899
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2900
self.assertFinished(client)
2902
def test_branch_fallback_locking(self):
2903
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2904
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2905
will be invoked, which will fail if the repo is unlocked.
2907
self.setup_smart_server_with_call_log()
2908
tree = self.make_branch_and_memory_tree('.')
2911
rev1 = tree.commit('First')
2912
rev2 = tree.commit('Second')
2914
branch = tree.branch
2915
self.assertFalse(branch.is_locked())
2916
self.reset_smart_call_log()
2917
verb = 'Repository.get_rev_id_for_revno'
2918
self.disable_verb(verb)
2919
self.assertEqual(rev1, branch.get_rev_id(1))
2920
self.assertLength(1, [call for call in self.hpss_calls if
2921
call.call.method == verb])
2924
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2926
def test_has_signature_for_revision_id(self):
2927
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2928
transport_path = 'quack'
2929
repo, client = self.setup_fake_client_and_repository(transport_path)
2930
client.add_success_response('yes')
2931
result = repo.has_signature_for_revision_id('A')
2933
[('call', 'Repository.has_signature_for_revision_id',
2936
self.assertEqual(True, result)
2938
def test_is_not_shared(self):
2939
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2940
transport_path = 'qwack'
2941
repo, client = self.setup_fake_client_and_repository(transport_path)
2942
client.add_success_response('no')
2943
result = repo.has_signature_for_revision_id('A')
2945
[('call', 'Repository.has_signature_for_revision_id',
2948
self.assertEqual(False, result)
2951
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2953
def test_get_physical_lock_status_yes(self):
2954
transport_path = 'qwack'
2955
repo, client = self.setup_fake_client_and_repository(transport_path)
2956
client.add_success_response('yes')
2957
result = repo.get_physical_lock_status()
2959
[('call', 'Repository.get_physical_lock_status',
2962
self.assertEqual(True, result)
2964
def test_get_physical_lock_status_no(self):
2965
transport_path = 'qwack'
2966
repo, client = self.setup_fake_client_and_repository(transport_path)
2967
client.add_success_response('no')
2968
result = repo.get_physical_lock_status()
2970
[('call', 'Repository.get_physical_lock_status',
2973
self.assertEqual(False, result)
2976
class TestRepositoryIsShared(TestRemoteRepository):
2978
def test_is_shared(self):
2979
# ('yes', ) for Repository.is_shared -> 'True'.
2980
transport_path = 'quack'
2981
repo, client = self.setup_fake_client_and_repository(transport_path)
2982
client.add_success_response('yes')
2983
result = repo.is_shared()
2985
[('call', 'Repository.is_shared', ('quack/',))],
2987
self.assertEqual(True, result)
2989
def test_is_not_shared(self):
2990
# ('no', ) for Repository.is_shared -> 'False'.
2991
transport_path = 'qwack'
2992
repo, client = self.setup_fake_client_and_repository(transport_path)
2993
client.add_success_response('no')
2994
result = repo.is_shared()
2996
[('call', 'Repository.is_shared', ('qwack/',))],
2998
self.assertEqual(False, result)
3001
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3003
def test_make_working_trees(self):
3004
# ('yes', ) for Repository.make_working_trees -> 'True'.
3005
transport_path = 'quack'
3006
repo, client = self.setup_fake_client_and_repository(transport_path)
3007
client.add_success_response('yes')
3008
result = repo.make_working_trees()
3010
[('call', 'Repository.make_working_trees', ('quack/',))],
3012
self.assertEqual(True, result)
3014
def test_no_working_trees(self):
3015
# ('no', ) for Repository.make_working_trees -> 'False'.
3016
transport_path = 'qwack'
3017
repo, client = self.setup_fake_client_and_repository(transport_path)
3018
client.add_success_response('no')
3019
result = repo.make_working_trees()
3021
[('call', 'Repository.make_working_trees', ('qwack/',))],
3023
self.assertEqual(False, result)
3026
class TestRepositoryLockWrite(TestRemoteRepository):
3028
def test_lock_write(self):
3029
transport_path = 'quack'
3030
repo, client = self.setup_fake_client_and_repository(transport_path)
3031
client.add_success_response('ok', 'a token')
3032
token = repo.lock_write().repository_token
3034
[('call', 'Repository.lock_write', ('quack/', ''))],
3036
self.assertEqual('a token', token)
3038
def test_lock_write_already_locked(self):
3039
transport_path = 'quack'
3040
repo, client = self.setup_fake_client_and_repository(transport_path)
3041
client.add_error_response('LockContention')
3042
self.assertRaises(errors.LockContention, repo.lock_write)
3044
[('call', 'Repository.lock_write', ('quack/', ''))],
3047
def test_lock_write_unlockable(self):
3048
transport_path = 'quack'
3049
repo, client = self.setup_fake_client_and_repository(transport_path)
3050
client.add_error_response('UnlockableTransport')
3051
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3053
[('call', 'Repository.lock_write', ('quack/', ''))],
3057
class TestRepositoryWriteGroups(TestRemoteRepository):
3059
def test_start_write_group(self):
3060
transport_path = 'quack'
3061
repo, client = self.setup_fake_client_and_repository(transport_path)
3062
client.add_expected_call(
3063
'Repository.lock_write', ('quack/', ''),
3064
'success', ('ok', 'a token'))
3065
client.add_expected_call(
3066
'Repository.start_write_group', ('quack/', 'a token'),
3067
'success', ('ok', ('token1', )))
3069
repo.start_write_group()
3071
def test_start_write_group_unsuspendable(self):
3072
# Some repositories do not support suspending write
3073
# groups. For those, fall back to the "real" repository.
3074
transport_path = 'quack'
3075
repo, client = self.setup_fake_client_and_repository(transport_path)
3076
def stub_ensure_real():
3077
client._calls.append(('_ensure_real',))
3078
repo._real_repository = _StubRealPackRepository(client._calls)
3079
repo._ensure_real = stub_ensure_real
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
'error', ('UnsuspendableWriteGroup',))
3087
repo.start_write_group()
3088
self.assertEquals(client._calls[-2:], [
3090
('start_write_group',)])
3092
def test_commit_write_group(self):
3093
transport_path = 'quack'
3094
repo, client = self.setup_fake_client_and_repository(transport_path)
3095
client.add_expected_call(
3096
'Repository.lock_write', ('quack/', ''),
3097
'success', ('ok', 'a token'))
3098
client.add_expected_call(
3099
'Repository.start_write_group', ('quack/', 'a token'),
3100
'success', ('ok', ['token1']))
3101
client.add_expected_call(
3102
'Repository.commit_write_group', ('quack/', 'a token', ['token1']),
3105
repo.start_write_group()
3106
repo.commit_write_group()
3108
def test_abort_write_group(self):
3109
transport_path = 'quack'
3110
repo, client = self.setup_fake_client_and_repository(transport_path)
3111
client.add_expected_call(
3112
'Repository.lock_write', ('quack/', ''),
3113
'success', ('ok', 'a token'))
3114
client.add_expected_call(
3115
'Repository.start_write_group', ('quack/', 'a token'),
3116
'success', ('ok', ['token1']))
3117
client.add_expected_call(
3118
'Repository.abort_write_group', ('quack/', 'a token', ['token1']),
3121
repo.start_write_group()
3122
repo.abort_write_group(False)
3124
def test_suspend_write_group(self):
3125
transport_path = 'quack'
3126
repo, client = self.setup_fake_client_and_repository(transport_path)
3127
self.assertEquals([], repo.suspend_write_group())
3129
def test_resume_write_group(self):
3130
transport_path = 'quack'
3131
repo, client = self.setup_fake_client_and_repository(transport_path)
3132
client.add_expected_call(
3133
'Repository.lock_write', ('quack/', ''),
3134
'success', ('ok', 'a token'))
3135
client.add_expected_call(
3136
'Repository.check_write_group', ('quack/', 'a token', ['token1']),
3139
repo.resume_write_group(['token1'])
3142
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3144
def test_backwards_compat(self):
3145
self.setup_smart_server_with_call_log()
3146
repo = self.make_repository('.')
3147
self.reset_smart_call_log()
3148
verb = 'Repository.set_make_working_trees'
3149
self.disable_verb(verb)
3150
repo.set_make_working_trees(True)
3151
call_count = len([call for call in self.hpss_calls if
3152
call.call.method == verb])
3153
self.assertEqual(1, call_count)
3155
def test_current(self):
3156
transport_path = 'quack'
3157
repo, client = self.setup_fake_client_and_repository(transport_path)
3158
client.add_expected_call(
3159
'Repository.set_make_working_trees', ('quack/', 'True'),
3161
client.add_expected_call(
3162
'Repository.set_make_working_trees', ('quack/', 'False'),
3164
repo.set_make_working_trees(True)
3165
repo.set_make_working_trees(False)
3168
class TestRepositoryUnlock(TestRemoteRepository):
3170
def test_unlock(self):
3171
transport_path = 'quack'
3172
repo, client = self.setup_fake_client_and_repository(transport_path)
3173
client.add_success_response('ok', 'a token')
3174
client.add_success_response('ok')
3178
[('call', 'Repository.lock_write', ('quack/', '')),
3179
('call', 'Repository.unlock', ('quack/', 'a token'))],
3182
def test_unlock_wrong_token(self):
3183
# If somehow the token is wrong, unlock will raise TokenMismatch.
3184
transport_path = 'quack'
3185
repo, client = self.setup_fake_client_and_repository(transport_path)
3186
client.add_success_response('ok', 'a token')
3187
client.add_error_response('TokenMismatch')
3189
self.assertRaises(errors.TokenMismatch, repo.unlock)
3192
class TestRepositoryHasRevision(TestRemoteRepository):
3194
def test_none(self):
3195
# repo.has_revision(None) should not cause any traffic.
3196
transport_path = 'quack'
3197
repo, client = self.setup_fake_client_and_repository(transport_path)
3199
# The null revision is always there, so has_revision(None) == True.
3200
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3202
# The remote repo shouldn't be accessed.
3203
self.assertEqual([], client._calls)
3206
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3207
"""Test Repository.iter_file_bytes."""
3209
def test_single(self):
3210
transport_path = 'quack'
3211
repo, client = self.setup_fake_client_and_repository(transport_path)
3212
client.add_expected_call(
3213
'Repository.iter_files_bytes', ('quack/', ),
3214
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3215
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3216
"somerev", "myid")]):
3217
self.assertEquals("myid", identifier)
3218
self.assertEquals("".join(byte_stream), "mydata" * 10)
3220
def test_missing(self):
3221
transport_path = 'quack'
3222
repo, client = self.setup_fake_client_and_repository(transport_path)
3223
client.add_expected_call(
3224
'Repository.iter_files_bytes',
3226
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3227
iter(["absent\0somefile\0somerev\n"]))
3228
self.assertRaises(errors.RevisionNotPresent, list,
3229
repo.iter_files_bytes(
3230
[("somefile", "somerev", "myid")]))
3233
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3234
"""Base class for Repository.insert_stream and .insert_stream_1.19
3238
def checkInsertEmptyStream(self, repo, client):
3239
"""Insert an empty stream, checking the result.
3241
This checks that there are no resume_tokens or missing_keys, and that
3242
the client is finished.
3244
sink = repo._get_sink()
3245
fmt = repository.format_registry.get_default()
3246
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3247
self.assertEqual([], resume_tokens)
3248
self.assertEqual(set(), missing_keys)
3249
self.assertFinished(client)
3252
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3253
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3256
This test case is very similar to TestRepositoryInsertStream_1_19.
3260
TestRemoteRepository.setUp(self)
3261
self.disable_verb('Repository.insert_stream_1.19')
3263
def test_unlocked_repo(self):
3264
transport_path = 'quack'
3265
repo, client = self.setup_fake_client_and_repository(transport_path)
3266
client.add_expected_call(
3267
'Repository.insert_stream_1.19', ('quack/', ''),
3268
'unknown', ('Repository.insert_stream_1.19',))
3269
client.add_expected_call(
3270
'Repository.insert_stream', ('quack/', ''),
3272
client.add_expected_call(
3273
'Repository.insert_stream', ('quack/', ''),
3275
self.checkInsertEmptyStream(repo, client)
3277
def test_locked_repo_with_no_lock_token(self):
3278
transport_path = 'quack'
3279
repo, client = self.setup_fake_client_and_repository(transport_path)
3280
client.add_expected_call(
3281
'Repository.lock_write', ('quack/', ''),
3282
'success', ('ok', ''))
3283
client.add_expected_call(
3284
'Repository.insert_stream_1.19', ('quack/', ''),
3285
'unknown', ('Repository.insert_stream_1.19',))
3286
client.add_expected_call(
3287
'Repository.insert_stream', ('quack/', ''),
3289
client.add_expected_call(
3290
'Repository.insert_stream', ('quack/', ''),
3293
self.checkInsertEmptyStream(repo, client)
3295
def test_locked_repo_with_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', 'a token'))
3301
client.add_expected_call(
3302
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3303
'unknown', ('Repository.insert_stream_1.19',))
3304
client.add_expected_call(
3305
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3307
client.add_expected_call(
3308
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3311
self.checkInsertEmptyStream(repo, client)
3313
def test_stream_with_inventory_deltas(self):
3314
"""'inventory-deltas' substreams cannot be sent to the
3315
Repository.insert_stream verb, because not all servers that implement
3316
that verb will accept them. So when one is encountered the RemoteSink
3317
immediately stops using that verb and falls back to VFS insert_stream.
3319
transport_path = 'quack'
3320
repo, client = self.setup_fake_client_and_repository(transport_path)
3321
client.add_expected_call(
3322
'Repository.insert_stream_1.19', ('quack/', ''),
3323
'unknown', ('Repository.insert_stream_1.19',))
3324
client.add_expected_call(
3325
'Repository.insert_stream', ('quack/', ''),
3327
client.add_expected_call(
3328
'Repository.insert_stream', ('quack/', ''),
3330
# Create a fake real repository for insert_stream to fall back on, so
3331
# that we can directly see the records the RemoteSink passes to the
3336
def insert_stream(self, stream, src_format, resume_tokens):
3337
for substream_kind, substream in stream:
3338
self.records.append(
3339
(substream_kind, [record.key for record in substream]))
3340
return ['fake tokens'], ['fake missing keys']
3341
fake_real_sink = FakeRealSink()
3342
class FakeRealRepository:
3343
def _get_sink(self):
3344
return fake_real_sink
3345
def is_in_write_group(self):
3347
def refresh_data(self):
3349
repo._real_repository = FakeRealRepository()
3350
sink = repo._get_sink()
3351
fmt = repository.format_registry.get_default()
3352
stream = self.make_stream_with_inv_deltas(fmt)
3353
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3354
# Every record from the first inventory delta should have been sent to
3356
expected_records = [
3357
('inventory-deltas', [('rev2',), ('rev3',)]),
3358
('texts', [('some-rev', 'some-file')])]
3359
self.assertEqual(expected_records, fake_real_sink.records)
3360
# The return values from the real sink's insert_stream are propagated
3361
# back to the original caller.
3362
self.assertEqual(['fake tokens'], resume_tokens)
3363
self.assertEqual(['fake missing keys'], missing_keys)
3364
self.assertFinished(client)
3366
def make_stream_with_inv_deltas(self, fmt):
3367
"""Make a simple stream with an inventory delta followed by more
3368
records and more substreams to test that all records and substreams
3369
from that point on are used.
3371
This sends, in order:
3372
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3374
* texts substream: (some-rev, some-file)
3376
# Define a stream using generators so that it isn't rewindable.
3377
inv = inventory.Inventory(revision_id='rev1')
3378
inv.root.revision = 'rev1'
3379
def stream_with_inv_delta():
3380
yield ('inventories', inventories_substream())
3381
yield ('inventory-deltas', inventory_delta_substream())
3383
versionedfile.FulltextContentFactory(
3384
('some-rev', 'some-file'), (), None, 'content')])
3385
def inventories_substream():
3386
# An empty inventory fulltext. This will be streamed normally.
3387
text = fmt._serializer.write_inventory_to_string(inv)
3388
yield versionedfile.FulltextContentFactory(
3389
('rev1',), (), None, text)
3390
def inventory_delta_substream():
3391
# An inventory delta. This can't be streamed via this verb, so it
3392
# will trigger a fallback to VFS insert_stream.
3393
entry = inv.make_entry(
3394
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3395
entry.revision = 'ghost'
3396
delta = [(None, 'newdir', 'newdir-id', entry)]
3397
serializer = inventory_delta.InventoryDeltaSerializer(
3398
versioned_root=True, tree_references=False)
3399
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3400
yield versionedfile.ChunkedContentFactory(
3401
('rev2',), (('rev1',)), None, lines)
3403
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3404
yield versionedfile.ChunkedContentFactory(
3405
('rev3',), (('rev1',)), None, lines)
3406
return stream_with_inv_delta()
3409
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3411
def test_unlocked_repo(self):
3412
transport_path = 'quack'
3413
repo, client = self.setup_fake_client_and_repository(transport_path)
3414
client.add_expected_call(
3415
'Repository.insert_stream_1.19', ('quack/', ''),
3417
client.add_expected_call(
3418
'Repository.insert_stream_1.19', ('quack/', ''),
3420
self.checkInsertEmptyStream(repo, client)
3422
def test_locked_repo_with_no_lock_token(self):
3423
transport_path = 'quack'
3424
repo, client = self.setup_fake_client_and_repository(transport_path)
3425
client.add_expected_call(
3426
'Repository.lock_write', ('quack/', ''),
3427
'success', ('ok', ''))
3428
client.add_expected_call(
3429
'Repository.insert_stream_1.19', ('quack/', ''),
3431
client.add_expected_call(
3432
'Repository.insert_stream_1.19', ('quack/', ''),
3435
self.checkInsertEmptyStream(repo, client)
3437
def test_locked_repo_with_lock_token(self):
3438
transport_path = 'quack'
3439
repo, client = self.setup_fake_client_and_repository(transport_path)
3440
client.add_expected_call(
3441
'Repository.lock_write', ('quack/', ''),
3442
'success', ('ok', 'a token'))
3443
client.add_expected_call(
3444
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3446
client.add_expected_call(
3447
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3450
self.checkInsertEmptyStream(repo, client)
3453
class TestRepositoryTarball(TestRemoteRepository):
3455
# This is a canned tarball reponse we can validate against
3457
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3458
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3459
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3460
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3461
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3462
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3463
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3464
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3465
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3466
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3467
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3468
'nWQ7QH/F3JFOFCQ0aSPfA='
3471
def test_repository_tarball(self):
3472
# Test that Repository.tarball generates the right operations
3473
transport_path = 'repo'
3474
expected_calls = [('call_expecting_body', 'Repository.tarball',
3475
('repo/', 'bz2',),),
3477
repo, client = self.setup_fake_client_and_repository(transport_path)
3478
client.add_success_response_with_body(self.tarball_content, 'ok')
3479
# Now actually ask for the tarball
3480
tarball_file = repo._get_tarball('bz2')
3482
self.assertEqual(expected_calls, client._calls)
3483
self.assertEqual(self.tarball_content, tarball_file.read())
3485
tarball_file.close()
3488
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3489
"""RemoteRepository.copy_content_into optimizations"""
3491
def test_copy_content_remote_to_local(self):
3492
self.transport_server = test_server.SmartTCPServer_for_testing
3493
src_repo = self.make_repository('repo1')
3494
src_repo = repository.Repository.open(self.get_url('repo1'))
3495
# At the moment the tarball-based copy_content_into can't write back
3496
# into a smart server. It would be good if it could upload the
3497
# tarball; once that works we'd have to create repositories of
3498
# different formats. -- mbp 20070410
3499
dest_url = self.get_vfs_only_url('repo2')
3500
dest_bzrdir = BzrDir.create(dest_url)
3501
dest_repo = dest_bzrdir.create_repository()
3502
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3503
self.assertTrue(isinstance(src_repo, RemoteRepository))
3504
src_repo.copy_content_into(dest_repo)
3507
class _StubRealPackRepository(object):
3509
def __init__(self, calls):
3511
self._pack_collection = _StubPackCollection(calls)
3513
def start_write_group(self):
3514
self.calls.append(('start_write_group',))
3516
def is_in_write_group(self):
3519
def refresh_data(self):
3520
self.calls.append(('pack collection reload_pack_names',))
3523
class _StubPackCollection(object):
3525
def __init__(self, calls):
3529
self.calls.append(('pack collection autopack',))
3532
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3533
"""Tests for RemoteRepository.autopack implementation."""
3536
"""When the server returns 'ok' and there's no _real_repository, then
3537
nothing else happens: the autopack method is done.
3539
transport_path = 'quack'
3540
repo, client = self.setup_fake_client_and_repository(transport_path)
3541
client.add_expected_call(
3542
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3544
self.assertFinished(client)
3546
def test_ok_with_real_repo(self):
3547
"""When the server returns 'ok' and there is a _real_repository, then
3548
the _real_repository's reload_pack_name's method will be called.
3550
transport_path = 'quack'
3551
repo, client = self.setup_fake_client_and_repository(transport_path)
3552
client.add_expected_call(
3553
'PackRepository.autopack', ('quack/',),
3555
repo._real_repository = _StubRealPackRepository(client._calls)
3558
[('call', 'PackRepository.autopack', ('quack/',)),
3559
('pack collection reload_pack_names',)],
3562
def test_backwards_compatibility(self):
3563
"""If the server does not recognise the PackRepository.autopack verb,
3564
fallback to the real_repository's implementation.
3566
transport_path = 'quack'
3567
repo, client = self.setup_fake_client_and_repository(transport_path)
3568
client.add_unknown_method_response('PackRepository.autopack')
3569
def stub_ensure_real():
3570
client._calls.append(('_ensure_real',))
3571
repo._real_repository = _StubRealPackRepository(client._calls)
3572
repo._ensure_real = stub_ensure_real
3575
[('call', 'PackRepository.autopack', ('quack/',)),
3577
('pack collection autopack',)],
3580
def test_oom_error_reporting(self):
3581
"""An out-of-memory condition on the server is reported clearly"""
3582
transport_path = 'quack'
3583
repo, client = self.setup_fake_client_and_repository(transport_path)
3584
client.add_expected_call(
3585
'PackRepository.autopack', ('quack/',),
3586
'error', ('MemoryError',))
3587
err = self.assertRaises(errors.BzrError, repo.autopack)
3588
self.assertContainsRe(str(err), "^remote server out of mem")
3591
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3592
"""Base class for unit tests for bzrlib.remote._translate_error."""
3594
def translateTuple(self, error_tuple, **context):
3595
"""Call _translate_error with an ErrorFromSmartServer built from the
3598
:param error_tuple: A tuple of a smart server response, as would be
3599
passed to an ErrorFromSmartServer.
3600
:kwargs context: context items to call _translate_error with.
3602
:returns: The error raised by _translate_error.
3604
# Raise the ErrorFromSmartServer before passing it as an argument,
3605
# because _translate_error may need to re-raise it with a bare 'raise'
3607
server_error = errors.ErrorFromSmartServer(error_tuple)
3608
translated_error = self.translateErrorFromSmartServer(
3609
server_error, **context)
3610
return translated_error
3612
def translateErrorFromSmartServer(self, error_object, **context):
3613
"""Like translateTuple, but takes an already constructed
3614
ErrorFromSmartServer rather than a tuple.
3618
except errors.ErrorFromSmartServer, server_error:
3619
translated_error = self.assertRaises(
3620
errors.BzrError, remote._translate_error, server_error,
3622
return translated_error
3625
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3626
"""Unit tests for bzrlib.remote._translate_error.
3628
Given an ErrorFromSmartServer (which has an error tuple from a smart
3629
server) and some context, _translate_error raises more specific errors from
3632
This test case covers the cases where _translate_error succeeds in
3633
translating an ErrorFromSmartServer to something better. See
3634
TestErrorTranslationRobustness for other cases.
3637
def test_NoSuchRevision(self):
3638
branch = self.make_branch('')
3640
translated_error = self.translateTuple(
3641
('NoSuchRevision', revid), branch=branch)
3642
expected_error = errors.NoSuchRevision(branch, revid)
3643
self.assertEqual(expected_error, translated_error)
3645
def test_nosuchrevision(self):
3646
repository = self.make_repository('')
3648
translated_error = self.translateTuple(
3649
('nosuchrevision', revid), repository=repository)
3650
expected_error = errors.NoSuchRevision(repository, revid)
3651
self.assertEqual(expected_error, translated_error)
3653
def test_nobranch(self):
3654
bzrdir = self.make_bzrdir('')
3655
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3656
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3657
self.assertEqual(expected_error, translated_error)
3659
def test_nobranch_one_arg(self):
3660
bzrdir = self.make_bzrdir('')
3661
translated_error = self.translateTuple(
3662
('nobranch', 'extra detail'), bzrdir=bzrdir)
3663
expected_error = errors.NotBranchError(
3664
path=bzrdir.root_transport.base,
3665
detail='extra detail')
3666
self.assertEqual(expected_error, translated_error)
3668
def test_norepository(self):
3669
bzrdir = self.make_bzrdir('')
3670
translated_error = self.translateTuple(('norepository',),
3672
expected_error = errors.NoRepositoryPresent(bzrdir)
3673
self.assertEqual(expected_error, translated_error)
3675
def test_LockContention(self):
3676
translated_error = self.translateTuple(('LockContention',))
3677
expected_error = errors.LockContention('(remote lock)')
3678
self.assertEqual(expected_error, translated_error)
3680
def test_UnlockableTransport(self):
3681
bzrdir = self.make_bzrdir('')
3682
translated_error = self.translateTuple(
3683
('UnlockableTransport',), bzrdir=bzrdir)
3684
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3685
self.assertEqual(expected_error, translated_error)
3687
def test_LockFailed(self):
3688
lock = 'str() of a server lock'
3689
why = 'str() of why'
3690
translated_error = self.translateTuple(('LockFailed', lock, why))
3691
expected_error = errors.LockFailed(lock, why)
3692
self.assertEqual(expected_error, translated_error)
3694
def test_TokenMismatch(self):
3695
token = 'a lock token'
3696
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3697
expected_error = errors.TokenMismatch(token, '(remote token)')
3698
self.assertEqual(expected_error, translated_error)
3700
def test_Diverged(self):
3701
branch = self.make_branch('a')
3702
other_branch = self.make_branch('b')
3703
translated_error = self.translateTuple(
3704
('Diverged',), branch=branch, other_branch=other_branch)
3705
expected_error = errors.DivergedBranches(branch, other_branch)
3706
self.assertEqual(expected_error, translated_error)
3708
def test_NotStacked(self):
3709
branch = self.make_branch('')
3710
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3711
expected_error = errors.NotStacked(branch)
3712
self.assertEqual(expected_error, translated_error)
3714
def test_ReadError_no_args(self):
3716
translated_error = self.translateTuple(('ReadError',), path=path)
3717
expected_error = errors.ReadError(path)
3718
self.assertEqual(expected_error, translated_error)
3720
def test_ReadError(self):
3722
translated_error = self.translateTuple(('ReadError', path))
3723
expected_error = errors.ReadError(path)
3724
self.assertEqual(expected_error, translated_error)
3726
def test_IncompatibleRepositories(self):
3727
translated_error = self.translateTuple(('IncompatibleRepositories',
3728
"repo1", "repo2", "details here"))
3729
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3731
self.assertEqual(expected_error, translated_error)
3733
def test_PermissionDenied_no_args(self):
3735
translated_error = self.translateTuple(('PermissionDenied',),
3737
expected_error = errors.PermissionDenied(path)
3738
self.assertEqual(expected_error, translated_error)
3740
def test_PermissionDenied_one_arg(self):
3742
translated_error = self.translateTuple(('PermissionDenied', path))
3743
expected_error = errors.PermissionDenied(path)
3744
self.assertEqual(expected_error, translated_error)
3746
def test_PermissionDenied_one_arg_and_context(self):
3747
"""Given a choice between a path from the local context and a path on
3748
the wire, _translate_error prefers the path from the local context.
3750
local_path = 'local path'
3751
remote_path = 'remote path'
3752
translated_error = self.translateTuple(
3753
('PermissionDenied', remote_path), path=local_path)
3754
expected_error = errors.PermissionDenied(local_path)
3755
self.assertEqual(expected_error, translated_error)
3757
def test_PermissionDenied_two_args(self):
3759
extra = 'a string with extra info'
3760
translated_error = self.translateTuple(
3761
('PermissionDenied', path, extra))
3762
expected_error = errors.PermissionDenied(path, extra)
3763
self.assertEqual(expected_error, translated_error)
3765
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3767
def test_NoSuchFile_context_path(self):
3768
local_path = "local path"
3769
translated_error = self.translateTuple(('ReadError', "remote path"),
3771
expected_error = errors.ReadError(local_path)
3772
self.assertEqual(expected_error, translated_error)
3774
def test_NoSuchFile_without_context(self):
3775
remote_path = "remote path"
3776
translated_error = self.translateTuple(('ReadError', remote_path))
3777
expected_error = errors.ReadError(remote_path)
3778
self.assertEqual(expected_error, translated_error)
3780
def test_ReadOnlyError(self):
3781
translated_error = self.translateTuple(('ReadOnlyError',))
3782
expected_error = errors.TransportNotPossible("readonly transport")
3783
self.assertEqual(expected_error, translated_error)
3785
def test_MemoryError(self):
3786
translated_error = self.translateTuple(('MemoryError',))
3787
self.assertStartsWith(str(translated_error),
3788
"remote server out of memory")
3790
def test_generic_IndexError_no_classname(self):
3791
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3792
translated_error = self.translateErrorFromSmartServer(err)
3793
expected_error = errors.UnknownErrorFromSmartServer(err)
3794
self.assertEqual(expected_error, translated_error)
3796
# GZ 2011-03-02: TODO test generic non-ascii error string
3798
def test_generic_KeyError(self):
3799
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3800
translated_error = self.translateErrorFromSmartServer(err)
3801
expected_error = errors.UnknownErrorFromSmartServer(err)
3802
self.assertEqual(expected_error, translated_error)
3805
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3806
"""Unit tests for bzrlib.remote._translate_error's robustness.
3808
TestErrorTranslationSuccess is for cases where _translate_error can
3809
translate successfully. This class about how _translate_err behaves when
3810
it fails to translate: it re-raises the original error.
3813
def test_unrecognised_server_error(self):
3814
"""If the error code from the server is not recognised, the original
3815
ErrorFromSmartServer is propagated unmodified.
3817
error_tuple = ('An unknown error tuple',)
3818
server_error = errors.ErrorFromSmartServer(error_tuple)
3819
translated_error = self.translateErrorFromSmartServer(server_error)
3820
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3821
self.assertEqual(expected_error, translated_error)
3823
def test_context_missing_a_key(self):
3824
"""In case of a bug in the client, or perhaps an unexpected response
3825
from a server, _translate_error returns the original error tuple from
3826
the server and mutters a warning.
3828
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3829
# in the context dict. So let's give it an empty context dict instead
3830
# to exercise its error recovery.
3832
error_tuple = ('NoSuchRevision', 'revid')
3833
server_error = errors.ErrorFromSmartServer(error_tuple)
3834
translated_error = self.translateErrorFromSmartServer(server_error)
3835
self.assertEqual(server_error, translated_error)
3836
# In addition to re-raising ErrorFromSmartServer, some debug info has
3837
# been muttered to the log file for developer to look at.
3838
self.assertContainsRe(
3840
"Missing key 'branch' in context")
3842
def test_path_missing(self):
3843
"""Some translations (PermissionDenied, ReadError) can determine the
3844
'path' variable from either the wire or the local context. If neither
3845
has it, then an error is raised.
3847
error_tuple = ('ReadError',)
3848
server_error = errors.ErrorFromSmartServer(error_tuple)
3849
translated_error = self.translateErrorFromSmartServer(server_error)
3850
self.assertEqual(server_error, translated_error)
3851
# In addition to re-raising ErrorFromSmartServer, some debug info has
3852
# been muttered to the log file for developer to look at.
3853
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3856
class TestStacking(tests.TestCaseWithTransport):
3857
"""Tests for operations on stacked remote repositories.
3859
The underlying format type must support stacking.
3862
def test_access_stacked_remote(self):
3863
# based on <http://launchpad.net/bugs/261315>
3864
# make a branch stacked on another repository containing an empty
3865
# revision, then open it over hpss - we should be able to see that
3867
base_transport = self.get_transport()
3868
base_builder = self.make_branch_builder('base', format='1.9')
3869
base_builder.start_series()
3870
base_revid = base_builder.build_snapshot('rev-id', None,
3871
[('add', ('', None, 'directory', None))],
3873
base_builder.finish_series()
3874
stacked_branch = self.make_branch('stacked', format='1.9')
3875
stacked_branch.set_stacked_on_url('../base')
3876
# start a server looking at this
3877
smart_server = test_server.SmartTCPServer_for_testing()
3878
self.start_server(smart_server)
3879
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3880
# can get its branch and repository
3881
remote_branch = remote_bzrdir.open_branch()
3882
remote_repo = remote_branch.repository
3883
remote_repo.lock_read()
3885
# it should have an appropriate fallback repository, which should also
3886
# be a RemoteRepository
3887
self.assertLength(1, remote_repo._fallback_repositories)
3888
self.assertIsInstance(remote_repo._fallback_repositories[0],
3890
# and it has the revision committed to the underlying repository;
3891
# these have varying implementations so we try several of them
3892
self.assertTrue(remote_repo.has_revisions([base_revid]))
3893
self.assertTrue(remote_repo.has_revision(base_revid))
3894
self.assertEqual(remote_repo.get_revision(base_revid).message,
3897
remote_repo.unlock()
3899
def prepare_stacked_remote_branch(self):
3900
"""Get stacked_upon and stacked branches with content in each."""
3901
self.setup_smart_server_with_call_log()
3902
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3903
tree1.commit('rev1', rev_id='rev1')
3904
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3905
).open_workingtree()
3906
local_tree = tree2.branch.create_checkout('local')
3907
local_tree.commit('local changes make me feel good.')
3908
branch2 = Branch.open(self.get_url('tree2'))
3910
self.addCleanup(branch2.unlock)
3911
return tree1.branch, branch2
3913
def test_stacked_get_parent_map(self):
3914
# the public implementation of get_parent_map obeys stacking
3915
_, branch = self.prepare_stacked_remote_branch()
3916
repo = branch.repository
3917
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3919
def test_unstacked_get_parent_map(self):
3920
# _unstacked_provider.get_parent_map ignores stacking
3921
_, branch = self.prepare_stacked_remote_branch()
3922
provider = branch.repository._unstacked_provider
3923
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3925
def fetch_stream_to_rev_order(self, stream):
3927
for kind, substream in stream:
3928
if not kind == 'revisions':
3931
for content in substream:
3932
result.append(content.key[-1])
3935
def get_ordered_revs(self, format, order, branch_factory=None):
3936
"""Get a list of the revisions in a stream to format format.
3938
:param format: The format of the target.
3939
:param order: the order that target should have requested.
3940
:param branch_factory: A callable to create a trunk and stacked branch
3941
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3942
:result: The revision ids in the stream, in the order seen,
3943
the topological order of revisions in the source.
3945
unordered_format = bzrdir.format_registry.get(format)()
3946
target_repository_format = unordered_format.repository_format
3948
self.assertEqual(order, target_repository_format._fetch_order)
3949
if branch_factory is None:
3950
branch_factory = self.prepare_stacked_remote_branch
3951
_, stacked = branch_factory()
3952
source = stacked.repository._get_source(target_repository_format)
3953
tip = stacked.last_revision()
3954
stacked.repository._ensure_real()
3955
graph = stacked.repository.get_graph()
3956
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3957
if r != NULL_REVISION]
3959
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3960
self.reset_smart_call_log()
3961
stream = source.get_stream(search)
3962
# We trust that if a revision is in the stream the rest of the new
3963
# content for it is too, as per our main fetch tests; here we are
3964
# checking that the revisions are actually included at all, and their
3966
return self.fetch_stream_to_rev_order(stream), revs
3968
def test_stacked_get_stream_unordered(self):
3969
# Repository._get_source.get_stream() from a stacked repository with
3970
# unordered yields the full data from both stacked and stacked upon
3972
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3973
self.assertEqual(set(expected_revs), set(rev_ord))
3974
# Getting unordered results should have made a streaming data request
3975
# from the server, then one from the backing branch.
3976
self.assertLength(2, self.hpss_calls)
3978
def test_stacked_on_stacked_get_stream_unordered(self):
3979
# Repository._get_source.get_stream() from a stacked repository which
3980
# is itself stacked yields the full data from all three sources.
3981
def make_stacked_stacked():
3982
_, stacked = self.prepare_stacked_remote_branch()
3983
tree = stacked.bzrdir.sprout('tree3', stacked=True
3984
).open_workingtree()
3985
local_tree = tree.branch.create_checkout('local-tree3')
3986
local_tree.commit('more local changes are better')
3987
branch = Branch.open(self.get_url('tree3'))
3989
self.addCleanup(branch.unlock)
3991
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3992
branch_factory=make_stacked_stacked)
3993
self.assertEqual(set(expected_revs), set(rev_ord))
3994
# Getting unordered results should have made a streaming data request
3995
# from the server, and one from each backing repo
3996
self.assertLength(3, self.hpss_calls)
3998
def test_stacked_get_stream_topological(self):
3999
# Repository._get_source.get_stream() from a stacked repository with
4000
# topological sorting yields the full data from both stacked and
4001
# stacked upon sources in topological order.
4002
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4003
self.assertEqual(expected_revs, rev_ord)
4004
# Getting topological sort requires VFS calls still - one of which is
4005
# pushing up from the bound branch.
4006
self.assertLength(14, self.hpss_calls)
4008
def test_stacked_get_stream_groupcompress(self):
4009
# Repository._get_source.get_stream() from a stacked repository with
4010
# groupcompress sorting yields the full data from both stacked and
4011
# stacked upon sources in groupcompress order.
4012
raise tests.TestSkipped('No groupcompress ordered format available')
4013
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4014
self.assertEqual(expected_revs, reversed(rev_ord))
4015
# Getting unordered results should have made a streaming data request
4016
# from the backing branch, and one from the stacked on branch.
4017
self.assertLength(2, self.hpss_calls)
4019
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4020
# When pulling some fixed amount of content that is more than the
4021
# source has (because some is coming from a fallback branch, no error
4022
# should be received. This was reported as bug 360791.
4023
# Need three branches: a trunk, a stacked branch, and a preexisting
4024
# branch pulling content from stacked and trunk.
4025
self.setup_smart_server_with_call_log()
4026
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4027
r1 = trunk.commit('start')
4028
stacked_branch = trunk.branch.create_clone_on_transport(
4029
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4030
local = self.make_branch('local', format='1.9-rich-root')
4031
local.repository.fetch(stacked_branch.repository,
4032
stacked_branch.last_revision())
4035
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4038
super(TestRemoteBranchEffort, self).setUp()
4039
# Create a smart server that publishes whatever the backing VFS server
4041
self.smart_server = test_server.SmartTCPServer_for_testing()
4042
self.start_server(self.smart_server, self.get_server())
4043
# Log all HPSS calls into self.hpss_calls.
4044
_SmartClient.hooks.install_named_hook(
4045
'call', self.capture_hpss_call, None)
4046
self.hpss_calls = []
4048
def capture_hpss_call(self, params):
4049
self.hpss_calls.append(params.method)
4051
def test_copy_content_into_avoids_revision_history(self):
4052
local = self.make_branch('local')
4053
builder = self.make_branch_builder('remote')
4054
builder.build_commit(message="Commit.")
4055
remote_branch_url = self.smart_server.get_url() + 'remote'
4056
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4057
local.repository.fetch(remote_branch.repository)
4058
self.hpss_calls = []
4059
remote_branch.copy_content_into(local)
4060
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4062
def test_fetch_everything_needs_just_one_call(self):
4063
local = self.make_branch('local')
4064
builder = self.make_branch_builder('remote')
4065
builder.build_commit(message="Commit.")
4066
remote_branch_url = self.smart_server.get_url() + 'remote'
4067
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4068
self.hpss_calls = []
4069
local.repository.fetch(
4070
remote_branch.repository,
4071
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4072
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4074
def override_verb(self, verb_name, verb):
4075
request_handlers = request.request_handlers
4076
orig_verb = request_handlers.get(verb_name)
4077
orig_info = request_handlers.get_info(verb_name)
4078
request_handlers.register(verb_name, verb, override_existing=True)
4079
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4080
override_existing=True, info=orig_info)
4082
def test_fetch_everything_backwards_compat(self):
4083
"""Can fetch with EverythingResult even with pre 2.4 servers.
4085
Pre-2.4 do not support 'everything' searches with the
4086
Repository.get_stream_1.19 verb.
4089
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4090
"""A version of the Repository.get_stream_1.19 verb patched to
4091
reject 'everything' searches the way 2.3 and earlier do.
4093
def recreate_search(self, repository, search_bytes,
4094
discard_excess=False):
4095
verb_log.append(search_bytes.split('\n', 1)[0])
4096
if search_bytes == 'everything':
4098
request.FailedSmartServerResponse(('BadSearch',)))
4099
return super(OldGetStreamVerb,
4100
self).recreate_search(repository, search_bytes,
4101
discard_excess=discard_excess)
4102
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4103
local = self.make_branch('local')
4104
builder = self.make_branch_builder('remote')
4105
builder.build_commit(message="Commit.")
4106
remote_branch_url = self.smart_server.get_url() + 'remote'
4107
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4108
self.hpss_calls = []
4109
local.repository.fetch(
4110
remote_branch.repository,
4111
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4112
# make sure the overridden verb was used
4113
self.assertLength(1, verb_log)
4114
# more than one HPSS call is needed, but because it's a VFS callback
4115
# its hard to predict exactly how many.
4116
self.assertTrue(len(self.hpss_calls) > 1)
4119
class TestUpdateBoundBranchWithModifiedBoundLocation(
4120
tests.TestCaseWithTransport):
4121
"""Ensure correct handling of bound_location modifications.
4123
This is tested against a smart server as http://pad.lv/786980 was about a
4124
ReadOnlyError (write attempt during a read-only transaction) which can only
4125
happen in this context.
4129
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4130
self.transport_server = test_server.SmartTCPServer_for_testing
4132
def make_master_and_checkout(self, master_name, checkout_name):
4133
# Create the master branch and its associated checkout
4134
self.master = self.make_branch_and_tree(master_name)
4135
self.checkout = self.master.branch.create_checkout(checkout_name)
4136
# Modify the master branch so there is something to update
4137
self.master.commit('add stuff')
4138
self.last_revid = self.master.commit('even more stuff')
4139
self.bound_location = self.checkout.branch.get_bound_location()
4141
def assertUpdateSucceeds(self, new_location):
4142
self.checkout.branch.set_bound_location(new_location)
4143
self.checkout.update()
4144
self.assertEquals(self.last_revid, self.checkout.last_revision())
4146
def test_without_final_slash(self):
4147
self.make_master_and_checkout('master', 'checkout')
4148
# For unclear reasons some users have a bound_location without a final
4149
# '/', simulate that by forcing such a value
4150
self.assertEndsWith(self.bound_location, '/')
4151
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4153
def test_plus_sign(self):
4154
self.make_master_and_checkout('+master', 'checkout')
4155
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4157
def test_tilda(self):
4158
# Embed ~ in the middle of the path just to avoid any $HOME
4160
self.make_master_and_checkout('mas~ter', 'checkout')
4161
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4164
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4166
def test_no_context(self):
4167
class OutOfCoffee(errors.BzrError):
4168
"""A dummy exception for testing."""
4170
def __init__(self, urgency):
4171
self.urgency = urgency
4172
remote.no_context_error_translators.register("OutOfCoffee",
4173
lambda err: OutOfCoffee(err.error_args[0]))
4174
transport = MemoryTransport()
4175
client = FakeClient(transport.base)
4176
client.add_expected_call(
4177
'Branch.get_stacked_on_url', ('quack/',),
4178
'error', ('NotStacked',))
4179
client.add_expected_call(
4180
'Branch.last_revision_info',
4182
'error', ('OutOfCoffee', 'low'))
4183
transport.mkdir('quack')
4184
transport = transport.clone('quack')
4185
branch = self.make_remote_branch(transport, client)
4186
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4187
self.assertFinished(client)
4189
def test_with_context(self):
4190
class OutOfTea(errors.BzrError):
4191
def __init__(self, branch, urgency):
4192
self.branch = branch
4193
self.urgency = urgency
4194
remote.error_translators.register("OutOfTea",
4195
lambda err, find, path: OutOfTea(err.error_args[0],
4197
transport = MemoryTransport()
4198
client = FakeClient(transport.base)
4199
client.add_expected_call(
4200
'Branch.get_stacked_on_url', ('quack/',),
4201
'error', ('NotStacked',))
4202
client.add_expected_call(
4203
'Branch.last_revision_info',
4205
'error', ('OutOfTea', 'low'))
4206
transport.mkdir('quack')
4207
transport = transport.clone('quack')
4208
branch = self.make_remote_branch(transport, client)
4209
self.assertRaises(OutOfTea, branch.last_revision_info)
4210
self.assertFinished(client)
4213
class TestRepositoryPack(TestRemoteRepository):
4215
def test_pack(self):
4216
transport_path = 'quack'
4217
repo, client = self.setup_fake_client_and_repository(transport_path)
4218
client.add_expected_call(
4219
'Repository.lock_write', ('quack/', ''),
4220
'success', ('ok', 'token'))
4221
client.add_expected_call(
4222
'Repository.pack', ('quack/', 'token', 'False'),
4223
'success', ('ok',), )
4224
client.add_expected_call(
4225
'Repository.unlock', ('quack/', 'token'),
4226
'success', ('ok', ))
4229
def test_pack_with_hint(self):
4230
transport_path = 'quack'
4231
repo, client = self.setup_fake_client_and_repository(transport_path)
4232
client.add_expected_call(
4233
'Repository.lock_write', ('quack/', ''),
4234
'success', ('ok', 'token'))
4235
client.add_expected_call(
4236
'Repository.pack', ('quack/', 'token', 'False'),
4237
'success', ('ok',), )
4238
client.add_expected_call(
4239
'Repository.unlock', ('quack/', 'token', 'False'),
4240
'success', ('ok', ))
4241
repo.pack(['hinta', 'hintb'])
4244
class TestRepositoryIterInventories(TestRemoteRepository):
4245
"""Test Repository.iter_inventories."""
4247
def _serialize_inv_delta(self, old_name, new_name, delta):
4248
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4249
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4251
def test_single_empty(self):
4252
transport_path = 'quack'
4253
repo, client = self.setup_fake_client_and_repository(transport_path)
4254
fmt = bzrdir.format_registry.get('2a')().repository_format
4256
stream = [('inventory-deltas', [
4257
versionedfile.FulltextContentFactory('somerevid', None, None,
4258
self._serialize_inv_delta('null:', 'somerevid', []))])]
4259
client.add_expected_call(
4260
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4261
'success', ('ok', ),
4262
_stream_to_byte_stream(stream, fmt))
4263
ret = list(repo.iter_inventories(["somerevid"]))
4264
self.assertLength(1, ret)
4266
self.assertEquals("somerevid", inv.revision_id)
4268
def test_empty(self):
4269
transport_path = 'quack'
4270
repo, client = self.setup_fake_client_and_repository(transport_path)
4271
ret = list(repo.iter_inventories([]))
4272
self.assertEquals(ret, [])
4274
def test_missing(self):
4275
transport_path = 'quack'
4276
repo, client = self.setup_fake_client_and_repository(transport_path)
4277
client.add_expected_call(
4278
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4279
'success', ('ok', ), iter([]))
4280
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(