1
# Copyright (C) 2006-2011 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(13, 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
branch = self.make_branch_with_tags()
1437
c = branch.get_config_stack()
1438
c.set('branch.fetch_tags', True)
1439
self.addCleanup(branch.lock_read().unlock)
1440
# Disable the heads_to_fetch verb
1441
verb = 'Branch.heads_to_fetch'
1442
self.disable_verb(verb)
1443
self.reset_smart_call_log()
1444
result = branch.heads_to_fetch()
1445
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1447
['Branch.last_revision_info', 'Branch.get_config_file',
1448
'Branch.get_tags_bytes'],
1449
[call.call.method for call in self.hpss_calls])
1451
def test_backwards_compatible_no_tags(self):
1452
branch = self.make_branch_with_tags()
1453
c = branch.get_config_stack()
1454
c.set('branch.fetch_tags', False)
1455
self.addCleanup(branch.lock_read().unlock)
1456
# Disable the heads_to_fetch verb
1457
verb = 'Branch.heads_to_fetch'
1458
self.disable_verb(verb)
1459
self.reset_smart_call_log()
1460
result = branch.heads_to_fetch()
1461
self.assertEqual((set(['tip']), set()), result)
1463
['Branch.last_revision_info', 'Branch.get_config_file'],
1464
[call.call.method for call in self.hpss_calls])
1467
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1469
def test_empty_branch(self):
1470
# in an empty branch we decode the response properly
1471
transport = MemoryTransport()
1472
client = FakeClient(transport.base)
1473
client.add_expected_call(
1474
'Branch.get_stacked_on_url', ('quack/',),
1475
'error', ('NotStacked',))
1476
client.add_expected_call(
1477
'Branch.last_revision_info', ('quack/',),
1478
'success', ('ok', '0', 'null:'))
1479
transport.mkdir('quack')
1480
transport = transport.clone('quack')
1481
branch = self.make_remote_branch(transport, client)
1482
result = branch.last_revision_info()
1483
self.assertFinished(client)
1484
self.assertEqual((0, NULL_REVISION), result)
1486
def test_non_empty_branch(self):
1487
# in a non-empty branch we also decode the response properly
1488
revid = u'\xc8'.encode('utf8')
1489
transport = MemoryTransport()
1490
client = FakeClient(transport.base)
1491
client.add_expected_call(
1492
'Branch.get_stacked_on_url', ('kwaak/',),
1493
'error', ('NotStacked',))
1494
client.add_expected_call(
1495
'Branch.last_revision_info', ('kwaak/',),
1496
'success', ('ok', '2', revid))
1497
transport.mkdir('kwaak')
1498
transport = transport.clone('kwaak')
1499
branch = self.make_remote_branch(transport, client)
1500
result = branch.last_revision_info()
1501
self.assertEqual((2, revid), result)
1504
class TestBranch_get_stacked_on_url(TestRemote):
1505
"""Test Branch._get_stacked_on_url rpc"""
1507
def test_get_stacked_on_invalid_url(self):
1508
# test that asking for a stacked on url the server can't access works.
1509
# This isn't perfect, but then as we're in the same process there
1510
# really isn't anything we can do to be 100% sure that the server
1511
# doesn't just open in - this test probably needs to be rewritten using
1512
# a spawn()ed server.
1513
stacked_branch = self.make_branch('stacked', format='1.9')
1514
memory_branch = self.make_branch('base', format='1.9')
1515
vfs_url = self.get_vfs_only_url('base')
1516
stacked_branch.set_stacked_on_url(vfs_url)
1517
transport = stacked_branch.bzrdir.root_transport
1518
client = FakeClient(transport.base)
1519
client.add_expected_call(
1520
'Branch.get_stacked_on_url', ('stacked/',),
1521
'success', ('ok', vfs_url))
1522
# XXX: Multiple calls are bad, this second call documents what is
1524
client.add_expected_call(
1525
'Branch.get_stacked_on_url', ('stacked/',),
1526
'success', ('ok', vfs_url))
1527
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1529
repo_fmt = remote.RemoteRepositoryFormat()
1530
repo_fmt._custom_format = stacked_branch.repository._format
1531
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1533
result = branch.get_stacked_on_url()
1534
self.assertEqual(vfs_url, result)
1536
def test_backwards_compatible(self):
1537
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1538
base_branch = self.make_branch('base', format='1.6')
1539
stacked_branch = self.make_branch('stacked', format='1.6')
1540
stacked_branch.set_stacked_on_url('../base')
1541
client = FakeClient(self.get_url())
1542
branch_network_name = self.get_branch_format().network_name()
1543
client.add_expected_call(
1544
'BzrDir.open_branchV3', ('stacked/',),
1545
'success', ('branch', branch_network_name))
1546
client.add_expected_call(
1547
'BzrDir.find_repositoryV3', ('stacked/',),
1548
'success', ('ok', '', 'no', 'no', 'yes',
1549
stacked_branch.repository._format.network_name()))
1550
# called twice, once from constructor and then again by us
1551
client.add_expected_call(
1552
'Branch.get_stacked_on_url', ('stacked/',),
1553
'unknown', ('Branch.get_stacked_on_url',))
1554
client.add_expected_call(
1555
'Branch.get_stacked_on_url', ('stacked/',),
1556
'unknown', ('Branch.get_stacked_on_url',))
1557
# this will also do vfs access, but that goes direct to the transport
1558
# and isn't seen by the FakeClient.
1559
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1560
RemoteBzrDirFormat(), _client=client)
1561
branch = bzrdir.open_branch()
1562
result = branch.get_stacked_on_url()
1563
self.assertEqual('../base', result)
1564
self.assertFinished(client)
1565
# it's in the fallback list both for the RemoteRepository and its vfs
1567
self.assertEqual(1, len(branch.repository._fallback_repositories))
1569
len(branch.repository._real_repository._fallback_repositories))
1571
def test_get_stacked_on_real_branch(self):
1572
base_branch = self.make_branch('base')
1573
stacked_branch = self.make_branch('stacked')
1574
stacked_branch.set_stacked_on_url('../base')
1575
reference_format = self.get_repo_format()
1576
network_name = reference_format.network_name()
1577
client = FakeClient(self.get_url())
1578
branch_network_name = self.get_branch_format().network_name()
1579
client.add_expected_call(
1580
'BzrDir.open_branchV3', ('stacked/',),
1581
'success', ('branch', branch_network_name))
1582
client.add_expected_call(
1583
'BzrDir.find_repositoryV3', ('stacked/',),
1584
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1585
# called twice, once from constructor and then again by us
1586
client.add_expected_call(
1587
'Branch.get_stacked_on_url', ('stacked/',),
1588
'success', ('ok', '../base'))
1589
client.add_expected_call(
1590
'Branch.get_stacked_on_url', ('stacked/',),
1591
'success', ('ok', '../base'))
1592
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1593
RemoteBzrDirFormat(), _client=client)
1594
branch = bzrdir.open_branch()
1595
result = branch.get_stacked_on_url()
1596
self.assertEqual('../base', result)
1597
self.assertFinished(client)
1598
# it's in the fallback list both for the RemoteRepository.
1599
self.assertEqual(1, len(branch.repository._fallback_repositories))
1600
# And we haven't had to construct a real repository.
1601
self.assertEqual(None, branch.repository._real_repository)
1604
class TestBranchSetLastRevision(RemoteBranchTestCase):
1606
def test_set_empty(self):
1607
# _set_last_revision_info('null:') is translated to calling
1608
# Branch.set_last_revision(path, '') on the wire.
1609
transport = MemoryTransport()
1610
transport.mkdir('branch')
1611
transport = transport.clone('branch')
1613
client = FakeClient(transport.base)
1614
client.add_expected_call(
1615
'Branch.get_stacked_on_url', ('branch/',),
1616
'error', ('NotStacked',))
1617
client.add_expected_call(
1618
'Branch.lock_write', ('branch/', '', ''),
1619
'success', ('ok', 'branch token', 'repo token'))
1620
client.add_expected_call(
1621
'Branch.last_revision_info',
1623
'success', ('ok', '0', 'null:'))
1624
client.add_expected_call(
1625
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1627
client.add_expected_call(
1628
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1630
branch = self.make_remote_branch(transport, client)
1632
result = branch._set_last_revision(NULL_REVISION)
1634
self.assertEqual(None, result)
1635
self.assertFinished(client)
1637
def test_set_nonempty(self):
1638
# set_last_revision_info(N, rev-idN) is translated to calling
1639
# Branch.set_last_revision(path, rev-idN) on the wire.
1640
transport = MemoryTransport()
1641
transport.mkdir('branch')
1642
transport = transport.clone('branch')
1644
client = FakeClient(transport.base)
1645
client.add_expected_call(
1646
'Branch.get_stacked_on_url', ('branch/',),
1647
'error', ('NotStacked',))
1648
client.add_expected_call(
1649
'Branch.lock_write', ('branch/', '', ''),
1650
'success', ('ok', 'branch token', 'repo token'))
1651
client.add_expected_call(
1652
'Branch.last_revision_info',
1654
'success', ('ok', '0', 'null:'))
1656
encoded_body = bz2.compress('\n'.join(lines))
1657
client.add_success_response_with_body(encoded_body, 'ok')
1658
client.add_expected_call(
1659
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1661
client.add_expected_call(
1662
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1664
branch = self.make_remote_branch(transport, client)
1665
# Lock the branch, reset the record of remote calls.
1667
result = branch._set_last_revision('rev-id2')
1669
self.assertEqual(None, result)
1670
self.assertFinished(client)
1672
def test_no_such_revision(self):
1673
transport = MemoryTransport()
1674
transport.mkdir('branch')
1675
transport = transport.clone('branch')
1676
# A response of 'NoSuchRevision' is translated into an exception.
1677
client = FakeClient(transport.base)
1678
client.add_expected_call(
1679
'Branch.get_stacked_on_url', ('branch/',),
1680
'error', ('NotStacked',))
1681
client.add_expected_call(
1682
'Branch.lock_write', ('branch/', '', ''),
1683
'success', ('ok', 'branch token', 'repo token'))
1684
client.add_expected_call(
1685
'Branch.last_revision_info',
1687
'success', ('ok', '0', 'null:'))
1688
# get_graph calls to construct the revision history, for the set_rh
1691
encoded_body = bz2.compress('\n'.join(lines))
1692
client.add_success_response_with_body(encoded_body, 'ok')
1693
client.add_expected_call(
1694
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1695
'error', ('NoSuchRevision', 'rev-id'))
1696
client.add_expected_call(
1697
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1700
branch = self.make_remote_branch(transport, client)
1703
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1705
self.assertFinished(client)
1707
def test_tip_change_rejected(self):
1708
"""TipChangeRejected responses cause a TipChangeRejected exception to
1711
transport = MemoryTransport()
1712
transport.mkdir('branch')
1713
transport = transport.clone('branch')
1714
client = FakeClient(transport.base)
1715
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1716
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1717
client.add_expected_call(
1718
'Branch.get_stacked_on_url', ('branch/',),
1719
'error', ('NotStacked',))
1720
client.add_expected_call(
1721
'Branch.lock_write', ('branch/', '', ''),
1722
'success', ('ok', 'branch token', 'repo token'))
1723
client.add_expected_call(
1724
'Branch.last_revision_info',
1726
'success', ('ok', '0', 'null:'))
1728
encoded_body = bz2.compress('\n'.join(lines))
1729
client.add_success_response_with_body(encoded_body, 'ok')
1730
client.add_expected_call(
1731
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1732
'error', ('TipChangeRejected', rejection_msg_utf8))
1733
client.add_expected_call(
1734
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1736
branch = self.make_remote_branch(transport, client)
1738
# The 'TipChangeRejected' error response triggered by calling
1739
# set_last_revision_info causes a TipChangeRejected exception.
1740
err = self.assertRaises(
1741
errors.TipChangeRejected,
1742
branch._set_last_revision, 'rev-id')
1743
# The UTF-8 message from the response has been decoded into a unicode
1745
self.assertIsInstance(err.msg, unicode)
1746
self.assertEqual(rejection_msg_unicode, err.msg)
1748
self.assertFinished(client)
1751
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1753
def test_set_last_revision_info(self):
1754
# set_last_revision_info(num, 'rev-id') is translated to calling
1755
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1756
transport = MemoryTransport()
1757
transport.mkdir('branch')
1758
transport = transport.clone('branch')
1759
client = FakeClient(transport.base)
1760
# get_stacked_on_url
1761
client.add_error_response('NotStacked')
1763
client.add_success_response('ok', 'branch token', 'repo token')
1764
# query the current revision
1765
client.add_success_response('ok', '0', 'null:')
1767
client.add_success_response('ok')
1769
client.add_success_response('ok')
1771
branch = self.make_remote_branch(transport, client)
1772
# Lock the branch, reset the record of remote calls.
1775
result = branch.set_last_revision_info(1234, 'a-revision-id')
1777
[('call', 'Branch.last_revision_info', ('branch/',)),
1778
('call', 'Branch.set_last_revision_info',
1779
('branch/', 'branch token', 'repo token',
1780
'1234', 'a-revision-id'))],
1782
self.assertEqual(None, result)
1784
def test_no_such_revision(self):
1785
# A response of 'NoSuchRevision' is translated into an exception.
1786
transport = MemoryTransport()
1787
transport.mkdir('branch')
1788
transport = transport.clone('branch')
1789
client = FakeClient(transport.base)
1790
# get_stacked_on_url
1791
client.add_error_response('NotStacked')
1793
client.add_success_response('ok', 'branch token', 'repo token')
1795
client.add_error_response('NoSuchRevision', 'revid')
1797
client.add_success_response('ok')
1799
branch = self.make_remote_branch(transport, client)
1800
# Lock the branch, reset the record of remote calls.
1805
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1808
def test_backwards_compatibility(self):
1809
"""If the server does not support the Branch.set_last_revision_info
1810
verb (which is new in 1.4), then the client falls back to VFS methods.
1812
# This test is a little messy. Unlike most tests in this file, it
1813
# doesn't purely test what a Remote* object sends over the wire, and
1814
# how it reacts to responses from the wire. It instead relies partly
1815
# on asserting that the RemoteBranch will call
1816
# self._real_branch.set_last_revision_info(...).
1818
# First, set up our RemoteBranch with a FakeClient that raises
1819
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1820
transport = MemoryTransport()
1821
transport.mkdir('branch')
1822
transport = transport.clone('branch')
1823
client = FakeClient(transport.base)
1824
client.add_expected_call(
1825
'Branch.get_stacked_on_url', ('branch/',),
1826
'error', ('NotStacked',))
1827
client.add_expected_call(
1828
'Branch.last_revision_info',
1830
'success', ('ok', '0', 'null:'))
1831
client.add_expected_call(
1832
'Branch.set_last_revision_info',
1833
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1834
'unknown', 'Branch.set_last_revision_info')
1836
branch = self.make_remote_branch(transport, client)
1837
class StubRealBranch(object):
1840
def set_last_revision_info(self, revno, revision_id):
1842
('set_last_revision_info', revno, revision_id))
1843
def _clear_cached_state(self):
1845
real_branch = StubRealBranch()
1846
branch._real_branch = real_branch
1847
self.lock_remote_branch(branch)
1849
# Call set_last_revision_info, and verify it behaved as expected.
1850
result = branch.set_last_revision_info(1234, 'a-revision-id')
1852
[('set_last_revision_info', 1234, 'a-revision-id')],
1854
self.assertFinished(client)
1856
def test_unexpected_error(self):
1857
# If the server sends an error the client doesn't understand, it gets
1858
# turned into an UnknownErrorFromSmartServer, which is presented as a
1859
# non-internal error to the user.
1860
transport = MemoryTransport()
1861
transport.mkdir('branch')
1862
transport = transport.clone('branch')
1863
client = FakeClient(transport.base)
1864
# get_stacked_on_url
1865
client.add_error_response('NotStacked')
1867
client.add_success_response('ok', 'branch token', 'repo token')
1869
client.add_error_response('UnexpectedError')
1871
client.add_success_response('ok')
1873
branch = self.make_remote_branch(transport, client)
1874
# Lock the branch, reset the record of remote calls.
1878
err = self.assertRaises(
1879
errors.UnknownErrorFromSmartServer,
1880
branch.set_last_revision_info, 123, 'revid')
1881
self.assertEqual(('UnexpectedError',), err.error_tuple)
1884
def test_tip_change_rejected(self):
1885
"""TipChangeRejected responses cause a TipChangeRejected exception to
1888
transport = MemoryTransport()
1889
transport.mkdir('branch')
1890
transport = transport.clone('branch')
1891
client = FakeClient(transport.base)
1892
# get_stacked_on_url
1893
client.add_error_response('NotStacked')
1895
client.add_success_response('ok', 'branch token', 'repo token')
1897
client.add_error_response('TipChangeRejected', 'rejection message')
1899
client.add_success_response('ok')
1901
branch = self.make_remote_branch(transport, client)
1902
# Lock the branch, reset the record of remote calls.
1904
self.addCleanup(branch.unlock)
1907
# The 'TipChangeRejected' error response triggered by calling
1908
# set_last_revision_info causes a TipChangeRejected exception.
1909
err = self.assertRaises(
1910
errors.TipChangeRejected,
1911
branch.set_last_revision_info, 123, 'revid')
1912
self.assertEqual('rejection message', err.msg)
1915
class TestBranchGetSetConfig(RemoteBranchTestCase):
1917
def test_get_branch_conf(self):
1918
# in an empty branch we decode the response properly
1919
client = FakeClient()
1920
client.add_expected_call(
1921
'Branch.get_stacked_on_url', ('memory:///',),
1922
'error', ('NotStacked',),)
1923
client.add_success_response_with_body('# config file body', 'ok')
1924
transport = MemoryTransport()
1925
branch = self.make_remote_branch(transport, client)
1926
config = branch.get_config()
1927
config.has_explicit_nickname()
1929
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1930
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1933
def test_get_multi_line_branch_conf(self):
1934
# Make sure that multiple-line branch.conf files are supported
1936
# https://bugs.launchpad.net/bzr/+bug/354075
1937
client = FakeClient()
1938
client.add_expected_call(
1939
'Branch.get_stacked_on_url', ('memory:///',),
1940
'error', ('NotStacked',),)
1941
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1942
transport = MemoryTransport()
1943
branch = self.make_remote_branch(transport, client)
1944
config = branch.get_config()
1945
self.assertEqual(u'2', config.get_user_option('b'))
1947
def test_set_option(self):
1948
client = FakeClient()
1949
client.add_expected_call(
1950
'Branch.get_stacked_on_url', ('memory:///',),
1951
'error', ('NotStacked',),)
1952
client.add_expected_call(
1953
'Branch.lock_write', ('memory:///', '', ''),
1954
'success', ('ok', 'branch token', 'repo token'))
1955
client.add_expected_call(
1956
'Branch.set_config_option', ('memory:///', 'branch token',
1957
'repo token', 'foo', 'bar', ''),
1959
client.add_expected_call(
1960
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1962
transport = MemoryTransport()
1963
branch = self.make_remote_branch(transport, client)
1965
config = branch._get_config()
1966
config.set_option('foo', 'bar')
1968
self.assertFinished(client)
1970
def test_set_option_with_dict(self):
1971
client = FakeClient()
1972
client.add_expected_call(
1973
'Branch.get_stacked_on_url', ('memory:///',),
1974
'error', ('NotStacked',),)
1975
client.add_expected_call(
1976
'Branch.lock_write', ('memory:///', '', ''),
1977
'success', ('ok', 'branch token', 'repo token'))
1978
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1979
client.add_expected_call(
1980
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1981
'repo token', encoded_dict_value, 'foo', ''),
1983
client.add_expected_call(
1984
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1986
transport = MemoryTransport()
1987
branch = self.make_remote_branch(transport, client)
1989
config = branch._get_config()
1991
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1994
self.assertFinished(client)
1996
def test_backwards_compat_set_option(self):
1997
self.setup_smart_server_with_call_log()
1998
branch = self.make_branch('.')
1999
verb = 'Branch.set_config_option'
2000
self.disable_verb(verb)
2002
self.addCleanup(branch.unlock)
2003
self.reset_smart_call_log()
2004
branch._get_config().set_option('value', 'name')
2005
self.assertLength(11, self.hpss_calls)
2006
self.assertEqual('value', branch._get_config().get_option('name'))
2008
def test_backwards_compat_set_option_with_dict(self):
2009
self.setup_smart_server_with_call_log()
2010
branch = self.make_branch('.')
2011
verb = 'Branch.set_config_option_dict'
2012
self.disable_verb(verb)
2014
self.addCleanup(branch.unlock)
2015
self.reset_smart_call_log()
2016
config = branch._get_config()
2017
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2018
config.set_option(value_dict, 'name')
2019
self.assertLength(11, self.hpss_calls)
2020
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2023
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2025
def test_get_branch_conf(self):
2026
# in an empty branch we decode the response properly
2027
client = FakeClient()
2028
client.add_expected_call(
2029
'Branch.get_stacked_on_url', ('memory:///',),
2030
'error', ('NotStacked',),)
2031
client.add_success_response_with_body('# config file body', 'ok')
2032
transport = MemoryTransport()
2033
branch = self.make_remote_branch(transport, client)
2034
config = branch.get_config_stack()
2036
config.get("log_format")
2038
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2039
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2042
def test_set_branch_conf(self):
2043
client = FakeClient()
2044
client.add_expected_call(
2045
'Branch.get_stacked_on_url', ('memory:///',),
2046
'error', ('NotStacked',),)
2047
client.add_expected_call(
2048
'Branch.lock_write', ('memory:///', '', ''),
2049
'success', ('ok', 'branch token', 'repo token'))
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_with_body_bytes_expecting_body', 'Branch.put_config_file',
2072
('memory:///', 'branch token', 'repo token'),
2073
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2074
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2078
class TestBranchLockWrite(RemoteBranchTestCase):
2080
def test_lock_write_unlockable(self):
2081
transport = MemoryTransport()
2082
client = FakeClient(transport.base)
2083
client.add_expected_call(
2084
'Branch.get_stacked_on_url', ('quack/',),
2085
'error', ('NotStacked',),)
2086
client.add_expected_call(
2087
'Branch.lock_write', ('quack/', '', ''),
2088
'error', ('UnlockableTransport',))
2089
transport.mkdir('quack')
2090
transport = transport.clone('quack')
2091
branch = self.make_remote_branch(transport, client)
2092
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2093
self.assertFinished(client)
2096
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2098
def test_simple(self):
2099
transport = MemoryTransport()
2100
client = FakeClient(transport.base)
2101
client.add_expected_call(
2102
'Branch.get_stacked_on_url', ('quack/',),
2103
'error', ('NotStacked',),)
2104
client.add_expected_call(
2105
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2106
'success', ('ok', '0',),)
2107
client.add_expected_call(
2108
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2109
'error', ('NoSuchRevision', 'unknown',),)
2110
transport.mkdir('quack')
2111
transport = transport.clone('quack')
2112
branch = self.make_remote_branch(transport, client)
2113
self.assertEquals(0, branch.revision_id_to_revno('null:'))
2114
self.assertRaises(errors.NoSuchRevision,
2115
branch.revision_id_to_revno, 'unknown')
2116
self.assertFinished(client)
2118
def test_dotted(self):
2119
transport = MemoryTransport()
2120
client = FakeClient(transport.base)
2121
client.add_expected_call(
2122
'Branch.get_stacked_on_url', ('quack/',),
2123
'error', ('NotStacked',),)
2124
client.add_expected_call(
2125
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2126
'success', ('ok', '0',),)
2127
client.add_expected_call(
2128
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2129
'error', ('NoSuchRevision', 'unknown',),)
2130
transport.mkdir('quack')
2131
transport = transport.clone('quack')
2132
branch = self.make_remote_branch(transport, client)
2133
self.assertEquals((0, ), branch.revision_id_to_dotted_revno('null:'))
2134
self.assertRaises(errors.NoSuchRevision,
2135
branch.revision_id_to_dotted_revno, 'unknown')
2136
self.assertFinished(client)
2138
def test_dotted_no_smart_verb(self):
2139
self.setup_smart_server_with_call_log()
2140
branch = self.make_branch('.')
2141
self.disable_verb('Branch.revision_id_to_revno')
2142
self.reset_smart_call_log()
2143
self.assertEquals((0, ),
2144
branch.revision_id_to_dotted_revno('null:'))
2145
self.assertLength(8, self.hpss_calls)
2148
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2150
def test__get_config(self):
2151
client = FakeClient()
2152
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2153
transport = MemoryTransport()
2154
bzrdir = self.make_remote_bzrdir(transport, client)
2155
config = bzrdir.get_config()
2156
self.assertEqual('/', config.get_default_stack_on())
2158
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2161
def test_set_option_uses_vfs(self):
2162
self.setup_smart_server_with_call_log()
2163
bzrdir = self.make_bzrdir('.')
2164
self.reset_smart_call_log()
2165
config = bzrdir.get_config()
2166
config.set_default_stack_on('/')
2167
self.assertLength(4, self.hpss_calls)
2169
def test_backwards_compat_get_option(self):
2170
self.setup_smart_server_with_call_log()
2171
bzrdir = self.make_bzrdir('.')
2172
verb = 'BzrDir.get_config_file'
2173
self.disable_verb(verb)
2174
self.reset_smart_call_log()
2175
self.assertEqual(None,
2176
bzrdir._get_config().get_option('default_stack_on'))
2177
self.assertLength(4, self.hpss_calls)
2180
class TestTransportIsReadonly(tests.TestCase):
2182
def test_true(self):
2183
client = FakeClient()
2184
client.add_success_response('yes')
2185
transport = RemoteTransport('bzr://example.com/', medium=False,
2187
self.assertEqual(True, transport.is_readonly())
2189
[('call', 'Transport.is_readonly', ())],
2192
def test_false(self):
2193
client = FakeClient()
2194
client.add_success_response('no')
2195
transport = RemoteTransport('bzr://example.com/', medium=False,
2197
self.assertEqual(False, transport.is_readonly())
2199
[('call', 'Transport.is_readonly', ())],
2202
def test_error_from_old_server(self):
2203
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2205
Clients should treat it as a "no" response, because is_readonly is only
2206
advisory anyway (a transport could be read-write, but then the
2207
underlying filesystem could be readonly anyway).
2209
client = FakeClient()
2210
client.add_unknown_method_response('Transport.is_readonly')
2211
transport = RemoteTransport('bzr://example.com/', medium=False,
2213
self.assertEqual(False, transport.is_readonly())
2215
[('call', 'Transport.is_readonly', ())],
2219
class TestTransportMkdir(tests.TestCase):
2221
def test_permissiondenied(self):
2222
client = FakeClient()
2223
client.add_error_response('PermissionDenied', 'remote path', 'extra')
2224
transport = RemoteTransport('bzr://example.com/', medium=False,
2226
exc = self.assertRaises(
2227
errors.PermissionDenied, transport.mkdir, 'client path')
2228
expected_error = errors.PermissionDenied('/client path', 'extra')
2229
self.assertEqual(expected_error, exc)
2232
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2234
def test_defaults_to_none(self):
2235
t = RemoteSSHTransport('bzr+ssh://example.com')
2236
self.assertIs(None, t._get_credentials()[0])
2238
def test_uses_authentication_config(self):
2239
conf = config.AuthenticationConfig()
2240
conf._get_config().update(
2241
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2244
t = RemoteSSHTransport('bzr+ssh://example.com')
2245
self.assertEqual('bar', t._get_credentials()[0])
2248
class TestRemoteRepository(TestRemote):
2249
"""Base for testing RemoteRepository protocol usage.
2251
These tests contain frozen requests and responses. We want any changes to
2252
what is sent or expected to be require a thoughtful update to these tests
2253
because they might break compatibility with different-versioned servers.
2256
def setup_fake_client_and_repository(self, transport_path):
2257
"""Create the fake client and repository for testing with.
2259
There's no real server here; we just have canned responses sent
2262
:param transport_path: Path below the root of the MemoryTransport
2263
where the repository will be created.
2265
transport = MemoryTransport()
2266
transport.mkdir(transport_path)
2267
client = FakeClient(transport.base)
2268
transport = transport.clone(transport_path)
2269
# we do not want bzrdir to make any remote calls
2270
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2272
repo = RemoteRepository(bzrdir, None, _client=client)
2276
def remoted_description(format):
2277
return 'Remote: ' + format.get_format_description()
2280
class TestBranchFormat(tests.TestCase):
2282
def test_get_format_description(self):
2283
remote_format = RemoteBranchFormat()
2284
real_format = branch.format_registry.get_default()
2285
remote_format._network_name = real_format.network_name()
2286
self.assertEqual(remoted_description(real_format),
2287
remote_format.get_format_description())
2290
class TestRepositoryFormat(TestRemoteRepository):
2292
def test_fast_delta(self):
2293
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2294
true_format = RemoteRepositoryFormat()
2295
true_format._network_name = true_name
2296
self.assertEqual(True, true_format.fast_deltas)
2297
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2298
false_format = RemoteRepositoryFormat()
2299
false_format._network_name = false_name
2300
self.assertEqual(False, false_format.fast_deltas)
2302
def test_get_format_description(self):
2303
remote_repo_format = RemoteRepositoryFormat()
2304
real_format = repository.format_registry.get_default()
2305
remote_repo_format._network_name = real_format.network_name()
2306
self.assertEqual(remoted_description(real_format),
2307
remote_repo_format.get_format_description())
2310
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2312
def test_empty(self):
2313
transport_path = 'quack'
2314
repo, client = self.setup_fake_client_and_repository(transport_path)
2315
client.add_success_response_with_body('', 'ok')
2316
self.assertEquals([], repo.all_revision_ids())
2318
[('call_expecting_body', 'Repository.all_revision_ids',
2322
def test_with_some_content(self):
2323
transport_path = 'quack'
2324
repo, client = self.setup_fake_client_and_repository(transport_path)
2325
client.add_success_response_with_body(
2326
'rev1\nrev2\nanotherrev\n', 'ok')
2327
self.assertEquals(["rev1", "rev2", "anotherrev"],
2328
repo.all_revision_ids())
2330
[('call_expecting_body', 'Repository.all_revision_ids',
2335
class TestRepositoryGatherStats(TestRemoteRepository):
2337
def test_revid_none(self):
2338
# ('ok',), body with revisions and size
2339
transport_path = 'quack'
2340
repo, client = self.setup_fake_client_and_repository(transport_path)
2341
client.add_success_response_with_body(
2342
'revisions: 2\nsize: 18\n', 'ok')
2343
result = repo.gather_stats(None)
2345
[('call_expecting_body', 'Repository.gather_stats',
2346
('quack/','','no'))],
2348
self.assertEqual({'revisions': 2, 'size': 18}, result)
2350
def test_revid_no_committers(self):
2351
# ('ok',), body without committers
2352
body = ('firstrev: 123456.300 3600\n'
2353
'latestrev: 654231.400 0\n'
2356
transport_path = 'quick'
2357
revid = u'\xc8'.encode('utf8')
2358
repo, client = self.setup_fake_client_and_repository(transport_path)
2359
client.add_success_response_with_body(body, 'ok')
2360
result = repo.gather_stats(revid)
2362
[('call_expecting_body', 'Repository.gather_stats',
2363
('quick/', revid, 'no'))],
2365
self.assertEqual({'revisions': 2, 'size': 18,
2366
'firstrev': (123456.300, 3600),
2367
'latestrev': (654231.400, 0),},
2370
def test_revid_with_committers(self):
2371
# ('ok',), body with committers
2372
body = ('committers: 128\n'
2373
'firstrev: 123456.300 3600\n'
2374
'latestrev: 654231.400 0\n'
2377
transport_path = 'buick'
2378
revid = u'\xc8'.encode('utf8')
2379
repo, client = self.setup_fake_client_and_repository(transport_path)
2380
client.add_success_response_with_body(body, 'ok')
2381
result = repo.gather_stats(revid, True)
2383
[('call_expecting_body', 'Repository.gather_stats',
2384
('buick/', revid, 'yes'))],
2386
self.assertEqual({'revisions': 2, 'size': 18,
2388
'firstrev': (123456.300, 3600),
2389
'latestrev': (654231.400, 0),},
2393
class TestRepositoryBreakLock(TestRemoteRepository):
2395
def test_break_lock(self):
2396
transport_path = 'quack'
2397
repo, client = self.setup_fake_client_and_repository(transport_path)
2398
client.add_success_response('ok')
2401
[('call', 'Repository.break_lock', ('quack/',))],
2405
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2407
def test_get_serializer_format(self):
2408
transport_path = 'hill'
2409
repo, client = self.setup_fake_client_and_repository(transport_path)
2410
client.add_success_response('ok', '7')
2411
self.assertEquals('7', repo.get_serializer_format())
2413
[('call', 'VersionedFileRepository.get_serializer_format',
2418
class TestRepositoryReconcile(TestRemoteRepository):
2420
def test_reconcile(self):
2421
transport_path = 'hill'
2422
repo, client = self.setup_fake_client_and_repository(transport_path)
2423
body = ("garbage_inventories: 2\n"
2424
"inconsistent_parents: 3\n")
2425
client.add_expected_call(
2426
'Repository.lock_write', ('hill/', ''),
2427
'success', ('ok', 'a token'))
2428
client.add_success_response_with_body(body, 'ok')
2429
reconciler = repo.reconcile()
2431
[('call', 'Repository.lock_write', ('hill/', '')),
2432
('call_expecting_body', 'Repository.reconcile',
2433
('hill/', 'a token'))],
2435
self.assertEquals(2, reconciler.garbage_inventories)
2436
self.assertEquals(3, reconciler.inconsistent_parents)
2439
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2441
def test_text(self):
2442
# ('ok',), body with signature text
2443
transport_path = 'quack'
2444
repo, client = self.setup_fake_client_and_repository(transport_path)
2445
client.add_success_response_with_body(
2447
self.assertEquals("THETEXT", repo.get_signature_text("revid"))
2449
[('call_expecting_body', 'Repository.get_revision_signature_text',
2450
('quack/', 'revid'))],
2453
def test_no_signature(self):
2454
transport_path = 'quick'
2455
repo, client = self.setup_fake_client_and_repository(transport_path)
2456
client.add_error_response('nosuchrevision', 'unknown')
2457
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2460
[('call_expecting_body', 'Repository.get_revision_signature_text',
2461
('quick/', 'unknown'))],
2465
class TestRepositoryGetGraph(TestRemoteRepository):
2467
def test_get_graph(self):
2468
# get_graph returns a graph with a custom parents provider.
2469
transport_path = 'quack'
2470
repo, client = self.setup_fake_client_and_repository(transport_path)
2471
graph = repo.get_graph()
2472
self.assertNotEqual(graph._parents_provider, repo)
2475
class TestRepositoryAddSignatureText(TestRemoteRepository):
2477
def test_add_signature_text(self):
2478
transport_path = 'quack'
2479
repo, client = self.setup_fake_client_and_repository(transport_path)
2480
client.add_expected_call(
2481
'Repository.lock_write', ('quack/', ''),
2482
'success', ('ok', 'a token'))
2483
client.add_expected_call(
2484
'Repository.start_write_group', ('quack/', 'a token'),
2485
'success', ('ok', ('token1', )))
2486
client.add_expected_call(
2487
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2489
'success', ('ok', ), None)
2491
repo.start_write_group()
2493
repo.add_signature_text("rev1", "every bloody emperor"))
2495
('call_with_body_bytes_expecting_body',
2496
'Repository.add_signature_text',
2497
('quack/', 'a token', 'rev1', 'token1'),
2498
'every bloody emperor'),
2502
class TestRepositoryGetParentMap(TestRemoteRepository):
2504
def test_get_parent_map_caching(self):
2505
# get_parent_map returns from cache until unlock()
2506
# setup a reponse with two revisions
2507
r1 = u'\u0e33'.encode('utf8')
2508
r2 = u'\u0dab'.encode('utf8')
2509
lines = [' '.join([r2, r1]), r1]
2510
encoded_body = bz2.compress('\n'.join(lines))
2512
transport_path = 'quack'
2513
repo, client = self.setup_fake_client_and_repository(transport_path)
2514
client.add_success_response_with_body(encoded_body, 'ok')
2515
client.add_success_response_with_body(encoded_body, 'ok')
2517
graph = repo.get_graph()
2518
parents = graph.get_parent_map([r2])
2519
self.assertEqual({r2: (r1,)}, parents)
2520
# locking and unlocking deeper should not reset
2523
parents = graph.get_parent_map([r1])
2524
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2526
[('call_with_body_bytes_expecting_body',
2527
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2531
# now we call again, and it should use the second response.
2533
graph = repo.get_graph()
2534
parents = graph.get_parent_map([r1])
2535
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2537
[('call_with_body_bytes_expecting_body',
2538
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2540
('call_with_body_bytes_expecting_body',
2541
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2547
def test_get_parent_map_reconnects_if_unknown_method(self):
2548
transport_path = 'quack'
2549
rev_id = 'revision-id'
2550
repo, client = self.setup_fake_client_and_repository(transport_path)
2551
client.add_unknown_method_response('Repository.get_parent_map')
2552
client.add_success_response_with_body(rev_id, 'ok')
2553
self.assertFalse(client._medium._is_remote_before((1, 2)))
2554
parents = repo.get_parent_map([rev_id])
2556
[('call_with_body_bytes_expecting_body',
2557
'Repository.get_parent_map',
2558
('quack/', 'include-missing:', rev_id), '\n\n0'),
2559
('disconnect medium',),
2560
('call_expecting_body', 'Repository.get_revision_graph',
2563
# The medium is now marked as being connected to an older server
2564
self.assertTrue(client._medium._is_remote_before((1, 2)))
2565
self.assertEqual({rev_id: ('null:',)}, parents)
2567
def test_get_parent_map_fallback_parentless_node(self):
2568
"""get_parent_map falls back to get_revision_graph on old servers. The
2569
results from get_revision_graph are tweaked to match the get_parent_map
2572
Specifically, a {key: ()} result from get_revision_graph means "no
2573
parents" for that key, which in get_parent_map results should be
2574
represented as {key: ('null:',)}.
2576
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2578
rev_id = 'revision-id'
2579
transport_path = 'quack'
2580
repo, client = self.setup_fake_client_and_repository(transport_path)
2581
client.add_success_response_with_body(rev_id, 'ok')
2582
client._medium._remember_remote_is_before((1, 2))
2583
parents = repo.get_parent_map([rev_id])
2585
[('call_expecting_body', 'Repository.get_revision_graph',
2588
self.assertEqual({rev_id: ('null:',)}, parents)
2590
def test_get_parent_map_unexpected_response(self):
2591
repo, client = self.setup_fake_client_and_repository('path')
2592
client.add_success_response('something unexpected!')
2594
errors.UnexpectedSmartServerResponse,
2595
repo.get_parent_map, ['a-revision-id'])
2597
def test_get_parent_map_negative_caches_missing_keys(self):
2598
self.setup_smart_server_with_call_log()
2599
repo = self.make_repository('foo')
2600
self.assertIsInstance(repo, RemoteRepository)
2602
self.addCleanup(repo.unlock)
2603
self.reset_smart_call_log()
2604
graph = repo.get_graph()
2605
self.assertEqual({},
2606
graph.get_parent_map(['some-missing', 'other-missing']))
2607
self.assertLength(1, self.hpss_calls)
2608
# No call if we repeat this
2609
self.reset_smart_call_log()
2610
graph = repo.get_graph()
2611
self.assertEqual({},
2612
graph.get_parent_map(['some-missing', 'other-missing']))
2613
self.assertLength(0, self.hpss_calls)
2614
# Asking for more unknown keys makes a request.
2615
self.reset_smart_call_log()
2616
graph = repo.get_graph()
2617
self.assertEqual({},
2618
graph.get_parent_map(['some-missing', 'other-missing',
2620
self.assertLength(1, self.hpss_calls)
2622
def disableExtraResults(self):
2623
self.overrideAttr(SmartServerRepositoryGetParentMap,
2624
'no_extra_results', True)
2626
def test_null_cached_missing_and_stop_key(self):
2627
self.setup_smart_server_with_call_log()
2628
# Make a branch with a single revision.
2629
builder = self.make_branch_builder('foo')
2630
builder.start_series()
2631
builder.build_snapshot('first', None, [
2632
('add', ('', 'root-id', 'directory', ''))])
2633
builder.finish_series()
2634
branch = builder.get_branch()
2635
repo = branch.repository
2636
self.assertIsInstance(repo, RemoteRepository)
2637
# Stop the server from sending extra results.
2638
self.disableExtraResults()
2640
self.addCleanup(repo.unlock)
2641
self.reset_smart_call_log()
2642
graph = repo.get_graph()
2643
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2644
# 'first' it will be a candidate for the stop_keys of subsequent
2645
# requests, and because 'null:' was queried but not returned it will be
2646
# cached as missing.
2647
self.assertEqual({'first': ('null:',)},
2648
graph.get_parent_map(['first', 'null:']))
2649
# Now query for another key. This request will pass along a recipe of
2650
# start and stop keys describing the already cached results, and this
2651
# recipe's revision count must be correct (or else it will trigger an
2652
# error from the server).
2653
self.assertEqual({}, graph.get_parent_map(['another-key']))
2654
# This assertion guards against disableExtraResults silently failing to
2655
# work, thus invalidating the test.
2656
self.assertLength(2, self.hpss_calls)
2658
def test_get_parent_map_gets_ghosts_from_result(self):
2659
# asking for a revision should negatively cache close ghosts in its
2661
self.setup_smart_server_with_call_log()
2662
tree = self.make_branch_and_memory_tree('foo')
2665
builder = treebuilder.TreeBuilder()
2666
builder.start_tree(tree)
2668
builder.finish_tree()
2669
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2670
rev_id = tree.commit('')
2674
self.addCleanup(tree.unlock)
2675
repo = tree.branch.repository
2676
self.assertIsInstance(repo, RemoteRepository)
2678
repo.get_parent_map([rev_id])
2679
self.reset_smart_call_log()
2680
# Now asking for rev_id's ghost parent should not make calls
2681
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2682
self.assertLength(0, self.hpss_calls)
2684
def test_exposes_get_cached_parent_map(self):
2685
"""RemoteRepository exposes get_cached_parent_map from
2688
r1 = u'\u0e33'.encode('utf8')
2689
r2 = u'\u0dab'.encode('utf8')
2690
lines = [' '.join([r2, r1]), r1]
2691
encoded_body = bz2.compress('\n'.join(lines))
2693
transport_path = 'quack'
2694
repo, client = self.setup_fake_client_and_repository(transport_path)
2695
client.add_success_response_with_body(encoded_body, 'ok')
2697
# get_cached_parent_map should *not* trigger an RPC
2698
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2699
self.assertEqual([], client._calls)
2700
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2701
self.assertEqual({r1: (NULL_REVISION,)},
2702
repo.get_cached_parent_map([r1]))
2704
[('call_with_body_bytes_expecting_body',
2705
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2711
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2713
def test_allows_new_revisions(self):
2714
"""get_parent_map's results can be updated by commit."""
2715
smart_server = test_server.SmartTCPServer_for_testing()
2716
self.start_server(smart_server)
2717
self.make_branch('branch')
2718
branch = Branch.open(smart_server.get_url() + '/branch')
2719
tree = branch.create_checkout('tree', lightweight=True)
2721
self.addCleanup(tree.unlock)
2722
graph = tree.branch.repository.get_graph()
2723
# This provides an opportunity for the missing rev-id to be cached.
2724
self.assertEqual({}, graph.get_parent_map(['rev1']))
2725
tree.commit('message', rev_id='rev1')
2726
graph = tree.branch.repository.get_graph()
2727
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2730
class TestRepositoryGetRevisions(TestRemoteRepository):
2732
def test_hpss_missing_revision(self):
2733
transport_path = 'quack'
2734
repo, client = self.setup_fake_client_and_repository(transport_path)
2735
client.add_success_response_with_body(
2737
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2738
['somerev1', 'anotherrev2'])
2740
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2741
('quack/', ), "somerev1\nanotherrev2")],
2744
def test_hpss_get_single_revision(self):
2745
transport_path = 'quack'
2746
repo, client = self.setup_fake_client_and_repository(transport_path)
2747
somerev1 = Revision("somerev1")
2748
somerev1.committer = "Joe Committer <joe@example.com>"
2749
somerev1.timestamp = 1321828927
2750
somerev1.timezone = -60
2751
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2752
somerev1.message = "Message"
2753
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2755
# Split up body into two bits to make sure the zlib compression object
2756
# gets data fed twice.
2757
client.add_success_response_with_body(
2758
[body[:10], body[10:]], 'ok', '10')
2759
revs = repo.get_revisions(['somerev1'])
2760
self.assertEquals(revs, [somerev1])
2762
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2763
('quack/', ), "somerev1")],
2767
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2769
def test_null_revision(self):
2770
# a null revision has the predictable result {}, we should have no wire
2771
# traffic when calling it with this argument
2772
transport_path = 'empty'
2773
repo, client = self.setup_fake_client_and_repository(transport_path)
2774
client.add_success_response('notused')
2775
# actual RemoteRepository.get_revision_graph is gone, but there's an
2776
# equivalent private method for testing
2777
result = repo._get_revision_graph(NULL_REVISION)
2778
self.assertEqual([], client._calls)
2779
self.assertEqual({}, result)
2781
def test_none_revision(self):
2782
# with none we want the entire graph
2783
r1 = u'\u0e33'.encode('utf8')
2784
r2 = u'\u0dab'.encode('utf8')
2785
lines = [' '.join([r2, r1]), r1]
2786
encoded_body = '\n'.join(lines)
2788
transport_path = 'sinhala'
2789
repo, client = self.setup_fake_client_and_repository(transport_path)
2790
client.add_success_response_with_body(encoded_body, 'ok')
2791
# actual RemoteRepository.get_revision_graph is gone, but there's an
2792
# equivalent private method for testing
2793
result = repo._get_revision_graph(None)
2795
[('call_expecting_body', 'Repository.get_revision_graph',
2798
self.assertEqual({r1: (), r2: (r1, )}, result)
2800
def test_specific_revision(self):
2801
# with a specific revision we want the graph for that
2802
# with none we want the entire graph
2803
r11 = u'\u0e33'.encode('utf8')
2804
r12 = u'\xc9'.encode('utf8')
2805
r2 = u'\u0dab'.encode('utf8')
2806
lines = [' '.join([r2, r11, r12]), r11, r12]
2807
encoded_body = '\n'.join(lines)
2809
transport_path = 'sinhala'
2810
repo, client = self.setup_fake_client_and_repository(transport_path)
2811
client.add_success_response_with_body(encoded_body, 'ok')
2812
result = repo._get_revision_graph(r2)
2814
[('call_expecting_body', 'Repository.get_revision_graph',
2817
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2819
def test_no_such_revision(self):
2821
transport_path = 'sinhala'
2822
repo, client = self.setup_fake_client_and_repository(transport_path)
2823
client.add_error_response('nosuchrevision', revid)
2824
# also check that the right revision is reported in the error
2825
self.assertRaises(errors.NoSuchRevision,
2826
repo._get_revision_graph, revid)
2828
[('call_expecting_body', 'Repository.get_revision_graph',
2829
('sinhala/', revid))],
2832
def test_unexpected_error(self):
2834
transport_path = 'sinhala'
2835
repo, client = self.setup_fake_client_and_repository(transport_path)
2836
client.add_error_response('AnUnexpectedError')
2837
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2838
repo._get_revision_graph, revid)
2839
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2842
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2845
repo, client = self.setup_fake_client_and_repository('quack')
2846
client.add_expected_call(
2847
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2848
'success', ('ok', 'rev-five'))
2849
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2850
self.assertEqual((True, 'rev-five'), result)
2851
self.assertFinished(client)
2853
def test_history_incomplete(self):
2854
repo, client = self.setup_fake_client_and_repository('quack')
2855
client.add_expected_call(
2856
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2857
'success', ('history-incomplete', 10, 'rev-ten'))
2858
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2859
self.assertEqual((False, (10, 'rev-ten')), result)
2860
self.assertFinished(client)
2862
def test_history_incomplete_with_fallback(self):
2863
"""A 'history-incomplete' response causes the fallback repository to be
2864
queried too, if one is set.
2866
# Make a repo with a fallback repo, both using a FakeClient.
2867
format = remote.response_tuple_to_repo_format(
2868
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2869
repo, client = self.setup_fake_client_and_repository('quack')
2870
repo._format = format
2871
fallback_repo, ignored = self.setup_fake_client_and_repository(
2873
fallback_repo._client = client
2874
fallback_repo._format = format
2875
repo.add_fallback_repository(fallback_repo)
2876
# First the client should ask the primary repo
2877
client.add_expected_call(
2878
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2879
'success', ('history-incomplete', 2, 'rev-two'))
2880
# Then it should ask the fallback, using revno/revid from the
2881
# history-incomplete response as the known revno/revid.
2882
client.add_expected_call(
2883
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2884
'success', ('ok', 'rev-one'))
2885
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2886
self.assertEqual((True, 'rev-one'), result)
2887
self.assertFinished(client)
2889
def test_nosuchrevision(self):
2890
# 'nosuchrevision' is returned when the known-revid is not found in the
2891
# remote repo. The client translates that response to NoSuchRevision.
2892
repo, client = self.setup_fake_client_and_repository('quack')
2893
client.add_expected_call(
2894
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2895
'error', ('nosuchrevision', 'rev-foo'))
2897
errors.NoSuchRevision,
2898
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2899
self.assertFinished(client)
2901
def test_branch_fallback_locking(self):
2902
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2903
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2904
will be invoked, which will fail if the repo is unlocked.
2906
self.setup_smart_server_with_call_log()
2907
tree = self.make_branch_and_memory_tree('.')
2910
rev1 = tree.commit('First')
2911
rev2 = tree.commit('Second')
2913
branch = tree.branch
2914
self.assertFalse(branch.is_locked())
2915
self.reset_smart_call_log()
2916
verb = 'Repository.get_rev_id_for_revno'
2917
self.disable_verb(verb)
2918
self.assertEqual(rev1, branch.get_rev_id(1))
2919
self.assertLength(1, [call for call in self.hpss_calls if
2920
call.call.method == verb])
2923
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2925
def test_has_signature_for_revision_id(self):
2926
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2927
transport_path = 'quack'
2928
repo, client = self.setup_fake_client_and_repository(transport_path)
2929
client.add_success_response('yes')
2930
result = repo.has_signature_for_revision_id('A')
2932
[('call', 'Repository.has_signature_for_revision_id',
2935
self.assertEqual(True, result)
2937
def test_is_not_shared(self):
2938
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2939
transport_path = 'qwack'
2940
repo, client = self.setup_fake_client_and_repository(transport_path)
2941
client.add_success_response('no')
2942
result = repo.has_signature_for_revision_id('A')
2944
[('call', 'Repository.has_signature_for_revision_id',
2947
self.assertEqual(False, result)
2950
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2952
def test_get_physical_lock_status_yes(self):
2953
transport_path = 'qwack'
2954
repo, client = self.setup_fake_client_and_repository(transport_path)
2955
client.add_success_response('yes')
2956
result = repo.get_physical_lock_status()
2958
[('call', 'Repository.get_physical_lock_status',
2961
self.assertEqual(True, result)
2963
def test_get_physical_lock_status_no(self):
2964
transport_path = 'qwack'
2965
repo, client = self.setup_fake_client_and_repository(transport_path)
2966
client.add_success_response('no')
2967
result = repo.get_physical_lock_status()
2969
[('call', 'Repository.get_physical_lock_status',
2972
self.assertEqual(False, result)
2975
class TestRepositoryIsShared(TestRemoteRepository):
2977
def test_is_shared(self):
2978
# ('yes', ) for Repository.is_shared -> 'True'.
2979
transport_path = 'quack'
2980
repo, client = self.setup_fake_client_and_repository(transport_path)
2981
client.add_success_response('yes')
2982
result = repo.is_shared()
2984
[('call', 'Repository.is_shared', ('quack/',))],
2986
self.assertEqual(True, result)
2988
def test_is_not_shared(self):
2989
# ('no', ) for Repository.is_shared -> 'False'.
2990
transport_path = 'qwack'
2991
repo, client = self.setup_fake_client_and_repository(transport_path)
2992
client.add_success_response('no')
2993
result = repo.is_shared()
2995
[('call', 'Repository.is_shared', ('qwack/',))],
2997
self.assertEqual(False, result)
3000
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3002
def test_make_working_trees(self):
3003
# ('yes', ) for Repository.make_working_trees -> 'True'.
3004
transport_path = 'quack'
3005
repo, client = self.setup_fake_client_and_repository(transport_path)
3006
client.add_success_response('yes')
3007
result = repo.make_working_trees()
3009
[('call', 'Repository.make_working_trees', ('quack/',))],
3011
self.assertEqual(True, result)
3013
def test_no_working_trees(self):
3014
# ('no', ) for Repository.make_working_trees -> 'False'.
3015
transport_path = 'qwack'
3016
repo, client = self.setup_fake_client_and_repository(transport_path)
3017
client.add_success_response('no')
3018
result = repo.make_working_trees()
3020
[('call', 'Repository.make_working_trees', ('qwack/',))],
3022
self.assertEqual(False, result)
3025
class TestRepositoryLockWrite(TestRemoteRepository):
3027
def test_lock_write(self):
3028
transport_path = 'quack'
3029
repo, client = self.setup_fake_client_and_repository(transport_path)
3030
client.add_success_response('ok', 'a token')
3031
token = repo.lock_write().repository_token
3033
[('call', 'Repository.lock_write', ('quack/', ''))],
3035
self.assertEqual('a token', token)
3037
def test_lock_write_already_locked(self):
3038
transport_path = 'quack'
3039
repo, client = self.setup_fake_client_and_repository(transport_path)
3040
client.add_error_response('LockContention')
3041
self.assertRaises(errors.LockContention, repo.lock_write)
3043
[('call', 'Repository.lock_write', ('quack/', ''))],
3046
def test_lock_write_unlockable(self):
3047
transport_path = 'quack'
3048
repo, client = self.setup_fake_client_and_repository(transport_path)
3049
client.add_error_response('UnlockableTransport')
3050
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3052
[('call', 'Repository.lock_write', ('quack/', ''))],
3056
class TestRepositoryWriteGroups(TestRemoteRepository):
3058
def test_start_write_group(self):
3059
transport_path = 'quack'
3060
repo, client = self.setup_fake_client_and_repository(transport_path)
3061
client.add_expected_call(
3062
'Repository.lock_write', ('quack/', ''),
3063
'success', ('ok', 'a token'))
3064
client.add_expected_call(
3065
'Repository.start_write_group', ('quack/', 'a token'),
3066
'success', ('ok', ('token1', )))
3068
repo.start_write_group()
3070
def test_start_write_group_unsuspendable(self):
3071
# Some repositories do not support suspending write
3072
# groups. For those, fall back to the "real" repository.
3073
transport_path = 'quack'
3074
repo, client = self.setup_fake_client_and_repository(transport_path)
3075
def stub_ensure_real():
3076
client._calls.append(('_ensure_real',))
3077
repo._real_repository = _StubRealPackRepository(client._calls)
3078
repo._ensure_real = stub_ensure_real
3079
client.add_expected_call(
3080
'Repository.lock_write', ('quack/', ''),
3081
'success', ('ok', 'a token'))
3082
client.add_expected_call(
3083
'Repository.start_write_group', ('quack/', 'a token'),
3084
'error', ('UnsuspendableWriteGroup',))
3086
repo.start_write_group()
3087
self.assertEquals(client._calls[-2:], [
3089
('start_write_group',)])
3091
def test_commit_write_group(self):
3092
transport_path = 'quack'
3093
repo, client = self.setup_fake_client_and_repository(transport_path)
3094
client.add_expected_call(
3095
'Repository.lock_write', ('quack/', ''),
3096
'success', ('ok', 'a token'))
3097
client.add_expected_call(
3098
'Repository.start_write_group', ('quack/', 'a token'),
3099
'success', ('ok', ['token1']))
3100
client.add_expected_call(
3101
'Repository.commit_write_group', ('quack/', 'a token', ['token1']),
3104
repo.start_write_group()
3105
repo.commit_write_group()
3107
def test_abort_write_group(self):
3108
transport_path = 'quack'
3109
repo, client = self.setup_fake_client_and_repository(transport_path)
3110
client.add_expected_call(
3111
'Repository.lock_write', ('quack/', ''),
3112
'success', ('ok', 'a token'))
3113
client.add_expected_call(
3114
'Repository.start_write_group', ('quack/', 'a token'),
3115
'success', ('ok', ['token1']))
3116
client.add_expected_call(
3117
'Repository.abort_write_group', ('quack/', 'a token', ['token1']),
3120
repo.start_write_group()
3121
repo.abort_write_group(False)
3123
def test_suspend_write_group(self):
3124
transport_path = 'quack'
3125
repo, client = self.setup_fake_client_and_repository(transport_path)
3126
self.assertEquals([], repo.suspend_write_group())
3128
def test_resume_write_group(self):
3129
transport_path = 'quack'
3130
repo, client = self.setup_fake_client_and_repository(transport_path)
3131
client.add_expected_call(
3132
'Repository.lock_write', ('quack/', ''),
3133
'success', ('ok', 'a token'))
3134
client.add_expected_call(
3135
'Repository.check_write_group', ('quack/', 'a token', ['token1']),
3138
repo.resume_write_group(['token1'])
3141
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3143
def test_backwards_compat(self):
3144
self.setup_smart_server_with_call_log()
3145
repo = self.make_repository('.')
3146
self.reset_smart_call_log()
3147
verb = 'Repository.set_make_working_trees'
3148
self.disable_verb(verb)
3149
repo.set_make_working_trees(True)
3150
call_count = len([call for call in self.hpss_calls if
3151
call.call.method == verb])
3152
self.assertEqual(1, call_count)
3154
def test_current(self):
3155
transport_path = 'quack'
3156
repo, client = self.setup_fake_client_and_repository(transport_path)
3157
client.add_expected_call(
3158
'Repository.set_make_working_trees', ('quack/', 'True'),
3160
client.add_expected_call(
3161
'Repository.set_make_working_trees', ('quack/', 'False'),
3163
repo.set_make_working_trees(True)
3164
repo.set_make_working_trees(False)
3167
class TestRepositoryUnlock(TestRemoteRepository):
3169
def test_unlock(self):
3170
transport_path = 'quack'
3171
repo, client = self.setup_fake_client_and_repository(transport_path)
3172
client.add_success_response('ok', 'a token')
3173
client.add_success_response('ok')
3177
[('call', 'Repository.lock_write', ('quack/', '')),
3178
('call', 'Repository.unlock', ('quack/', 'a token'))],
3181
def test_unlock_wrong_token(self):
3182
# If somehow the token is wrong, unlock will raise TokenMismatch.
3183
transport_path = 'quack'
3184
repo, client = self.setup_fake_client_and_repository(transport_path)
3185
client.add_success_response('ok', 'a token')
3186
client.add_error_response('TokenMismatch')
3188
self.assertRaises(errors.TokenMismatch, repo.unlock)
3191
class TestRepositoryHasRevision(TestRemoteRepository):
3193
def test_none(self):
3194
# repo.has_revision(None) should not cause any traffic.
3195
transport_path = 'quack'
3196
repo, client = self.setup_fake_client_and_repository(transport_path)
3198
# The null revision is always there, so has_revision(None) == True.
3199
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3201
# The remote repo shouldn't be accessed.
3202
self.assertEqual([], client._calls)
3205
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3206
"""Test Repository.iter_file_bytes."""
3208
def test_single(self):
3209
transport_path = 'quack'
3210
repo, client = self.setup_fake_client_and_repository(transport_path)
3211
client.add_expected_call(
3212
'Repository.iter_files_bytes', ('quack/', ),
3213
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3214
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3215
"somerev", "myid")]):
3216
self.assertEquals("myid", identifier)
3217
self.assertEquals("".join(byte_stream), "mydata" * 10)
3219
def test_missing(self):
3220
transport_path = 'quack'
3221
repo, client = self.setup_fake_client_and_repository(transport_path)
3222
client.add_expected_call(
3223
'Repository.iter_files_bytes',
3225
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3226
iter(["absent\0somefile\0somerev\n"]))
3227
self.assertRaises(errors.RevisionNotPresent, list,
3228
repo.iter_files_bytes(
3229
[("somefile", "somerev", "myid")]))
3232
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3233
"""Base class for Repository.insert_stream and .insert_stream_1.19
3237
def checkInsertEmptyStream(self, repo, client):
3238
"""Insert an empty stream, checking the result.
3240
This checks that there are no resume_tokens or missing_keys, and that
3241
the client is finished.
3243
sink = repo._get_sink()
3244
fmt = repository.format_registry.get_default()
3245
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3246
self.assertEqual([], resume_tokens)
3247
self.assertEqual(set(), missing_keys)
3248
self.assertFinished(client)
3251
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3252
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3255
This test case is very similar to TestRepositoryInsertStream_1_19.
3259
TestRemoteRepository.setUp(self)
3260
self.disable_verb('Repository.insert_stream_1.19')
3262
def test_unlocked_repo(self):
3263
transport_path = 'quack'
3264
repo, client = self.setup_fake_client_and_repository(transport_path)
3265
client.add_expected_call(
3266
'Repository.insert_stream_1.19', ('quack/', ''),
3267
'unknown', ('Repository.insert_stream_1.19',))
3268
client.add_expected_call(
3269
'Repository.insert_stream', ('quack/', ''),
3271
client.add_expected_call(
3272
'Repository.insert_stream', ('quack/', ''),
3274
self.checkInsertEmptyStream(repo, client)
3276
def test_locked_repo_with_no_lock_token(self):
3277
transport_path = 'quack'
3278
repo, client = self.setup_fake_client_and_repository(transport_path)
3279
client.add_expected_call(
3280
'Repository.lock_write', ('quack/', ''),
3281
'success', ('ok', ''))
3282
client.add_expected_call(
3283
'Repository.insert_stream_1.19', ('quack/', ''),
3284
'unknown', ('Repository.insert_stream_1.19',))
3285
client.add_expected_call(
3286
'Repository.insert_stream', ('quack/', ''),
3288
client.add_expected_call(
3289
'Repository.insert_stream', ('quack/', ''),
3292
self.checkInsertEmptyStream(repo, client)
3294
def test_locked_repo_with_lock_token(self):
3295
transport_path = 'quack'
3296
repo, client = self.setup_fake_client_and_repository(transport_path)
3297
client.add_expected_call(
3298
'Repository.lock_write', ('quack/', ''),
3299
'success', ('ok', 'a token'))
3300
client.add_expected_call(
3301
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3302
'unknown', ('Repository.insert_stream_1.19',))
3303
client.add_expected_call(
3304
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3306
client.add_expected_call(
3307
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3310
self.checkInsertEmptyStream(repo, client)
3312
def test_stream_with_inventory_deltas(self):
3313
"""'inventory-deltas' substreams cannot be sent to the
3314
Repository.insert_stream verb, because not all servers that implement
3315
that verb will accept them. So when one is encountered the RemoteSink
3316
immediately stops using that verb and falls back to VFS insert_stream.
3318
transport_path = 'quack'
3319
repo, client = self.setup_fake_client_and_repository(transport_path)
3320
client.add_expected_call(
3321
'Repository.insert_stream_1.19', ('quack/', ''),
3322
'unknown', ('Repository.insert_stream_1.19',))
3323
client.add_expected_call(
3324
'Repository.insert_stream', ('quack/', ''),
3326
client.add_expected_call(
3327
'Repository.insert_stream', ('quack/', ''),
3329
# Create a fake real repository for insert_stream to fall back on, so
3330
# that we can directly see the records the RemoteSink passes to the
3335
def insert_stream(self, stream, src_format, resume_tokens):
3336
for substream_kind, substream in stream:
3337
self.records.append(
3338
(substream_kind, [record.key for record in substream]))
3339
return ['fake tokens'], ['fake missing keys']
3340
fake_real_sink = FakeRealSink()
3341
class FakeRealRepository:
3342
def _get_sink(self):
3343
return fake_real_sink
3344
def is_in_write_group(self):
3346
def refresh_data(self):
3348
repo._real_repository = FakeRealRepository()
3349
sink = repo._get_sink()
3350
fmt = repository.format_registry.get_default()
3351
stream = self.make_stream_with_inv_deltas(fmt)
3352
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3353
# Every record from the first inventory delta should have been sent to
3355
expected_records = [
3356
('inventory-deltas', [('rev2',), ('rev3',)]),
3357
('texts', [('some-rev', 'some-file')])]
3358
self.assertEqual(expected_records, fake_real_sink.records)
3359
# The return values from the real sink's insert_stream are propagated
3360
# back to the original caller.
3361
self.assertEqual(['fake tokens'], resume_tokens)
3362
self.assertEqual(['fake missing keys'], missing_keys)
3363
self.assertFinished(client)
3365
def make_stream_with_inv_deltas(self, fmt):
3366
"""Make a simple stream with an inventory delta followed by more
3367
records and more substreams to test that all records and substreams
3368
from that point on are used.
3370
This sends, in order:
3371
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3373
* texts substream: (some-rev, some-file)
3375
# Define a stream using generators so that it isn't rewindable.
3376
inv = inventory.Inventory(revision_id='rev1')
3377
inv.root.revision = 'rev1'
3378
def stream_with_inv_delta():
3379
yield ('inventories', inventories_substream())
3380
yield ('inventory-deltas', inventory_delta_substream())
3382
versionedfile.FulltextContentFactory(
3383
('some-rev', 'some-file'), (), None, 'content')])
3384
def inventories_substream():
3385
# An empty inventory fulltext. This will be streamed normally.
3386
text = fmt._serializer.write_inventory_to_string(inv)
3387
yield versionedfile.FulltextContentFactory(
3388
('rev1',), (), None, text)
3389
def inventory_delta_substream():
3390
# An inventory delta. This can't be streamed via this verb, so it
3391
# will trigger a fallback to VFS insert_stream.
3392
entry = inv.make_entry(
3393
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3394
entry.revision = 'ghost'
3395
delta = [(None, 'newdir', 'newdir-id', entry)]
3396
serializer = inventory_delta.InventoryDeltaSerializer(
3397
versioned_root=True, tree_references=False)
3398
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3399
yield versionedfile.ChunkedContentFactory(
3400
('rev2',), (('rev1',)), None, lines)
3402
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3403
yield versionedfile.ChunkedContentFactory(
3404
('rev3',), (('rev1',)), None, lines)
3405
return stream_with_inv_delta()
3408
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3410
def test_unlocked_repo(self):
3411
transport_path = 'quack'
3412
repo, client = self.setup_fake_client_and_repository(transport_path)
3413
client.add_expected_call(
3414
'Repository.insert_stream_1.19', ('quack/', ''),
3416
client.add_expected_call(
3417
'Repository.insert_stream_1.19', ('quack/', ''),
3419
self.checkInsertEmptyStream(repo, client)
3421
def test_locked_repo_with_no_lock_token(self):
3422
transport_path = 'quack'
3423
repo, client = self.setup_fake_client_and_repository(transport_path)
3424
client.add_expected_call(
3425
'Repository.lock_write', ('quack/', ''),
3426
'success', ('ok', ''))
3427
client.add_expected_call(
3428
'Repository.insert_stream_1.19', ('quack/', ''),
3430
client.add_expected_call(
3431
'Repository.insert_stream_1.19', ('quack/', ''),
3434
self.checkInsertEmptyStream(repo, client)
3436
def test_locked_repo_with_lock_token(self):
3437
transport_path = 'quack'
3438
repo, client = self.setup_fake_client_and_repository(transport_path)
3439
client.add_expected_call(
3440
'Repository.lock_write', ('quack/', ''),
3441
'success', ('ok', 'a token'))
3442
client.add_expected_call(
3443
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3445
client.add_expected_call(
3446
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3449
self.checkInsertEmptyStream(repo, client)
3452
class TestRepositoryTarball(TestRemoteRepository):
3454
# This is a canned tarball reponse we can validate against
3456
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3457
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3458
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3459
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3460
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3461
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3462
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3463
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3464
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3465
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3466
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3467
'nWQ7QH/F3JFOFCQ0aSPfA='
3470
def test_repository_tarball(self):
3471
# Test that Repository.tarball generates the right operations
3472
transport_path = 'repo'
3473
expected_calls = [('call_expecting_body', 'Repository.tarball',
3474
('repo/', 'bz2',),),
3476
repo, client = self.setup_fake_client_and_repository(transport_path)
3477
client.add_success_response_with_body(self.tarball_content, 'ok')
3478
# Now actually ask for the tarball
3479
tarball_file = repo._get_tarball('bz2')
3481
self.assertEqual(expected_calls, client._calls)
3482
self.assertEqual(self.tarball_content, tarball_file.read())
3484
tarball_file.close()
3487
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3488
"""RemoteRepository.copy_content_into optimizations"""
3490
def test_copy_content_remote_to_local(self):
3491
self.transport_server = test_server.SmartTCPServer_for_testing
3492
src_repo = self.make_repository('repo1')
3493
src_repo = repository.Repository.open(self.get_url('repo1'))
3494
# At the moment the tarball-based copy_content_into can't write back
3495
# into a smart server. It would be good if it could upload the
3496
# tarball; once that works we'd have to create repositories of
3497
# different formats. -- mbp 20070410
3498
dest_url = self.get_vfs_only_url('repo2')
3499
dest_bzrdir = BzrDir.create(dest_url)
3500
dest_repo = dest_bzrdir.create_repository()
3501
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3502
self.assertTrue(isinstance(src_repo, RemoteRepository))
3503
src_repo.copy_content_into(dest_repo)
3506
class _StubRealPackRepository(object):
3508
def __init__(self, calls):
3510
self._pack_collection = _StubPackCollection(calls)
3512
def start_write_group(self):
3513
self.calls.append(('start_write_group',))
3515
def is_in_write_group(self):
3518
def refresh_data(self):
3519
self.calls.append(('pack collection reload_pack_names',))
3522
class _StubPackCollection(object):
3524
def __init__(self, calls):
3528
self.calls.append(('pack collection autopack',))
3531
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3532
"""Tests for RemoteRepository.autopack implementation."""
3535
"""When the server returns 'ok' and there's no _real_repository, then
3536
nothing else happens: the autopack method is done.
3538
transport_path = 'quack'
3539
repo, client = self.setup_fake_client_and_repository(transport_path)
3540
client.add_expected_call(
3541
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3543
self.assertFinished(client)
3545
def test_ok_with_real_repo(self):
3546
"""When the server returns 'ok' and there is a _real_repository, then
3547
the _real_repository's reload_pack_name's method will be called.
3549
transport_path = 'quack'
3550
repo, client = self.setup_fake_client_and_repository(transport_path)
3551
client.add_expected_call(
3552
'PackRepository.autopack', ('quack/',),
3554
repo._real_repository = _StubRealPackRepository(client._calls)
3557
[('call', 'PackRepository.autopack', ('quack/',)),
3558
('pack collection reload_pack_names',)],
3561
def test_backwards_compatibility(self):
3562
"""If the server does not recognise the PackRepository.autopack verb,
3563
fallback to the real_repository's implementation.
3565
transport_path = 'quack'
3566
repo, client = self.setup_fake_client_and_repository(transport_path)
3567
client.add_unknown_method_response('PackRepository.autopack')
3568
def stub_ensure_real():
3569
client._calls.append(('_ensure_real',))
3570
repo._real_repository = _StubRealPackRepository(client._calls)
3571
repo._ensure_real = stub_ensure_real
3574
[('call', 'PackRepository.autopack', ('quack/',)),
3576
('pack collection autopack',)],
3579
def test_oom_error_reporting(self):
3580
"""An out-of-memory condition on the server is reported clearly"""
3581
transport_path = 'quack'
3582
repo, client = self.setup_fake_client_and_repository(transport_path)
3583
client.add_expected_call(
3584
'PackRepository.autopack', ('quack/',),
3585
'error', ('MemoryError',))
3586
err = self.assertRaises(errors.BzrError, repo.autopack)
3587
self.assertContainsRe(str(err), "^remote server out of mem")
3590
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3591
"""Base class for unit tests for bzrlib.remote._translate_error."""
3593
def translateTuple(self, error_tuple, **context):
3594
"""Call _translate_error with an ErrorFromSmartServer built from the
3597
:param error_tuple: A tuple of a smart server response, as would be
3598
passed to an ErrorFromSmartServer.
3599
:kwargs context: context items to call _translate_error with.
3601
:returns: The error raised by _translate_error.
3603
# Raise the ErrorFromSmartServer before passing it as an argument,
3604
# because _translate_error may need to re-raise it with a bare 'raise'
3606
server_error = errors.ErrorFromSmartServer(error_tuple)
3607
translated_error = self.translateErrorFromSmartServer(
3608
server_error, **context)
3609
return translated_error
3611
def translateErrorFromSmartServer(self, error_object, **context):
3612
"""Like translateTuple, but takes an already constructed
3613
ErrorFromSmartServer rather than a tuple.
3617
except errors.ErrorFromSmartServer, server_error:
3618
translated_error = self.assertRaises(
3619
errors.BzrError, remote._translate_error, server_error,
3621
return translated_error
3624
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3625
"""Unit tests for bzrlib.remote._translate_error.
3627
Given an ErrorFromSmartServer (which has an error tuple from a smart
3628
server) and some context, _translate_error raises more specific errors from
3631
This test case covers the cases where _translate_error succeeds in
3632
translating an ErrorFromSmartServer to something better. See
3633
TestErrorTranslationRobustness for other cases.
3636
def test_NoSuchRevision(self):
3637
branch = self.make_branch('')
3639
translated_error = self.translateTuple(
3640
('NoSuchRevision', revid), branch=branch)
3641
expected_error = errors.NoSuchRevision(branch, revid)
3642
self.assertEqual(expected_error, translated_error)
3644
def test_nosuchrevision(self):
3645
repository = self.make_repository('')
3647
translated_error = self.translateTuple(
3648
('nosuchrevision', revid), repository=repository)
3649
expected_error = errors.NoSuchRevision(repository, revid)
3650
self.assertEqual(expected_error, translated_error)
3652
def test_nobranch(self):
3653
bzrdir = self.make_bzrdir('')
3654
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3655
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3656
self.assertEqual(expected_error, translated_error)
3658
def test_nobranch_one_arg(self):
3659
bzrdir = self.make_bzrdir('')
3660
translated_error = self.translateTuple(
3661
('nobranch', 'extra detail'), bzrdir=bzrdir)
3662
expected_error = errors.NotBranchError(
3663
path=bzrdir.root_transport.base,
3664
detail='extra detail')
3665
self.assertEqual(expected_error, translated_error)
3667
def test_norepository(self):
3668
bzrdir = self.make_bzrdir('')
3669
translated_error = self.translateTuple(('norepository',),
3671
expected_error = errors.NoRepositoryPresent(bzrdir)
3672
self.assertEqual(expected_error, translated_error)
3674
def test_LockContention(self):
3675
translated_error = self.translateTuple(('LockContention',))
3676
expected_error = errors.LockContention('(remote lock)')
3677
self.assertEqual(expected_error, translated_error)
3679
def test_UnlockableTransport(self):
3680
bzrdir = self.make_bzrdir('')
3681
translated_error = self.translateTuple(
3682
('UnlockableTransport',), bzrdir=bzrdir)
3683
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3684
self.assertEqual(expected_error, translated_error)
3686
def test_LockFailed(self):
3687
lock = 'str() of a server lock'
3688
why = 'str() of why'
3689
translated_error = self.translateTuple(('LockFailed', lock, why))
3690
expected_error = errors.LockFailed(lock, why)
3691
self.assertEqual(expected_error, translated_error)
3693
def test_TokenMismatch(self):
3694
token = 'a lock token'
3695
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3696
expected_error = errors.TokenMismatch(token, '(remote token)')
3697
self.assertEqual(expected_error, translated_error)
3699
def test_Diverged(self):
3700
branch = self.make_branch('a')
3701
other_branch = self.make_branch('b')
3702
translated_error = self.translateTuple(
3703
('Diverged',), branch=branch, other_branch=other_branch)
3704
expected_error = errors.DivergedBranches(branch, other_branch)
3705
self.assertEqual(expected_error, translated_error)
3707
def test_NotStacked(self):
3708
branch = self.make_branch('')
3709
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3710
expected_error = errors.NotStacked(branch)
3711
self.assertEqual(expected_error, translated_error)
3713
def test_ReadError_no_args(self):
3715
translated_error = self.translateTuple(('ReadError',), path=path)
3716
expected_error = errors.ReadError(path)
3717
self.assertEqual(expected_error, translated_error)
3719
def test_ReadError(self):
3721
translated_error = self.translateTuple(('ReadError', path))
3722
expected_error = errors.ReadError(path)
3723
self.assertEqual(expected_error, translated_error)
3725
def test_IncompatibleRepositories(self):
3726
translated_error = self.translateTuple(('IncompatibleRepositories',
3727
"repo1", "repo2", "details here"))
3728
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3730
self.assertEqual(expected_error, translated_error)
3732
def test_PermissionDenied_no_args(self):
3734
translated_error = self.translateTuple(('PermissionDenied',),
3736
expected_error = errors.PermissionDenied(path)
3737
self.assertEqual(expected_error, translated_error)
3739
def test_PermissionDenied_one_arg(self):
3741
translated_error = self.translateTuple(('PermissionDenied', path))
3742
expected_error = errors.PermissionDenied(path)
3743
self.assertEqual(expected_error, translated_error)
3745
def test_PermissionDenied_one_arg_and_context(self):
3746
"""Given a choice between a path from the local context and a path on
3747
the wire, _translate_error prefers the path from the local context.
3749
local_path = 'local path'
3750
remote_path = 'remote path'
3751
translated_error = self.translateTuple(
3752
('PermissionDenied', remote_path), path=local_path)
3753
expected_error = errors.PermissionDenied(local_path)
3754
self.assertEqual(expected_error, translated_error)
3756
def test_PermissionDenied_two_args(self):
3758
extra = 'a string with extra info'
3759
translated_error = self.translateTuple(
3760
('PermissionDenied', path, extra))
3761
expected_error = errors.PermissionDenied(path, extra)
3762
self.assertEqual(expected_error, translated_error)
3764
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3766
def test_NoSuchFile_context_path(self):
3767
local_path = "local path"
3768
translated_error = self.translateTuple(('ReadError', "remote path"),
3770
expected_error = errors.ReadError(local_path)
3771
self.assertEqual(expected_error, translated_error)
3773
def test_NoSuchFile_without_context(self):
3774
remote_path = "remote path"
3775
translated_error = self.translateTuple(('ReadError', remote_path))
3776
expected_error = errors.ReadError(remote_path)
3777
self.assertEqual(expected_error, translated_error)
3779
def test_ReadOnlyError(self):
3780
translated_error = self.translateTuple(('ReadOnlyError',))
3781
expected_error = errors.TransportNotPossible("readonly transport")
3782
self.assertEqual(expected_error, translated_error)
3784
def test_MemoryError(self):
3785
translated_error = self.translateTuple(('MemoryError',))
3786
self.assertStartsWith(str(translated_error),
3787
"remote server out of memory")
3789
def test_generic_IndexError_no_classname(self):
3790
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3791
translated_error = self.translateErrorFromSmartServer(err)
3792
expected_error = errors.UnknownErrorFromSmartServer(err)
3793
self.assertEqual(expected_error, translated_error)
3795
# GZ 2011-03-02: TODO test generic non-ascii error string
3797
def test_generic_KeyError(self):
3798
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3799
translated_error = self.translateErrorFromSmartServer(err)
3800
expected_error = errors.UnknownErrorFromSmartServer(err)
3801
self.assertEqual(expected_error, translated_error)
3804
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3805
"""Unit tests for bzrlib.remote._translate_error's robustness.
3807
TestErrorTranslationSuccess is for cases where _translate_error can
3808
translate successfully. This class about how _translate_err behaves when
3809
it fails to translate: it re-raises the original error.
3812
def test_unrecognised_server_error(self):
3813
"""If the error code from the server is not recognised, the original
3814
ErrorFromSmartServer is propagated unmodified.
3816
error_tuple = ('An unknown error tuple',)
3817
server_error = errors.ErrorFromSmartServer(error_tuple)
3818
translated_error = self.translateErrorFromSmartServer(server_error)
3819
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3820
self.assertEqual(expected_error, translated_error)
3822
def test_context_missing_a_key(self):
3823
"""In case of a bug in the client, or perhaps an unexpected response
3824
from a server, _translate_error returns the original error tuple from
3825
the server and mutters a warning.
3827
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3828
# in the context dict. So let's give it an empty context dict instead
3829
# to exercise its error recovery.
3831
error_tuple = ('NoSuchRevision', 'revid')
3832
server_error = errors.ErrorFromSmartServer(error_tuple)
3833
translated_error = self.translateErrorFromSmartServer(server_error)
3834
self.assertEqual(server_error, translated_error)
3835
# In addition to re-raising ErrorFromSmartServer, some debug info has
3836
# been muttered to the log file for developer to look at.
3837
self.assertContainsRe(
3839
"Missing key 'branch' in context")
3841
def test_path_missing(self):
3842
"""Some translations (PermissionDenied, ReadError) can determine the
3843
'path' variable from either the wire or the local context. If neither
3844
has it, then an error is raised.
3846
error_tuple = ('ReadError',)
3847
server_error = errors.ErrorFromSmartServer(error_tuple)
3848
translated_error = self.translateErrorFromSmartServer(server_error)
3849
self.assertEqual(server_error, translated_error)
3850
# In addition to re-raising ErrorFromSmartServer, some debug info has
3851
# been muttered to the log file for developer to look at.
3852
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3855
class TestStacking(tests.TestCaseWithTransport):
3856
"""Tests for operations on stacked remote repositories.
3858
The underlying format type must support stacking.
3861
def test_access_stacked_remote(self):
3862
# based on <http://launchpad.net/bugs/261315>
3863
# make a branch stacked on another repository containing an empty
3864
# revision, then open it over hpss - we should be able to see that
3866
base_transport = self.get_transport()
3867
base_builder = self.make_branch_builder('base', format='1.9')
3868
base_builder.start_series()
3869
base_revid = base_builder.build_snapshot('rev-id', None,
3870
[('add', ('', None, 'directory', None))],
3872
base_builder.finish_series()
3873
stacked_branch = self.make_branch('stacked', format='1.9')
3874
stacked_branch.set_stacked_on_url('../base')
3875
# start a server looking at this
3876
smart_server = test_server.SmartTCPServer_for_testing()
3877
self.start_server(smart_server)
3878
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3879
# can get its branch and repository
3880
remote_branch = remote_bzrdir.open_branch()
3881
remote_repo = remote_branch.repository
3882
remote_repo.lock_read()
3884
# it should have an appropriate fallback repository, which should also
3885
# be a RemoteRepository
3886
self.assertLength(1, remote_repo._fallback_repositories)
3887
self.assertIsInstance(remote_repo._fallback_repositories[0],
3889
# and it has the revision committed to the underlying repository;
3890
# these have varying implementations so we try several of them
3891
self.assertTrue(remote_repo.has_revisions([base_revid]))
3892
self.assertTrue(remote_repo.has_revision(base_revid))
3893
self.assertEqual(remote_repo.get_revision(base_revid).message,
3896
remote_repo.unlock()
3898
def prepare_stacked_remote_branch(self):
3899
"""Get stacked_upon and stacked branches with content in each."""
3900
self.setup_smart_server_with_call_log()
3901
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3902
tree1.commit('rev1', rev_id='rev1')
3903
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3904
).open_workingtree()
3905
local_tree = tree2.branch.create_checkout('local')
3906
local_tree.commit('local changes make me feel good.')
3907
branch2 = Branch.open(self.get_url('tree2'))
3909
self.addCleanup(branch2.unlock)
3910
return tree1.branch, branch2
3912
def test_stacked_get_parent_map(self):
3913
# the public implementation of get_parent_map obeys stacking
3914
_, branch = self.prepare_stacked_remote_branch()
3915
repo = branch.repository
3916
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3918
def test_unstacked_get_parent_map(self):
3919
# _unstacked_provider.get_parent_map ignores stacking
3920
_, branch = self.prepare_stacked_remote_branch()
3921
provider = branch.repository._unstacked_provider
3922
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3924
def fetch_stream_to_rev_order(self, stream):
3926
for kind, substream in stream:
3927
if not kind == 'revisions':
3930
for content in substream:
3931
result.append(content.key[-1])
3934
def get_ordered_revs(self, format, order, branch_factory=None):
3935
"""Get a list of the revisions in a stream to format format.
3937
:param format: The format of the target.
3938
:param order: the order that target should have requested.
3939
:param branch_factory: A callable to create a trunk and stacked branch
3940
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3941
:result: The revision ids in the stream, in the order seen,
3942
the topological order of revisions in the source.
3944
unordered_format = bzrdir.format_registry.get(format)()
3945
target_repository_format = unordered_format.repository_format
3947
self.assertEqual(order, target_repository_format._fetch_order)
3948
if branch_factory is None:
3949
branch_factory = self.prepare_stacked_remote_branch
3950
_, stacked = branch_factory()
3951
source = stacked.repository._get_source(target_repository_format)
3952
tip = stacked.last_revision()
3953
stacked.repository._ensure_real()
3954
graph = stacked.repository.get_graph()
3955
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3956
if r != NULL_REVISION]
3958
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3959
self.reset_smart_call_log()
3960
stream = source.get_stream(search)
3961
# We trust that if a revision is in the stream the rest of the new
3962
# content for it is too, as per our main fetch tests; here we are
3963
# checking that the revisions are actually included at all, and their
3965
return self.fetch_stream_to_rev_order(stream), revs
3967
def test_stacked_get_stream_unordered(self):
3968
# Repository._get_source.get_stream() from a stacked repository with
3969
# unordered yields the full data from both stacked and stacked upon
3971
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3972
self.assertEqual(set(expected_revs), set(rev_ord))
3973
# Getting unordered results should have made a streaming data request
3974
# from the server, then one from the backing branch.
3975
self.assertLength(2, self.hpss_calls)
3977
def test_stacked_on_stacked_get_stream_unordered(self):
3978
# Repository._get_source.get_stream() from a stacked repository which
3979
# is itself stacked yields the full data from all three sources.
3980
def make_stacked_stacked():
3981
_, stacked = self.prepare_stacked_remote_branch()
3982
tree = stacked.bzrdir.sprout('tree3', stacked=True
3983
).open_workingtree()
3984
local_tree = tree.branch.create_checkout('local-tree3')
3985
local_tree.commit('more local changes are better')
3986
branch = Branch.open(self.get_url('tree3'))
3988
self.addCleanup(branch.unlock)
3990
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3991
branch_factory=make_stacked_stacked)
3992
self.assertEqual(set(expected_revs), set(rev_ord))
3993
# Getting unordered results should have made a streaming data request
3994
# from the server, and one from each backing repo
3995
self.assertLength(3, self.hpss_calls)
3997
def test_stacked_get_stream_topological(self):
3998
# Repository._get_source.get_stream() from a stacked repository with
3999
# topological sorting yields the full data from both stacked and
4000
# stacked upon sources in topological order.
4001
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4002
self.assertEqual(expected_revs, rev_ord)
4003
# Getting topological sort requires VFS calls still - one of which is
4004
# pushing up from the bound branch.
4005
self.assertLength(14, self.hpss_calls)
4007
def test_stacked_get_stream_groupcompress(self):
4008
# Repository._get_source.get_stream() from a stacked repository with
4009
# groupcompress sorting yields the full data from both stacked and
4010
# stacked upon sources in groupcompress order.
4011
raise tests.TestSkipped('No groupcompress ordered format available')
4012
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4013
self.assertEqual(expected_revs, reversed(rev_ord))
4014
# Getting unordered results should have made a streaming data request
4015
# from the backing branch, and one from the stacked on branch.
4016
self.assertLength(2, self.hpss_calls)
4018
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4019
# When pulling some fixed amount of content that is more than the
4020
# source has (because some is coming from a fallback branch, no error
4021
# should be received. This was reported as bug 360791.
4022
# Need three branches: a trunk, a stacked branch, and a preexisting
4023
# branch pulling content from stacked and trunk.
4024
self.setup_smart_server_with_call_log()
4025
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4026
r1 = trunk.commit('start')
4027
stacked_branch = trunk.branch.create_clone_on_transport(
4028
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4029
local = self.make_branch('local', format='1.9-rich-root')
4030
local.repository.fetch(stacked_branch.repository,
4031
stacked_branch.last_revision())
4034
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4037
super(TestRemoteBranchEffort, self).setUp()
4038
# Create a smart server that publishes whatever the backing VFS server
4040
self.smart_server = test_server.SmartTCPServer_for_testing()
4041
self.start_server(self.smart_server, self.get_server())
4042
# Log all HPSS calls into self.hpss_calls.
4043
_SmartClient.hooks.install_named_hook(
4044
'call', self.capture_hpss_call, None)
4045
self.hpss_calls = []
4047
def capture_hpss_call(self, params):
4048
self.hpss_calls.append(params.method)
4050
def test_copy_content_into_avoids_revision_history(self):
4051
local = self.make_branch('local')
4052
builder = self.make_branch_builder('remote')
4053
builder.build_commit(message="Commit.")
4054
remote_branch_url = self.smart_server.get_url() + 'remote'
4055
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4056
local.repository.fetch(remote_branch.repository)
4057
self.hpss_calls = []
4058
remote_branch.copy_content_into(local)
4059
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4061
def test_fetch_everything_needs_just_one_call(self):
4062
local = self.make_branch('local')
4063
builder = self.make_branch_builder('remote')
4064
builder.build_commit(message="Commit.")
4065
remote_branch_url = self.smart_server.get_url() + 'remote'
4066
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4067
self.hpss_calls = []
4068
local.repository.fetch(
4069
remote_branch.repository,
4070
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4071
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4073
def override_verb(self, verb_name, verb):
4074
request_handlers = request.request_handlers
4075
orig_verb = request_handlers.get(verb_name)
4076
orig_info = request_handlers.get_info(verb_name)
4077
request_handlers.register(verb_name, verb, override_existing=True)
4078
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4079
override_existing=True, info=orig_info)
4081
def test_fetch_everything_backwards_compat(self):
4082
"""Can fetch with EverythingResult even with pre 2.4 servers.
4084
Pre-2.4 do not support 'everything' searches with the
4085
Repository.get_stream_1.19 verb.
4088
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4089
"""A version of the Repository.get_stream_1.19 verb patched to
4090
reject 'everything' searches the way 2.3 and earlier do.
4092
def recreate_search(self, repository, search_bytes,
4093
discard_excess=False):
4094
verb_log.append(search_bytes.split('\n', 1)[0])
4095
if search_bytes == 'everything':
4097
request.FailedSmartServerResponse(('BadSearch',)))
4098
return super(OldGetStreamVerb,
4099
self).recreate_search(repository, search_bytes,
4100
discard_excess=discard_excess)
4101
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4102
local = self.make_branch('local')
4103
builder = self.make_branch_builder('remote')
4104
builder.build_commit(message="Commit.")
4105
remote_branch_url = self.smart_server.get_url() + 'remote'
4106
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4107
self.hpss_calls = []
4108
local.repository.fetch(
4109
remote_branch.repository,
4110
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4111
# make sure the overridden verb was used
4112
self.assertLength(1, verb_log)
4113
# more than one HPSS call is needed, but because it's a VFS callback
4114
# its hard to predict exactly how many.
4115
self.assertTrue(len(self.hpss_calls) > 1)
4118
class TestUpdateBoundBranchWithModifiedBoundLocation(
4119
tests.TestCaseWithTransport):
4120
"""Ensure correct handling of bound_location modifications.
4122
This is tested against a smart server as http://pad.lv/786980 was about a
4123
ReadOnlyError (write attempt during a read-only transaction) which can only
4124
happen in this context.
4128
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4129
self.transport_server = test_server.SmartTCPServer_for_testing
4131
def make_master_and_checkout(self, master_name, checkout_name):
4132
# Create the master branch and its associated checkout
4133
self.master = self.make_branch_and_tree(master_name)
4134
self.checkout = self.master.branch.create_checkout(checkout_name)
4135
# Modify the master branch so there is something to update
4136
self.master.commit('add stuff')
4137
self.last_revid = self.master.commit('even more stuff')
4138
self.bound_location = self.checkout.branch.get_bound_location()
4140
def assertUpdateSucceeds(self, new_location):
4141
self.checkout.branch.set_bound_location(new_location)
4142
self.checkout.update()
4143
self.assertEquals(self.last_revid, self.checkout.last_revision())
4145
def test_without_final_slash(self):
4146
self.make_master_and_checkout('master', 'checkout')
4147
# For unclear reasons some users have a bound_location without a final
4148
# '/', simulate that by forcing such a value
4149
self.assertEndsWith(self.bound_location, '/')
4150
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4152
def test_plus_sign(self):
4153
self.make_master_and_checkout('+master', 'checkout')
4154
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4156
def test_tilda(self):
4157
# Embed ~ in the middle of the path just to avoid any $HOME
4159
self.make_master_and_checkout('mas~ter', 'checkout')
4160
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4163
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4165
def test_no_context(self):
4166
class OutOfCoffee(errors.BzrError):
4167
"""A dummy exception for testing."""
4169
def __init__(self, urgency):
4170
self.urgency = urgency
4171
remote.no_context_error_translators.register("OutOfCoffee",
4172
lambda err: OutOfCoffee(err.error_args[0]))
4173
transport = MemoryTransport()
4174
client = FakeClient(transport.base)
4175
client.add_expected_call(
4176
'Branch.get_stacked_on_url', ('quack/',),
4177
'error', ('NotStacked',))
4178
client.add_expected_call(
4179
'Branch.last_revision_info',
4181
'error', ('OutOfCoffee', 'low'))
4182
transport.mkdir('quack')
4183
transport = transport.clone('quack')
4184
branch = self.make_remote_branch(transport, client)
4185
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4186
self.assertFinished(client)
4188
def test_with_context(self):
4189
class OutOfTea(errors.BzrError):
4190
def __init__(self, branch, urgency):
4191
self.branch = branch
4192
self.urgency = urgency
4193
remote.error_translators.register("OutOfTea",
4194
lambda err, find, path: OutOfTea(err.error_args[0],
4196
transport = MemoryTransport()
4197
client = FakeClient(transport.base)
4198
client.add_expected_call(
4199
'Branch.get_stacked_on_url', ('quack/',),
4200
'error', ('NotStacked',))
4201
client.add_expected_call(
4202
'Branch.last_revision_info',
4204
'error', ('OutOfTea', 'low'))
4205
transport.mkdir('quack')
4206
transport = transport.clone('quack')
4207
branch = self.make_remote_branch(transport, client)
4208
self.assertRaises(OutOfTea, branch.last_revision_info)
4209
self.assertFinished(client)
4212
class TestRepositoryPack(TestRemoteRepository):
4214
def test_pack(self):
4215
transport_path = 'quack'
4216
repo, client = self.setup_fake_client_and_repository(transport_path)
4217
client.add_expected_call(
4218
'Repository.lock_write', ('quack/', ''),
4219
'success', ('ok', 'token'))
4220
client.add_expected_call(
4221
'Repository.pack', ('quack/', 'token', 'False'),
4222
'success', ('ok',), )
4223
client.add_expected_call(
4224
'Repository.unlock', ('quack/', 'token'),
4225
'success', ('ok', ))
4228
def test_pack_with_hint(self):
4229
transport_path = 'quack'
4230
repo, client = self.setup_fake_client_and_repository(transport_path)
4231
client.add_expected_call(
4232
'Repository.lock_write', ('quack/', ''),
4233
'success', ('ok', 'token'))
4234
client.add_expected_call(
4235
'Repository.pack', ('quack/', 'token', 'False'),
4236
'success', ('ok',), )
4237
client.add_expected_call(
4238
'Repository.unlock', ('quack/', 'token', 'False'),
4239
'success', ('ok', ))
4240
repo.pack(['hinta', 'hintb'])
4243
class TestRepositoryIterInventories(TestRemoteRepository):
4244
"""Test Repository.iter_inventories."""
4246
def _serialize_inv_delta(self, old_name, new_name, delta):
4247
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4248
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4250
def test_single_empty(self):
4251
transport_path = 'quack'
4252
repo, client = self.setup_fake_client_and_repository(transport_path)
4253
fmt = bzrdir.format_registry.get('2a')().repository_format
4255
stream = [('inventory-deltas', [
4256
versionedfile.FulltextContentFactory('somerevid', None, None,
4257
self._serialize_inv_delta('null:', 'somerevid', []))])]
4258
client.add_expected_call(
4259
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4260
'success', ('ok', ),
4261
_stream_to_byte_stream(stream, fmt))
4262
ret = list(repo.iter_inventories(["somerevid"]))
4263
self.assertLength(1, ret)
4265
self.assertEquals("somerevid", inv.revision_id)
4267
def test_empty(self):
4268
transport_path = 'quack'
4269
repo, client = self.setup_fake_client_and_repository(transport_path)
4270
ret = list(repo.iter_inventories([]))
4271
self.assertEquals(ret, [])
4273
def test_missing(self):
4274
transport_path = 'quack'
4275
repo, client = self.setup_fake_client_and_repository(transport_path)
4276
client.add_expected_call(
4277
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4278
'success', ('ok', ), iter([]))
4279
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(