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_with_body(
937
reference_format.get_format_string(), 'ok')
938
# PackRepository wants to do a stat
939
client.add_success_response('stat', '0', '65535')
940
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
942
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
944
repo = bzrdir.open_repository()
946
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
947
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
948
('call', 'BzrDir.find_repository', ('quack/',)),
949
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
950
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
951
('call', 'stat', ('/quack/.bzr/repository',)),
954
self.assertEqual(network_name, repo._format.network_name())
956
def test_backwards_compat_2(self):
957
# fallback to find_repositoryV2
958
reference_format = self.get_repo_format()
959
network_name = reference_format.network_name()
960
server_url = 'bzr://example.com/'
961
self.permit_url(server_url)
962
client = FakeClient(server_url)
963
client.add_unknown_method_response('BzrDir.find_repositoryV3')
964
client.add_success_response('ok', '', 'no', 'no', 'no')
965
# A real repository instance will be created to determine the network
967
client.add_success_response_with_body(
968
"Bazaar-NG meta directory, format 1\n", 'ok')
969
client.add_success_response_with_body(
970
reference_format.get_format_string(), 'ok')
971
# PackRepository wants to do a stat
972
client.add_success_response('stat', '0', '65535')
973
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
975
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
977
repo = bzrdir.open_repository()
979
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
980
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
981
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
982
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
983
('call', 'stat', ('/quack/.bzr/repository',)),
986
self.assertEqual(network_name, repo._format.network_name())
988
def test_current_server(self):
989
reference_format = self.get_repo_format()
990
network_name = reference_format.network_name()
991
transport = MemoryTransport()
992
transport.mkdir('quack')
993
transport = transport.clone('quack')
994
client = FakeClient(transport.base)
995
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
996
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
998
repo = bzrdir.open_repository()
1000
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
1002
self.assertEqual(network_name, repo._format.network_name())
1005
class TestBzrDirFormatInitializeEx(TestRemote):
1007
def test_success(self):
1008
"""Simple test for typical successful call."""
1009
fmt = RemoteBzrDirFormat()
1010
default_format_name = BzrDirFormat.get_default_format().network_name()
1011
transport = self.get_transport()
1012
client = FakeClient(transport.base)
1013
client.add_expected_call(
1014
'BzrDirFormat.initialize_ex_1.16',
1015
(default_format_name, 'path', 'False', 'False', 'False', '',
1016
'', '', '', 'False'),
1018
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
1019
'bzrdir fmt', 'False', '', '', 'repo lock token'))
1020
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1021
# it's currently hard to test that without supplying a real remote
1022
# transport connected to a real server.
1023
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1024
transport, False, False, False, None, None, None, None, False)
1025
self.assertFinished(client)
1027
def test_error(self):
1028
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1029
corresponding error from the client.
1031
fmt = RemoteBzrDirFormat()
1032
default_format_name = BzrDirFormat.get_default_format().network_name()
1033
transport = self.get_transport()
1034
client = FakeClient(transport.base)
1035
client.add_expected_call(
1036
'BzrDirFormat.initialize_ex_1.16',
1037
(default_format_name, 'path', 'False', 'False', 'False', '',
1038
'', '', '', 'False'),
1040
('PermissionDenied', 'path', 'extra info'))
1041
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1042
# it's currently hard to test that without supplying a real remote
1043
# transport connected to a real server.
1044
err = self.assertRaises(errors.PermissionDenied,
1045
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1046
False, False, False, None, None, None, None, False)
1047
self.assertEqual('path', err.path)
1048
self.assertEqual(': extra info', err.extra)
1049
self.assertFinished(client)
1051
def test_error_from_real_server(self):
1052
"""Integration test for error translation."""
1053
transport = self.make_smart_server('foo')
1054
transport = transport.clone('no-such-path')
1055
fmt = RemoteBzrDirFormat()
1056
err = self.assertRaises(errors.NoSuchFile,
1057
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1060
class OldSmartClient(object):
1061
"""A fake smart client for test_old_version that just returns a version one
1062
response to the 'hello' (query version) command.
1065
def get_request(self):
1066
input_file = StringIO('ok\x011\n')
1067
output_file = StringIO()
1068
client_medium = medium.SmartSimplePipesClientMedium(
1069
input_file, output_file)
1070
return medium.SmartClientStreamMediumRequest(client_medium)
1072
def protocol_version(self):
1076
class OldServerTransport(object):
1077
"""A fake transport for test_old_server that reports it's smart server
1078
protocol version as version one.
1084
def get_smart_client(self):
1085
return OldSmartClient()
1088
class RemoteBzrDirTestCase(TestRemote):
1090
def make_remote_bzrdir(self, transport, client):
1091
"""Make a RemotebzrDir using 'client' as the _client."""
1092
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1096
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1098
def lock_remote_branch(self, branch):
1099
"""Trick a RemoteBranch into thinking it is locked."""
1100
branch._lock_mode = 'w'
1101
branch._lock_count = 2
1102
branch._lock_token = 'branch token'
1103
branch._repo_lock_token = 'repo token'
1104
branch.repository._lock_mode = 'w'
1105
branch.repository._lock_count = 2
1106
branch.repository._lock_token = 'repo token'
1108
def make_remote_branch(self, transport, client):
1109
"""Make a RemoteBranch using 'client' as its _SmartClient.
1111
A RemoteBzrDir and RemoteRepository will also be created to fill out
1112
the RemoteBranch, albeit with stub values for some of their attributes.
1114
# we do not want bzrdir to make any remote calls, so use False as its
1115
# _client. If it tries to make a remote call, this will fail
1117
bzrdir = self.make_remote_bzrdir(transport, False)
1118
repo = RemoteRepository(bzrdir, None, _client=client)
1119
branch_format = self.get_branch_format()
1120
format = RemoteBranchFormat(network_name=branch_format.network_name())
1121
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1124
class TestBranchBreakLock(RemoteBranchTestCase):
1126
def test_break_lock(self):
1127
transport_path = 'quack'
1128
transport = MemoryTransport()
1129
client = FakeClient(transport.base)
1130
client.add_expected_call(
1131
'Branch.get_stacked_on_url', ('quack/',),
1132
'error', ('NotStacked',))
1133
client.add_expected_call(
1134
'Branch.break_lock', ('quack/',),
1136
transport.mkdir('quack')
1137
transport = transport.clone('quack')
1138
branch = self.make_remote_branch(transport, client)
1140
self.assertFinished(client)
1143
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1145
def test_get_physical_lock_status_yes(self):
1146
transport = MemoryTransport()
1147
client = FakeClient(transport.base)
1148
client.add_expected_call(
1149
'Branch.get_stacked_on_url', ('quack/',),
1150
'error', ('NotStacked',))
1151
client.add_expected_call(
1152
'Branch.get_physical_lock_status', ('quack/',),
1153
'success', ('yes',))
1154
transport.mkdir('quack')
1155
transport = transport.clone('quack')
1156
branch = self.make_remote_branch(transport, client)
1157
result = branch.get_physical_lock_status()
1158
self.assertFinished(client)
1159
self.assertEqual(True, result)
1161
def test_get_physical_lock_status_no(self):
1162
transport = MemoryTransport()
1163
client = FakeClient(transport.base)
1164
client.add_expected_call(
1165
'Branch.get_stacked_on_url', ('quack/',),
1166
'error', ('NotStacked',))
1167
client.add_expected_call(
1168
'Branch.get_physical_lock_status', ('quack/',),
1170
transport.mkdir('quack')
1171
transport = transport.clone('quack')
1172
branch = self.make_remote_branch(transport, client)
1173
result = branch.get_physical_lock_status()
1174
self.assertFinished(client)
1175
self.assertEqual(False, result)
1178
class TestBranchGetParent(RemoteBranchTestCase):
1180
def test_no_parent(self):
1181
# in an empty branch we decode the response properly
1182
transport = MemoryTransport()
1183
client = FakeClient(transport.base)
1184
client.add_expected_call(
1185
'Branch.get_stacked_on_url', ('quack/',),
1186
'error', ('NotStacked',))
1187
client.add_expected_call(
1188
'Branch.get_parent', ('quack/',),
1190
transport.mkdir('quack')
1191
transport = transport.clone('quack')
1192
branch = self.make_remote_branch(transport, client)
1193
result = branch.get_parent()
1194
self.assertFinished(client)
1195
self.assertEqual(None, result)
1197
def test_parent_relative(self):
1198
transport = MemoryTransport()
1199
client = FakeClient(transport.base)
1200
client.add_expected_call(
1201
'Branch.get_stacked_on_url', ('kwaak/',),
1202
'error', ('NotStacked',))
1203
client.add_expected_call(
1204
'Branch.get_parent', ('kwaak/',),
1205
'success', ('../foo/',))
1206
transport.mkdir('kwaak')
1207
transport = transport.clone('kwaak')
1208
branch = self.make_remote_branch(transport, client)
1209
result = branch.get_parent()
1210
self.assertEqual(transport.clone('../foo').base, result)
1212
def test_parent_absolute(self):
1213
transport = MemoryTransport()
1214
client = FakeClient(transport.base)
1215
client.add_expected_call(
1216
'Branch.get_stacked_on_url', ('kwaak/',),
1217
'error', ('NotStacked',))
1218
client.add_expected_call(
1219
'Branch.get_parent', ('kwaak/',),
1220
'success', ('http://foo/',))
1221
transport.mkdir('kwaak')
1222
transport = transport.clone('kwaak')
1223
branch = self.make_remote_branch(transport, client)
1224
result = branch.get_parent()
1225
self.assertEqual('http://foo/', result)
1226
self.assertFinished(client)
1229
class TestBranchSetParentLocation(RemoteBranchTestCase):
1231
def test_no_parent(self):
1232
# We call the verb when setting parent to None
1233
transport = MemoryTransport()
1234
client = FakeClient(transport.base)
1235
client.add_expected_call(
1236
'Branch.get_stacked_on_url', ('quack/',),
1237
'error', ('NotStacked',))
1238
client.add_expected_call(
1239
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1241
transport.mkdir('quack')
1242
transport = transport.clone('quack')
1243
branch = self.make_remote_branch(transport, client)
1244
branch._lock_token = 'b'
1245
branch._repo_lock_token = 'r'
1246
branch._set_parent_location(None)
1247
self.assertFinished(client)
1249
def test_parent(self):
1250
transport = MemoryTransport()
1251
client = FakeClient(transport.base)
1252
client.add_expected_call(
1253
'Branch.get_stacked_on_url', ('kwaak/',),
1254
'error', ('NotStacked',))
1255
client.add_expected_call(
1256
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1258
transport.mkdir('kwaak')
1259
transport = transport.clone('kwaak')
1260
branch = self.make_remote_branch(transport, client)
1261
branch._lock_token = 'b'
1262
branch._repo_lock_token = 'r'
1263
branch._set_parent_location('foo')
1264
self.assertFinished(client)
1266
def test_backwards_compat(self):
1267
self.setup_smart_server_with_call_log()
1268
branch = self.make_branch('.')
1269
self.reset_smart_call_log()
1270
verb = 'Branch.set_parent_location'
1271
self.disable_verb(verb)
1272
branch.set_parent('http://foo/')
1273
self.assertLength(12, self.hpss_calls)
1276
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1278
def test_backwards_compat(self):
1279
self.setup_smart_server_with_call_log()
1280
branch = self.make_branch('.')
1281
self.reset_smart_call_log()
1282
verb = 'Branch.get_tags_bytes'
1283
self.disable_verb(verb)
1284
branch.tags.get_tag_dict()
1285
call_count = len([call for call in self.hpss_calls if
1286
call.call.method == verb])
1287
self.assertEqual(1, call_count)
1289
def test_trivial(self):
1290
transport = MemoryTransport()
1291
client = FakeClient(transport.base)
1292
client.add_expected_call(
1293
'Branch.get_stacked_on_url', ('quack/',),
1294
'error', ('NotStacked',))
1295
client.add_expected_call(
1296
'Branch.get_tags_bytes', ('quack/',),
1298
transport.mkdir('quack')
1299
transport = transport.clone('quack')
1300
branch = self.make_remote_branch(transport, client)
1301
result = branch.tags.get_tag_dict()
1302
self.assertFinished(client)
1303
self.assertEqual({}, result)
1306
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1308
def test_trivial(self):
1309
transport = MemoryTransport()
1310
client = FakeClient(transport.base)
1311
client.add_expected_call(
1312
'Branch.get_stacked_on_url', ('quack/',),
1313
'error', ('NotStacked',))
1314
client.add_expected_call(
1315
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1317
transport.mkdir('quack')
1318
transport = transport.clone('quack')
1319
branch = self.make_remote_branch(transport, client)
1320
self.lock_remote_branch(branch)
1321
branch._set_tags_bytes('tags bytes')
1322
self.assertFinished(client)
1323
self.assertEqual('tags bytes', client._calls[-1][-1])
1325
def test_backwards_compatible(self):
1326
transport = MemoryTransport()
1327
client = FakeClient(transport.base)
1328
client.add_expected_call(
1329
'Branch.get_stacked_on_url', ('quack/',),
1330
'error', ('NotStacked',))
1331
client.add_expected_call(
1332
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1333
'unknown', ('Branch.set_tags_bytes',))
1334
transport.mkdir('quack')
1335
transport = transport.clone('quack')
1336
branch = self.make_remote_branch(transport, client)
1337
self.lock_remote_branch(branch)
1338
class StubRealBranch(object):
1341
def _set_tags_bytes(self, bytes):
1342
self.calls.append(('set_tags_bytes', bytes))
1343
real_branch = StubRealBranch()
1344
branch._real_branch = real_branch
1345
branch._set_tags_bytes('tags bytes')
1346
# Call a second time, to exercise the 'remote version already inferred'
1348
branch._set_tags_bytes('tags bytes')
1349
self.assertFinished(client)
1351
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1354
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1356
def test_uses_last_revision_info_and_tags_by_default(self):
1357
transport = MemoryTransport()
1358
client = FakeClient(transport.base)
1359
client.add_expected_call(
1360
'Branch.get_stacked_on_url', ('quack/',),
1361
'error', ('NotStacked',))
1362
client.add_expected_call(
1363
'Branch.last_revision_info', ('quack/',),
1364
'success', ('ok', '1', 'rev-tip'))
1365
client.add_expected_call(
1366
'Branch.get_config_file', ('quack/',),
1367
'success', ('ok',), '')
1368
transport.mkdir('quack')
1369
transport = transport.clone('quack')
1370
branch = self.make_remote_branch(transport, client)
1371
result = branch.heads_to_fetch()
1372
self.assertFinished(client)
1373
self.assertEqual((set(['rev-tip']), set()), result)
1375
def test_uses_last_revision_info_and_tags_when_set(self):
1376
transport = MemoryTransport()
1377
client = FakeClient(transport.base)
1378
client.add_expected_call(
1379
'Branch.get_stacked_on_url', ('quack/',),
1380
'error', ('NotStacked',))
1381
client.add_expected_call(
1382
'Branch.last_revision_info', ('quack/',),
1383
'success', ('ok', '1', 'rev-tip'))
1384
client.add_expected_call(
1385
'Branch.get_config_file', ('quack/',),
1386
'success', ('ok',), 'branch.fetch_tags = True')
1387
# XXX: this will break if the default format's serialization of tags
1388
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1389
client.add_expected_call(
1390
'Branch.get_tags_bytes', ('quack/',),
1391
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1392
transport.mkdir('quack')
1393
transport = transport.clone('quack')
1394
branch = self.make_remote_branch(transport, client)
1395
result = branch.heads_to_fetch()
1396
self.assertFinished(client)
1398
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1400
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1401
transport = MemoryTransport()
1402
client = FakeClient(transport.base)
1403
client.add_expected_call(
1404
'Branch.get_stacked_on_url', ('quack/',),
1405
'error', ('NotStacked',))
1406
client.add_expected_call(
1407
'Branch.heads_to_fetch', ('quack/',),
1408
'success', (['tip'], ['tagged-1', 'tagged-2']))
1409
transport.mkdir('quack')
1410
transport = transport.clone('quack')
1411
branch = self.make_remote_branch(transport, client)
1412
branch._format._use_default_local_heads_to_fetch = lambda: False
1413
result = branch.heads_to_fetch()
1414
self.assertFinished(client)
1415
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1417
def make_branch_with_tags(self):
1418
self.setup_smart_server_with_call_log()
1419
# Make a branch with a single revision.
1420
builder = self.make_branch_builder('foo')
1421
builder.start_series()
1422
builder.build_snapshot('tip', None, [
1423
('add', ('', 'root-id', 'directory', ''))])
1424
builder.finish_series()
1425
branch = builder.get_branch()
1426
# Add two tags to that branch
1427
branch.tags.set_tag('tag-1', 'rev-1')
1428
branch.tags.set_tag('tag-2', 'rev-2')
1431
def test_backwards_compatible(self):
1432
branch = self.make_branch_with_tags()
1433
c = branch.get_config()
1434
c.set_user_option('branch.fetch_tags', 'True')
1435
self.addCleanup(branch.lock_read().unlock)
1436
# Disable the heads_to_fetch verb
1437
verb = 'Branch.heads_to_fetch'
1438
self.disable_verb(verb)
1439
self.reset_smart_call_log()
1440
result = branch.heads_to_fetch()
1441
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1443
['Branch.last_revision_info', 'Branch.get_config_file',
1444
'Branch.get_tags_bytes'],
1445
[call.call.method for call in self.hpss_calls])
1447
def test_backwards_compatible_no_tags(self):
1448
branch = self.make_branch_with_tags()
1449
c = branch.get_config()
1450
c.set_user_option('branch.fetch_tags', 'False')
1451
self.addCleanup(branch.lock_read().unlock)
1452
# Disable the heads_to_fetch verb
1453
verb = 'Branch.heads_to_fetch'
1454
self.disable_verb(verb)
1455
self.reset_smart_call_log()
1456
result = branch.heads_to_fetch()
1457
self.assertEqual((set(['tip']), set()), result)
1459
['Branch.last_revision_info', 'Branch.get_config_file'],
1460
[call.call.method for call in self.hpss_calls])
1463
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1465
def test_empty_branch(self):
1466
# in an empty branch we decode the response properly
1467
transport = MemoryTransport()
1468
client = FakeClient(transport.base)
1469
client.add_expected_call(
1470
'Branch.get_stacked_on_url', ('quack/',),
1471
'error', ('NotStacked',))
1472
client.add_expected_call(
1473
'Branch.last_revision_info', ('quack/',),
1474
'success', ('ok', '0', 'null:'))
1475
transport.mkdir('quack')
1476
transport = transport.clone('quack')
1477
branch = self.make_remote_branch(transport, client)
1478
result = branch.last_revision_info()
1479
self.assertFinished(client)
1480
self.assertEqual((0, NULL_REVISION), result)
1482
def test_non_empty_branch(self):
1483
# in a non-empty branch we also decode the response properly
1484
revid = u'\xc8'.encode('utf8')
1485
transport = MemoryTransport()
1486
client = FakeClient(transport.base)
1487
client.add_expected_call(
1488
'Branch.get_stacked_on_url', ('kwaak/',),
1489
'error', ('NotStacked',))
1490
client.add_expected_call(
1491
'Branch.last_revision_info', ('kwaak/',),
1492
'success', ('ok', '2', revid))
1493
transport.mkdir('kwaak')
1494
transport = transport.clone('kwaak')
1495
branch = self.make_remote_branch(transport, client)
1496
result = branch.last_revision_info()
1497
self.assertEqual((2, revid), result)
1500
class TestBranch_get_stacked_on_url(TestRemote):
1501
"""Test Branch._get_stacked_on_url rpc"""
1503
def test_get_stacked_on_invalid_url(self):
1504
# test that asking for a stacked on url the server can't access works.
1505
# This isn't perfect, but then as we're in the same process there
1506
# really isn't anything we can do to be 100% sure that the server
1507
# doesn't just open in - this test probably needs to be rewritten using
1508
# a spawn()ed server.
1509
stacked_branch = self.make_branch('stacked', format='1.9')
1510
memory_branch = self.make_branch('base', format='1.9')
1511
vfs_url = self.get_vfs_only_url('base')
1512
stacked_branch.set_stacked_on_url(vfs_url)
1513
transport = stacked_branch.bzrdir.root_transport
1514
client = FakeClient(transport.base)
1515
client.add_expected_call(
1516
'Branch.get_stacked_on_url', ('stacked/',),
1517
'success', ('ok', vfs_url))
1518
# XXX: Multiple calls are bad, this second call documents what is
1520
client.add_expected_call(
1521
'Branch.get_stacked_on_url', ('stacked/',),
1522
'success', ('ok', vfs_url))
1523
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1525
repo_fmt = remote.RemoteRepositoryFormat()
1526
repo_fmt._custom_format = stacked_branch.repository._format
1527
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1529
result = branch.get_stacked_on_url()
1530
self.assertEqual(vfs_url, result)
1532
def test_backwards_compatible(self):
1533
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1534
base_branch = self.make_branch('base', format='1.6')
1535
stacked_branch = self.make_branch('stacked', format='1.6')
1536
stacked_branch.set_stacked_on_url('../base')
1537
client = FakeClient(self.get_url())
1538
branch_network_name = self.get_branch_format().network_name()
1539
client.add_expected_call(
1540
'BzrDir.open_branchV3', ('stacked/',),
1541
'success', ('branch', branch_network_name))
1542
client.add_expected_call(
1543
'BzrDir.find_repositoryV3', ('stacked/',),
1544
'success', ('ok', '', 'no', 'no', 'yes',
1545
stacked_branch.repository._format.network_name()))
1546
# called twice, once from constructor and then again by us
1547
client.add_expected_call(
1548
'Branch.get_stacked_on_url', ('stacked/',),
1549
'unknown', ('Branch.get_stacked_on_url',))
1550
client.add_expected_call(
1551
'Branch.get_stacked_on_url', ('stacked/',),
1552
'unknown', ('Branch.get_stacked_on_url',))
1553
# this will also do vfs access, but that goes direct to the transport
1554
# and isn't seen by the FakeClient.
1555
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1556
RemoteBzrDirFormat(), _client=client)
1557
branch = bzrdir.open_branch()
1558
result = branch.get_stacked_on_url()
1559
self.assertEqual('../base', result)
1560
self.assertFinished(client)
1561
# it's in the fallback list both for the RemoteRepository and its vfs
1563
self.assertEqual(1, len(branch.repository._fallback_repositories))
1565
len(branch.repository._real_repository._fallback_repositories))
1567
def test_get_stacked_on_real_branch(self):
1568
base_branch = self.make_branch('base')
1569
stacked_branch = self.make_branch('stacked')
1570
stacked_branch.set_stacked_on_url('../base')
1571
reference_format = self.get_repo_format()
1572
network_name = reference_format.network_name()
1573
client = FakeClient(self.get_url())
1574
branch_network_name = self.get_branch_format().network_name()
1575
client.add_expected_call(
1576
'BzrDir.open_branchV3', ('stacked/',),
1577
'success', ('branch', branch_network_name))
1578
client.add_expected_call(
1579
'BzrDir.find_repositoryV3', ('stacked/',),
1580
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1581
# called twice, once from constructor and then again by us
1582
client.add_expected_call(
1583
'Branch.get_stacked_on_url', ('stacked/',),
1584
'success', ('ok', '../base'))
1585
client.add_expected_call(
1586
'Branch.get_stacked_on_url', ('stacked/',),
1587
'success', ('ok', '../base'))
1588
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1589
RemoteBzrDirFormat(), _client=client)
1590
branch = bzrdir.open_branch()
1591
result = branch.get_stacked_on_url()
1592
self.assertEqual('../base', result)
1593
self.assertFinished(client)
1594
# it's in the fallback list both for the RemoteRepository.
1595
self.assertEqual(1, len(branch.repository._fallback_repositories))
1596
# And we haven't had to construct a real repository.
1597
self.assertEqual(None, branch.repository._real_repository)
1600
class TestBranchSetLastRevision(RemoteBranchTestCase):
1602
def test_set_empty(self):
1603
# _set_last_revision_info('null:') is translated to calling
1604
# Branch.set_last_revision(path, '') on the wire.
1605
transport = MemoryTransport()
1606
transport.mkdir('branch')
1607
transport = transport.clone('branch')
1609
client = FakeClient(transport.base)
1610
client.add_expected_call(
1611
'Branch.get_stacked_on_url', ('branch/',),
1612
'error', ('NotStacked',))
1613
client.add_expected_call(
1614
'Branch.lock_write', ('branch/', '', ''),
1615
'success', ('ok', 'branch token', 'repo token'))
1616
client.add_expected_call(
1617
'Branch.last_revision_info',
1619
'success', ('ok', '0', 'null:'))
1620
client.add_expected_call(
1621
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1623
client.add_expected_call(
1624
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1626
branch = self.make_remote_branch(transport, client)
1628
result = branch._set_last_revision(NULL_REVISION)
1630
self.assertEqual(None, result)
1631
self.assertFinished(client)
1633
def test_set_nonempty(self):
1634
# set_last_revision_info(N, rev-idN) is translated to calling
1635
# Branch.set_last_revision(path, rev-idN) on the wire.
1636
transport = MemoryTransport()
1637
transport.mkdir('branch')
1638
transport = transport.clone('branch')
1640
client = FakeClient(transport.base)
1641
client.add_expected_call(
1642
'Branch.get_stacked_on_url', ('branch/',),
1643
'error', ('NotStacked',))
1644
client.add_expected_call(
1645
'Branch.lock_write', ('branch/', '', ''),
1646
'success', ('ok', 'branch token', 'repo token'))
1647
client.add_expected_call(
1648
'Branch.last_revision_info',
1650
'success', ('ok', '0', 'null:'))
1652
encoded_body = bz2.compress('\n'.join(lines))
1653
client.add_success_response_with_body(encoded_body, 'ok')
1654
client.add_expected_call(
1655
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1657
client.add_expected_call(
1658
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1660
branch = self.make_remote_branch(transport, client)
1661
# Lock the branch, reset the record of remote calls.
1663
result = branch._set_last_revision('rev-id2')
1665
self.assertEqual(None, result)
1666
self.assertFinished(client)
1668
def test_no_such_revision(self):
1669
transport = MemoryTransport()
1670
transport.mkdir('branch')
1671
transport = transport.clone('branch')
1672
# A response of 'NoSuchRevision' is translated into an exception.
1673
client = FakeClient(transport.base)
1674
client.add_expected_call(
1675
'Branch.get_stacked_on_url', ('branch/',),
1676
'error', ('NotStacked',))
1677
client.add_expected_call(
1678
'Branch.lock_write', ('branch/', '', ''),
1679
'success', ('ok', 'branch token', 'repo token'))
1680
client.add_expected_call(
1681
'Branch.last_revision_info',
1683
'success', ('ok', '0', 'null:'))
1684
# get_graph calls to construct the revision history, for the set_rh
1687
encoded_body = bz2.compress('\n'.join(lines))
1688
client.add_success_response_with_body(encoded_body, 'ok')
1689
client.add_expected_call(
1690
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1691
'error', ('NoSuchRevision', 'rev-id'))
1692
client.add_expected_call(
1693
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1696
branch = self.make_remote_branch(transport, client)
1699
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1701
self.assertFinished(client)
1703
def test_tip_change_rejected(self):
1704
"""TipChangeRejected responses cause a TipChangeRejected exception to
1707
transport = MemoryTransport()
1708
transport.mkdir('branch')
1709
transport = transport.clone('branch')
1710
client = FakeClient(transport.base)
1711
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1712
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1713
client.add_expected_call(
1714
'Branch.get_stacked_on_url', ('branch/',),
1715
'error', ('NotStacked',))
1716
client.add_expected_call(
1717
'Branch.lock_write', ('branch/', '', ''),
1718
'success', ('ok', 'branch token', 'repo token'))
1719
client.add_expected_call(
1720
'Branch.last_revision_info',
1722
'success', ('ok', '0', 'null:'))
1724
encoded_body = bz2.compress('\n'.join(lines))
1725
client.add_success_response_with_body(encoded_body, 'ok')
1726
client.add_expected_call(
1727
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1728
'error', ('TipChangeRejected', rejection_msg_utf8))
1729
client.add_expected_call(
1730
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1732
branch = self.make_remote_branch(transport, client)
1734
# The 'TipChangeRejected' error response triggered by calling
1735
# set_last_revision_info causes a TipChangeRejected exception.
1736
err = self.assertRaises(
1737
errors.TipChangeRejected,
1738
branch._set_last_revision, 'rev-id')
1739
# The UTF-8 message from the response has been decoded into a unicode
1741
self.assertIsInstance(err.msg, unicode)
1742
self.assertEqual(rejection_msg_unicode, err.msg)
1744
self.assertFinished(client)
1747
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1749
def test_set_last_revision_info(self):
1750
# set_last_revision_info(num, 'rev-id') is translated to calling
1751
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1752
transport = MemoryTransport()
1753
transport.mkdir('branch')
1754
transport = transport.clone('branch')
1755
client = FakeClient(transport.base)
1756
# get_stacked_on_url
1757
client.add_error_response('NotStacked')
1759
client.add_success_response('ok', 'branch token', 'repo token')
1760
# query the current revision
1761
client.add_success_response('ok', '0', 'null:')
1763
client.add_success_response('ok')
1765
client.add_success_response('ok')
1767
branch = self.make_remote_branch(transport, client)
1768
# Lock the branch, reset the record of remote calls.
1771
result = branch.set_last_revision_info(1234, 'a-revision-id')
1773
[('call', 'Branch.last_revision_info', ('branch/',)),
1774
('call', 'Branch.set_last_revision_info',
1775
('branch/', 'branch token', 'repo token',
1776
'1234', 'a-revision-id'))],
1778
self.assertEqual(None, result)
1780
def test_no_such_revision(self):
1781
# A response of 'NoSuchRevision' is translated into an exception.
1782
transport = MemoryTransport()
1783
transport.mkdir('branch')
1784
transport = transport.clone('branch')
1785
client = FakeClient(transport.base)
1786
# get_stacked_on_url
1787
client.add_error_response('NotStacked')
1789
client.add_success_response('ok', 'branch token', 'repo token')
1791
client.add_error_response('NoSuchRevision', 'revid')
1793
client.add_success_response('ok')
1795
branch = self.make_remote_branch(transport, client)
1796
# Lock the branch, reset the record of remote calls.
1801
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1804
def test_backwards_compatibility(self):
1805
"""If the server does not support the Branch.set_last_revision_info
1806
verb (which is new in 1.4), then the client falls back to VFS methods.
1808
# This test is a little messy. Unlike most tests in this file, it
1809
# doesn't purely test what a Remote* object sends over the wire, and
1810
# how it reacts to responses from the wire. It instead relies partly
1811
# on asserting that the RemoteBranch will call
1812
# self._real_branch.set_last_revision_info(...).
1814
# First, set up our RemoteBranch with a FakeClient that raises
1815
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1816
transport = MemoryTransport()
1817
transport.mkdir('branch')
1818
transport = transport.clone('branch')
1819
client = FakeClient(transport.base)
1820
client.add_expected_call(
1821
'Branch.get_stacked_on_url', ('branch/',),
1822
'error', ('NotStacked',))
1823
client.add_expected_call(
1824
'Branch.last_revision_info',
1826
'success', ('ok', '0', 'null:'))
1827
client.add_expected_call(
1828
'Branch.set_last_revision_info',
1829
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1830
'unknown', 'Branch.set_last_revision_info')
1832
branch = self.make_remote_branch(transport, client)
1833
class StubRealBranch(object):
1836
def set_last_revision_info(self, revno, revision_id):
1838
('set_last_revision_info', revno, revision_id))
1839
def _clear_cached_state(self):
1841
real_branch = StubRealBranch()
1842
branch._real_branch = real_branch
1843
self.lock_remote_branch(branch)
1845
# Call set_last_revision_info, and verify it behaved as expected.
1846
result = branch.set_last_revision_info(1234, 'a-revision-id')
1848
[('set_last_revision_info', 1234, 'a-revision-id')],
1850
self.assertFinished(client)
1852
def test_unexpected_error(self):
1853
# If the server sends an error the client doesn't understand, it gets
1854
# turned into an UnknownErrorFromSmartServer, which is presented as a
1855
# non-internal error to the user.
1856
transport = MemoryTransport()
1857
transport.mkdir('branch')
1858
transport = transport.clone('branch')
1859
client = FakeClient(transport.base)
1860
# get_stacked_on_url
1861
client.add_error_response('NotStacked')
1863
client.add_success_response('ok', 'branch token', 'repo token')
1865
client.add_error_response('UnexpectedError')
1867
client.add_success_response('ok')
1869
branch = self.make_remote_branch(transport, client)
1870
# Lock the branch, reset the record of remote calls.
1874
err = self.assertRaises(
1875
errors.UnknownErrorFromSmartServer,
1876
branch.set_last_revision_info, 123, 'revid')
1877
self.assertEqual(('UnexpectedError',), err.error_tuple)
1880
def test_tip_change_rejected(self):
1881
"""TipChangeRejected responses cause a TipChangeRejected exception to
1884
transport = MemoryTransport()
1885
transport.mkdir('branch')
1886
transport = transport.clone('branch')
1887
client = FakeClient(transport.base)
1888
# get_stacked_on_url
1889
client.add_error_response('NotStacked')
1891
client.add_success_response('ok', 'branch token', 'repo token')
1893
client.add_error_response('TipChangeRejected', 'rejection message')
1895
client.add_success_response('ok')
1897
branch = self.make_remote_branch(transport, client)
1898
# Lock the branch, reset the record of remote calls.
1900
self.addCleanup(branch.unlock)
1903
# The 'TipChangeRejected' error response triggered by calling
1904
# set_last_revision_info causes a TipChangeRejected exception.
1905
err = self.assertRaises(
1906
errors.TipChangeRejected,
1907
branch.set_last_revision_info, 123, 'revid')
1908
self.assertEqual('rejection message', err.msg)
1911
class TestBranchGetSetConfig(RemoteBranchTestCase):
1913
def test_get_branch_conf(self):
1914
# in an empty branch we decode the response properly
1915
client = FakeClient()
1916
client.add_expected_call(
1917
'Branch.get_stacked_on_url', ('memory:///',),
1918
'error', ('NotStacked',),)
1919
client.add_success_response_with_body('# config file body', 'ok')
1920
transport = MemoryTransport()
1921
branch = self.make_remote_branch(transport, client)
1922
config = branch.get_config()
1923
config.has_explicit_nickname()
1925
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1926
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1929
def test_get_multi_line_branch_conf(self):
1930
# Make sure that multiple-line branch.conf files are supported
1932
# https://bugs.launchpad.net/bzr/+bug/354075
1933
client = FakeClient()
1934
client.add_expected_call(
1935
'Branch.get_stacked_on_url', ('memory:///',),
1936
'error', ('NotStacked',),)
1937
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1938
transport = MemoryTransport()
1939
branch = self.make_remote_branch(transport, client)
1940
config = branch.get_config()
1941
self.assertEqual(u'2', config.get_user_option('b'))
1943
def test_set_option(self):
1944
client = FakeClient()
1945
client.add_expected_call(
1946
'Branch.get_stacked_on_url', ('memory:///',),
1947
'error', ('NotStacked',),)
1948
client.add_expected_call(
1949
'Branch.lock_write', ('memory:///', '', ''),
1950
'success', ('ok', 'branch token', 'repo token'))
1951
client.add_expected_call(
1952
'Branch.set_config_option', ('memory:///', 'branch token',
1953
'repo token', 'foo', 'bar', ''),
1955
client.add_expected_call(
1956
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1958
transport = MemoryTransport()
1959
branch = self.make_remote_branch(transport, client)
1961
config = branch._get_config()
1962
config.set_option('foo', 'bar')
1964
self.assertFinished(client)
1966
def test_set_option_with_dict(self):
1967
client = FakeClient()
1968
client.add_expected_call(
1969
'Branch.get_stacked_on_url', ('memory:///',),
1970
'error', ('NotStacked',),)
1971
client.add_expected_call(
1972
'Branch.lock_write', ('memory:///', '', ''),
1973
'success', ('ok', 'branch token', 'repo token'))
1974
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1975
client.add_expected_call(
1976
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1977
'repo token', encoded_dict_value, 'foo', ''),
1979
client.add_expected_call(
1980
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1982
transport = MemoryTransport()
1983
branch = self.make_remote_branch(transport, client)
1985
config = branch._get_config()
1987
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1990
self.assertFinished(client)
1992
def test_backwards_compat_set_option(self):
1993
self.setup_smart_server_with_call_log()
1994
branch = self.make_branch('.')
1995
verb = 'Branch.set_config_option'
1996
self.disable_verb(verb)
1998
self.addCleanup(branch.unlock)
1999
self.reset_smart_call_log()
2000
branch._get_config().set_option('value', 'name')
2001
self.assertLength(10, self.hpss_calls)
2002
self.assertEqual('value', branch._get_config().get_option('name'))
2004
def test_backwards_compat_set_option_with_dict(self):
2005
self.setup_smart_server_with_call_log()
2006
branch = self.make_branch('.')
2007
verb = 'Branch.set_config_option_dict'
2008
self.disable_verb(verb)
2010
self.addCleanup(branch.unlock)
2011
self.reset_smart_call_log()
2012
config = branch._get_config()
2013
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2014
config.set_option(value_dict, 'name')
2015
self.assertLength(10, self.hpss_calls)
2016
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2019
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2021
def test_get_branch_conf(self):
2022
# in an empty branch we decode the response properly
2023
client = FakeClient()
2024
client.add_expected_call(
2025
'Branch.get_stacked_on_url', ('memory:///',),
2026
'error', ('NotStacked',),)
2027
client.add_success_response_with_body('# config file body', 'ok')
2028
transport = MemoryTransport()
2029
branch = self.make_remote_branch(transport, client)
2030
config = branch.get_config_stack()
2032
config.get("log_format")
2034
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2035
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2038
def test_set_branch_conf(self):
2039
client = FakeClient()
2040
client.add_expected_call(
2041
'Branch.get_stacked_on_url', ('memory:///',),
2042
'error', ('NotStacked',),)
2043
client.add_expected_call(
2044
'Branch.lock_write', ('memory:///', '', ''),
2045
'success', ('ok', 'branch token', 'repo token'))
2046
client.add_expected_call(
2047
'Branch.get_config_file', ('memory:///', ),
2048
'success', ('ok', ), "# line 1\n")
2049
client.add_expected_call(
2050
'Branch.put_config_file', ('memory:///', 'branch token',
2053
client.add_expected_call(
2054
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2056
transport = MemoryTransport()
2057
branch = self.make_remote_branch(transport, client)
2059
config = branch.get_config_stack()
2060
config.set('email', 'The Dude <lebowski@example.com>')
2062
self.assertFinished(client)
2064
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2065
('call', 'Branch.lock_write', ('memory:///', '', '')),
2066
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2067
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2068
('memory:///', 'branch token', 'repo token'),
2069
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2070
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2074
class TestBranchLockWrite(RemoteBranchTestCase):
2076
def test_lock_write_unlockable(self):
2077
transport = MemoryTransport()
2078
client = FakeClient(transport.base)
2079
client.add_expected_call(
2080
'Branch.get_stacked_on_url', ('quack/',),
2081
'error', ('NotStacked',),)
2082
client.add_expected_call(
2083
'Branch.lock_write', ('quack/', '', ''),
2084
'error', ('UnlockableTransport',))
2085
transport.mkdir('quack')
2086
transport = transport.clone('quack')
2087
branch = self.make_remote_branch(transport, client)
2088
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2089
self.assertFinished(client)
2092
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2094
def test_simple(self):
2095
transport = MemoryTransport()
2096
client = FakeClient(transport.base)
2097
client.add_expected_call(
2098
'Branch.get_stacked_on_url', ('quack/',),
2099
'error', ('NotStacked',),)
2100
client.add_expected_call(
2101
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2102
'success', ('ok', '0',),)
2103
client.add_expected_call(
2104
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2105
'error', ('NoSuchRevision', 'unknown',),)
2106
transport.mkdir('quack')
2107
transport = transport.clone('quack')
2108
branch = self.make_remote_branch(transport, client)
2109
self.assertEquals(0, branch.revision_id_to_revno('null:'))
2110
self.assertRaises(errors.NoSuchRevision,
2111
branch.revision_id_to_revno, 'unknown')
2112
self.assertFinished(client)
2114
def test_dotted(self):
2115
transport = MemoryTransport()
2116
client = FakeClient(transport.base)
2117
client.add_expected_call(
2118
'Branch.get_stacked_on_url', ('quack/',),
2119
'error', ('NotStacked',),)
2120
client.add_expected_call(
2121
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2122
'success', ('ok', '0',),)
2123
client.add_expected_call(
2124
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2125
'error', ('NoSuchRevision', 'unknown',),)
2126
transport.mkdir('quack')
2127
transport = transport.clone('quack')
2128
branch = self.make_remote_branch(transport, client)
2129
self.assertEquals((0, ), branch.revision_id_to_dotted_revno('null:'))
2130
self.assertRaises(errors.NoSuchRevision,
2131
branch.revision_id_to_dotted_revno, 'unknown')
2132
self.assertFinished(client)
2134
def test_dotted_no_smart_verb(self):
2135
self.setup_smart_server_with_call_log()
2136
branch = self.make_branch('.')
2137
self.disable_verb('Branch.revision_id_to_revno')
2138
self.reset_smart_call_log()
2139
self.assertEquals((0, ),
2140
branch.revision_id_to_dotted_revno('null:'))
2141
self.assertLength(7, self.hpss_calls)
2144
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2146
def test__get_config(self):
2147
client = FakeClient()
2148
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2149
transport = MemoryTransport()
2150
bzrdir = self.make_remote_bzrdir(transport, client)
2151
config = bzrdir.get_config()
2152
self.assertEqual('/', config.get_default_stack_on())
2154
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2157
def test_set_option_uses_vfs(self):
2158
self.setup_smart_server_with_call_log()
2159
bzrdir = self.make_bzrdir('.')
2160
self.reset_smart_call_log()
2161
config = bzrdir.get_config()
2162
config.set_default_stack_on('/')
2163
self.assertLength(3, self.hpss_calls)
2165
def test_backwards_compat_get_option(self):
2166
self.setup_smart_server_with_call_log()
2167
bzrdir = self.make_bzrdir('.')
2168
verb = 'BzrDir.get_config_file'
2169
self.disable_verb(verb)
2170
self.reset_smart_call_log()
2171
self.assertEqual(None,
2172
bzrdir._get_config().get_option('default_stack_on'))
2173
self.assertLength(3, self.hpss_calls)
2176
class TestTransportIsReadonly(tests.TestCase):
2178
def test_true(self):
2179
client = FakeClient()
2180
client.add_success_response('yes')
2181
transport = RemoteTransport('bzr://example.com/', medium=False,
2183
self.assertEqual(True, transport.is_readonly())
2185
[('call', 'Transport.is_readonly', ())],
2188
def test_false(self):
2189
client = FakeClient()
2190
client.add_success_response('no')
2191
transport = RemoteTransport('bzr://example.com/', medium=False,
2193
self.assertEqual(False, transport.is_readonly())
2195
[('call', 'Transport.is_readonly', ())],
2198
def test_error_from_old_server(self):
2199
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2201
Clients should treat it as a "no" response, because is_readonly is only
2202
advisory anyway (a transport could be read-write, but then the
2203
underlying filesystem could be readonly anyway).
2205
client = FakeClient()
2206
client.add_unknown_method_response('Transport.is_readonly')
2207
transport = RemoteTransport('bzr://example.com/', medium=False,
2209
self.assertEqual(False, transport.is_readonly())
2211
[('call', 'Transport.is_readonly', ())],
2215
class TestTransportMkdir(tests.TestCase):
2217
def test_permissiondenied(self):
2218
client = FakeClient()
2219
client.add_error_response('PermissionDenied', 'remote path', 'extra')
2220
transport = RemoteTransport('bzr://example.com/', medium=False,
2222
exc = self.assertRaises(
2223
errors.PermissionDenied, transport.mkdir, 'client path')
2224
expected_error = errors.PermissionDenied('/client path', 'extra')
2225
self.assertEqual(expected_error, exc)
2228
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2230
def test_defaults_to_none(self):
2231
t = RemoteSSHTransport('bzr+ssh://example.com')
2232
self.assertIs(None, t._get_credentials()[0])
2234
def test_uses_authentication_config(self):
2235
conf = config.AuthenticationConfig()
2236
conf._get_config().update(
2237
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2240
t = RemoteSSHTransport('bzr+ssh://example.com')
2241
self.assertEqual('bar', t._get_credentials()[0])
2244
class TestRemoteRepository(TestRemote):
2245
"""Base for testing RemoteRepository protocol usage.
2247
These tests contain frozen requests and responses. We want any changes to
2248
what is sent or expected to be require a thoughtful update to these tests
2249
because they might break compatibility with different-versioned servers.
2252
def setup_fake_client_and_repository(self, transport_path):
2253
"""Create the fake client and repository for testing with.
2255
There's no real server here; we just have canned responses sent
2258
:param transport_path: Path below the root of the MemoryTransport
2259
where the repository will be created.
2261
transport = MemoryTransport()
2262
transport.mkdir(transport_path)
2263
client = FakeClient(transport.base)
2264
transport = transport.clone(transport_path)
2265
# we do not want bzrdir to make any remote calls
2266
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2268
repo = RemoteRepository(bzrdir, None, _client=client)
2272
def remoted_description(format):
2273
return 'Remote: ' + format.get_format_description()
2276
class TestBranchFormat(tests.TestCase):
2278
def test_get_format_description(self):
2279
remote_format = RemoteBranchFormat()
2280
real_format = branch.format_registry.get_default()
2281
remote_format._network_name = real_format.network_name()
2282
self.assertEqual(remoted_description(real_format),
2283
remote_format.get_format_description())
2286
class TestRepositoryFormat(TestRemoteRepository):
2288
def test_fast_delta(self):
2289
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2290
true_format = RemoteRepositoryFormat()
2291
true_format._network_name = true_name
2292
self.assertEqual(True, true_format.fast_deltas)
2293
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2294
false_format = RemoteRepositoryFormat()
2295
false_format._network_name = false_name
2296
self.assertEqual(False, false_format.fast_deltas)
2298
def test_get_format_description(self):
2299
remote_repo_format = RemoteRepositoryFormat()
2300
real_format = repository.format_registry.get_default()
2301
remote_repo_format._network_name = real_format.network_name()
2302
self.assertEqual(remoted_description(real_format),
2303
remote_repo_format.get_format_description())
2306
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2308
def test_empty(self):
2309
transport_path = 'quack'
2310
repo, client = self.setup_fake_client_and_repository(transport_path)
2311
client.add_success_response_with_body('', 'ok')
2312
self.assertEquals([], repo.all_revision_ids())
2314
[('call_expecting_body', 'Repository.all_revision_ids',
2318
def test_with_some_content(self):
2319
transport_path = 'quack'
2320
repo, client = self.setup_fake_client_and_repository(transport_path)
2321
client.add_success_response_with_body(
2322
'rev1\nrev2\nanotherrev\n', 'ok')
2323
self.assertEquals(["rev1", "rev2", "anotherrev"],
2324
repo.all_revision_ids())
2326
[('call_expecting_body', 'Repository.all_revision_ids',
2331
class TestRepositoryGatherStats(TestRemoteRepository):
2333
def test_revid_none(self):
2334
# ('ok',), body with revisions and size
2335
transport_path = 'quack'
2336
repo, client = self.setup_fake_client_and_repository(transport_path)
2337
client.add_success_response_with_body(
2338
'revisions: 2\nsize: 18\n', 'ok')
2339
result = repo.gather_stats(None)
2341
[('call_expecting_body', 'Repository.gather_stats',
2342
('quack/','','no'))],
2344
self.assertEqual({'revisions': 2, 'size': 18}, result)
2346
def test_revid_no_committers(self):
2347
# ('ok',), body without committers
2348
body = ('firstrev: 123456.300 3600\n'
2349
'latestrev: 654231.400 0\n'
2352
transport_path = 'quick'
2353
revid = u'\xc8'.encode('utf8')
2354
repo, client = self.setup_fake_client_and_repository(transport_path)
2355
client.add_success_response_with_body(body, 'ok')
2356
result = repo.gather_stats(revid)
2358
[('call_expecting_body', 'Repository.gather_stats',
2359
('quick/', revid, 'no'))],
2361
self.assertEqual({'revisions': 2, 'size': 18,
2362
'firstrev': (123456.300, 3600),
2363
'latestrev': (654231.400, 0),},
2366
def test_revid_with_committers(self):
2367
# ('ok',), body with committers
2368
body = ('committers: 128\n'
2369
'firstrev: 123456.300 3600\n'
2370
'latestrev: 654231.400 0\n'
2373
transport_path = 'buick'
2374
revid = u'\xc8'.encode('utf8')
2375
repo, client = self.setup_fake_client_and_repository(transport_path)
2376
client.add_success_response_with_body(body, 'ok')
2377
result = repo.gather_stats(revid, True)
2379
[('call_expecting_body', 'Repository.gather_stats',
2380
('buick/', revid, 'yes'))],
2382
self.assertEqual({'revisions': 2, 'size': 18,
2384
'firstrev': (123456.300, 3600),
2385
'latestrev': (654231.400, 0),},
2389
class TestRepositoryBreakLock(TestRemoteRepository):
2391
def test_break_lock(self):
2392
transport_path = 'quack'
2393
repo, client = self.setup_fake_client_and_repository(transport_path)
2394
client.add_success_response('ok')
2397
[('call', 'Repository.break_lock', ('quack/',))],
2401
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2403
def test_get_serializer_format(self):
2404
transport_path = 'hill'
2405
repo, client = self.setup_fake_client_and_repository(transport_path)
2406
client.add_success_response('ok', '7')
2407
self.assertEquals('7', repo.get_serializer_format())
2409
[('call', 'VersionedFileRepository.get_serializer_format',
2414
class TestRepositoryReconcile(TestRemoteRepository):
2416
def test_reconcile(self):
2417
transport_path = 'hill'
2418
repo, client = self.setup_fake_client_and_repository(transport_path)
2419
body = ("garbage_inventories: 2\n"
2420
"inconsistent_parents: 3\n")
2421
client.add_expected_call(
2422
'Repository.lock_write', ('hill/', ''),
2423
'success', ('ok', 'a token'))
2424
client.add_success_response_with_body(body, 'ok')
2425
reconciler = repo.reconcile()
2427
[('call', 'Repository.lock_write', ('hill/', '')),
2428
('call_expecting_body', 'Repository.reconcile',
2429
('hill/', 'a token'))],
2431
self.assertEquals(2, reconciler.garbage_inventories)
2432
self.assertEquals(3, reconciler.inconsistent_parents)
2435
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2437
def test_text(self):
2438
# ('ok',), body with signature text
2439
transport_path = 'quack'
2440
repo, client = self.setup_fake_client_and_repository(transport_path)
2441
client.add_success_response_with_body(
2443
self.assertEquals("THETEXT", repo.get_signature_text("revid"))
2445
[('call_expecting_body', 'Repository.get_revision_signature_text',
2446
('quack/', 'revid'))],
2449
def test_no_signature(self):
2450
transport_path = 'quick'
2451
repo, client = self.setup_fake_client_and_repository(transport_path)
2452
client.add_error_response('nosuchrevision', 'unknown')
2453
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2456
[('call_expecting_body', 'Repository.get_revision_signature_text',
2457
('quick/', 'unknown'))],
2461
class TestRepositoryGetGraph(TestRemoteRepository):
2463
def test_get_graph(self):
2464
# get_graph returns a graph with a custom parents provider.
2465
transport_path = 'quack'
2466
repo, client = self.setup_fake_client_and_repository(transport_path)
2467
graph = repo.get_graph()
2468
self.assertNotEqual(graph._parents_provider, repo)
2471
class TestRepositoryAddSignatureText(TestRemoteRepository):
2473
def test_add_signature_text(self):
2474
transport_path = 'quack'
2475
repo, client = self.setup_fake_client_and_repository(transport_path)
2476
client.add_expected_call(
2477
'Repository.lock_write', ('quack/', ''),
2478
'success', ('ok', 'a token'))
2479
client.add_expected_call(
2480
'Repository.start_write_group', ('quack/', 'a token'),
2481
'success', ('ok', ('token1', )))
2482
client.add_expected_call(
2483
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2485
'success', ('ok', ), None)
2487
repo.start_write_group()
2489
repo.add_signature_text("rev1", "every bloody emperor"))
2491
('call_with_body_bytes_expecting_body',
2492
'Repository.add_signature_text',
2493
('quack/', 'a token', 'rev1', 'token1'),
2494
'every bloody emperor'),
2498
class TestRepositoryGetParentMap(TestRemoteRepository):
2500
def test_get_parent_map_caching(self):
2501
# get_parent_map returns from cache until unlock()
2502
# setup a reponse with two revisions
2503
r1 = u'\u0e33'.encode('utf8')
2504
r2 = u'\u0dab'.encode('utf8')
2505
lines = [' '.join([r2, r1]), r1]
2506
encoded_body = bz2.compress('\n'.join(lines))
2508
transport_path = 'quack'
2509
repo, client = self.setup_fake_client_and_repository(transport_path)
2510
client.add_success_response_with_body(encoded_body, 'ok')
2511
client.add_success_response_with_body(encoded_body, 'ok')
2513
graph = repo.get_graph()
2514
parents = graph.get_parent_map([r2])
2515
self.assertEqual({r2: (r1,)}, parents)
2516
# locking and unlocking deeper should not reset
2519
parents = graph.get_parent_map([r1])
2520
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2522
[('call_with_body_bytes_expecting_body',
2523
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2527
# now we call again, and it should use the second response.
2529
graph = repo.get_graph()
2530
parents = graph.get_parent_map([r1])
2531
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2533
[('call_with_body_bytes_expecting_body',
2534
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2536
('call_with_body_bytes_expecting_body',
2537
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2543
def test_get_parent_map_reconnects_if_unknown_method(self):
2544
transport_path = 'quack'
2545
rev_id = 'revision-id'
2546
repo, client = self.setup_fake_client_and_repository(transport_path)
2547
client.add_unknown_method_response('Repository.get_parent_map')
2548
client.add_success_response_with_body(rev_id, 'ok')
2549
self.assertFalse(client._medium._is_remote_before((1, 2)))
2550
parents = repo.get_parent_map([rev_id])
2552
[('call_with_body_bytes_expecting_body',
2553
'Repository.get_parent_map',
2554
('quack/', 'include-missing:', rev_id), '\n\n0'),
2555
('disconnect medium',),
2556
('call_expecting_body', 'Repository.get_revision_graph',
2559
# The medium is now marked as being connected to an older server
2560
self.assertTrue(client._medium._is_remote_before((1, 2)))
2561
self.assertEqual({rev_id: ('null:',)}, parents)
2563
def test_get_parent_map_fallback_parentless_node(self):
2564
"""get_parent_map falls back to get_revision_graph on old servers. The
2565
results from get_revision_graph are tweaked to match the get_parent_map
2568
Specifically, a {key: ()} result from get_revision_graph means "no
2569
parents" for that key, which in get_parent_map results should be
2570
represented as {key: ('null:',)}.
2572
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2574
rev_id = 'revision-id'
2575
transport_path = 'quack'
2576
repo, client = self.setup_fake_client_and_repository(transport_path)
2577
client.add_success_response_with_body(rev_id, 'ok')
2578
client._medium._remember_remote_is_before((1, 2))
2579
parents = repo.get_parent_map([rev_id])
2581
[('call_expecting_body', 'Repository.get_revision_graph',
2584
self.assertEqual({rev_id: ('null:',)}, parents)
2586
def test_get_parent_map_unexpected_response(self):
2587
repo, client = self.setup_fake_client_and_repository('path')
2588
client.add_success_response('something unexpected!')
2590
errors.UnexpectedSmartServerResponse,
2591
repo.get_parent_map, ['a-revision-id'])
2593
def test_get_parent_map_negative_caches_missing_keys(self):
2594
self.setup_smart_server_with_call_log()
2595
repo = self.make_repository('foo')
2596
self.assertIsInstance(repo, RemoteRepository)
2598
self.addCleanup(repo.unlock)
2599
self.reset_smart_call_log()
2600
graph = repo.get_graph()
2601
self.assertEqual({},
2602
graph.get_parent_map(['some-missing', 'other-missing']))
2603
self.assertLength(1, self.hpss_calls)
2604
# No call if we repeat this
2605
self.reset_smart_call_log()
2606
graph = repo.get_graph()
2607
self.assertEqual({},
2608
graph.get_parent_map(['some-missing', 'other-missing']))
2609
self.assertLength(0, self.hpss_calls)
2610
# Asking for more unknown keys makes a request.
2611
self.reset_smart_call_log()
2612
graph = repo.get_graph()
2613
self.assertEqual({},
2614
graph.get_parent_map(['some-missing', 'other-missing',
2616
self.assertLength(1, self.hpss_calls)
2618
def disableExtraResults(self):
2619
self.overrideAttr(SmartServerRepositoryGetParentMap,
2620
'no_extra_results', True)
2622
def test_null_cached_missing_and_stop_key(self):
2623
self.setup_smart_server_with_call_log()
2624
# Make a branch with a single revision.
2625
builder = self.make_branch_builder('foo')
2626
builder.start_series()
2627
builder.build_snapshot('first', None, [
2628
('add', ('', 'root-id', 'directory', ''))])
2629
builder.finish_series()
2630
branch = builder.get_branch()
2631
repo = branch.repository
2632
self.assertIsInstance(repo, RemoteRepository)
2633
# Stop the server from sending extra results.
2634
self.disableExtraResults()
2636
self.addCleanup(repo.unlock)
2637
self.reset_smart_call_log()
2638
graph = repo.get_graph()
2639
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2640
# 'first' it will be a candidate for the stop_keys of subsequent
2641
# requests, and because 'null:' was queried but not returned it will be
2642
# cached as missing.
2643
self.assertEqual({'first': ('null:',)},
2644
graph.get_parent_map(['first', 'null:']))
2645
# Now query for another key. This request will pass along a recipe of
2646
# start and stop keys describing the already cached results, and this
2647
# recipe's revision count must be correct (or else it will trigger an
2648
# error from the server).
2649
self.assertEqual({}, graph.get_parent_map(['another-key']))
2650
# This assertion guards against disableExtraResults silently failing to
2651
# work, thus invalidating the test.
2652
self.assertLength(2, self.hpss_calls)
2654
def test_get_parent_map_gets_ghosts_from_result(self):
2655
# asking for a revision should negatively cache close ghosts in its
2657
self.setup_smart_server_with_call_log()
2658
tree = self.make_branch_and_memory_tree('foo')
2661
builder = treebuilder.TreeBuilder()
2662
builder.start_tree(tree)
2664
builder.finish_tree()
2665
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2666
rev_id = tree.commit('')
2670
self.addCleanup(tree.unlock)
2671
repo = tree.branch.repository
2672
self.assertIsInstance(repo, RemoteRepository)
2674
repo.get_parent_map([rev_id])
2675
self.reset_smart_call_log()
2676
# Now asking for rev_id's ghost parent should not make calls
2677
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2678
self.assertLength(0, self.hpss_calls)
2680
def test_exposes_get_cached_parent_map(self):
2681
"""RemoteRepository exposes get_cached_parent_map from
2684
r1 = u'\u0e33'.encode('utf8')
2685
r2 = u'\u0dab'.encode('utf8')
2686
lines = [' '.join([r2, r1]), r1]
2687
encoded_body = bz2.compress('\n'.join(lines))
2689
transport_path = 'quack'
2690
repo, client = self.setup_fake_client_and_repository(transport_path)
2691
client.add_success_response_with_body(encoded_body, 'ok')
2693
# get_cached_parent_map should *not* trigger an RPC
2694
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2695
self.assertEqual([], client._calls)
2696
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2697
self.assertEqual({r1: (NULL_REVISION,)},
2698
repo.get_cached_parent_map([r1]))
2700
[('call_with_body_bytes_expecting_body',
2701
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2707
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2709
def test_allows_new_revisions(self):
2710
"""get_parent_map's results can be updated by commit."""
2711
smart_server = test_server.SmartTCPServer_for_testing()
2712
self.start_server(smart_server)
2713
self.make_branch('branch')
2714
branch = Branch.open(smart_server.get_url() + '/branch')
2715
tree = branch.create_checkout('tree', lightweight=True)
2717
self.addCleanup(tree.unlock)
2718
graph = tree.branch.repository.get_graph()
2719
# This provides an opportunity for the missing rev-id to be cached.
2720
self.assertEqual({}, graph.get_parent_map(['rev1']))
2721
tree.commit('message', rev_id='rev1')
2722
graph = tree.branch.repository.get_graph()
2723
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2726
class TestRepositoryGetRevisions(TestRemoteRepository):
2728
def test_hpss_missing_revision(self):
2729
transport_path = 'quack'
2730
repo, client = self.setup_fake_client_and_repository(transport_path)
2731
client.add_success_response_with_body(
2733
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2734
['somerev1', 'anotherrev2'])
2736
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2737
('quack/', ), "somerev1\nanotherrev2")],
2740
def test_hpss_get_single_revision(self):
2741
transport_path = 'quack'
2742
repo, client = self.setup_fake_client_and_repository(transport_path)
2743
somerev1 = Revision("somerev1")
2744
somerev1.committer = "Joe Committer <joe@example.com>"
2745
somerev1.timestamp = 1321828927
2746
somerev1.timezone = -60
2747
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2748
somerev1.message = "Message"
2749
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2751
# Split up body into two bits to make sure the zlib compression object
2752
# gets data fed twice.
2753
client.add_success_response_with_body(
2754
[body[:10], body[10:]], 'ok', '10')
2755
revs = repo.get_revisions(['somerev1'])
2756
self.assertEquals(revs, [somerev1])
2758
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2759
('quack/', ), "somerev1")],
2763
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2765
def test_null_revision(self):
2766
# a null revision has the predictable result {}, we should have no wire
2767
# traffic when calling it with this argument
2768
transport_path = 'empty'
2769
repo, client = self.setup_fake_client_and_repository(transport_path)
2770
client.add_success_response('notused')
2771
# actual RemoteRepository.get_revision_graph is gone, but there's an
2772
# equivalent private method for testing
2773
result = repo._get_revision_graph(NULL_REVISION)
2774
self.assertEqual([], client._calls)
2775
self.assertEqual({}, result)
2777
def test_none_revision(self):
2778
# with none we want the entire graph
2779
r1 = u'\u0e33'.encode('utf8')
2780
r2 = u'\u0dab'.encode('utf8')
2781
lines = [' '.join([r2, r1]), r1]
2782
encoded_body = '\n'.join(lines)
2784
transport_path = 'sinhala'
2785
repo, client = self.setup_fake_client_and_repository(transport_path)
2786
client.add_success_response_with_body(encoded_body, 'ok')
2787
# actual RemoteRepository.get_revision_graph is gone, but there's an
2788
# equivalent private method for testing
2789
result = repo._get_revision_graph(None)
2791
[('call_expecting_body', 'Repository.get_revision_graph',
2794
self.assertEqual({r1: (), r2: (r1, )}, result)
2796
def test_specific_revision(self):
2797
# with a specific revision we want the graph for that
2798
# with none we want the entire graph
2799
r11 = u'\u0e33'.encode('utf8')
2800
r12 = u'\xc9'.encode('utf8')
2801
r2 = u'\u0dab'.encode('utf8')
2802
lines = [' '.join([r2, r11, r12]), r11, r12]
2803
encoded_body = '\n'.join(lines)
2805
transport_path = 'sinhala'
2806
repo, client = self.setup_fake_client_and_repository(transport_path)
2807
client.add_success_response_with_body(encoded_body, 'ok')
2808
result = repo._get_revision_graph(r2)
2810
[('call_expecting_body', 'Repository.get_revision_graph',
2813
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2815
def test_no_such_revision(self):
2817
transport_path = 'sinhala'
2818
repo, client = self.setup_fake_client_and_repository(transport_path)
2819
client.add_error_response('nosuchrevision', revid)
2820
# also check that the right revision is reported in the error
2821
self.assertRaises(errors.NoSuchRevision,
2822
repo._get_revision_graph, revid)
2824
[('call_expecting_body', 'Repository.get_revision_graph',
2825
('sinhala/', revid))],
2828
def test_unexpected_error(self):
2830
transport_path = 'sinhala'
2831
repo, client = self.setup_fake_client_and_repository(transport_path)
2832
client.add_error_response('AnUnexpectedError')
2833
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2834
repo._get_revision_graph, revid)
2835
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2838
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2841
repo, client = self.setup_fake_client_and_repository('quack')
2842
client.add_expected_call(
2843
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2844
'success', ('ok', 'rev-five'))
2845
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2846
self.assertEqual((True, 'rev-five'), result)
2847
self.assertFinished(client)
2849
def test_history_incomplete(self):
2850
repo, client = self.setup_fake_client_and_repository('quack')
2851
client.add_expected_call(
2852
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2853
'success', ('history-incomplete', 10, 'rev-ten'))
2854
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2855
self.assertEqual((False, (10, 'rev-ten')), result)
2856
self.assertFinished(client)
2858
def test_history_incomplete_with_fallback(self):
2859
"""A 'history-incomplete' response causes the fallback repository to be
2860
queried too, if one is set.
2862
# Make a repo with a fallback repo, both using a FakeClient.
2863
format = remote.response_tuple_to_repo_format(
2864
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2865
repo, client = self.setup_fake_client_and_repository('quack')
2866
repo._format = format
2867
fallback_repo, ignored = self.setup_fake_client_and_repository(
2869
fallback_repo._client = client
2870
fallback_repo._format = format
2871
repo.add_fallback_repository(fallback_repo)
2872
# First the client should ask the primary repo
2873
client.add_expected_call(
2874
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2875
'success', ('history-incomplete', 2, 'rev-two'))
2876
# Then it should ask the fallback, using revno/revid from the
2877
# history-incomplete response as the known revno/revid.
2878
client.add_expected_call(
2879
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2880
'success', ('ok', 'rev-one'))
2881
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2882
self.assertEqual((True, 'rev-one'), result)
2883
self.assertFinished(client)
2885
def test_nosuchrevision(self):
2886
# 'nosuchrevision' is returned when the known-revid is not found in the
2887
# remote repo. The client translates that response to NoSuchRevision.
2888
repo, client = self.setup_fake_client_and_repository('quack')
2889
client.add_expected_call(
2890
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2891
'error', ('nosuchrevision', 'rev-foo'))
2893
errors.NoSuchRevision,
2894
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2895
self.assertFinished(client)
2897
def test_branch_fallback_locking(self):
2898
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2899
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2900
will be invoked, which will fail if the repo is unlocked.
2902
self.setup_smart_server_with_call_log()
2903
tree = self.make_branch_and_memory_tree('.')
2906
rev1 = tree.commit('First')
2907
rev2 = tree.commit('Second')
2909
branch = tree.branch
2910
self.assertFalse(branch.is_locked())
2911
self.reset_smart_call_log()
2912
verb = 'Repository.get_rev_id_for_revno'
2913
self.disable_verb(verb)
2914
self.assertEqual(rev1, branch.get_rev_id(1))
2915
self.assertLength(1, [call for call in self.hpss_calls if
2916
call.call.method == verb])
2919
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2921
def test_has_signature_for_revision_id(self):
2922
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2923
transport_path = 'quack'
2924
repo, client = self.setup_fake_client_and_repository(transport_path)
2925
client.add_success_response('yes')
2926
result = repo.has_signature_for_revision_id('A')
2928
[('call', 'Repository.has_signature_for_revision_id',
2931
self.assertEqual(True, result)
2933
def test_is_not_shared(self):
2934
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2935
transport_path = 'qwack'
2936
repo, client = self.setup_fake_client_and_repository(transport_path)
2937
client.add_success_response('no')
2938
result = repo.has_signature_for_revision_id('A')
2940
[('call', 'Repository.has_signature_for_revision_id',
2943
self.assertEqual(False, result)
2946
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2948
def test_get_physical_lock_status_yes(self):
2949
transport_path = 'qwack'
2950
repo, client = self.setup_fake_client_and_repository(transport_path)
2951
client.add_success_response('yes')
2952
result = repo.get_physical_lock_status()
2954
[('call', 'Repository.get_physical_lock_status',
2957
self.assertEqual(True, result)
2959
def test_get_physical_lock_status_no(self):
2960
transport_path = 'qwack'
2961
repo, client = self.setup_fake_client_and_repository(transport_path)
2962
client.add_success_response('no')
2963
result = repo.get_physical_lock_status()
2965
[('call', 'Repository.get_physical_lock_status',
2968
self.assertEqual(False, result)
2971
class TestRepositoryIsShared(TestRemoteRepository):
2973
def test_is_shared(self):
2974
# ('yes', ) for Repository.is_shared -> 'True'.
2975
transport_path = 'quack'
2976
repo, client = self.setup_fake_client_and_repository(transport_path)
2977
client.add_success_response('yes')
2978
result = repo.is_shared()
2980
[('call', 'Repository.is_shared', ('quack/',))],
2982
self.assertEqual(True, result)
2984
def test_is_not_shared(self):
2985
# ('no', ) for Repository.is_shared -> 'False'.
2986
transport_path = 'qwack'
2987
repo, client = self.setup_fake_client_and_repository(transport_path)
2988
client.add_success_response('no')
2989
result = repo.is_shared()
2991
[('call', 'Repository.is_shared', ('qwack/',))],
2993
self.assertEqual(False, result)
2996
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
2998
def test_make_working_trees(self):
2999
# ('yes', ) for Repository.make_working_trees -> 'True'.
3000
transport_path = 'quack'
3001
repo, client = self.setup_fake_client_and_repository(transport_path)
3002
client.add_success_response('yes')
3003
result = repo.make_working_trees()
3005
[('call', 'Repository.make_working_trees', ('quack/',))],
3007
self.assertEqual(True, result)
3009
def test_no_working_trees(self):
3010
# ('no', ) for Repository.make_working_trees -> 'False'.
3011
transport_path = 'qwack'
3012
repo, client = self.setup_fake_client_and_repository(transport_path)
3013
client.add_success_response('no')
3014
result = repo.make_working_trees()
3016
[('call', 'Repository.make_working_trees', ('qwack/',))],
3018
self.assertEqual(False, result)
3021
class TestRepositoryLockWrite(TestRemoteRepository):
3023
def test_lock_write(self):
3024
transport_path = 'quack'
3025
repo, client = self.setup_fake_client_and_repository(transport_path)
3026
client.add_success_response('ok', 'a token')
3027
token = repo.lock_write().repository_token
3029
[('call', 'Repository.lock_write', ('quack/', ''))],
3031
self.assertEqual('a token', token)
3033
def test_lock_write_already_locked(self):
3034
transport_path = 'quack'
3035
repo, client = self.setup_fake_client_and_repository(transport_path)
3036
client.add_error_response('LockContention')
3037
self.assertRaises(errors.LockContention, repo.lock_write)
3039
[('call', 'Repository.lock_write', ('quack/', ''))],
3042
def test_lock_write_unlockable(self):
3043
transport_path = 'quack'
3044
repo, client = self.setup_fake_client_and_repository(transport_path)
3045
client.add_error_response('UnlockableTransport')
3046
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3048
[('call', 'Repository.lock_write', ('quack/', ''))],
3052
class TestRepositoryWriteGroups(TestRemoteRepository):
3054
def test_start_write_group(self):
3055
transport_path = 'quack'
3056
repo, client = self.setup_fake_client_and_repository(transport_path)
3057
client.add_expected_call(
3058
'Repository.lock_write', ('quack/', ''),
3059
'success', ('ok', 'a token'))
3060
client.add_expected_call(
3061
'Repository.start_write_group', ('quack/', 'a token'),
3062
'success', ('ok', ('token1', )))
3064
repo.start_write_group()
3066
def test_start_write_group_unsuspendable(self):
3067
# Some repositories do not support suspending write
3068
# groups. For those, fall back to the "real" repository.
3069
transport_path = 'quack'
3070
repo, client = self.setup_fake_client_and_repository(transport_path)
3071
def stub_ensure_real():
3072
client._calls.append(('_ensure_real',))
3073
repo._real_repository = _StubRealPackRepository(client._calls)
3074
repo._ensure_real = stub_ensure_real
3075
client.add_expected_call(
3076
'Repository.lock_write', ('quack/', ''),
3077
'success', ('ok', 'a token'))
3078
client.add_expected_call(
3079
'Repository.start_write_group', ('quack/', 'a token'),
3080
'error', ('UnsuspendableWriteGroup',))
3082
repo.start_write_group()
3083
self.assertEquals(client._calls[-2:], [
3085
('start_write_group',)])
3087
def test_commit_write_group(self):
3088
transport_path = 'quack'
3089
repo, client = self.setup_fake_client_and_repository(transport_path)
3090
client.add_expected_call(
3091
'Repository.lock_write', ('quack/', ''),
3092
'success', ('ok', 'a token'))
3093
client.add_expected_call(
3094
'Repository.start_write_group', ('quack/', 'a token'),
3095
'success', ('ok', ['token1']))
3096
client.add_expected_call(
3097
'Repository.commit_write_group', ('quack/', 'a token', ['token1']),
3100
repo.start_write_group()
3101
repo.commit_write_group()
3103
def test_abort_write_group(self):
3104
transport_path = 'quack'
3105
repo, client = self.setup_fake_client_and_repository(transport_path)
3106
client.add_expected_call(
3107
'Repository.lock_write', ('quack/', ''),
3108
'success', ('ok', 'a token'))
3109
client.add_expected_call(
3110
'Repository.start_write_group', ('quack/', 'a token'),
3111
'success', ('ok', ['token1']))
3112
client.add_expected_call(
3113
'Repository.abort_write_group', ('quack/', 'a token', ['token1']),
3116
repo.start_write_group()
3117
repo.abort_write_group(False)
3119
def test_suspend_write_group(self):
3120
transport_path = 'quack'
3121
repo, client = self.setup_fake_client_and_repository(transport_path)
3122
self.assertEquals([], repo.suspend_write_group())
3124
def test_resume_write_group(self):
3125
transport_path = 'quack'
3126
repo, client = self.setup_fake_client_and_repository(transport_path)
3127
client.add_expected_call(
3128
'Repository.lock_write', ('quack/', ''),
3129
'success', ('ok', 'a token'))
3130
client.add_expected_call(
3131
'Repository.check_write_group', ('quack/', 'a token', ['token1']),
3134
repo.resume_write_group(['token1'])
3137
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3139
def test_backwards_compat(self):
3140
self.setup_smart_server_with_call_log()
3141
repo = self.make_repository('.')
3142
self.reset_smart_call_log()
3143
verb = 'Repository.set_make_working_trees'
3144
self.disable_verb(verb)
3145
repo.set_make_working_trees(True)
3146
call_count = len([call for call in self.hpss_calls if
3147
call.call.method == verb])
3148
self.assertEqual(1, call_count)
3150
def test_current(self):
3151
transport_path = 'quack'
3152
repo, client = self.setup_fake_client_and_repository(transport_path)
3153
client.add_expected_call(
3154
'Repository.set_make_working_trees', ('quack/', 'True'),
3156
client.add_expected_call(
3157
'Repository.set_make_working_trees', ('quack/', 'False'),
3159
repo.set_make_working_trees(True)
3160
repo.set_make_working_trees(False)
3163
class TestRepositoryUnlock(TestRemoteRepository):
3165
def test_unlock(self):
3166
transport_path = 'quack'
3167
repo, client = self.setup_fake_client_and_repository(transport_path)
3168
client.add_success_response('ok', 'a token')
3169
client.add_success_response('ok')
3173
[('call', 'Repository.lock_write', ('quack/', '')),
3174
('call', 'Repository.unlock', ('quack/', 'a token'))],
3177
def test_unlock_wrong_token(self):
3178
# If somehow the token is wrong, unlock will raise TokenMismatch.
3179
transport_path = 'quack'
3180
repo, client = self.setup_fake_client_and_repository(transport_path)
3181
client.add_success_response('ok', 'a token')
3182
client.add_error_response('TokenMismatch')
3184
self.assertRaises(errors.TokenMismatch, repo.unlock)
3187
class TestRepositoryHasRevision(TestRemoteRepository):
3189
def test_none(self):
3190
# repo.has_revision(None) should not cause any traffic.
3191
transport_path = 'quack'
3192
repo, client = self.setup_fake_client_and_repository(transport_path)
3194
# The null revision is always there, so has_revision(None) == True.
3195
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3197
# The remote repo shouldn't be accessed.
3198
self.assertEqual([], client._calls)
3201
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3202
"""Test Repository.iter_file_bytes."""
3204
def test_single(self):
3205
transport_path = 'quack'
3206
repo, client = self.setup_fake_client_and_repository(transport_path)
3207
client.add_expected_call(
3208
'Repository.iter_files_bytes', ('quack/', ),
3209
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3210
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3211
"somerev", "myid")]):
3212
self.assertEquals("myid", identifier)
3213
self.assertEquals("".join(byte_stream), "mydata" * 10)
3215
def test_missing(self):
3216
transport_path = 'quack'
3217
repo, client = self.setup_fake_client_and_repository(transport_path)
3218
client.add_expected_call(
3219
'Repository.iter_files_bytes',
3221
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3222
iter(["absent\0somefile\0somerev\n"]))
3223
self.assertRaises(errors.RevisionNotPresent, list,
3224
repo.iter_files_bytes(
3225
[("somefile", "somerev", "myid")]))
3228
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3229
"""Base class for Repository.insert_stream and .insert_stream_1.19
3233
def checkInsertEmptyStream(self, repo, client):
3234
"""Insert an empty stream, checking the result.
3236
This checks that there are no resume_tokens or missing_keys, and that
3237
the client is finished.
3239
sink = repo._get_sink()
3240
fmt = repository.format_registry.get_default()
3241
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3242
self.assertEqual([], resume_tokens)
3243
self.assertEqual(set(), missing_keys)
3244
self.assertFinished(client)
3247
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3248
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3251
This test case is very similar to TestRepositoryInsertStream_1_19.
3255
TestRemoteRepository.setUp(self)
3256
self.disable_verb('Repository.insert_stream_1.19')
3258
def test_unlocked_repo(self):
3259
transport_path = 'quack'
3260
repo, client = self.setup_fake_client_and_repository(transport_path)
3261
client.add_expected_call(
3262
'Repository.insert_stream_1.19', ('quack/', ''),
3263
'unknown', ('Repository.insert_stream_1.19',))
3264
client.add_expected_call(
3265
'Repository.insert_stream', ('quack/', ''),
3267
client.add_expected_call(
3268
'Repository.insert_stream', ('quack/', ''),
3270
self.checkInsertEmptyStream(repo, client)
3272
def test_locked_repo_with_no_lock_token(self):
3273
transport_path = 'quack'
3274
repo, client = self.setup_fake_client_and_repository(transport_path)
3275
client.add_expected_call(
3276
'Repository.lock_write', ('quack/', ''),
3277
'success', ('ok', ''))
3278
client.add_expected_call(
3279
'Repository.insert_stream_1.19', ('quack/', ''),
3280
'unknown', ('Repository.insert_stream_1.19',))
3281
client.add_expected_call(
3282
'Repository.insert_stream', ('quack/', ''),
3284
client.add_expected_call(
3285
'Repository.insert_stream', ('quack/', ''),
3288
self.checkInsertEmptyStream(repo, client)
3290
def test_locked_repo_with_lock_token(self):
3291
transport_path = 'quack'
3292
repo, client = self.setup_fake_client_and_repository(transport_path)
3293
client.add_expected_call(
3294
'Repository.lock_write', ('quack/', ''),
3295
'success', ('ok', 'a token'))
3296
client.add_expected_call(
3297
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3298
'unknown', ('Repository.insert_stream_1.19',))
3299
client.add_expected_call(
3300
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3302
client.add_expected_call(
3303
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3306
self.checkInsertEmptyStream(repo, client)
3308
def test_stream_with_inventory_deltas(self):
3309
"""'inventory-deltas' substreams cannot be sent to the
3310
Repository.insert_stream verb, because not all servers that implement
3311
that verb will accept them. So when one is encountered the RemoteSink
3312
immediately stops using that verb and falls back to VFS insert_stream.
3314
transport_path = 'quack'
3315
repo, client = self.setup_fake_client_and_repository(transport_path)
3316
client.add_expected_call(
3317
'Repository.insert_stream_1.19', ('quack/', ''),
3318
'unknown', ('Repository.insert_stream_1.19',))
3319
client.add_expected_call(
3320
'Repository.insert_stream', ('quack/', ''),
3322
client.add_expected_call(
3323
'Repository.insert_stream', ('quack/', ''),
3325
# Create a fake real repository for insert_stream to fall back on, so
3326
# that we can directly see the records the RemoteSink passes to the
3331
def insert_stream(self, stream, src_format, resume_tokens):
3332
for substream_kind, substream in stream:
3333
self.records.append(
3334
(substream_kind, [record.key for record in substream]))
3335
return ['fake tokens'], ['fake missing keys']
3336
fake_real_sink = FakeRealSink()
3337
class FakeRealRepository:
3338
def _get_sink(self):
3339
return fake_real_sink
3340
def is_in_write_group(self):
3342
def refresh_data(self):
3344
repo._real_repository = FakeRealRepository()
3345
sink = repo._get_sink()
3346
fmt = repository.format_registry.get_default()
3347
stream = self.make_stream_with_inv_deltas(fmt)
3348
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3349
# Every record from the first inventory delta should have been sent to
3351
expected_records = [
3352
('inventory-deltas', [('rev2',), ('rev3',)]),
3353
('texts', [('some-rev', 'some-file')])]
3354
self.assertEqual(expected_records, fake_real_sink.records)
3355
# The return values from the real sink's insert_stream are propagated
3356
# back to the original caller.
3357
self.assertEqual(['fake tokens'], resume_tokens)
3358
self.assertEqual(['fake missing keys'], missing_keys)
3359
self.assertFinished(client)
3361
def make_stream_with_inv_deltas(self, fmt):
3362
"""Make a simple stream with an inventory delta followed by more
3363
records and more substreams to test that all records and substreams
3364
from that point on are used.
3366
This sends, in order:
3367
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3369
* texts substream: (some-rev, some-file)
3371
# Define a stream using generators so that it isn't rewindable.
3372
inv = inventory.Inventory(revision_id='rev1')
3373
inv.root.revision = 'rev1'
3374
def stream_with_inv_delta():
3375
yield ('inventories', inventories_substream())
3376
yield ('inventory-deltas', inventory_delta_substream())
3378
versionedfile.FulltextContentFactory(
3379
('some-rev', 'some-file'), (), None, 'content')])
3380
def inventories_substream():
3381
# An empty inventory fulltext. This will be streamed normally.
3382
text = fmt._serializer.write_inventory_to_string(inv)
3383
yield versionedfile.FulltextContentFactory(
3384
('rev1',), (), None, text)
3385
def inventory_delta_substream():
3386
# An inventory delta. This can't be streamed via this verb, so it
3387
# will trigger a fallback to VFS insert_stream.
3388
entry = inv.make_entry(
3389
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3390
entry.revision = 'ghost'
3391
delta = [(None, 'newdir', 'newdir-id', entry)]
3392
serializer = inventory_delta.InventoryDeltaSerializer(
3393
versioned_root=True, tree_references=False)
3394
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3395
yield versionedfile.ChunkedContentFactory(
3396
('rev2',), (('rev1',)), None, lines)
3398
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3399
yield versionedfile.ChunkedContentFactory(
3400
('rev3',), (('rev1',)), None, lines)
3401
return stream_with_inv_delta()
3404
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3406
def test_unlocked_repo(self):
3407
transport_path = 'quack'
3408
repo, client = self.setup_fake_client_and_repository(transport_path)
3409
client.add_expected_call(
3410
'Repository.insert_stream_1.19', ('quack/', ''),
3412
client.add_expected_call(
3413
'Repository.insert_stream_1.19', ('quack/', ''),
3415
self.checkInsertEmptyStream(repo, client)
3417
def test_locked_repo_with_no_lock_token(self):
3418
transport_path = 'quack'
3419
repo, client = self.setup_fake_client_and_repository(transport_path)
3420
client.add_expected_call(
3421
'Repository.lock_write', ('quack/', ''),
3422
'success', ('ok', ''))
3423
client.add_expected_call(
3424
'Repository.insert_stream_1.19', ('quack/', ''),
3426
client.add_expected_call(
3427
'Repository.insert_stream_1.19', ('quack/', ''),
3430
self.checkInsertEmptyStream(repo, client)
3432
def test_locked_repo_with_lock_token(self):
3433
transport_path = 'quack'
3434
repo, client = self.setup_fake_client_and_repository(transport_path)
3435
client.add_expected_call(
3436
'Repository.lock_write', ('quack/', ''),
3437
'success', ('ok', 'a token'))
3438
client.add_expected_call(
3439
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3441
client.add_expected_call(
3442
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3445
self.checkInsertEmptyStream(repo, client)
3448
class TestRepositoryTarball(TestRemoteRepository):
3450
# This is a canned tarball reponse we can validate against
3452
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3453
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3454
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3455
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3456
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3457
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3458
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3459
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3460
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3461
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3462
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3463
'nWQ7QH/F3JFOFCQ0aSPfA='
3466
def test_repository_tarball(self):
3467
# Test that Repository.tarball generates the right operations
3468
transport_path = 'repo'
3469
expected_calls = [('call_expecting_body', 'Repository.tarball',
3470
('repo/', 'bz2',),),
3472
repo, client = self.setup_fake_client_and_repository(transport_path)
3473
client.add_success_response_with_body(self.tarball_content, 'ok')
3474
# Now actually ask for the tarball
3475
tarball_file = repo._get_tarball('bz2')
3477
self.assertEqual(expected_calls, client._calls)
3478
self.assertEqual(self.tarball_content, tarball_file.read())
3480
tarball_file.close()
3483
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3484
"""RemoteRepository.copy_content_into optimizations"""
3486
def test_copy_content_remote_to_local(self):
3487
self.transport_server = test_server.SmartTCPServer_for_testing
3488
src_repo = self.make_repository('repo1')
3489
src_repo = repository.Repository.open(self.get_url('repo1'))
3490
# At the moment the tarball-based copy_content_into can't write back
3491
# into a smart server. It would be good if it could upload the
3492
# tarball; once that works we'd have to create repositories of
3493
# different formats. -- mbp 20070410
3494
dest_url = self.get_vfs_only_url('repo2')
3495
dest_bzrdir = BzrDir.create(dest_url)
3496
dest_repo = dest_bzrdir.create_repository()
3497
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3498
self.assertTrue(isinstance(src_repo, RemoteRepository))
3499
src_repo.copy_content_into(dest_repo)
3502
class _StubRealPackRepository(object):
3504
def __init__(self, calls):
3506
self._pack_collection = _StubPackCollection(calls)
3508
def start_write_group(self):
3509
self.calls.append(('start_write_group',))
3511
def is_in_write_group(self):
3514
def refresh_data(self):
3515
self.calls.append(('pack collection reload_pack_names',))
3518
class _StubPackCollection(object):
3520
def __init__(self, calls):
3524
self.calls.append(('pack collection autopack',))
3527
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3528
"""Tests for RemoteRepository.autopack implementation."""
3531
"""When the server returns 'ok' and there's no _real_repository, then
3532
nothing else happens: the autopack method is done.
3534
transport_path = 'quack'
3535
repo, client = self.setup_fake_client_and_repository(transport_path)
3536
client.add_expected_call(
3537
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3539
self.assertFinished(client)
3541
def test_ok_with_real_repo(self):
3542
"""When the server returns 'ok' and there is a _real_repository, then
3543
the _real_repository's reload_pack_name's method will be called.
3545
transport_path = 'quack'
3546
repo, client = self.setup_fake_client_and_repository(transport_path)
3547
client.add_expected_call(
3548
'PackRepository.autopack', ('quack/',),
3550
repo._real_repository = _StubRealPackRepository(client._calls)
3553
[('call', 'PackRepository.autopack', ('quack/',)),
3554
('pack collection reload_pack_names',)],
3557
def test_backwards_compatibility(self):
3558
"""If the server does not recognise the PackRepository.autopack verb,
3559
fallback to the real_repository's implementation.
3561
transport_path = 'quack'
3562
repo, client = self.setup_fake_client_and_repository(transport_path)
3563
client.add_unknown_method_response('PackRepository.autopack')
3564
def stub_ensure_real():
3565
client._calls.append(('_ensure_real',))
3566
repo._real_repository = _StubRealPackRepository(client._calls)
3567
repo._ensure_real = stub_ensure_real
3570
[('call', 'PackRepository.autopack', ('quack/',)),
3572
('pack collection autopack',)],
3575
def test_oom_error_reporting(self):
3576
"""An out-of-memory condition on the server is reported clearly"""
3577
transport_path = 'quack'
3578
repo, client = self.setup_fake_client_and_repository(transport_path)
3579
client.add_expected_call(
3580
'PackRepository.autopack', ('quack/',),
3581
'error', ('MemoryError',))
3582
err = self.assertRaises(errors.BzrError, repo.autopack)
3583
self.assertContainsRe(str(err), "^remote server out of mem")
3586
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3587
"""Base class for unit tests for bzrlib.remote._translate_error."""
3589
def translateTuple(self, error_tuple, **context):
3590
"""Call _translate_error with an ErrorFromSmartServer built from the
3593
:param error_tuple: A tuple of a smart server response, as would be
3594
passed to an ErrorFromSmartServer.
3595
:kwargs context: context items to call _translate_error with.
3597
:returns: The error raised by _translate_error.
3599
# Raise the ErrorFromSmartServer before passing it as an argument,
3600
# because _translate_error may need to re-raise it with a bare 'raise'
3602
server_error = errors.ErrorFromSmartServer(error_tuple)
3603
translated_error = self.translateErrorFromSmartServer(
3604
server_error, **context)
3605
return translated_error
3607
def translateErrorFromSmartServer(self, error_object, **context):
3608
"""Like translateTuple, but takes an already constructed
3609
ErrorFromSmartServer rather than a tuple.
3613
except errors.ErrorFromSmartServer, server_error:
3614
translated_error = self.assertRaises(
3615
errors.BzrError, remote._translate_error, server_error,
3617
return translated_error
3620
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3621
"""Unit tests for bzrlib.remote._translate_error.
3623
Given an ErrorFromSmartServer (which has an error tuple from a smart
3624
server) and some context, _translate_error raises more specific errors from
3627
This test case covers the cases where _translate_error succeeds in
3628
translating an ErrorFromSmartServer to something better. See
3629
TestErrorTranslationRobustness for other cases.
3632
def test_NoSuchRevision(self):
3633
branch = self.make_branch('')
3635
translated_error = self.translateTuple(
3636
('NoSuchRevision', revid), branch=branch)
3637
expected_error = errors.NoSuchRevision(branch, revid)
3638
self.assertEqual(expected_error, translated_error)
3640
def test_nosuchrevision(self):
3641
repository = self.make_repository('')
3643
translated_error = self.translateTuple(
3644
('nosuchrevision', revid), repository=repository)
3645
expected_error = errors.NoSuchRevision(repository, revid)
3646
self.assertEqual(expected_error, translated_error)
3648
def test_nobranch(self):
3649
bzrdir = self.make_bzrdir('')
3650
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3651
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3652
self.assertEqual(expected_error, translated_error)
3654
def test_nobranch_one_arg(self):
3655
bzrdir = self.make_bzrdir('')
3656
translated_error = self.translateTuple(
3657
('nobranch', 'extra detail'), bzrdir=bzrdir)
3658
expected_error = errors.NotBranchError(
3659
path=bzrdir.root_transport.base,
3660
detail='extra detail')
3661
self.assertEqual(expected_error, translated_error)
3663
def test_norepository(self):
3664
bzrdir = self.make_bzrdir('')
3665
translated_error = self.translateTuple(('norepository',),
3667
expected_error = errors.NoRepositoryPresent(bzrdir)
3668
self.assertEqual(expected_error, translated_error)
3670
def test_LockContention(self):
3671
translated_error = self.translateTuple(('LockContention',))
3672
expected_error = errors.LockContention('(remote lock)')
3673
self.assertEqual(expected_error, translated_error)
3675
def test_UnlockableTransport(self):
3676
bzrdir = self.make_bzrdir('')
3677
translated_error = self.translateTuple(
3678
('UnlockableTransport',), bzrdir=bzrdir)
3679
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3680
self.assertEqual(expected_error, translated_error)
3682
def test_LockFailed(self):
3683
lock = 'str() of a server lock'
3684
why = 'str() of why'
3685
translated_error = self.translateTuple(('LockFailed', lock, why))
3686
expected_error = errors.LockFailed(lock, why)
3687
self.assertEqual(expected_error, translated_error)
3689
def test_TokenMismatch(self):
3690
token = 'a lock token'
3691
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3692
expected_error = errors.TokenMismatch(token, '(remote token)')
3693
self.assertEqual(expected_error, translated_error)
3695
def test_Diverged(self):
3696
branch = self.make_branch('a')
3697
other_branch = self.make_branch('b')
3698
translated_error = self.translateTuple(
3699
('Diverged',), branch=branch, other_branch=other_branch)
3700
expected_error = errors.DivergedBranches(branch, other_branch)
3701
self.assertEqual(expected_error, translated_error)
3703
def test_NotStacked(self):
3704
branch = self.make_branch('')
3705
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3706
expected_error = errors.NotStacked(branch)
3707
self.assertEqual(expected_error, translated_error)
3709
def test_ReadError_no_args(self):
3711
translated_error = self.translateTuple(('ReadError',), path=path)
3712
expected_error = errors.ReadError(path)
3713
self.assertEqual(expected_error, translated_error)
3715
def test_ReadError(self):
3717
translated_error = self.translateTuple(('ReadError', path))
3718
expected_error = errors.ReadError(path)
3719
self.assertEqual(expected_error, translated_error)
3721
def test_IncompatibleRepositories(self):
3722
translated_error = self.translateTuple(('IncompatibleRepositories',
3723
"repo1", "repo2", "details here"))
3724
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3726
self.assertEqual(expected_error, translated_error)
3728
def test_PermissionDenied_no_args(self):
3730
translated_error = self.translateTuple(('PermissionDenied',),
3732
expected_error = errors.PermissionDenied(path)
3733
self.assertEqual(expected_error, translated_error)
3735
def test_PermissionDenied_one_arg(self):
3737
translated_error = self.translateTuple(('PermissionDenied', path))
3738
expected_error = errors.PermissionDenied(path)
3739
self.assertEqual(expected_error, translated_error)
3741
def test_PermissionDenied_one_arg_and_context(self):
3742
"""Given a choice between a path from the local context and a path on
3743
the wire, _translate_error prefers the path from the local context.
3745
local_path = 'local path'
3746
remote_path = 'remote path'
3747
translated_error = self.translateTuple(
3748
('PermissionDenied', remote_path), path=local_path)
3749
expected_error = errors.PermissionDenied(local_path)
3750
self.assertEqual(expected_error, translated_error)
3752
def test_PermissionDenied_two_args(self):
3754
extra = 'a string with extra info'
3755
translated_error = self.translateTuple(
3756
('PermissionDenied', path, extra))
3757
expected_error = errors.PermissionDenied(path, extra)
3758
self.assertEqual(expected_error, translated_error)
3760
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3762
def test_NoSuchFile_context_path(self):
3763
local_path = "local path"
3764
translated_error = self.translateTuple(('ReadError', "remote path"),
3766
expected_error = errors.ReadError(local_path)
3767
self.assertEqual(expected_error, translated_error)
3769
def test_NoSuchFile_without_context(self):
3770
remote_path = "remote path"
3771
translated_error = self.translateTuple(('ReadError', remote_path))
3772
expected_error = errors.ReadError(remote_path)
3773
self.assertEqual(expected_error, translated_error)
3775
def test_ReadOnlyError(self):
3776
translated_error = self.translateTuple(('ReadOnlyError',))
3777
expected_error = errors.TransportNotPossible("readonly transport")
3778
self.assertEqual(expected_error, translated_error)
3780
def test_MemoryError(self):
3781
translated_error = self.translateTuple(('MemoryError',))
3782
self.assertStartsWith(str(translated_error),
3783
"remote server out of memory")
3785
def test_generic_IndexError_no_classname(self):
3786
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3787
translated_error = self.translateErrorFromSmartServer(err)
3788
expected_error = errors.UnknownErrorFromSmartServer(err)
3789
self.assertEqual(expected_error, translated_error)
3791
# GZ 2011-03-02: TODO test generic non-ascii error string
3793
def test_generic_KeyError(self):
3794
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3795
translated_error = self.translateErrorFromSmartServer(err)
3796
expected_error = errors.UnknownErrorFromSmartServer(err)
3797
self.assertEqual(expected_error, translated_error)
3800
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3801
"""Unit tests for bzrlib.remote._translate_error's robustness.
3803
TestErrorTranslationSuccess is for cases where _translate_error can
3804
translate successfully. This class about how _translate_err behaves when
3805
it fails to translate: it re-raises the original error.
3808
def test_unrecognised_server_error(self):
3809
"""If the error code from the server is not recognised, the original
3810
ErrorFromSmartServer is propagated unmodified.
3812
error_tuple = ('An unknown error tuple',)
3813
server_error = errors.ErrorFromSmartServer(error_tuple)
3814
translated_error = self.translateErrorFromSmartServer(server_error)
3815
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3816
self.assertEqual(expected_error, translated_error)
3818
def test_context_missing_a_key(self):
3819
"""In case of a bug in the client, or perhaps an unexpected response
3820
from a server, _translate_error returns the original error tuple from
3821
the server and mutters a warning.
3823
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3824
# in the context dict. So let's give it an empty context dict instead
3825
# to exercise its error recovery.
3827
error_tuple = ('NoSuchRevision', 'revid')
3828
server_error = errors.ErrorFromSmartServer(error_tuple)
3829
translated_error = self.translateErrorFromSmartServer(server_error)
3830
self.assertEqual(server_error, translated_error)
3831
# In addition to re-raising ErrorFromSmartServer, some debug info has
3832
# been muttered to the log file for developer to look at.
3833
self.assertContainsRe(
3835
"Missing key 'branch' in context")
3837
def test_path_missing(self):
3838
"""Some translations (PermissionDenied, ReadError) can determine the
3839
'path' variable from either the wire or the local context. If neither
3840
has it, then an error is raised.
3842
error_tuple = ('ReadError',)
3843
server_error = errors.ErrorFromSmartServer(error_tuple)
3844
translated_error = self.translateErrorFromSmartServer(server_error)
3845
self.assertEqual(server_error, translated_error)
3846
# In addition to re-raising ErrorFromSmartServer, some debug info has
3847
# been muttered to the log file for developer to look at.
3848
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3851
class TestStacking(tests.TestCaseWithTransport):
3852
"""Tests for operations on stacked remote repositories.
3854
The underlying format type must support stacking.
3857
def test_access_stacked_remote(self):
3858
# based on <http://launchpad.net/bugs/261315>
3859
# make a branch stacked on another repository containing an empty
3860
# revision, then open it over hpss - we should be able to see that
3862
base_transport = self.get_transport()
3863
base_builder = self.make_branch_builder('base', format='1.9')
3864
base_builder.start_series()
3865
base_revid = base_builder.build_snapshot('rev-id', None,
3866
[('add', ('', None, 'directory', None))],
3868
base_builder.finish_series()
3869
stacked_branch = self.make_branch('stacked', format='1.9')
3870
stacked_branch.set_stacked_on_url('../base')
3871
# start a server looking at this
3872
smart_server = test_server.SmartTCPServer_for_testing()
3873
self.start_server(smart_server)
3874
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3875
# can get its branch and repository
3876
remote_branch = remote_bzrdir.open_branch()
3877
remote_repo = remote_branch.repository
3878
remote_repo.lock_read()
3880
# it should have an appropriate fallback repository, which should also
3881
# be a RemoteRepository
3882
self.assertLength(1, remote_repo._fallback_repositories)
3883
self.assertIsInstance(remote_repo._fallback_repositories[0],
3885
# and it has the revision committed to the underlying repository;
3886
# these have varying implementations so we try several of them
3887
self.assertTrue(remote_repo.has_revisions([base_revid]))
3888
self.assertTrue(remote_repo.has_revision(base_revid))
3889
self.assertEqual(remote_repo.get_revision(base_revid).message,
3892
remote_repo.unlock()
3894
def prepare_stacked_remote_branch(self):
3895
"""Get stacked_upon and stacked branches with content in each."""
3896
self.setup_smart_server_with_call_log()
3897
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3898
tree1.commit('rev1', rev_id='rev1')
3899
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3900
).open_workingtree()
3901
local_tree = tree2.branch.create_checkout('local')
3902
local_tree.commit('local changes make me feel good.')
3903
branch2 = Branch.open(self.get_url('tree2'))
3905
self.addCleanup(branch2.unlock)
3906
return tree1.branch, branch2
3908
def test_stacked_get_parent_map(self):
3909
# the public implementation of get_parent_map obeys stacking
3910
_, branch = self.prepare_stacked_remote_branch()
3911
repo = branch.repository
3912
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3914
def test_unstacked_get_parent_map(self):
3915
# _unstacked_provider.get_parent_map ignores stacking
3916
_, branch = self.prepare_stacked_remote_branch()
3917
provider = branch.repository._unstacked_provider
3918
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3920
def fetch_stream_to_rev_order(self, stream):
3922
for kind, substream in stream:
3923
if not kind == 'revisions':
3926
for content in substream:
3927
result.append(content.key[-1])
3930
def get_ordered_revs(self, format, order, branch_factory=None):
3931
"""Get a list of the revisions in a stream to format format.
3933
:param format: The format of the target.
3934
:param order: the order that target should have requested.
3935
:param branch_factory: A callable to create a trunk and stacked branch
3936
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3937
:result: The revision ids in the stream, in the order seen,
3938
the topological order of revisions in the source.
3940
unordered_format = bzrdir.format_registry.get(format)()
3941
target_repository_format = unordered_format.repository_format
3943
self.assertEqual(order, target_repository_format._fetch_order)
3944
if branch_factory is None:
3945
branch_factory = self.prepare_stacked_remote_branch
3946
_, stacked = branch_factory()
3947
source = stacked.repository._get_source(target_repository_format)
3948
tip = stacked.last_revision()
3949
stacked.repository._ensure_real()
3950
graph = stacked.repository.get_graph()
3951
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3952
if r != NULL_REVISION]
3954
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3955
self.reset_smart_call_log()
3956
stream = source.get_stream(search)
3957
# We trust that if a revision is in the stream the rest of the new
3958
# content for it is too, as per our main fetch tests; here we are
3959
# checking that the revisions are actually included at all, and their
3961
return self.fetch_stream_to_rev_order(stream), revs
3963
def test_stacked_get_stream_unordered(self):
3964
# Repository._get_source.get_stream() from a stacked repository with
3965
# unordered yields the full data from both stacked and stacked upon
3967
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3968
self.assertEqual(set(expected_revs), set(rev_ord))
3969
# Getting unordered results should have made a streaming data request
3970
# from the server, then one from the backing branch.
3971
self.assertLength(2, self.hpss_calls)
3973
def test_stacked_on_stacked_get_stream_unordered(self):
3974
# Repository._get_source.get_stream() from a stacked repository which
3975
# is itself stacked yields the full data from all three sources.
3976
def make_stacked_stacked():
3977
_, stacked = self.prepare_stacked_remote_branch()
3978
tree = stacked.bzrdir.sprout('tree3', stacked=True
3979
).open_workingtree()
3980
local_tree = tree.branch.create_checkout('local-tree3')
3981
local_tree.commit('more local changes are better')
3982
branch = Branch.open(self.get_url('tree3'))
3984
self.addCleanup(branch.unlock)
3986
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3987
branch_factory=make_stacked_stacked)
3988
self.assertEqual(set(expected_revs), set(rev_ord))
3989
# Getting unordered results should have made a streaming data request
3990
# from the server, and one from each backing repo
3991
self.assertLength(3, self.hpss_calls)
3993
def test_stacked_get_stream_topological(self):
3994
# Repository._get_source.get_stream() from a stacked repository with
3995
# topological sorting yields the full data from both stacked and
3996
# stacked upon sources in topological order.
3997
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3998
self.assertEqual(expected_revs, rev_ord)
3999
# Getting topological sort requires VFS calls still - one of which is
4000
# pushing up from the bound branch.
4001
self.assertLength(14, self.hpss_calls)
4003
def test_stacked_get_stream_groupcompress(self):
4004
# Repository._get_source.get_stream() from a stacked repository with
4005
# groupcompress sorting yields the full data from both stacked and
4006
# stacked upon sources in groupcompress order.
4007
raise tests.TestSkipped('No groupcompress ordered format available')
4008
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4009
self.assertEqual(expected_revs, reversed(rev_ord))
4010
# Getting unordered results should have made a streaming data request
4011
# from the backing branch, and one from the stacked on branch.
4012
self.assertLength(2, self.hpss_calls)
4014
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4015
# When pulling some fixed amount of content that is more than the
4016
# source has (because some is coming from a fallback branch, no error
4017
# should be received. This was reported as bug 360791.
4018
# Need three branches: a trunk, a stacked branch, and a preexisting
4019
# branch pulling content from stacked and trunk.
4020
self.setup_smart_server_with_call_log()
4021
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4022
r1 = trunk.commit('start')
4023
stacked_branch = trunk.branch.create_clone_on_transport(
4024
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4025
local = self.make_branch('local', format='1.9-rich-root')
4026
local.repository.fetch(stacked_branch.repository,
4027
stacked_branch.last_revision())
4030
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4033
super(TestRemoteBranchEffort, self).setUp()
4034
# Create a smart server that publishes whatever the backing VFS server
4036
self.smart_server = test_server.SmartTCPServer_for_testing()
4037
self.start_server(self.smart_server, self.get_server())
4038
# Log all HPSS calls into self.hpss_calls.
4039
_SmartClient.hooks.install_named_hook(
4040
'call', self.capture_hpss_call, None)
4041
self.hpss_calls = []
4043
def capture_hpss_call(self, params):
4044
self.hpss_calls.append(params.method)
4046
def test_copy_content_into_avoids_revision_history(self):
4047
local = self.make_branch('local')
4048
builder = self.make_branch_builder('remote')
4049
builder.build_commit(message="Commit.")
4050
remote_branch_url = self.smart_server.get_url() + 'remote'
4051
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4052
local.repository.fetch(remote_branch.repository)
4053
self.hpss_calls = []
4054
remote_branch.copy_content_into(local)
4055
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4057
def test_fetch_everything_needs_just_one_call(self):
4058
local = self.make_branch('local')
4059
builder = self.make_branch_builder('remote')
4060
builder.build_commit(message="Commit.")
4061
remote_branch_url = self.smart_server.get_url() + 'remote'
4062
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4063
self.hpss_calls = []
4064
local.repository.fetch(
4065
remote_branch.repository,
4066
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4067
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4069
def override_verb(self, verb_name, verb):
4070
request_handlers = request.request_handlers
4071
orig_verb = request_handlers.get(verb_name)
4072
orig_info = request_handlers.get_info(verb_name)
4073
request_handlers.register(verb_name, verb, override_existing=True)
4074
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4075
override_existing=True, info=orig_info)
4077
def test_fetch_everything_backwards_compat(self):
4078
"""Can fetch with EverythingResult even with pre 2.4 servers.
4080
Pre-2.4 do not support 'everything' searches with the
4081
Repository.get_stream_1.19 verb.
4084
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4085
"""A version of the Repository.get_stream_1.19 verb patched to
4086
reject 'everything' searches the way 2.3 and earlier do.
4088
def recreate_search(self, repository, search_bytes,
4089
discard_excess=False):
4090
verb_log.append(search_bytes.split('\n', 1)[0])
4091
if search_bytes == 'everything':
4093
request.FailedSmartServerResponse(('BadSearch',)))
4094
return super(OldGetStreamVerb,
4095
self).recreate_search(repository, search_bytes,
4096
discard_excess=discard_excess)
4097
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4098
local = self.make_branch('local')
4099
builder = self.make_branch_builder('remote')
4100
builder.build_commit(message="Commit.")
4101
remote_branch_url = self.smart_server.get_url() + 'remote'
4102
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4103
self.hpss_calls = []
4104
local.repository.fetch(
4105
remote_branch.repository,
4106
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4107
# make sure the overridden verb was used
4108
self.assertLength(1, verb_log)
4109
# more than one HPSS call is needed, but because it's a VFS callback
4110
# its hard to predict exactly how many.
4111
self.assertTrue(len(self.hpss_calls) > 1)
4114
class TestUpdateBoundBranchWithModifiedBoundLocation(
4115
tests.TestCaseWithTransport):
4116
"""Ensure correct handling of bound_location modifications.
4118
This is tested against a smart server as http://pad.lv/786980 was about a
4119
ReadOnlyError (write attempt during a read-only transaction) which can only
4120
happen in this context.
4124
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4125
self.transport_server = test_server.SmartTCPServer_for_testing
4127
def make_master_and_checkout(self, master_name, checkout_name):
4128
# Create the master branch and its associated checkout
4129
self.master = self.make_branch_and_tree(master_name)
4130
self.checkout = self.master.branch.create_checkout(checkout_name)
4131
# Modify the master branch so there is something to update
4132
self.master.commit('add stuff')
4133
self.last_revid = self.master.commit('even more stuff')
4134
self.bound_location = self.checkout.branch.get_bound_location()
4136
def assertUpdateSucceeds(self, new_location):
4137
self.checkout.branch.set_bound_location(new_location)
4138
self.checkout.update()
4139
self.assertEquals(self.last_revid, self.checkout.last_revision())
4141
def test_without_final_slash(self):
4142
self.make_master_and_checkout('master', 'checkout')
4143
# For unclear reasons some users have a bound_location without a final
4144
# '/', simulate that by forcing such a value
4145
self.assertEndsWith(self.bound_location, '/')
4146
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4148
def test_plus_sign(self):
4149
self.make_master_and_checkout('+master', 'checkout')
4150
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4152
def test_tilda(self):
4153
# Embed ~ in the middle of the path just to avoid any $HOME
4155
self.make_master_and_checkout('mas~ter', 'checkout')
4156
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4159
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4161
def test_no_context(self):
4162
class OutOfCoffee(errors.BzrError):
4163
"""A dummy exception for testing."""
4165
def __init__(self, urgency):
4166
self.urgency = urgency
4167
remote.no_context_error_translators.register("OutOfCoffee",
4168
lambda err: OutOfCoffee(err.error_args[0]))
4169
transport = MemoryTransport()
4170
client = FakeClient(transport.base)
4171
client.add_expected_call(
4172
'Branch.get_stacked_on_url', ('quack/',),
4173
'error', ('NotStacked',))
4174
client.add_expected_call(
4175
'Branch.last_revision_info',
4177
'error', ('OutOfCoffee', 'low'))
4178
transport.mkdir('quack')
4179
transport = transport.clone('quack')
4180
branch = self.make_remote_branch(transport, client)
4181
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4182
self.assertFinished(client)
4184
def test_with_context(self):
4185
class OutOfTea(errors.BzrError):
4186
def __init__(self, branch, urgency):
4187
self.branch = branch
4188
self.urgency = urgency
4189
remote.error_translators.register("OutOfTea",
4190
lambda err, find, path: OutOfTea(err.error_args[0],
4192
transport = MemoryTransport()
4193
client = FakeClient(transport.base)
4194
client.add_expected_call(
4195
'Branch.get_stacked_on_url', ('quack/',),
4196
'error', ('NotStacked',))
4197
client.add_expected_call(
4198
'Branch.last_revision_info',
4200
'error', ('OutOfTea', 'low'))
4201
transport.mkdir('quack')
4202
transport = transport.clone('quack')
4203
branch = self.make_remote_branch(transport, client)
4204
self.assertRaises(OutOfTea, branch.last_revision_info)
4205
self.assertFinished(client)
4208
class TestRepositoryPack(TestRemoteRepository):
4210
def test_pack(self):
4211
transport_path = 'quack'
4212
repo, client = self.setup_fake_client_and_repository(transport_path)
4213
client.add_expected_call(
4214
'Repository.lock_write', ('quack/', ''),
4215
'success', ('ok', 'token'))
4216
client.add_expected_call(
4217
'Repository.pack', ('quack/', 'token', 'False'),
4218
'success', ('ok',), )
4219
client.add_expected_call(
4220
'Repository.unlock', ('quack/', 'token'),
4221
'success', ('ok', ))
4224
def test_pack_with_hint(self):
4225
transport_path = 'quack'
4226
repo, client = self.setup_fake_client_and_repository(transport_path)
4227
client.add_expected_call(
4228
'Repository.lock_write', ('quack/', ''),
4229
'success', ('ok', 'token'))
4230
client.add_expected_call(
4231
'Repository.pack', ('quack/', 'token', 'False'),
4232
'success', ('ok',), )
4233
client.add_expected_call(
4234
'Repository.unlock', ('quack/', 'token', 'False'),
4235
'success', ('ok', ))
4236
repo.pack(['hinta', 'hintb'])
4239
class TestRepositoryIterInventories(TestRemoteRepository):
4240
"""Test Repository.iter_inventories."""
4242
def _serialize_inv_delta(self, old_name, new_name, delta):
4243
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4244
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4246
def test_single_empty(self):
4247
transport_path = 'quack'
4248
repo, client = self.setup_fake_client_and_repository(transport_path)
4249
fmt = bzrdir.format_registry.get('2a')().repository_format
4251
stream = [('inventory-deltas', [
4252
versionedfile.FulltextContentFactory('somerevid', None, None,
4253
self._serialize_inv_delta('null:', 'somerevid', []))])]
4254
client.add_expected_call(
4255
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4256
'success', ('ok', ),
4257
_stream_to_byte_stream(stream, fmt))
4258
ret = list(repo.iter_inventories(["somerevid"]))
4259
self.assertLength(1, ret)
4261
self.assertEquals("somerevid", inv.revision_id)
4263
def test_empty(self):
4264
transport_path = 'quack'
4265
repo, client = self.setup_fake_client_and_repository(transport_path)
4266
ret = list(repo.iter_inventories([]))
4267
self.assertEquals(ret, [])
4269
def test_missing(self):
4270
transport_path = 'quack'
4271
repo, client = self.setup_fake_client_and_repository(transport_path)
4272
client.add_expected_call(
4273
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4274
'success', ('ok', ), iter([]))
4275
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(