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
46
from bzrlib.branch import Branch
47
from bzrlib.bzrdir import (
52
from bzrlib.chk_serializer import chk_bencode_serializer
53
from bzrlib.remote import (
59
RemoteRepositoryFormat,
61
from bzrlib.repofmt import groupcompress_repo, knitpack_repo
62
from bzrlib.revision import (
66
from bzrlib.smart import medium, request
67
from bzrlib.smart.client import _SmartClient
68
from bzrlib.smart.repository import (
69
SmartServerRepositoryGetParentMap,
70
SmartServerRepositoryGetStream_1_19,
72
from bzrlib.symbol_versioning import deprecated_in
73
from bzrlib.tests import (
76
from bzrlib.tests.scenarios import load_tests_apply_scenarios
77
from bzrlib.transport.memory import MemoryTransport
78
from bzrlib.transport.remote import (
85
load_tests = load_tests_apply_scenarios
88
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
92
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
94
{'transport_server': test_server.SmartTCPServer_for_testing})]
98
super(BasicRemoteObjectTests, self).setUp()
99
self.transport = self.get_transport()
100
# make a branch that can be opened over the smart transport
101
self.local_wt = BzrDir.create_standalone_workingtree('.')
102
self.addCleanup(self.transport.disconnect)
104
def test_create_remote_bzrdir(self):
105
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
106
self.assertIsInstance(b, BzrDir)
108
def test_open_remote_branch(self):
109
# open a standalone branch in the working directory
110
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
111
branch = b.open_branch()
112
self.assertIsInstance(branch, Branch)
114
def test_remote_repository(self):
115
b = BzrDir.open_from_transport(self.transport)
116
repo = b.open_repository()
117
revid = u'\xc823123123'.encode('utf8')
118
self.assertFalse(repo.has_revision(revid))
119
self.local_wt.commit(message='test commit', rev_id=revid)
120
self.assertTrue(repo.has_revision(revid))
122
def test_remote_branch_revision_history(self):
123
b = BzrDir.open_from_transport(self.transport).open_branch()
125
self.applyDeprecated(deprecated_in((2, 5, 0)), b.revision_history))
126
r1 = self.local_wt.commit('1st commit')
127
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
128
self.assertEqual([r1, r2],
129
self.applyDeprecated(deprecated_in((2, 5, 0)), b.revision_history))
131
def test_find_correct_format(self):
132
"""Should open a RemoteBzrDir over a RemoteTransport"""
133
fmt = BzrDirFormat.find_format(self.transport)
134
self.assertTrue(bzrdir.RemoteBzrProber
135
in controldir.ControlDirFormat._server_probers)
136
self.assertIsInstance(fmt, RemoteBzrDirFormat)
138
def test_open_detected_smart_format(self):
139
fmt = BzrDirFormat.find_format(self.transport)
140
d = fmt.open(self.transport)
141
self.assertIsInstance(d, BzrDir)
143
def test_remote_branch_repr(self):
144
b = BzrDir.open_from_transport(self.transport).open_branch()
145
self.assertStartsWith(str(b), 'RemoteBranch(')
147
def test_remote_bzrdir_repr(self):
148
b = BzrDir.open_from_transport(self.transport)
149
self.assertStartsWith(str(b), 'RemoteBzrDir(')
151
def test_remote_branch_format_supports_stacking(self):
153
self.make_branch('unstackable', format='pack-0.92')
154
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
155
self.assertFalse(b._format.supports_stacking())
156
self.make_branch('stackable', format='1.9')
157
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
158
self.assertTrue(b._format.supports_stacking())
160
def test_remote_repo_format_supports_external_references(self):
162
bd = self.make_bzrdir('unstackable', format='pack-0.92')
163
r = bd.create_repository()
164
self.assertFalse(r._format.supports_external_lookups)
165
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
166
self.assertFalse(r._format.supports_external_lookups)
167
bd = self.make_bzrdir('stackable', format='1.9')
168
r = bd.create_repository()
169
self.assertTrue(r._format.supports_external_lookups)
170
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
171
self.assertTrue(r._format.supports_external_lookups)
173
def test_remote_branch_set_append_revisions_only(self):
174
# Make a format 1.9 branch, which supports append_revisions_only
175
branch = self.make_branch('branch', format='1.9')
176
config = branch.get_config()
177
branch.set_append_revisions_only(True)
179
'True', config.get_user_option('append_revisions_only'))
180
branch.set_append_revisions_only(False)
182
'False', config.get_user_option('append_revisions_only'))
184
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
185
branch = self.make_branch('branch', format='knit')
186
config = branch.get_config()
188
errors.UpgradeRequired, branch.set_append_revisions_only, True)
191
class FakeProtocol(object):
192
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
194
def __init__(self, body, fake_client):
196
self._body_buffer = None
197
self._fake_client = fake_client
199
def read_body_bytes(self, count=-1):
200
if self._body_buffer is None:
201
self._body_buffer = StringIO(self.body)
202
bytes = self._body_buffer.read(count)
203
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
204
self._fake_client.expecting_body = False
207
def cancel_read_body(self):
208
self._fake_client.expecting_body = False
210
def read_streamed_body(self):
214
class FakeClient(_SmartClient):
215
"""Lookalike for _SmartClient allowing testing."""
217
def __init__(self, fake_medium_base='fake base'):
218
"""Create a FakeClient."""
221
self.expecting_body = False
222
# if non-None, this is the list of expected calls, with only the
223
# method name and arguments included. the body might be hard to
224
# compute so is not included. If a call is None, that call can
226
self._expected_calls = None
227
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
229
def add_expected_call(self, call_name, call_args, response_type,
230
response_args, response_body=None):
231
if self._expected_calls is None:
232
self._expected_calls = []
233
self._expected_calls.append((call_name, call_args))
234
self.responses.append((response_type, response_args, response_body))
236
def add_success_response(self, *args):
237
self.responses.append(('success', args, None))
239
def add_success_response_with_body(self, body, *args):
240
self.responses.append(('success', args, body))
241
if self._expected_calls is not None:
242
self._expected_calls.append(None)
244
def add_error_response(self, *args):
245
self.responses.append(('error', args))
247
def add_unknown_method_response(self, verb):
248
self.responses.append(('unknown', verb))
250
def finished_test(self):
251
if self._expected_calls:
252
raise AssertionError("%r finished but was still expecting %r"
253
% (self, self._expected_calls[0]))
255
def _get_next_response(self):
257
response_tuple = self.responses.pop(0)
258
except IndexError, e:
259
raise AssertionError("%r didn't expect any more calls"
261
if response_tuple[0] == 'unknown':
262
raise errors.UnknownSmartMethod(response_tuple[1])
263
elif response_tuple[0] == 'error':
264
raise errors.ErrorFromSmartServer(response_tuple[1])
265
return response_tuple
267
def _check_call(self, method, args):
268
if self._expected_calls is None:
269
# the test should be updated to say what it expects
272
next_call = self._expected_calls.pop(0)
274
raise AssertionError("%r didn't expect any more calls "
276
% (self, method, args,))
277
if next_call is None:
279
if method != next_call[0] or args != next_call[1]:
280
raise AssertionError("%r expected %r%r "
282
% (self, next_call[0], next_call[1], method, args,))
284
def call(self, method, *args):
285
self._check_call(method, args)
286
self._calls.append(('call', method, args))
287
return self._get_next_response()[1]
289
def call_expecting_body(self, method, *args):
290
self._check_call(method, args)
291
self._calls.append(('call_expecting_body', method, args))
292
result = self._get_next_response()
293
self.expecting_body = True
294
return result[1], FakeProtocol(result[2], self)
296
def call_with_body_bytes(self, method, args, body):
297
self._check_call(method, args)
298
self._calls.append(('call_with_body_bytes', method, args, body))
299
result = self._get_next_response()
300
return result[1], FakeProtocol(result[2], self)
302
def call_with_body_bytes_expecting_body(self, method, args, body):
303
self._check_call(method, args)
304
self._calls.append(('call_with_body_bytes_expecting_body', method,
306
result = self._get_next_response()
307
self.expecting_body = True
308
return result[1], FakeProtocol(result[2], self)
310
def call_with_body_stream(self, args, stream):
311
# Explicitly consume the stream before checking for an error, because
312
# that's what happens a real medium.
313
stream = list(stream)
314
self._check_call(args[0], args[1:])
315
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
316
result = self._get_next_response()
317
# The second value returned from call_with_body_stream is supposed to
318
# be a response_handler object, but so far no tests depend on that.
319
response_handler = None
320
return result[1], response_handler
323
class FakeMedium(medium.SmartClientMedium):
325
def __init__(self, client_calls, base):
326
medium.SmartClientMedium.__init__(self, base)
327
self._client_calls = client_calls
329
def disconnect(self):
330
self._client_calls.append(('disconnect medium',))
333
class TestVfsHas(tests.TestCase):
335
def test_unicode_path(self):
336
client = FakeClient('/')
337
client.add_success_response('yes',)
338
transport = RemoteTransport('bzr://localhost/', _client=client)
339
filename = u'/hell\u00d8'.encode('utf8')
340
result = transport.has(filename)
342
[('call', 'has', (filename,))],
344
self.assertTrue(result)
347
class TestRemote(tests.TestCaseWithMemoryTransport):
349
def get_branch_format(self):
350
reference_bzrdir_format = bzrdir.format_registry.get('default')()
351
return reference_bzrdir_format.get_branch_format()
353
def get_repo_format(self):
354
reference_bzrdir_format = bzrdir.format_registry.get('default')()
355
return reference_bzrdir_format.repository_format
357
def assertFinished(self, fake_client):
358
"""Assert that all of a FakeClient's expected calls have occurred."""
359
fake_client.finished_test()
362
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
363
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
365
def assertRemotePath(self, expected, client_base, transport_base):
366
"""Assert that the result of
367
SmartClientMedium.remote_path_from_transport is the expected value for
368
a given client_base and transport_base.
370
client_medium = medium.SmartClientMedium(client_base)
371
t = transport.get_transport(transport_base)
372
result = client_medium.remote_path_from_transport(t)
373
self.assertEqual(expected, result)
375
def test_remote_path_from_transport(self):
376
"""SmartClientMedium.remote_path_from_transport calculates a URL for
377
the given transport relative to the root of the client base URL.
379
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
380
self.assertRemotePath(
381
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
383
def assertRemotePathHTTP(self, expected, transport_base, relpath):
384
"""Assert that the result of
385
HttpTransportBase.remote_path_from_transport is the expected value for
386
a given transport_base and relpath of that transport. (Note that
387
HttpTransportBase is a subclass of SmartClientMedium)
389
base_transport = transport.get_transport(transport_base)
390
client_medium = base_transport.get_smart_medium()
391
cloned_transport = base_transport.clone(relpath)
392
result = client_medium.remote_path_from_transport(cloned_transport)
393
self.assertEqual(expected, result)
395
def test_remote_path_from_transport_http(self):
396
"""Remote paths for HTTP transports are calculated differently to other
397
transports. They are just relative to the client base, not the root
398
directory of the host.
400
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
401
self.assertRemotePathHTTP(
402
'../xyz/', scheme + '//host/path', '../xyz/')
403
self.assertRemotePathHTTP(
404
'xyz/', scheme + '//host/path', 'xyz/')
407
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
408
"""Tests for the behaviour of client_medium.remote_is_at_least."""
410
def test_initially_unlimited(self):
411
"""A fresh medium assumes that the remote side supports all
414
client_medium = medium.SmartClientMedium('dummy base')
415
self.assertFalse(client_medium._is_remote_before((99, 99)))
417
def test__remember_remote_is_before(self):
418
"""Calling _remember_remote_is_before ratchets down the known remote
421
client_medium = medium.SmartClientMedium('dummy base')
422
# Mark the remote side as being less than 1.6. The remote side may
424
client_medium._remember_remote_is_before((1, 6))
425
self.assertTrue(client_medium._is_remote_before((1, 6)))
426
self.assertFalse(client_medium._is_remote_before((1, 5)))
427
# Calling _remember_remote_is_before again with a lower value works.
428
client_medium._remember_remote_is_before((1, 5))
429
self.assertTrue(client_medium._is_remote_before((1, 5)))
430
# If you call _remember_remote_is_before with a higher value it logs a
431
# warning, and continues to remember the lower value.
432
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
433
client_medium._remember_remote_is_before((1, 9))
434
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
435
self.assertTrue(client_medium._is_remote_before((1, 5)))
438
class TestBzrDirCloningMetaDir(TestRemote):
440
def test_backwards_compat(self):
441
self.setup_smart_server_with_call_log()
442
a_dir = self.make_bzrdir('.')
443
self.reset_smart_call_log()
444
verb = 'BzrDir.cloning_metadir'
445
self.disable_verb(verb)
446
format = a_dir.cloning_metadir()
447
call_count = len([call for call in self.hpss_calls if
448
call.call.method == verb])
449
self.assertEqual(1, call_count)
451
def test_branch_reference(self):
452
transport = self.get_transport('quack')
453
referenced = self.make_branch('referenced')
454
expected = referenced.bzrdir.cloning_metadir()
455
client = FakeClient(transport.base)
456
client.add_expected_call(
457
'BzrDir.cloning_metadir', ('quack/', 'False'),
458
'error', ('BranchReference',)),
459
client.add_expected_call(
460
'BzrDir.open_branchV3', ('quack/',),
461
'success', ('ref', self.get_url('referenced'))),
462
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
464
result = a_bzrdir.cloning_metadir()
465
# We should have got a control dir matching the referenced branch.
466
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
467
self.assertEqual(expected._repository_format, result._repository_format)
468
self.assertEqual(expected._branch_format, result._branch_format)
469
self.assertFinished(client)
471
def test_current_server(self):
472
transport = self.get_transport('.')
473
transport = transport.clone('quack')
474
self.make_bzrdir('quack')
475
client = FakeClient(transport.base)
476
reference_bzrdir_format = bzrdir.format_registry.get('default')()
477
control_name = reference_bzrdir_format.network_name()
478
client.add_expected_call(
479
'BzrDir.cloning_metadir', ('quack/', 'False'),
480
'success', (control_name, '', ('branch', ''))),
481
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
483
result = a_bzrdir.cloning_metadir()
484
# We should have got a reference control dir with default branch and
485
# repository formats.
486
# This pokes a little, just to be sure.
487
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
488
self.assertEqual(None, result._repository_format)
489
self.assertEqual(None, result._branch_format)
490
self.assertFinished(client)
492
def test_unknown(self):
493
transport = self.get_transport('quack')
494
referenced = self.make_branch('referenced')
495
expected = referenced.bzrdir.cloning_metadir()
496
client = FakeClient(transport.base)
497
client.add_expected_call(
498
'BzrDir.cloning_metadir', ('quack/', 'False'),
499
'success', ('unknown', 'unknown', ('branch', ''))),
500
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
502
self.assertRaises(errors.UnknownFormatError, a_bzrdir.cloning_metadir)
505
class TestBzrDirDestroyBranch(TestRemote):
507
def test_destroy_default(self):
508
transport = self.get_transport('quack')
509
referenced = self.make_branch('referenced')
510
client = FakeClient(transport.base)
511
client.add_expected_call(
512
'BzrDir.destroy_branch', ('quack/', ),
514
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
516
a_bzrdir.destroy_branch()
517
self.assertFinished(client)
519
def test_destroy_named(self):
520
transport = self.get_transport('quack')
521
referenced = self.make_branch('referenced')
522
client = FakeClient(transport.base)
523
client.add_expected_call(
524
'BzrDir.destroy_branch', ('quack/', "foo"),
526
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
528
a_bzrdir.destroy_branch("foo")
529
self.assertFinished(client)
532
class TestBzrDirHasWorkingTree(TestRemote):
534
def test_has_workingtree(self):
535
transport = self.get_transport('quack')
536
client = FakeClient(transport.base)
537
client.add_expected_call(
538
'BzrDir.has_workingtree', ('quack/',),
539
'success', ('yes',)),
540
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
542
self.assertTrue(a_bzrdir.has_workingtree())
543
self.assertFinished(client)
545
def test_no_workingtree(self):
546
transport = self.get_transport('quack')
547
client = FakeClient(transport.base)
548
client.add_expected_call(
549
'BzrDir.has_workingtree', ('quack/',),
551
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
553
self.assertFalse(a_bzrdir.has_workingtree())
554
self.assertFinished(client)
557
class TestBzrDirDestroyRepository(TestRemote):
559
def test_destroy_repository(self):
560
transport = self.get_transport('quack')
561
client = FakeClient(transport.base)
562
client.add_expected_call(
563
'BzrDir.destroy_repository', ('quack/',),
565
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
567
a_bzrdir.destroy_repository()
568
self.assertFinished(client)
571
class TestBzrDirOpen(TestRemote):
573
def make_fake_client_and_transport(self, path='quack'):
574
transport = MemoryTransport()
575
transport.mkdir(path)
576
transport = transport.clone(path)
577
client = FakeClient(transport.base)
578
return client, transport
580
def test_absent(self):
581
client, transport = self.make_fake_client_and_transport()
582
client.add_expected_call(
583
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
584
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
585
RemoteBzrDirFormat(), _client=client, _force_probe=True)
586
self.assertFinished(client)
588
def test_present_without_workingtree(self):
589
client, transport = self.make_fake_client_and_transport()
590
client.add_expected_call(
591
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
592
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
593
_client=client, _force_probe=True)
594
self.assertIsInstance(bd, RemoteBzrDir)
595
self.assertFalse(bd.has_workingtree())
596
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
597
self.assertFinished(client)
599
def test_present_with_workingtree(self):
600
client, transport = self.make_fake_client_and_transport()
601
client.add_expected_call(
602
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
603
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
604
_client=client, _force_probe=True)
605
self.assertIsInstance(bd, RemoteBzrDir)
606
self.assertTrue(bd.has_workingtree())
607
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
608
self.assertFinished(client)
610
def test_backwards_compat(self):
611
client, transport = self.make_fake_client_and_transport()
612
client.add_expected_call(
613
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
614
client.add_expected_call(
615
'BzrDir.open', ('quack/',), 'success', ('yes',))
616
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
617
_client=client, _force_probe=True)
618
self.assertIsInstance(bd, RemoteBzrDir)
619
self.assertFinished(client)
621
def test_backwards_compat_hpss_v2(self):
622
client, transport = self.make_fake_client_and_transport()
623
# Monkey-patch fake client to simulate real-world behaviour with v2
624
# server: upon first RPC call detect the protocol version, and because
625
# the version is 2 also do _remember_remote_is_before((1, 6)) before
626
# continuing with the RPC.
627
orig_check_call = client._check_call
628
def check_call(method, args):
629
client._medium._protocol_version = 2
630
client._medium._remember_remote_is_before((1, 6))
631
client._check_call = orig_check_call
632
client._check_call(method, args)
633
client._check_call = check_call
634
client.add_expected_call(
635
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
636
client.add_expected_call(
637
'BzrDir.open', ('quack/',), 'success', ('yes',))
638
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
639
_client=client, _force_probe=True)
640
self.assertIsInstance(bd, RemoteBzrDir)
641
self.assertFinished(client)
644
class TestBzrDirOpenBranch(TestRemote):
646
def test_backwards_compat(self):
647
self.setup_smart_server_with_call_log()
648
self.make_branch('.')
649
a_dir = BzrDir.open(self.get_url('.'))
650
self.reset_smart_call_log()
651
verb = 'BzrDir.open_branchV3'
652
self.disable_verb(verb)
653
format = a_dir.open_branch()
654
call_count = len([call for call in self.hpss_calls if
655
call.call.method == verb])
656
self.assertEqual(1, call_count)
658
def test_branch_present(self):
659
reference_format = self.get_repo_format()
660
network_name = reference_format.network_name()
661
branch_network_name = self.get_branch_format().network_name()
662
transport = MemoryTransport()
663
transport.mkdir('quack')
664
transport = transport.clone('quack')
665
client = FakeClient(transport.base)
666
client.add_expected_call(
667
'BzrDir.open_branchV3', ('quack/',),
668
'success', ('branch', branch_network_name))
669
client.add_expected_call(
670
'BzrDir.find_repositoryV3', ('quack/',),
671
'success', ('ok', '', 'no', 'no', 'no', network_name))
672
client.add_expected_call(
673
'Branch.get_stacked_on_url', ('quack/',),
674
'error', ('NotStacked',))
675
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
677
result = bzrdir.open_branch()
678
self.assertIsInstance(result, RemoteBranch)
679
self.assertEqual(bzrdir, result.bzrdir)
680
self.assertFinished(client)
682
def test_branch_missing(self):
683
transport = MemoryTransport()
684
transport.mkdir('quack')
685
transport = transport.clone('quack')
686
client = FakeClient(transport.base)
687
client.add_error_response('nobranch')
688
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
690
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
692
[('call', 'BzrDir.open_branchV3', ('quack/',))],
695
def test__get_tree_branch(self):
696
# _get_tree_branch is a form of open_branch, but it should only ask for
697
# branch opening, not any other network requests.
699
def open_branch(name=None, possible_transports=None):
700
calls.append("Called")
702
transport = MemoryTransport()
703
# no requests on the network - catches other api calls being made.
704
client = FakeClient(transport.base)
705
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
707
# patch the open_branch call to record that it was called.
708
bzrdir.open_branch = open_branch
709
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
710
self.assertEqual(["Called"], calls)
711
self.assertEqual([], client._calls)
713
def test_url_quoting_of_path(self):
714
# Relpaths on the wire should not be URL-escaped. So "~" should be
715
# transmitted as "~", not "%7E".
716
transport = RemoteTCPTransport('bzr://localhost/~hello/')
717
client = FakeClient(transport.base)
718
reference_format = self.get_repo_format()
719
network_name = reference_format.network_name()
720
branch_network_name = self.get_branch_format().network_name()
721
client.add_expected_call(
722
'BzrDir.open_branchV3', ('~hello/',),
723
'success', ('branch', branch_network_name))
724
client.add_expected_call(
725
'BzrDir.find_repositoryV3', ('~hello/',),
726
'success', ('ok', '', 'no', 'no', 'no', network_name))
727
client.add_expected_call(
728
'Branch.get_stacked_on_url', ('~hello/',),
729
'error', ('NotStacked',))
730
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
732
result = bzrdir.open_branch()
733
self.assertFinished(client)
735
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
736
reference_format = self.get_repo_format()
737
network_name = reference_format.network_name()
738
transport = MemoryTransport()
739
transport.mkdir('quack')
740
transport = transport.clone('quack')
742
rich_response = 'yes'
746
subtree_response = 'yes'
748
subtree_response = 'no'
749
client = FakeClient(transport.base)
750
client.add_success_response(
751
'ok', '', rich_response, subtree_response, external_lookup,
753
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
755
result = bzrdir.open_repository()
757
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
759
self.assertIsInstance(result, RemoteRepository)
760
self.assertEqual(bzrdir, result.bzrdir)
761
self.assertEqual(rich_root, result._format.rich_root_data)
762
self.assertEqual(subtrees, result._format.supports_tree_reference)
764
def test_open_repository_sets_format_attributes(self):
765
self.check_open_repository(True, True)
766
self.check_open_repository(False, True)
767
self.check_open_repository(True, False)
768
self.check_open_repository(False, False)
769
self.check_open_repository(False, False, 'yes')
771
def test_old_server(self):
772
"""RemoteBzrDirFormat should fail to probe if the server version is too
775
self.assertRaises(errors.NotBranchError,
776
RemoteBzrProber.probe_transport, OldServerTransport())
779
class TestBzrDirCreateBranch(TestRemote):
781
def test_backwards_compat(self):
782
self.setup_smart_server_with_call_log()
783
repo = self.make_repository('.')
784
self.reset_smart_call_log()
785
self.disable_verb('BzrDir.create_branch')
786
branch = repo.bzrdir.create_branch()
787
create_branch_call_count = len([call for call in self.hpss_calls if
788
call.call.method == 'BzrDir.create_branch'])
789
self.assertEqual(1, create_branch_call_count)
791
def test_current_server(self):
792
transport = self.get_transport('.')
793
transport = transport.clone('quack')
794
self.make_repository('quack')
795
client = FakeClient(transport.base)
796
reference_bzrdir_format = bzrdir.format_registry.get('default')()
797
reference_format = reference_bzrdir_format.get_branch_format()
798
network_name = reference_format.network_name()
799
reference_repo_fmt = reference_bzrdir_format.repository_format
800
reference_repo_name = reference_repo_fmt.network_name()
801
client.add_expected_call(
802
'BzrDir.create_branch', ('quack/', network_name),
803
'success', ('ok', network_name, '', 'no', 'no', 'yes',
804
reference_repo_name))
805
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
807
branch = a_bzrdir.create_branch()
808
# We should have got a remote branch
809
self.assertIsInstance(branch, remote.RemoteBranch)
810
# its format should have the settings from the response
811
format = branch._format
812
self.assertEqual(network_name, format.network_name())
814
def test_already_open_repo_and_reused_medium(self):
815
"""Bug 726584: create_branch(..., repository=repo) should work
816
regardless of what the smart medium's base URL is.
818
self.transport_server = test_server.SmartTCPServer_for_testing
819
transport = self.get_transport('.')
820
repo = self.make_repository('quack')
821
# Client's medium rooted a transport root (not at the bzrdir)
822
client = FakeClient(transport.base)
823
transport = transport.clone('quack')
824
reference_bzrdir_format = bzrdir.format_registry.get('default')()
825
reference_format = reference_bzrdir_format.get_branch_format()
826
network_name = reference_format.network_name()
827
reference_repo_fmt = reference_bzrdir_format.repository_format
828
reference_repo_name = reference_repo_fmt.network_name()
829
client.add_expected_call(
830
'BzrDir.create_branch', ('extra/quack/', network_name),
831
'success', ('ok', network_name, '', 'no', 'no', 'yes',
832
reference_repo_name))
833
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
835
branch = a_bzrdir.create_branch(repository=repo)
836
# We should have got a remote branch
837
self.assertIsInstance(branch, remote.RemoteBranch)
838
# its format should have the settings from the response
839
format = branch._format
840
self.assertEqual(network_name, format.network_name())
843
class TestBzrDirCreateRepository(TestRemote):
845
def test_backwards_compat(self):
846
self.setup_smart_server_with_call_log()
847
bzrdir = self.make_bzrdir('.')
848
self.reset_smart_call_log()
849
self.disable_verb('BzrDir.create_repository')
850
repo = bzrdir.create_repository()
851
create_repo_call_count = len([call for call in self.hpss_calls if
852
call.call.method == 'BzrDir.create_repository'])
853
self.assertEqual(1, create_repo_call_count)
855
def test_current_server(self):
856
transport = self.get_transport('.')
857
transport = transport.clone('quack')
858
self.make_bzrdir('quack')
859
client = FakeClient(transport.base)
860
reference_bzrdir_format = bzrdir.format_registry.get('default')()
861
reference_format = reference_bzrdir_format.repository_format
862
network_name = reference_format.network_name()
863
client.add_expected_call(
864
'BzrDir.create_repository', ('quack/',
865
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
867
'success', ('ok', 'yes', 'yes', 'yes', network_name))
868
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
870
repo = a_bzrdir.create_repository()
871
# We should have got a remote repository
872
self.assertIsInstance(repo, remote.RemoteRepository)
873
# its format should have the settings from the response
874
format = repo._format
875
self.assertTrue(format.rich_root_data)
876
self.assertTrue(format.supports_tree_reference)
877
self.assertTrue(format.supports_external_lookups)
878
self.assertEqual(network_name, format.network_name())
881
class TestBzrDirOpenRepository(TestRemote):
883
def test_backwards_compat_1_2_3(self):
884
# fallback all the way to the first version.
885
reference_format = self.get_repo_format()
886
network_name = reference_format.network_name()
887
server_url = 'bzr://example.com/'
888
self.permit_url(server_url)
889
client = FakeClient(server_url)
890
client.add_unknown_method_response('BzrDir.find_repositoryV3')
891
client.add_unknown_method_response('BzrDir.find_repositoryV2')
892
client.add_success_response('ok', '', 'no', 'no')
893
# A real repository instance will be created to determine the network
895
client.add_success_response_with_body(
896
"Bazaar-NG meta directory, format 1\n", 'ok')
897
client.add_success_response_with_body(
898
reference_format.get_format_string(), 'ok')
899
# PackRepository wants to do a stat
900
client.add_success_response('stat', '0', '65535')
901
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
903
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
905
repo = bzrdir.open_repository()
907
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
908
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
909
('call', 'BzrDir.find_repository', ('quack/',)),
910
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
911
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
912
('call', 'stat', ('/quack/.bzr/repository',)),
915
self.assertEqual(network_name, repo._format.network_name())
917
def test_backwards_compat_2(self):
918
# fallback to find_repositoryV2
919
reference_format = self.get_repo_format()
920
network_name = reference_format.network_name()
921
server_url = 'bzr://example.com/'
922
self.permit_url(server_url)
923
client = FakeClient(server_url)
924
client.add_unknown_method_response('BzrDir.find_repositoryV3')
925
client.add_success_response('ok', '', 'no', 'no', 'no')
926
# A real repository instance will be created to determine the network
928
client.add_success_response_with_body(
929
"Bazaar-NG meta directory, format 1\n", 'ok')
930
client.add_success_response_with_body(
931
reference_format.get_format_string(), 'ok')
932
# PackRepository wants to do a stat
933
client.add_success_response('stat', '0', '65535')
934
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
936
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
938
repo = bzrdir.open_repository()
940
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
941
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
942
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
943
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
944
('call', 'stat', ('/quack/.bzr/repository',)),
947
self.assertEqual(network_name, repo._format.network_name())
949
def test_current_server(self):
950
reference_format = self.get_repo_format()
951
network_name = reference_format.network_name()
952
transport = MemoryTransport()
953
transport.mkdir('quack')
954
transport = transport.clone('quack')
955
client = FakeClient(transport.base)
956
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
957
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
959
repo = bzrdir.open_repository()
961
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
963
self.assertEqual(network_name, repo._format.network_name())
966
class TestBzrDirFormatInitializeEx(TestRemote):
968
def test_success(self):
969
"""Simple test for typical successful call."""
970
fmt = RemoteBzrDirFormat()
971
default_format_name = BzrDirFormat.get_default_format().network_name()
972
transport = self.get_transport()
973
client = FakeClient(transport.base)
974
client.add_expected_call(
975
'BzrDirFormat.initialize_ex_1.16',
976
(default_format_name, 'path', 'False', 'False', 'False', '',
977
'', '', '', 'False'),
979
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
980
'bzrdir fmt', 'False', '', '', 'repo lock token'))
981
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
982
# it's currently hard to test that without supplying a real remote
983
# transport connected to a real server.
984
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
985
transport, False, False, False, None, None, None, None, False)
986
self.assertFinished(client)
988
def test_error(self):
989
"""Error responses are translated, e.g. 'PermissionDenied' raises the
990
corresponding error from the client.
992
fmt = RemoteBzrDirFormat()
993
default_format_name = BzrDirFormat.get_default_format().network_name()
994
transport = self.get_transport()
995
client = FakeClient(transport.base)
996
client.add_expected_call(
997
'BzrDirFormat.initialize_ex_1.16',
998
(default_format_name, 'path', 'False', 'False', 'False', '',
999
'', '', '', 'False'),
1001
('PermissionDenied', 'path', 'extra info'))
1002
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1003
# it's currently hard to test that without supplying a real remote
1004
# transport connected to a real server.
1005
err = self.assertRaises(errors.PermissionDenied,
1006
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1007
False, False, False, None, None, None, None, False)
1008
self.assertEqual('path', err.path)
1009
self.assertEqual(': extra info', err.extra)
1010
self.assertFinished(client)
1012
def test_error_from_real_server(self):
1013
"""Integration test for error translation."""
1014
transport = self.make_smart_server('foo')
1015
transport = transport.clone('no-such-path')
1016
fmt = RemoteBzrDirFormat()
1017
err = self.assertRaises(errors.NoSuchFile,
1018
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1021
class OldSmartClient(object):
1022
"""A fake smart client for test_old_version that just returns a version one
1023
response to the 'hello' (query version) command.
1026
def get_request(self):
1027
input_file = StringIO('ok\x011\n')
1028
output_file = StringIO()
1029
client_medium = medium.SmartSimplePipesClientMedium(
1030
input_file, output_file)
1031
return medium.SmartClientStreamMediumRequest(client_medium)
1033
def protocol_version(self):
1037
class OldServerTransport(object):
1038
"""A fake transport for test_old_server that reports it's smart server
1039
protocol version as version one.
1045
def get_smart_client(self):
1046
return OldSmartClient()
1049
class RemoteBzrDirTestCase(TestRemote):
1051
def make_remote_bzrdir(self, transport, client):
1052
"""Make a RemotebzrDir using 'client' as the _client."""
1053
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1057
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1059
def lock_remote_branch(self, branch):
1060
"""Trick a RemoteBranch into thinking it is locked."""
1061
branch._lock_mode = 'w'
1062
branch._lock_count = 2
1063
branch._lock_token = 'branch token'
1064
branch._repo_lock_token = 'repo token'
1065
branch.repository._lock_mode = 'w'
1066
branch.repository._lock_count = 2
1067
branch.repository._lock_token = 'repo token'
1069
def make_remote_branch(self, transport, client):
1070
"""Make a RemoteBranch using 'client' as its _SmartClient.
1072
A RemoteBzrDir and RemoteRepository will also be created to fill out
1073
the RemoteBranch, albeit with stub values for some of their attributes.
1075
# we do not want bzrdir to make any remote calls, so use False as its
1076
# _client. If it tries to make a remote call, this will fail
1078
bzrdir = self.make_remote_bzrdir(transport, False)
1079
repo = RemoteRepository(bzrdir, None, _client=client)
1080
branch_format = self.get_branch_format()
1081
format = RemoteBranchFormat(network_name=branch_format.network_name())
1082
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1085
class TestBranchBreakLock(RemoteBranchTestCase):
1087
def test_break_lock(self):
1088
transport_path = 'quack'
1089
transport = MemoryTransport()
1090
client = FakeClient(transport.base)
1091
client.add_expected_call(
1092
'Branch.get_stacked_on_url', ('quack/',),
1093
'error', ('NotStacked',))
1094
client.add_expected_call(
1095
'Branch.break_lock', ('quack/',),
1097
transport.mkdir('quack')
1098
transport = transport.clone('quack')
1099
branch = self.make_remote_branch(transport, client)
1101
self.assertFinished(client)
1104
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1106
def test_get_physical_lock_status_yes(self):
1107
transport = MemoryTransport()
1108
client = FakeClient(transport.base)
1109
client.add_expected_call(
1110
'Branch.get_stacked_on_url', ('quack/',),
1111
'error', ('NotStacked',))
1112
client.add_expected_call(
1113
'Branch.get_physical_lock_status', ('quack/',),
1114
'success', ('yes',))
1115
transport.mkdir('quack')
1116
transport = transport.clone('quack')
1117
branch = self.make_remote_branch(transport, client)
1118
result = branch.get_physical_lock_status()
1119
self.assertFinished(client)
1120
self.assertEqual(True, result)
1122
def test_get_physical_lock_status_no(self):
1123
transport = MemoryTransport()
1124
client = FakeClient(transport.base)
1125
client.add_expected_call(
1126
'Branch.get_stacked_on_url', ('quack/',),
1127
'error', ('NotStacked',))
1128
client.add_expected_call(
1129
'Branch.get_physical_lock_status', ('quack/',),
1131
transport.mkdir('quack')
1132
transport = transport.clone('quack')
1133
branch = self.make_remote_branch(transport, client)
1134
result = branch.get_physical_lock_status()
1135
self.assertFinished(client)
1136
self.assertEqual(False, result)
1139
class TestBranchGetParent(RemoteBranchTestCase):
1141
def test_no_parent(self):
1142
# in an empty branch we decode the response properly
1143
transport = MemoryTransport()
1144
client = FakeClient(transport.base)
1145
client.add_expected_call(
1146
'Branch.get_stacked_on_url', ('quack/',),
1147
'error', ('NotStacked',))
1148
client.add_expected_call(
1149
'Branch.get_parent', ('quack/',),
1151
transport.mkdir('quack')
1152
transport = transport.clone('quack')
1153
branch = self.make_remote_branch(transport, client)
1154
result = branch.get_parent()
1155
self.assertFinished(client)
1156
self.assertEqual(None, result)
1158
def test_parent_relative(self):
1159
transport = MemoryTransport()
1160
client = FakeClient(transport.base)
1161
client.add_expected_call(
1162
'Branch.get_stacked_on_url', ('kwaak/',),
1163
'error', ('NotStacked',))
1164
client.add_expected_call(
1165
'Branch.get_parent', ('kwaak/',),
1166
'success', ('../foo/',))
1167
transport.mkdir('kwaak')
1168
transport = transport.clone('kwaak')
1169
branch = self.make_remote_branch(transport, client)
1170
result = branch.get_parent()
1171
self.assertEqual(transport.clone('../foo').base, result)
1173
def test_parent_absolute(self):
1174
transport = MemoryTransport()
1175
client = FakeClient(transport.base)
1176
client.add_expected_call(
1177
'Branch.get_stacked_on_url', ('kwaak/',),
1178
'error', ('NotStacked',))
1179
client.add_expected_call(
1180
'Branch.get_parent', ('kwaak/',),
1181
'success', ('http://foo/',))
1182
transport.mkdir('kwaak')
1183
transport = transport.clone('kwaak')
1184
branch = self.make_remote_branch(transport, client)
1185
result = branch.get_parent()
1186
self.assertEqual('http://foo/', result)
1187
self.assertFinished(client)
1190
class TestBranchSetParentLocation(RemoteBranchTestCase):
1192
def test_no_parent(self):
1193
# We call the verb when setting parent to None
1194
transport = MemoryTransport()
1195
client = FakeClient(transport.base)
1196
client.add_expected_call(
1197
'Branch.get_stacked_on_url', ('quack/',),
1198
'error', ('NotStacked',))
1199
client.add_expected_call(
1200
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1202
transport.mkdir('quack')
1203
transport = transport.clone('quack')
1204
branch = self.make_remote_branch(transport, client)
1205
branch._lock_token = 'b'
1206
branch._repo_lock_token = 'r'
1207
branch._set_parent_location(None)
1208
self.assertFinished(client)
1210
def test_parent(self):
1211
transport = MemoryTransport()
1212
client = FakeClient(transport.base)
1213
client.add_expected_call(
1214
'Branch.get_stacked_on_url', ('kwaak/',),
1215
'error', ('NotStacked',))
1216
client.add_expected_call(
1217
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1219
transport.mkdir('kwaak')
1220
transport = transport.clone('kwaak')
1221
branch = self.make_remote_branch(transport, client)
1222
branch._lock_token = 'b'
1223
branch._repo_lock_token = 'r'
1224
branch._set_parent_location('foo')
1225
self.assertFinished(client)
1227
def test_backwards_compat(self):
1228
self.setup_smart_server_with_call_log()
1229
branch = self.make_branch('.')
1230
self.reset_smart_call_log()
1231
verb = 'Branch.set_parent_location'
1232
self.disable_verb(verb)
1233
branch.set_parent('http://foo/')
1234
self.assertLength(12, self.hpss_calls)
1237
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1239
def test_backwards_compat(self):
1240
self.setup_smart_server_with_call_log()
1241
branch = self.make_branch('.')
1242
self.reset_smart_call_log()
1243
verb = 'Branch.get_tags_bytes'
1244
self.disable_verb(verb)
1245
branch.tags.get_tag_dict()
1246
call_count = len([call for call in self.hpss_calls if
1247
call.call.method == verb])
1248
self.assertEqual(1, call_count)
1250
def test_trivial(self):
1251
transport = MemoryTransport()
1252
client = FakeClient(transport.base)
1253
client.add_expected_call(
1254
'Branch.get_stacked_on_url', ('quack/',),
1255
'error', ('NotStacked',))
1256
client.add_expected_call(
1257
'Branch.get_tags_bytes', ('quack/',),
1259
transport.mkdir('quack')
1260
transport = transport.clone('quack')
1261
branch = self.make_remote_branch(transport, client)
1262
result = branch.tags.get_tag_dict()
1263
self.assertFinished(client)
1264
self.assertEqual({}, result)
1267
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1269
def test_trivial(self):
1270
transport = MemoryTransport()
1271
client = FakeClient(transport.base)
1272
client.add_expected_call(
1273
'Branch.get_stacked_on_url', ('quack/',),
1274
'error', ('NotStacked',))
1275
client.add_expected_call(
1276
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1278
transport.mkdir('quack')
1279
transport = transport.clone('quack')
1280
branch = self.make_remote_branch(transport, client)
1281
self.lock_remote_branch(branch)
1282
branch._set_tags_bytes('tags bytes')
1283
self.assertFinished(client)
1284
self.assertEqual('tags bytes', client._calls[-1][-1])
1286
def test_backwards_compatible(self):
1287
transport = MemoryTransport()
1288
client = FakeClient(transport.base)
1289
client.add_expected_call(
1290
'Branch.get_stacked_on_url', ('quack/',),
1291
'error', ('NotStacked',))
1292
client.add_expected_call(
1293
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1294
'unknown', ('Branch.set_tags_bytes',))
1295
transport.mkdir('quack')
1296
transport = transport.clone('quack')
1297
branch = self.make_remote_branch(transport, client)
1298
self.lock_remote_branch(branch)
1299
class StubRealBranch(object):
1302
def _set_tags_bytes(self, bytes):
1303
self.calls.append(('set_tags_bytes', bytes))
1304
real_branch = StubRealBranch()
1305
branch._real_branch = real_branch
1306
branch._set_tags_bytes('tags bytes')
1307
# Call a second time, to exercise the 'remote version already inferred'
1309
branch._set_tags_bytes('tags bytes')
1310
self.assertFinished(client)
1312
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1315
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1317
def test_uses_last_revision_info_and_tags_by_default(self):
1318
transport = MemoryTransport()
1319
client = FakeClient(transport.base)
1320
client.add_expected_call(
1321
'Branch.get_stacked_on_url', ('quack/',),
1322
'error', ('NotStacked',))
1323
client.add_expected_call(
1324
'Branch.last_revision_info', ('quack/',),
1325
'success', ('ok', '1', 'rev-tip'))
1326
client.add_expected_call(
1327
'Branch.get_config_file', ('quack/',),
1328
'success', ('ok',), '')
1329
transport.mkdir('quack')
1330
transport = transport.clone('quack')
1331
branch = self.make_remote_branch(transport, client)
1332
result = branch.heads_to_fetch()
1333
self.assertFinished(client)
1334
self.assertEqual((set(['rev-tip']), set()), result)
1336
def test_uses_last_revision_info_and_tags_when_set(self):
1337
transport = MemoryTransport()
1338
client = FakeClient(transport.base)
1339
client.add_expected_call(
1340
'Branch.get_stacked_on_url', ('quack/',),
1341
'error', ('NotStacked',))
1342
client.add_expected_call(
1343
'Branch.last_revision_info', ('quack/',),
1344
'success', ('ok', '1', 'rev-tip'))
1345
client.add_expected_call(
1346
'Branch.get_config_file', ('quack/',),
1347
'success', ('ok',), 'branch.fetch_tags = True')
1348
# XXX: this will break if the default format's serialization of tags
1349
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1350
client.add_expected_call(
1351
'Branch.get_tags_bytes', ('quack/',),
1352
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1353
transport.mkdir('quack')
1354
transport = transport.clone('quack')
1355
branch = self.make_remote_branch(transport, client)
1356
result = branch.heads_to_fetch()
1357
self.assertFinished(client)
1359
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1361
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1362
transport = MemoryTransport()
1363
client = FakeClient(transport.base)
1364
client.add_expected_call(
1365
'Branch.get_stacked_on_url', ('quack/',),
1366
'error', ('NotStacked',))
1367
client.add_expected_call(
1368
'Branch.heads_to_fetch', ('quack/',),
1369
'success', (['tip'], ['tagged-1', 'tagged-2']))
1370
transport.mkdir('quack')
1371
transport = transport.clone('quack')
1372
branch = self.make_remote_branch(transport, client)
1373
branch._format._use_default_local_heads_to_fetch = lambda: False
1374
result = branch.heads_to_fetch()
1375
self.assertFinished(client)
1376
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1378
def make_branch_with_tags(self):
1379
self.setup_smart_server_with_call_log()
1380
# Make a branch with a single revision.
1381
builder = self.make_branch_builder('foo')
1382
builder.start_series()
1383
builder.build_snapshot('tip', None, [
1384
('add', ('', 'root-id', 'directory', ''))])
1385
builder.finish_series()
1386
branch = builder.get_branch()
1387
# Add two tags to that branch
1388
branch.tags.set_tag('tag-1', 'rev-1')
1389
branch.tags.set_tag('tag-2', 'rev-2')
1392
def test_backwards_compatible(self):
1393
branch = self.make_branch_with_tags()
1394
c = branch.get_config()
1395
c.set_user_option('branch.fetch_tags', 'True')
1396
self.addCleanup(branch.lock_read().unlock)
1397
# Disable the heads_to_fetch verb
1398
verb = 'Branch.heads_to_fetch'
1399
self.disable_verb(verb)
1400
self.reset_smart_call_log()
1401
result = branch.heads_to_fetch()
1402
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1404
['Branch.last_revision_info', 'Branch.get_config_file',
1405
'Branch.get_tags_bytes'],
1406
[call.call.method for call in self.hpss_calls])
1408
def test_backwards_compatible_no_tags(self):
1409
branch = self.make_branch_with_tags()
1410
c = branch.get_config()
1411
c.set_user_option('branch.fetch_tags', 'False')
1412
self.addCleanup(branch.lock_read().unlock)
1413
# Disable the heads_to_fetch verb
1414
verb = 'Branch.heads_to_fetch'
1415
self.disable_verb(verb)
1416
self.reset_smart_call_log()
1417
result = branch.heads_to_fetch()
1418
self.assertEqual((set(['tip']), set()), result)
1420
['Branch.last_revision_info', 'Branch.get_config_file'],
1421
[call.call.method for call in self.hpss_calls])
1424
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1426
def test_empty_branch(self):
1427
# in an empty branch we decode the response properly
1428
transport = MemoryTransport()
1429
client = FakeClient(transport.base)
1430
client.add_expected_call(
1431
'Branch.get_stacked_on_url', ('quack/',),
1432
'error', ('NotStacked',))
1433
client.add_expected_call(
1434
'Branch.last_revision_info', ('quack/',),
1435
'success', ('ok', '0', 'null:'))
1436
transport.mkdir('quack')
1437
transport = transport.clone('quack')
1438
branch = self.make_remote_branch(transport, client)
1439
result = branch.last_revision_info()
1440
self.assertFinished(client)
1441
self.assertEqual((0, NULL_REVISION), result)
1443
def test_non_empty_branch(self):
1444
# in a non-empty branch we also decode the response properly
1445
revid = u'\xc8'.encode('utf8')
1446
transport = MemoryTransport()
1447
client = FakeClient(transport.base)
1448
client.add_expected_call(
1449
'Branch.get_stacked_on_url', ('kwaak/',),
1450
'error', ('NotStacked',))
1451
client.add_expected_call(
1452
'Branch.last_revision_info', ('kwaak/',),
1453
'success', ('ok', '2', revid))
1454
transport.mkdir('kwaak')
1455
transport = transport.clone('kwaak')
1456
branch = self.make_remote_branch(transport, client)
1457
result = branch.last_revision_info()
1458
self.assertEqual((2, revid), result)
1461
class TestBranch_get_stacked_on_url(TestRemote):
1462
"""Test Branch._get_stacked_on_url rpc"""
1464
def test_get_stacked_on_invalid_url(self):
1465
# test that asking for a stacked on url the server can't access works.
1466
# This isn't perfect, but then as we're in the same process there
1467
# really isn't anything we can do to be 100% sure that the server
1468
# doesn't just open in - this test probably needs to be rewritten using
1469
# a spawn()ed server.
1470
stacked_branch = self.make_branch('stacked', format='1.9')
1471
memory_branch = self.make_branch('base', format='1.9')
1472
vfs_url = self.get_vfs_only_url('base')
1473
stacked_branch.set_stacked_on_url(vfs_url)
1474
transport = stacked_branch.bzrdir.root_transport
1475
client = FakeClient(transport.base)
1476
client.add_expected_call(
1477
'Branch.get_stacked_on_url', ('stacked/',),
1478
'success', ('ok', vfs_url))
1479
# XXX: Multiple calls are bad, this second call documents what is
1481
client.add_expected_call(
1482
'Branch.get_stacked_on_url', ('stacked/',),
1483
'success', ('ok', vfs_url))
1484
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1486
repo_fmt = remote.RemoteRepositoryFormat()
1487
repo_fmt._custom_format = stacked_branch.repository._format
1488
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1490
result = branch.get_stacked_on_url()
1491
self.assertEqual(vfs_url, result)
1493
def test_backwards_compatible(self):
1494
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1495
base_branch = self.make_branch('base', format='1.6')
1496
stacked_branch = self.make_branch('stacked', format='1.6')
1497
stacked_branch.set_stacked_on_url('../base')
1498
client = FakeClient(self.get_url())
1499
branch_network_name = self.get_branch_format().network_name()
1500
client.add_expected_call(
1501
'BzrDir.open_branchV3', ('stacked/',),
1502
'success', ('branch', branch_network_name))
1503
client.add_expected_call(
1504
'BzrDir.find_repositoryV3', ('stacked/',),
1505
'success', ('ok', '', 'no', 'no', 'yes',
1506
stacked_branch.repository._format.network_name()))
1507
# called twice, once from constructor and then again by us
1508
client.add_expected_call(
1509
'Branch.get_stacked_on_url', ('stacked/',),
1510
'unknown', ('Branch.get_stacked_on_url',))
1511
client.add_expected_call(
1512
'Branch.get_stacked_on_url', ('stacked/',),
1513
'unknown', ('Branch.get_stacked_on_url',))
1514
# this will also do vfs access, but that goes direct to the transport
1515
# and isn't seen by the FakeClient.
1516
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1517
RemoteBzrDirFormat(), _client=client)
1518
branch = bzrdir.open_branch()
1519
result = branch.get_stacked_on_url()
1520
self.assertEqual('../base', result)
1521
self.assertFinished(client)
1522
# it's in the fallback list both for the RemoteRepository and its vfs
1524
self.assertEqual(1, len(branch.repository._fallback_repositories))
1526
len(branch.repository._real_repository._fallback_repositories))
1528
def test_get_stacked_on_real_branch(self):
1529
base_branch = self.make_branch('base')
1530
stacked_branch = self.make_branch('stacked')
1531
stacked_branch.set_stacked_on_url('../base')
1532
reference_format = self.get_repo_format()
1533
network_name = reference_format.network_name()
1534
client = FakeClient(self.get_url())
1535
branch_network_name = self.get_branch_format().network_name()
1536
client.add_expected_call(
1537
'BzrDir.open_branchV3', ('stacked/',),
1538
'success', ('branch', branch_network_name))
1539
client.add_expected_call(
1540
'BzrDir.find_repositoryV3', ('stacked/',),
1541
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1542
# called twice, once from constructor and then again by us
1543
client.add_expected_call(
1544
'Branch.get_stacked_on_url', ('stacked/',),
1545
'success', ('ok', '../base'))
1546
client.add_expected_call(
1547
'Branch.get_stacked_on_url', ('stacked/',),
1548
'success', ('ok', '../base'))
1549
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1550
RemoteBzrDirFormat(), _client=client)
1551
branch = bzrdir.open_branch()
1552
result = branch.get_stacked_on_url()
1553
self.assertEqual('../base', result)
1554
self.assertFinished(client)
1555
# it's in the fallback list both for the RemoteRepository.
1556
self.assertEqual(1, len(branch.repository._fallback_repositories))
1557
# And we haven't had to construct a real repository.
1558
self.assertEqual(None, branch.repository._real_repository)
1561
class TestBranchSetLastRevision(RemoteBranchTestCase):
1563
def test_set_empty(self):
1564
# _set_last_revision_info('null:') is translated to calling
1565
# Branch.set_last_revision(path, '') on the wire.
1566
transport = MemoryTransport()
1567
transport.mkdir('branch')
1568
transport = transport.clone('branch')
1570
client = FakeClient(transport.base)
1571
client.add_expected_call(
1572
'Branch.get_stacked_on_url', ('branch/',),
1573
'error', ('NotStacked',))
1574
client.add_expected_call(
1575
'Branch.lock_write', ('branch/', '', ''),
1576
'success', ('ok', 'branch token', 'repo token'))
1577
client.add_expected_call(
1578
'Branch.last_revision_info',
1580
'success', ('ok', '0', 'null:'))
1581
client.add_expected_call(
1582
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1584
client.add_expected_call(
1585
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1587
branch = self.make_remote_branch(transport, client)
1589
result = branch._set_last_revision(NULL_REVISION)
1591
self.assertEqual(None, result)
1592
self.assertFinished(client)
1594
def test_set_nonempty(self):
1595
# set_last_revision_info(N, rev-idN) is translated to calling
1596
# Branch.set_last_revision(path, rev-idN) on the wire.
1597
transport = MemoryTransport()
1598
transport.mkdir('branch')
1599
transport = transport.clone('branch')
1601
client = FakeClient(transport.base)
1602
client.add_expected_call(
1603
'Branch.get_stacked_on_url', ('branch/',),
1604
'error', ('NotStacked',))
1605
client.add_expected_call(
1606
'Branch.lock_write', ('branch/', '', ''),
1607
'success', ('ok', 'branch token', 'repo token'))
1608
client.add_expected_call(
1609
'Branch.last_revision_info',
1611
'success', ('ok', '0', 'null:'))
1613
encoded_body = bz2.compress('\n'.join(lines))
1614
client.add_success_response_with_body(encoded_body, 'ok')
1615
client.add_expected_call(
1616
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1618
client.add_expected_call(
1619
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1621
branch = self.make_remote_branch(transport, client)
1622
# Lock the branch, reset the record of remote calls.
1624
result = branch._set_last_revision('rev-id2')
1626
self.assertEqual(None, result)
1627
self.assertFinished(client)
1629
def test_no_such_revision(self):
1630
transport = MemoryTransport()
1631
transport.mkdir('branch')
1632
transport = transport.clone('branch')
1633
# A response of 'NoSuchRevision' is translated into an exception.
1634
client = FakeClient(transport.base)
1635
client.add_expected_call(
1636
'Branch.get_stacked_on_url', ('branch/',),
1637
'error', ('NotStacked',))
1638
client.add_expected_call(
1639
'Branch.lock_write', ('branch/', '', ''),
1640
'success', ('ok', 'branch token', 'repo token'))
1641
client.add_expected_call(
1642
'Branch.last_revision_info',
1644
'success', ('ok', '0', 'null:'))
1645
# get_graph calls to construct the revision history, for the set_rh
1648
encoded_body = bz2.compress('\n'.join(lines))
1649
client.add_success_response_with_body(encoded_body, 'ok')
1650
client.add_expected_call(
1651
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1652
'error', ('NoSuchRevision', 'rev-id'))
1653
client.add_expected_call(
1654
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1657
branch = self.make_remote_branch(transport, client)
1660
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1662
self.assertFinished(client)
1664
def test_tip_change_rejected(self):
1665
"""TipChangeRejected responses cause a TipChangeRejected exception to
1668
transport = MemoryTransport()
1669
transport.mkdir('branch')
1670
transport = transport.clone('branch')
1671
client = FakeClient(transport.base)
1672
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1673
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
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:'))
1685
encoded_body = bz2.compress('\n'.join(lines))
1686
client.add_success_response_with_body(encoded_body, 'ok')
1687
client.add_expected_call(
1688
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1689
'error', ('TipChangeRejected', rejection_msg_utf8))
1690
client.add_expected_call(
1691
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1693
branch = self.make_remote_branch(transport, client)
1695
# The 'TipChangeRejected' error response triggered by calling
1696
# set_last_revision_info causes a TipChangeRejected exception.
1697
err = self.assertRaises(
1698
errors.TipChangeRejected,
1699
branch._set_last_revision, 'rev-id')
1700
# The UTF-8 message from the response has been decoded into a unicode
1702
self.assertIsInstance(err.msg, unicode)
1703
self.assertEqual(rejection_msg_unicode, err.msg)
1705
self.assertFinished(client)
1708
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1710
def test_set_last_revision_info(self):
1711
# set_last_revision_info(num, 'rev-id') is translated to calling
1712
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1713
transport = MemoryTransport()
1714
transport.mkdir('branch')
1715
transport = transport.clone('branch')
1716
client = FakeClient(transport.base)
1717
# get_stacked_on_url
1718
client.add_error_response('NotStacked')
1720
client.add_success_response('ok', 'branch token', 'repo token')
1721
# query the current revision
1722
client.add_success_response('ok', '0', 'null:')
1724
client.add_success_response('ok')
1726
client.add_success_response('ok')
1728
branch = self.make_remote_branch(transport, client)
1729
# Lock the branch, reset the record of remote calls.
1732
result = branch.set_last_revision_info(1234, 'a-revision-id')
1734
[('call', 'Branch.last_revision_info', ('branch/',)),
1735
('call', 'Branch.set_last_revision_info',
1736
('branch/', 'branch token', 'repo token',
1737
'1234', 'a-revision-id'))],
1739
self.assertEqual(None, result)
1741
def test_no_such_revision(self):
1742
# A response of 'NoSuchRevision' is translated into an exception.
1743
transport = MemoryTransport()
1744
transport.mkdir('branch')
1745
transport = transport.clone('branch')
1746
client = FakeClient(transport.base)
1747
# get_stacked_on_url
1748
client.add_error_response('NotStacked')
1750
client.add_success_response('ok', 'branch token', 'repo token')
1752
client.add_error_response('NoSuchRevision', 'revid')
1754
client.add_success_response('ok')
1756
branch = self.make_remote_branch(transport, client)
1757
# Lock the branch, reset the record of remote calls.
1762
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1765
def test_backwards_compatibility(self):
1766
"""If the server does not support the Branch.set_last_revision_info
1767
verb (which is new in 1.4), then the client falls back to VFS methods.
1769
# This test is a little messy. Unlike most tests in this file, it
1770
# doesn't purely test what a Remote* object sends over the wire, and
1771
# how it reacts to responses from the wire. It instead relies partly
1772
# on asserting that the RemoteBranch will call
1773
# self._real_branch.set_last_revision_info(...).
1775
# First, set up our RemoteBranch with a FakeClient that raises
1776
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1777
transport = MemoryTransport()
1778
transport.mkdir('branch')
1779
transport = transport.clone('branch')
1780
client = FakeClient(transport.base)
1781
client.add_expected_call(
1782
'Branch.get_stacked_on_url', ('branch/',),
1783
'error', ('NotStacked',))
1784
client.add_expected_call(
1785
'Branch.last_revision_info',
1787
'success', ('ok', '0', 'null:'))
1788
client.add_expected_call(
1789
'Branch.set_last_revision_info',
1790
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1791
'unknown', 'Branch.set_last_revision_info')
1793
branch = self.make_remote_branch(transport, client)
1794
class StubRealBranch(object):
1797
def set_last_revision_info(self, revno, revision_id):
1799
('set_last_revision_info', revno, revision_id))
1800
def _clear_cached_state(self):
1802
real_branch = StubRealBranch()
1803
branch._real_branch = real_branch
1804
self.lock_remote_branch(branch)
1806
# Call set_last_revision_info, and verify it behaved as expected.
1807
result = branch.set_last_revision_info(1234, 'a-revision-id')
1809
[('set_last_revision_info', 1234, 'a-revision-id')],
1811
self.assertFinished(client)
1813
def test_unexpected_error(self):
1814
# If the server sends an error the client doesn't understand, it gets
1815
# turned into an UnknownErrorFromSmartServer, which is presented as a
1816
# non-internal error to the user.
1817
transport = MemoryTransport()
1818
transport.mkdir('branch')
1819
transport = transport.clone('branch')
1820
client = FakeClient(transport.base)
1821
# get_stacked_on_url
1822
client.add_error_response('NotStacked')
1824
client.add_success_response('ok', 'branch token', 'repo token')
1826
client.add_error_response('UnexpectedError')
1828
client.add_success_response('ok')
1830
branch = self.make_remote_branch(transport, client)
1831
# Lock the branch, reset the record of remote calls.
1835
err = self.assertRaises(
1836
errors.UnknownErrorFromSmartServer,
1837
branch.set_last_revision_info, 123, 'revid')
1838
self.assertEqual(('UnexpectedError',), err.error_tuple)
1841
def test_tip_change_rejected(self):
1842
"""TipChangeRejected responses cause a TipChangeRejected exception to
1845
transport = MemoryTransport()
1846
transport.mkdir('branch')
1847
transport = transport.clone('branch')
1848
client = FakeClient(transport.base)
1849
# get_stacked_on_url
1850
client.add_error_response('NotStacked')
1852
client.add_success_response('ok', 'branch token', 'repo token')
1854
client.add_error_response('TipChangeRejected', 'rejection message')
1856
client.add_success_response('ok')
1858
branch = self.make_remote_branch(transport, client)
1859
# Lock the branch, reset the record of remote calls.
1861
self.addCleanup(branch.unlock)
1864
# The 'TipChangeRejected' error response triggered by calling
1865
# set_last_revision_info causes a TipChangeRejected exception.
1866
err = self.assertRaises(
1867
errors.TipChangeRejected,
1868
branch.set_last_revision_info, 123, 'revid')
1869
self.assertEqual('rejection message', err.msg)
1872
class TestBranchGetSetConfig(RemoteBranchTestCase):
1874
def test_get_branch_conf(self):
1875
# in an empty branch we decode the response properly
1876
client = FakeClient()
1877
client.add_expected_call(
1878
'Branch.get_stacked_on_url', ('memory:///',),
1879
'error', ('NotStacked',),)
1880
client.add_success_response_with_body('# config file body', 'ok')
1881
transport = MemoryTransport()
1882
branch = self.make_remote_branch(transport, client)
1883
config = branch.get_config()
1884
config.has_explicit_nickname()
1886
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1887
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1890
def test_get_multi_line_branch_conf(self):
1891
# Make sure that multiple-line branch.conf files are supported
1893
# https://bugs.launchpad.net/bzr/+bug/354075
1894
client = FakeClient()
1895
client.add_expected_call(
1896
'Branch.get_stacked_on_url', ('memory:///',),
1897
'error', ('NotStacked',),)
1898
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1899
transport = MemoryTransport()
1900
branch = self.make_remote_branch(transport, client)
1901
config = branch.get_config()
1902
self.assertEqual(u'2', config.get_user_option('b'))
1904
def test_set_option(self):
1905
client = FakeClient()
1906
client.add_expected_call(
1907
'Branch.get_stacked_on_url', ('memory:///',),
1908
'error', ('NotStacked',),)
1909
client.add_expected_call(
1910
'Branch.lock_write', ('memory:///', '', ''),
1911
'success', ('ok', 'branch token', 'repo token'))
1912
client.add_expected_call(
1913
'Branch.set_config_option', ('memory:///', 'branch token',
1914
'repo token', 'foo', 'bar', ''),
1916
client.add_expected_call(
1917
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1919
transport = MemoryTransport()
1920
branch = self.make_remote_branch(transport, client)
1922
config = branch._get_config()
1923
config.set_option('foo', 'bar')
1925
self.assertFinished(client)
1927
def test_set_option_with_dict(self):
1928
client = FakeClient()
1929
client.add_expected_call(
1930
'Branch.get_stacked_on_url', ('memory:///',),
1931
'error', ('NotStacked',),)
1932
client.add_expected_call(
1933
'Branch.lock_write', ('memory:///', '', ''),
1934
'success', ('ok', 'branch token', 'repo token'))
1935
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1936
client.add_expected_call(
1937
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1938
'repo token', encoded_dict_value, 'foo', ''),
1940
client.add_expected_call(
1941
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1943
transport = MemoryTransport()
1944
branch = self.make_remote_branch(transport, client)
1946
config = branch._get_config()
1948
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1951
self.assertFinished(client)
1953
def test_backwards_compat_set_option(self):
1954
self.setup_smart_server_with_call_log()
1955
branch = self.make_branch('.')
1956
verb = 'Branch.set_config_option'
1957
self.disable_verb(verb)
1959
self.addCleanup(branch.unlock)
1960
self.reset_smart_call_log()
1961
branch._get_config().set_option('value', 'name')
1962
self.assertLength(10, self.hpss_calls)
1963
self.assertEqual('value', branch._get_config().get_option('name'))
1965
def test_backwards_compat_set_option_with_dict(self):
1966
self.setup_smart_server_with_call_log()
1967
branch = self.make_branch('.')
1968
verb = 'Branch.set_config_option_dict'
1969
self.disable_verb(verb)
1971
self.addCleanup(branch.unlock)
1972
self.reset_smart_call_log()
1973
config = branch._get_config()
1974
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
1975
config.set_option(value_dict, 'name')
1976
self.assertLength(10, self.hpss_calls)
1977
self.assertEqual(value_dict, branch._get_config().get_option('name'))
1980
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
1982
def test_get_branch_conf(self):
1983
# in an empty branch we decode the response properly
1984
client = FakeClient()
1985
client.add_expected_call(
1986
'Branch.get_stacked_on_url', ('memory:///',),
1987
'error', ('NotStacked',),)
1988
client.add_success_response_with_body('# config file body', 'ok')
1989
transport = MemoryTransport()
1990
branch = self.make_remote_branch(transport, client)
1991
config = branch.get_config_stack()
1993
config.get("log_format")
1995
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1996
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1999
def test_set_branch_conf(self):
2000
client = FakeClient()
2001
client.add_expected_call(
2002
'Branch.get_stacked_on_url', ('memory:///',),
2003
'error', ('NotStacked',),)
2004
client.add_expected_call(
2005
'Branch.lock_write', ('memory:///', '', ''),
2006
'success', ('ok', 'branch token', 'repo token'))
2007
client.add_expected_call(
2008
'Branch.get_config_file', ('memory:///', ),
2009
'success', ('ok', ), "# line 1\n")
2010
client.add_expected_call(
2011
'Branch.put_config_file', ('memory:///', 'branch token',
2014
client.add_expected_call(
2015
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2017
transport = MemoryTransport()
2018
branch = self.make_remote_branch(transport, client)
2020
config = branch.get_config_stack()
2021
config.set('email', 'The Dude <lebowski@example.com>')
2023
self.assertFinished(client)
2025
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2026
('call', 'Branch.lock_write', ('memory:///', '', '')),
2027
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2028
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2029
('memory:///', 'branch token', 'repo token'),
2030
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2031
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2035
class TestBranchLockWrite(RemoteBranchTestCase):
2037
def test_lock_write_unlockable(self):
2038
transport = MemoryTransport()
2039
client = FakeClient(transport.base)
2040
client.add_expected_call(
2041
'Branch.get_stacked_on_url', ('quack/',),
2042
'error', ('NotStacked',),)
2043
client.add_expected_call(
2044
'Branch.lock_write', ('quack/', '', ''),
2045
'error', ('UnlockableTransport',))
2046
transport.mkdir('quack')
2047
transport = transport.clone('quack')
2048
branch = self.make_remote_branch(transport, client)
2049
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2050
self.assertFinished(client)
2053
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2055
def test_simple(self):
2056
transport = MemoryTransport()
2057
client = FakeClient(transport.base)
2058
client.add_expected_call(
2059
'Branch.get_stacked_on_url', ('quack/',),
2060
'error', ('NotStacked',),)
2061
client.add_expected_call(
2062
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2063
'success', ('ok', '0',),)
2064
client.add_expected_call(
2065
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2066
'error', ('NoSuchRevision', 'unknown',),)
2067
transport.mkdir('quack')
2068
transport = transport.clone('quack')
2069
branch = self.make_remote_branch(transport, client)
2070
self.assertEquals(0, branch.revision_id_to_revno('null:'))
2071
self.assertRaises(errors.NoSuchRevision,
2072
branch.revision_id_to_revno, 'unknown')
2073
self.assertFinished(client)
2075
def test_dotted(self):
2076
transport = MemoryTransport()
2077
client = FakeClient(transport.base)
2078
client.add_expected_call(
2079
'Branch.get_stacked_on_url', ('quack/',),
2080
'error', ('NotStacked',),)
2081
client.add_expected_call(
2082
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2083
'success', ('ok', '0',),)
2084
client.add_expected_call(
2085
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2086
'error', ('NoSuchRevision', 'unknown',),)
2087
transport.mkdir('quack')
2088
transport = transport.clone('quack')
2089
branch = self.make_remote_branch(transport, client)
2090
self.assertEquals((0, ), branch.revision_id_to_dotted_revno('null:'))
2091
self.assertRaises(errors.NoSuchRevision,
2092
branch.revision_id_to_dotted_revno, 'unknown')
2093
self.assertFinished(client)
2095
def test_dotted_no_smart_verb(self):
2096
self.setup_smart_server_with_call_log()
2097
branch = self.make_branch('.')
2098
self.disable_verb('Branch.revision_id_to_revno')
2099
self.reset_smart_call_log()
2100
self.assertEquals((0, ),
2101
branch.revision_id_to_dotted_revno('null:'))
2102
self.assertLength(7, self.hpss_calls)
2105
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2107
def test__get_config(self):
2108
client = FakeClient()
2109
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2110
transport = MemoryTransport()
2111
bzrdir = self.make_remote_bzrdir(transport, client)
2112
config = bzrdir.get_config()
2113
self.assertEqual('/', config.get_default_stack_on())
2115
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2118
def test_set_option_uses_vfs(self):
2119
self.setup_smart_server_with_call_log()
2120
bzrdir = self.make_bzrdir('.')
2121
self.reset_smart_call_log()
2122
config = bzrdir.get_config()
2123
config.set_default_stack_on('/')
2124
self.assertLength(3, self.hpss_calls)
2126
def test_backwards_compat_get_option(self):
2127
self.setup_smart_server_with_call_log()
2128
bzrdir = self.make_bzrdir('.')
2129
verb = 'BzrDir.get_config_file'
2130
self.disable_verb(verb)
2131
self.reset_smart_call_log()
2132
self.assertEqual(None,
2133
bzrdir._get_config().get_option('default_stack_on'))
2134
self.assertLength(3, self.hpss_calls)
2137
class TestTransportIsReadonly(tests.TestCase):
2139
def test_true(self):
2140
client = FakeClient()
2141
client.add_success_response('yes')
2142
transport = RemoteTransport('bzr://example.com/', medium=False,
2144
self.assertEqual(True, transport.is_readonly())
2146
[('call', 'Transport.is_readonly', ())],
2149
def test_false(self):
2150
client = FakeClient()
2151
client.add_success_response('no')
2152
transport = RemoteTransport('bzr://example.com/', medium=False,
2154
self.assertEqual(False, transport.is_readonly())
2156
[('call', 'Transport.is_readonly', ())],
2159
def test_error_from_old_server(self):
2160
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2162
Clients should treat it as a "no" response, because is_readonly is only
2163
advisory anyway (a transport could be read-write, but then the
2164
underlying filesystem could be readonly anyway).
2166
client = FakeClient()
2167
client.add_unknown_method_response('Transport.is_readonly')
2168
transport = RemoteTransport('bzr://example.com/', medium=False,
2170
self.assertEqual(False, transport.is_readonly())
2172
[('call', 'Transport.is_readonly', ())],
2176
class TestTransportMkdir(tests.TestCase):
2178
def test_permissiondenied(self):
2179
client = FakeClient()
2180
client.add_error_response('PermissionDenied', 'remote path', 'extra')
2181
transport = RemoteTransport('bzr://example.com/', medium=False,
2183
exc = self.assertRaises(
2184
errors.PermissionDenied, transport.mkdir, 'client path')
2185
expected_error = errors.PermissionDenied('/client path', 'extra')
2186
self.assertEqual(expected_error, exc)
2189
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2191
def test_defaults_to_none(self):
2192
t = RemoteSSHTransport('bzr+ssh://example.com')
2193
self.assertIs(None, t._get_credentials()[0])
2195
def test_uses_authentication_config(self):
2196
conf = config.AuthenticationConfig()
2197
conf._get_config().update(
2198
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2201
t = RemoteSSHTransport('bzr+ssh://example.com')
2202
self.assertEqual('bar', t._get_credentials()[0])
2205
class TestRemoteRepository(TestRemote):
2206
"""Base for testing RemoteRepository protocol usage.
2208
These tests contain frozen requests and responses. We want any changes to
2209
what is sent or expected to be require a thoughtful update to these tests
2210
because they might break compatibility with different-versioned servers.
2213
def setup_fake_client_and_repository(self, transport_path):
2214
"""Create the fake client and repository for testing with.
2216
There's no real server here; we just have canned responses sent
2219
:param transport_path: Path below the root of the MemoryTransport
2220
where the repository will be created.
2222
transport = MemoryTransport()
2223
transport.mkdir(transport_path)
2224
client = FakeClient(transport.base)
2225
transport = transport.clone(transport_path)
2226
# we do not want bzrdir to make any remote calls
2227
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2229
repo = RemoteRepository(bzrdir, None, _client=client)
2233
def remoted_description(format):
2234
return 'Remote: ' + format.get_format_description()
2237
class TestBranchFormat(tests.TestCase):
2239
def test_get_format_description(self):
2240
remote_format = RemoteBranchFormat()
2241
real_format = branch.format_registry.get_default()
2242
remote_format._network_name = real_format.network_name()
2243
self.assertEqual(remoted_description(real_format),
2244
remote_format.get_format_description())
2247
class TestRepositoryFormat(TestRemoteRepository):
2249
def test_fast_delta(self):
2250
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2251
true_format = RemoteRepositoryFormat()
2252
true_format._network_name = true_name
2253
self.assertEqual(True, true_format.fast_deltas)
2254
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2255
false_format = RemoteRepositoryFormat()
2256
false_format._network_name = false_name
2257
self.assertEqual(False, false_format.fast_deltas)
2259
def test_get_format_description(self):
2260
remote_repo_format = RemoteRepositoryFormat()
2261
real_format = repository.format_registry.get_default()
2262
remote_repo_format._network_name = real_format.network_name()
2263
self.assertEqual(remoted_description(real_format),
2264
remote_repo_format.get_format_description())
2267
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2269
def test_empty(self):
2270
transport_path = 'quack'
2271
repo, client = self.setup_fake_client_and_repository(transport_path)
2272
client.add_success_response_with_body('', 'ok')
2273
self.assertEquals([], repo.all_revision_ids())
2275
[('call_expecting_body', 'Repository.all_revision_ids',
2279
def test_with_some_content(self):
2280
transport_path = 'quack'
2281
repo, client = self.setup_fake_client_and_repository(transport_path)
2282
client.add_success_response_with_body(
2283
'rev1\nrev2\nanotherrev\n', 'ok')
2284
self.assertEquals(["rev1", "rev2", "anotherrev"],
2285
repo.all_revision_ids())
2287
[('call_expecting_body', 'Repository.all_revision_ids',
2292
class TestRepositoryGatherStats(TestRemoteRepository):
2294
def test_revid_none(self):
2295
# ('ok',), body with revisions and size
2296
transport_path = 'quack'
2297
repo, client = self.setup_fake_client_and_repository(transport_path)
2298
client.add_success_response_with_body(
2299
'revisions: 2\nsize: 18\n', 'ok')
2300
result = repo.gather_stats(None)
2302
[('call_expecting_body', 'Repository.gather_stats',
2303
('quack/','','no'))],
2305
self.assertEqual({'revisions': 2, 'size': 18}, result)
2307
def test_revid_no_committers(self):
2308
# ('ok',), body without committers
2309
body = ('firstrev: 123456.300 3600\n'
2310
'latestrev: 654231.400 0\n'
2313
transport_path = 'quick'
2314
revid = u'\xc8'.encode('utf8')
2315
repo, client = self.setup_fake_client_and_repository(transport_path)
2316
client.add_success_response_with_body(body, 'ok')
2317
result = repo.gather_stats(revid)
2319
[('call_expecting_body', 'Repository.gather_stats',
2320
('quick/', revid, 'no'))],
2322
self.assertEqual({'revisions': 2, 'size': 18,
2323
'firstrev': (123456.300, 3600),
2324
'latestrev': (654231.400, 0),},
2327
def test_revid_with_committers(self):
2328
# ('ok',), body with committers
2329
body = ('committers: 128\n'
2330
'firstrev: 123456.300 3600\n'
2331
'latestrev: 654231.400 0\n'
2334
transport_path = 'buick'
2335
revid = u'\xc8'.encode('utf8')
2336
repo, client = self.setup_fake_client_and_repository(transport_path)
2337
client.add_success_response_with_body(body, 'ok')
2338
result = repo.gather_stats(revid, True)
2340
[('call_expecting_body', 'Repository.gather_stats',
2341
('buick/', revid, 'yes'))],
2343
self.assertEqual({'revisions': 2, 'size': 18,
2345
'firstrev': (123456.300, 3600),
2346
'latestrev': (654231.400, 0),},
2350
class TestRepositoryBreakLock(TestRemoteRepository):
2352
def test_break_lock(self):
2353
transport_path = 'quack'
2354
repo, client = self.setup_fake_client_and_repository(transport_path)
2355
client.add_success_response('ok')
2358
[('call', 'Repository.break_lock', ('quack/',))],
2362
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2364
def test_get_serializer_format(self):
2365
transport_path = 'hill'
2366
repo, client = self.setup_fake_client_and_repository(transport_path)
2367
client.add_success_response('ok', '7')
2368
self.assertEquals('7', repo.get_serializer_format())
2370
[('call', 'VersionedFileRepository.get_serializer_format',
2375
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2377
def test_text(self):
2378
# ('ok',), body with signature text
2379
transport_path = 'quack'
2380
repo, client = self.setup_fake_client_and_repository(transport_path)
2381
client.add_success_response_with_body(
2383
self.assertEquals("THETEXT", repo.get_signature_text("revid"))
2385
[('call_expecting_body', 'Repository.get_revision_signature_text',
2386
('quack/', 'revid'))],
2389
def test_no_signature(self):
2390
transport_path = 'quick'
2391
repo, client = self.setup_fake_client_and_repository(transport_path)
2392
client.add_error_response('nosuchrevision', 'unknown')
2393
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2396
[('call_expecting_body', 'Repository.get_revision_signature_text',
2397
('quick/', 'unknown'))],
2401
class TestRepositoryGetGraph(TestRemoteRepository):
2403
def test_get_graph(self):
2404
# get_graph returns a graph with a custom parents provider.
2405
transport_path = 'quack'
2406
repo, client = self.setup_fake_client_and_repository(transport_path)
2407
graph = repo.get_graph()
2408
self.assertNotEqual(graph._parents_provider, repo)
2411
class TestRepositoryAddSignatureText(TestRemoteRepository):
2413
def test_add_signature_text(self):
2414
transport_path = 'quack'
2415
repo, client = self.setup_fake_client_and_repository(transport_path)
2416
client.add_expected_call(
2417
'Repository.lock_write', ('quack/', ''),
2418
'success', ('ok', 'a token'))
2419
client.add_expected_call(
2420
'Repository.start_write_group', ('quack/', 'a token'),
2421
'success', ('ok', ('token1', )))
2422
client.add_expected_call(
2423
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2425
'success', ('ok', ), None)
2427
repo.start_write_group()
2429
repo.add_signature_text("rev1", "every bloody emperor"))
2431
('call_with_body_bytes_expecting_body',
2432
'Repository.add_signature_text',
2433
('quack/', 'a token', 'rev1', 'token1'),
2434
'every bloody emperor'),
2438
class TestRepositoryGetParentMap(TestRemoteRepository):
2440
def test_get_parent_map_caching(self):
2441
# get_parent_map returns from cache until unlock()
2442
# setup a reponse with two revisions
2443
r1 = u'\u0e33'.encode('utf8')
2444
r2 = u'\u0dab'.encode('utf8')
2445
lines = [' '.join([r2, r1]), r1]
2446
encoded_body = bz2.compress('\n'.join(lines))
2448
transport_path = 'quack'
2449
repo, client = self.setup_fake_client_and_repository(transport_path)
2450
client.add_success_response_with_body(encoded_body, 'ok')
2451
client.add_success_response_with_body(encoded_body, 'ok')
2453
graph = repo.get_graph()
2454
parents = graph.get_parent_map([r2])
2455
self.assertEqual({r2: (r1,)}, parents)
2456
# locking and unlocking deeper should not reset
2459
parents = graph.get_parent_map([r1])
2460
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2462
[('call_with_body_bytes_expecting_body',
2463
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2467
# now we call again, and it should use the second response.
2469
graph = repo.get_graph()
2470
parents = graph.get_parent_map([r1])
2471
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2473
[('call_with_body_bytes_expecting_body',
2474
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2476
('call_with_body_bytes_expecting_body',
2477
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2483
def test_get_parent_map_reconnects_if_unknown_method(self):
2484
transport_path = 'quack'
2485
rev_id = 'revision-id'
2486
repo, client = self.setup_fake_client_and_repository(transport_path)
2487
client.add_unknown_method_response('Repository.get_parent_map')
2488
client.add_success_response_with_body(rev_id, 'ok')
2489
self.assertFalse(client._medium._is_remote_before((1, 2)))
2490
parents = repo.get_parent_map([rev_id])
2492
[('call_with_body_bytes_expecting_body',
2493
'Repository.get_parent_map',
2494
('quack/', 'include-missing:', rev_id), '\n\n0'),
2495
('disconnect medium',),
2496
('call_expecting_body', 'Repository.get_revision_graph',
2499
# The medium is now marked as being connected to an older server
2500
self.assertTrue(client._medium._is_remote_before((1, 2)))
2501
self.assertEqual({rev_id: ('null:',)}, parents)
2503
def test_get_parent_map_fallback_parentless_node(self):
2504
"""get_parent_map falls back to get_revision_graph on old servers. The
2505
results from get_revision_graph are tweaked to match the get_parent_map
2508
Specifically, a {key: ()} result from get_revision_graph means "no
2509
parents" for that key, which in get_parent_map results should be
2510
represented as {key: ('null:',)}.
2512
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2514
rev_id = 'revision-id'
2515
transport_path = 'quack'
2516
repo, client = self.setup_fake_client_and_repository(transport_path)
2517
client.add_success_response_with_body(rev_id, 'ok')
2518
client._medium._remember_remote_is_before((1, 2))
2519
parents = repo.get_parent_map([rev_id])
2521
[('call_expecting_body', 'Repository.get_revision_graph',
2524
self.assertEqual({rev_id: ('null:',)}, parents)
2526
def test_get_parent_map_unexpected_response(self):
2527
repo, client = self.setup_fake_client_and_repository('path')
2528
client.add_success_response('something unexpected!')
2530
errors.UnexpectedSmartServerResponse,
2531
repo.get_parent_map, ['a-revision-id'])
2533
def test_get_parent_map_negative_caches_missing_keys(self):
2534
self.setup_smart_server_with_call_log()
2535
repo = self.make_repository('foo')
2536
self.assertIsInstance(repo, RemoteRepository)
2538
self.addCleanup(repo.unlock)
2539
self.reset_smart_call_log()
2540
graph = repo.get_graph()
2541
self.assertEqual({},
2542
graph.get_parent_map(['some-missing', 'other-missing']))
2543
self.assertLength(1, self.hpss_calls)
2544
# No call if we repeat this
2545
self.reset_smart_call_log()
2546
graph = repo.get_graph()
2547
self.assertEqual({},
2548
graph.get_parent_map(['some-missing', 'other-missing']))
2549
self.assertLength(0, self.hpss_calls)
2550
# Asking for more unknown keys makes a request.
2551
self.reset_smart_call_log()
2552
graph = repo.get_graph()
2553
self.assertEqual({},
2554
graph.get_parent_map(['some-missing', 'other-missing',
2556
self.assertLength(1, self.hpss_calls)
2558
def disableExtraResults(self):
2559
self.overrideAttr(SmartServerRepositoryGetParentMap,
2560
'no_extra_results', True)
2562
def test_null_cached_missing_and_stop_key(self):
2563
self.setup_smart_server_with_call_log()
2564
# Make a branch with a single revision.
2565
builder = self.make_branch_builder('foo')
2566
builder.start_series()
2567
builder.build_snapshot('first', None, [
2568
('add', ('', 'root-id', 'directory', ''))])
2569
builder.finish_series()
2570
branch = builder.get_branch()
2571
repo = branch.repository
2572
self.assertIsInstance(repo, RemoteRepository)
2573
# Stop the server from sending extra results.
2574
self.disableExtraResults()
2576
self.addCleanup(repo.unlock)
2577
self.reset_smart_call_log()
2578
graph = repo.get_graph()
2579
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2580
# 'first' it will be a candidate for the stop_keys of subsequent
2581
# requests, and because 'null:' was queried but not returned it will be
2582
# cached as missing.
2583
self.assertEqual({'first': ('null:',)},
2584
graph.get_parent_map(['first', 'null:']))
2585
# Now query for another key. This request will pass along a recipe of
2586
# start and stop keys describing the already cached results, and this
2587
# recipe's revision count must be correct (or else it will trigger an
2588
# error from the server).
2589
self.assertEqual({}, graph.get_parent_map(['another-key']))
2590
# This assertion guards against disableExtraResults silently failing to
2591
# work, thus invalidating the test.
2592
self.assertLength(2, self.hpss_calls)
2594
def test_get_parent_map_gets_ghosts_from_result(self):
2595
# asking for a revision should negatively cache close ghosts in its
2597
self.setup_smart_server_with_call_log()
2598
tree = self.make_branch_and_memory_tree('foo')
2601
builder = treebuilder.TreeBuilder()
2602
builder.start_tree(tree)
2604
builder.finish_tree()
2605
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2606
rev_id = tree.commit('')
2610
self.addCleanup(tree.unlock)
2611
repo = tree.branch.repository
2612
self.assertIsInstance(repo, RemoteRepository)
2614
repo.get_parent_map([rev_id])
2615
self.reset_smart_call_log()
2616
# Now asking for rev_id's ghost parent should not make calls
2617
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2618
self.assertLength(0, self.hpss_calls)
2620
def test_exposes_get_cached_parent_map(self):
2621
"""RemoteRepository exposes get_cached_parent_map from
2624
r1 = u'\u0e33'.encode('utf8')
2625
r2 = u'\u0dab'.encode('utf8')
2626
lines = [' '.join([r2, r1]), r1]
2627
encoded_body = bz2.compress('\n'.join(lines))
2629
transport_path = 'quack'
2630
repo, client = self.setup_fake_client_and_repository(transport_path)
2631
client.add_success_response_with_body(encoded_body, 'ok')
2633
# get_cached_parent_map should *not* trigger an RPC
2634
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2635
self.assertEqual([], client._calls)
2636
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2637
self.assertEqual({r1: (NULL_REVISION,)},
2638
repo.get_cached_parent_map([r1]))
2640
[('call_with_body_bytes_expecting_body',
2641
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2647
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2649
def test_allows_new_revisions(self):
2650
"""get_parent_map's results can be updated by commit."""
2651
smart_server = test_server.SmartTCPServer_for_testing()
2652
self.start_server(smart_server)
2653
self.make_branch('branch')
2654
branch = Branch.open(smart_server.get_url() + '/branch')
2655
tree = branch.create_checkout('tree', lightweight=True)
2657
self.addCleanup(tree.unlock)
2658
graph = tree.branch.repository.get_graph()
2659
# This provides an opportunity for the missing rev-id to be cached.
2660
self.assertEqual({}, graph.get_parent_map(['rev1']))
2661
tree.commit('message', rev_id='rev1')
2662
graph = tree.branch.repository.get_graph()
2663
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2666
class TestRepositoryGetRevisions(TestRemoteRepository):
2668
def test_hpss_missing_revision(self):
2669
transport_path = 'quack'
2670
repo, client = self.setup_fake_client_and_repository(transport_path)
2671
client.add_success_response_with_body(
2673
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2674
['somerev1', 'anotherrev2'])
2676
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2677
('quack/', ), "somerev1\nanotherrev2")],
2680
def test_hpss_get_single_revision(self):
2681
transport_path = 'quack'
2682
repo, client = self.setup_fake_client_and_repository(transport_path)
2683
somerev1 = Revision("somerev1")
2684
somerev1.committer = "Joe Committer <joe@example.com>"
2685
somerev1.timestamp = 1321828927
2686
somerev1.timezone = -60
2687
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2688
somerev1.message = "Message"
2689
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2691
# Split up body into two bits to make sure the zlib compression object
2692
# gets data fed twice.
2693
client.add_success_response_with_body(
2694
[body[:10], body[10:]], 'ok', '10')
2695
revs = repo.get_revisions(['somerev1'])
2696
self.assertEquals(revs, [somerev1])
2698
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2699
('quack/', ), "somerev1")],
2703
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2705
def test_null_revision(self):
2706
# a null revision has the predictable result {}, we should have no wire
2707
# traffic when calling it with this argument
2708
transport_path = 'empty'
2709
repo, client = self.setup_fake_client_and_repository(transport_path)
2710
client.add_success_response('notused')
2711
# actual RemoteRepository.get_revision_graph is gone, but there's an
2712
# equivalent private method for testing
2713
result = repo._get_revision_graph(NULL_REVISION)
2714
self.assertEqual([], client._calls)
2715
self.assertEqual({}, result)
2717
def test_none_revision(self):
2718
# with none we want the entire graph
2719
r1 = u'\u0e33'.encode('utf8')
2720
r2 = u'\u0dab'.encode('utf8')
2721
lines = [' '.join([r2, r1]), r1]
2722
encoded_body = '\n'.join(lines)
2724
transport_path = 'sinhala'
2725
repo, client = self.setup_fake_client_and_repository(transport_path)
2726
client.add_success_response_with_body(encoded_body, 'ok')
2727
# actual RemoteRepository.get_revision_graph is gone, but there's an
2728
# equivalent private method for testing
2729
result = repo._get_revision_graph(None)
2731
[('call_expecting_body', 'Repository.get_revision_graph',
2734
self.assertEqual({r1: (), r2: (r1, )}, result)
2736
def test_specific_revision(self):
2737
# with a specific revision we want the graph for that
2738
# with none we want the entire graph
2739
r11 = u'\u0e33'.encode('utf8')
2740
r12 = u'\xc9'.encode('utf8')
2741
r2 = u'\u0dab'.encode('utf8')
2742
lines = [' '.join([r2, r11, r12]), r11, r12]
2743
encoded_body = '\n'.join(lines)
2745
transport_path = 'sinhala'
2746
repo, client = self.setup_fake_client_and_repository(transport_path)
2747
client.add_success_response_with_body(encoded_body, 'ok')
2748
result = repo._get_revision_graph(r2)
2750
[('call_expecting_body', 'Repository.get_revision_graph',
2753
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2755
def test_no_such_revision(self):
2757
transport_path = 'sinhala'
2758
repo, client = self.setup_fake_client_and_repository(transport_path)
2759
client.add_error_response('nosuchrevision', revid)
2760
# also check that the right revision is reported in the error
2761
self.assertRaises(errors.NoSuchRevision,
2762
repo._get_revision_graph, revid)
2764
[('call_expecting_body', 'Repository.get_revision_graph',
2765
('sinhala/', revid))],
2768
def test_unexpected_error(self):
2770
transport_path = 'sinhala'
2771
repo, client = self.setup_fake_client_and_repository(transport_path)
2772
client.add_error_response('AnUnexpectedError')
2773
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2774
repo._get_revision_graph, revid)
2775
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2778
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2781
repo, client = self.setup_fake_client_and_repository('quack')
2782
client.add_expected_call(
2783
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2784
'success', ('ok', 'rev-five'))
2785
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2786
self.assertEqual((True, 'rev-five'), result)
2787
self.assertFinished(client)
2789
def test_history_incomplete(self):
2790
repo, client = self.setup_fake_client_and_repository('quack')
2791
client.add_expected_call(
2792
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2793
'success', ('history-incomplete', 10, 'rev-ten'))
2794
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2795
self.assertEqual((False, (10, 'rev-ten')), result)
2796
self.assertFinished(client)
2798
def test_history_incomplete_with_fallback(self):
2799
"""A 'history-incomplete' response causes the fallback repository to be
2800
queried too, if one is set.
2802
# Make a repo with a fallback repo, both using a FakeClient.
2803
format = remote.response_tuple_to_repo_format(
2804
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2805
repo, client = self.setup_fake_client_and_repository('quack')
2806
repo._format = format
2807
fallback_repo, ignored = self.setup_fake_client_and_repository(
2809
fallback_repo._client = client
2810
fallback_repo._format = format
2811
repo.add_fallback_repository(fallback_repo)
2812
# First the client should ask the primary repo
2813
client.add_expected_call(
2814
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2815
'success', ('history-incomplete', 2, 'rev-two'))
2816
# Then it should ask the fallback, using revno/revid from the
2817
# history-incomplete response as the known revno/revid.
2818
client.add_expected_call(
2819
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2820
'success', ('ok', 'rev-one'))
2821
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2822
self.assertEqual((True, 'rev-one'), result)
2823
self.assertFinished(client)
2825
def test_nosuchrevision(self):
2826
# 'nosuchrevision' is returned when the known-revid is not found in the
2827
# remote repo. The client translates that response to NoSuchRevision.
2828
repo, client = self.setup_fake_client_and_repository('quack')
2829
client.add_expected_call(
2830
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2831
'error', ('nosuchrevision', 'rev-foo'))
2833
errors.NoSuchRevision,
2834
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2835
self.assertFinished(client)
2837
def test_branch_fallback_locking(self):
2838
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2839
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2840
will be invoked, which will fail if the repo is unlocked.
2842
self.setup_smart_server_with_call_log()
2843
tree = self.make_branch_and_memory_tree('.')
2846
rev1 = tree.commit('First')
2847
rev2 = tree.commit('Second')
2849
branch = tree.branch
2850
self.assertFalse(branch.is_locked())
2851
self.reset_smart_call_log()
2852
verb = 'Repository.get_rev_id_for_revno'
2853
self.disable_verb(verb)
2854
self.assertEqual(rev1, branch.get_rev_id(1))
2855
self.assertLength(1, [call for call in self.hpss_calls if
2856
call.call.method == verb])
2859
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2861
def test_has_signature_for_revision_id(self):
2862
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2863
transport_path = 'quack'
2864
repo, client = self.setup_fake_client_and_repository(transport_path)
2865
client.add_success_response('yes')
2866
result = repo.has_signature_for_revision_id('A')
2868
[('call', 'Repository.has_signature_for_revision_id',
2871
self.assertEqual(True, result)
2873
def test_is_not_shared(self):
2874
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2875
transport_path = 'qwack'
2876
repo, client = self.setup_fake_client_and_repository(transport_path)
2877
client.add_success_response('no')
2878
result = repo.has_signature_for_revision_id('A')
2880
[('call', 'Repository.has_signature_for_revision_id',
2883
self.assertEqual(False, result)
2886
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2888
def test_get_physical_lock_status_yes(self):
2889
transport_path = 'qwack'
2890
repo, client = self.setup_fake_client_and_repository(transport_path)
2891
client.add_success_response('yes')
2892
result = repo.get_physical_lock_status()
2894
[('call', 'Repository.get_physical_lock_status',
2897
self.assertEqual(True, result)
2899
def test_get_physical_lock_status_no(self):
2900
transport_path = 'qwack'
2901
repo, client = self.setup_fake_client_and_repository(transport_path)
2902
client.add_success_response('no')
2903
result = repo.get_physical_lock_status()
2905
[('call', 'Repository.get_physical_lock_status',
2908
self.assertEqual(False, result)
2911
class TestRepositoryIsShared(TestRemoteRepository):
2913
def test_is_shared(self):
2914
# ('yes', ) for Repository.is_shared -> 'True'.
2915
transport_path = 'quack'
2916
repo, client = self.setup_fake_client_and_repository(transport_path)
2917
client.add_success_response('yes')
2918
result = repo.is_shared()
2920
[('call', 'Repository.is_shared', ('quack/',))],
2922
self.assertEqual(True, result)
2924
def test_is_not_shared(self):
2925
# ('no', ) for Repository.is_shared -> 'False'.
2926
transport_path = 'qwack'
2927
repo, client = self.setup_fake_client_and_repository(transport_path)
2928
client.add_success_response('no')
2929
result = repo.is_shared()
2931
[('call', 'Repository.is_shared', ('qwack/',))],
2933
self.assertEqual(False, result)
2936
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
2938
def test_make_working_trees(self):
2939
# ('yes', ) for Repository.make_working_trees -> 'True'.
2940
transport_path = 'quack'
2941
repo, client = self.setup_fake_client_and_repository(transport_path)
2942
client.add_success_response('yes')
2943
result = repo.make_working_trees()
2945
[('call', 'Repository.make_working_trees', ('quack/',))],
2947
self.assertEqual(True, result)
2949
def test_no_working_trees(self):
2950
# ('no', ) for Repository.make_working_trees -> 'False'.
2951
transport_path = 'qwack'
2952
repo, client = self.setup_fake_client_and_repository(transport_path)
2953
client.add_success_response('no')
2954
result = repo.make_working_trees()
2956
[('call', 'Repository.make_working_trees', ('qwack/',))],
2958
self.assertEqual(False, result)
2961
class TestRepositoryLockWrite(TestRemoteRepository):
2963
def test_lock_write(self):
2964
transport_path = 'quack'
2965
repo, client = self.setup_fake_client_and_repository(transport_path)
2966
client.add_success_response('ok', 'a token')
2967
token = repo.lock_write().repository_token
2969
[('call', 'Repository.lock_write', ('quack/', ''))],
2971
self.assertEqual('a token', token)
2973
def test_lock_write_already_locked(self):
2974
transport_path = 'quack'
2975
repo, client = self.setup_fake_client_and_repository(transport_path)
2976
client.add_error_response('LockContention')
2977
self.assertRaises(errors.LockContention, repo.lock_write)
2979
[('call', 'Repository.lock_write', ('quack/', ''))],
2982
def test_lock_write_unlockable(self):
2983
transport_path = 'quack'
2984
repo, client = self.setup_fake_client_and_repository(transport_path)
2985
client.add_error_response('UnlockableTransport')
2986
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2988
[('call', 'Repository.lock_write', ('quack/', ''))],
2992
class TestRepositoryWriteGroups(TestRemoteRepository):
2994
def test_start_write_group(self):
2995
transport_path = 'quack'
2996
repo, client = self.setup_fake_client_and_repository(transport_path)
2997
client.add_expected_call(
2998
'Repository.lock_write', ('quack/', ''),
2999
'success', ('ok', 'a token'))
3000
client.add_expected_call(
3001
'Repository.start_write_group', ('quack/', 'a token'),
3002
'success', ('ok', ('token1', )))
3004
repo.start_write_group()
3006
def test_start_write_group_unsuspendable(self):
3007
# Some repositories do not support suspending write
3008
# groups. For those, fall back to the "real" repository.
3009
transport_path = 'quack'
3010
repo, client = self.setup_fake_client_and_repository(transport_path)
3011
def stub_ensure_real():
3012
client._calls.append(('_ensure_real',))
3013
repo._real_repository = _StubRealPackRepository(client._calls)
3014
repo._ensure_real = stub_ensure_real
3015
client.add_expected_call(
3016
'Repository.lock_write', ('quack/', ''),
3017
'success', ('ok', 'a token'))
3018
client.add_expected_call(
3019
'Repository.start_write_group', ('quack/', 'a token'),
3020
'error', ('UnsuspendableWriteGroup',))
3022
repo.start_write_group()
3023
self.assertEquals(client._calls[-2:], [
3025
('start_write_group',)])
3027
def test_commit_write_group(self):
3028
transport_path = 'quack'
3029
repo, client = self.setup_fake_client_and_repository(transport_path)
3030
client.add_expected_call(
3031
'Repository.lock_write', ('quack/', ''),
3032
'success', ('ok', 'a token'))
3033
client.add_expected_call(
3034
'Repository.start_write_group', ('quack/', 'a token'),
3035
'success', ('ok', ['token1']))
3036
client.add_expected_call(
3037
'Repository.commit_write_group', ('quack/', 'a token', ['token1']),
3040
repo.start_write_group()
3041
repo.commit_write_group()
3043
def test_abort_write_group(self):
3044
transport_path = 'quack'
3045
repo, client = self.setup_fake_client_and_repository(transport_path)
3046
client.add_expected_call(
3047
'Repository.lock_write', ('quack/', ''),
3048
'success', ('ok', 'a token'))
3049
client.add_expected_call(
3050
'Repository.start_write_group', ('quack/', 'a token'),
3051
'success', ('ok', ['token1']))
3052
client.add_expected_call(
3053
'Repository.abort_write_group', ('quack/', 'a token', ['token1']),
3056
repo.start_write_group()
3057
repo.abort_write_group(False)
3059
def test_suspend_write_group(self):
3060
transport_path = 'quack'
3061
repo, client = self.setup_fake_client_and_repository(transport_path)
3062
self.assertEquals([], repo.suspend_write_group())
3064
def test_resume_write_group(self):
3065
transport_path = 'quack'
3066
repo, client = self.setup_fake_client_and_repository(transport_path)
3067
client.add_expected_call(
3068
'Repository.lock_write', ('quack/', ''),
3069
'success', ('ok', 'a token'))
3070
client.add_expected_call(
3071
'Repository.check_write_group', ('quack/', 'a token', ['token1']),
3074
repo.resume_write_group(['token1'])
3077
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3079
def test_backwards_compat(self):
3080
self.setup_smart_server_with_call_log()
3081
repo = self.make_repository('.')
3082
self.reset_smart_call_log()
3083
verb = 'Repository.set_make_working_trees'
3084
self.disable_verb(verb)
3085
repo.set_make_working_trees(True)
3086
call_count = len([call for call in self.hpss_calls if
3087
call.call.method == verb])
3088
self.assertEqual(1, call_count)
3090
def test_current(self):
3091
transport_path = 'quack'
3092
repo, client = self.setup_fake_client_and_repository(transport_path)
3093
client.add_expected_call(
3094
'Repository.set_make_working_trees', ('quack/', 'True'),
3096
client.add_expected_call(
3097
'Repository.set_make_working_trees', ('quack/', 'False'),
3099
repo.set_make_working_trees(True)
3100
repo.set_make_working_trees(False)
3103
class TestRepositoryUnlock(TestRemoteRepository):
3105
def test_unlock(self):
3106
transport_path = 'quack'
3107
repo, client = self.setup_fake_client_and_repository(transport_path)
3108
client.add_success_response('ok', 'a token')
3109
client.add_success_response('ok')
3113
[('call', 'Repository.lock_write', ('quack/', '')),
3114
('call', 'Repository.unlock', ('quack/', 'a token'))],
3117
def test_unlock_wrong_token(self):
3118
# If somehow the token is wrong, unlock will raise TokenMismatch.
3119
transport_path = 'quack'
3120
repo, client = self.setup_fake_client_and_repository(transport_path)
3121
client.add_success_response('ok', 'a token')
3122
client.add_error_response('TokenMismatch')
3124
self.assertRaises(errors.TokenMismatch, repo.unlock)
3127
class TestRepositoryHasRevision(TestRemoteRepository):
3129
def test_none(self):
3130
# repo.has_revision(None) should not cause any traffic.
3131
transport_path = 'quack'
3132
repo, client = self.setup_fake_client_and_repository(transport_path)
3134
# The null revision is always there, so has_revision(None) == True.
3135
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3137
# The remote repo shouldn't be accessed.
3138
self.assertEqual([], client._calls)
3141
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3142
"""Test Repository.iter_file_bytes."""
3144
def test_single(self):
3145
transport_path = 'quack'
3146
repo, client = self.setup_fake_client_and_repository(transport_path)
3147
client.add_expected_call(
3148
'Repository.iter_files_bytes', ('quack/', ),
3149
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3150
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3151
"somerev", "myid")]):
3152
self.assertEquals("myid", identifier)
3153
self.assertEquals("".join(byte_stream), "mydata" * 10)
3155
def test_missing(self):
3156
transport_path = 'quack'
3157
repo, client = self.setup_fake_client_and_repository(transport_path)
3158
client.add_expected_call(
3159
'Repository.iter_files_bytes',
3161
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3162
iter(["absent\0somefile\0somerev\n"]))
3163
self.assertRaises(errors.RevisionNotPresent, list,
3164
repo.iter_files_bytes(
3165
[("somefile", "somerev", "myid")]))
3168
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3169
"""Base class for Repository.insert_stream and .insert_stream_1.19
3173
def checkInsertEmptyStream(self, repo, client):
3174
"""Insert an empty stream, checking the result.
3176
This checks that there are no resume_tokens or missing_keys, and that
3177
the client is finished.
3179
sink = repo._get_sink()
3180
fmt = repository.format_registry.get_default()
3181
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3182
self.assertEqual([], resume_tokens)
3183
self.assertEqual(set(), missing_keys)
3184
self.assertFinished(client)
3187
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3188
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3191
This test case is very similar to TestRepositoryInsertStream_1_19.
3195
TestRemoteRepository.setUp(self)
3196
self.disable_verb('Repository.insert_stream_1.19')
3198
def test_unlocked_repo(self):
3199
transport_path = 'quack'
3200
repo, client = self.setup_fake_client_and_repository(transport_path)
3201
client.add_expected_call(
3202
'Repository.insert_stream_1.19', ('quack/', ''),
3203
'unknown', ('Repository.insert_stream_1.19',))
3204
client.add_expected_call(
3205
'Repository.insert_stream', ('quack/', ''),
3207
client.add_expected_call(
3208
'Repository.insert_stream', ('quack/', ''),
3210
self.checkInsertEmptyStream(repo, client)
3212
def test_locked_repo_with_no_lock_token(self):
3213
transport_path = 'quack'
3214
repo, client = self.setup_fake_client_and_repository(transport_path)
3215
client.add_expected_call(
3216
'Repository.lock_write', ('quack/', ''),
3217
'success', ('ok', ''))
3218
client.add_expected_call(
3219
'Repository.insert_stream_1.19', ('quack/', ''),
3220
'unknown', ('Repository.insert_stream_1.19',))
3221
client.add_expected_call(
3222
'Repository.insert_stream', ('quack/', ''),
3224
client.add_expected_call(
3225
'Repository.insert_stream', ('quack/', ''),
3228
self.checkInsertEmptyStream(repo, client)
3230
def test_locked_repo_with_lock_token(self):
3231
transport_path = 'quack'
3232
repo, client = self.setup_fake_client_and_repository(transport_path)
3233
client.add_expected_call(
3234
'Repository.lock_write', ('quack/', ''),
3235
'success', ('ok', 'a token'))
3236
client.add_expected_call(
3237
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3238
'unknown', ('Repository.insert_stream_1.19',))
3239
client.add_expected_call(
3240
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3242
client.add_expected_call(
3243
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3246
self.checkInsertEmptyStream(repo, client)
3248
def test_stream_with_inventory_deltas(self):
3249
"""'inventory-deltas' substreams cannot be sent to the
3250
Repository.insert_stream verb, because not all servers that implement
3251
that verb will accept them. So when one is encountered the RemoteSink
3252
immediately stops using that verb and falls back to VFS insert_stream.
3254
transport_path = 'quack'
3255
repo, client = self.setup_fake_client_and_repository(transport_path)
3256
client.add_expected_call(
3257
'Repository.insert_stream_1.19', ('quack/', ''),
3258
'unknown', ('Repository.insert_stream_1.19',))
3259
client.add_expected_call(
3260
'Repository.insert_stream', ('quack/', ''),
3262
client.add_expected_call(
3263
'Repository.insert_stream', ('quack/', ''),
3265
# Create a fake real repository for insert_stream to fall back on, so
3266
# that we can directly see the records the RemoteSink passes to the
3271
def insert_stream(self, stream, src_format, resume_tokens):
3272
for substream_kind, substream in stream:
3273
self.records.append(
3274
(substream_kind, [record.key for record in substream]))
3275
return ['fake tokens'], ['fake missing keys']
3276
fake_real_sink = FakeRealSink()
3277
class FakeRealRepository:
3278
def _get_sink(self):
3279
return fake_real_sink
3280
def is_in_write_group(self):
3282
def refresh_data(self):
3284
repo._real_repository = FakeRealRepository()
3285
sink = repo._get_sink()
3286
fmt = repository.format_registry.get_default()
3287
stream = self.make_stream_with_inv_deltas(fmt)
3288
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3289
# Every record from the first inventory delta should have been sent to
3291
expected_records = [
3292
('inventory-deltas', [('rev2',), ('rev3',)]),
3293
('texts', [('some-rev', 'some-file')])]
3294
self.assertEqual(expected_records, fake_real_sink.records)
3295
# The return values from the real sink's insert_stream are propagated
3296
# back to the original caller.
3297
self.assertEqual(['fake tokens'], resume_tokens)
3298
self.assertEqual(['fake missing keys'], missing_keys)
3299
self.assertFinished(client)
3301
def make_stream_with_inv_deltas(self, fmt):
3302
"""Make a simple stream with an inventory delta followed by more
3303
records and more substreams to test that all records and substreams
3304
from that point on are used.
3306
This sends, in order:
3307
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3309
* texts substream: (some-rev, some-file)
3311
# Define a stream using generators so that it isn't rewindable.
3312
inv = inventory.Inventory(revision_id='rev1')
3313
inv.root.revision = 'rev1'
3314
def stream_with_inv_delta():
3315
yield ('inventories', inventories_substream())
3316
yield ('inventory-deltas', inventory_delta_substream())
3318
versionedfile.FulltextContentFactory(
3319
('some-rev', 'some-file'), (), None, 'content')])
3320
def inventories_substream():
3321
# An empty inventory fulltext. This will be streamed normally.
3322
text = fmt._serializer.write_inventory_to_string(inv)
3323
yield versionedfile.FulltextContentFactory(
3324
('rev1',), (), None, text)
3325
def inventory_delta_substream():
3326
# An inventory delta. This can't be streamed via this verb, so it
3327
# will trigger a fallback to VFS insert_stream.
3328
entry = inv.make_entry(
3329
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3330
entry.revision = 'ghost'
3331
delta = [(None, 'newdir', 'newdir-id', entry)]
3332
serializer = inventory_delta.InventoryDeltaSerializer(
3333
versioned_root=True, tree_references=False)
3334
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3335
yield versionedfile.ChunkedContentFactory(
3336
('rev2',), (('rev1',)), None, lines)
3338
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3339
yield versionedfile.ChunkedContentFactory(
3340
('rev3',), (('rev1',)), None, lines)
3341
return stream_with_inv_delta()
3344
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3346
def test_unlocked_repo(self):
3347
transport_path = 'quack'
3348
repo, client = self.setup_fake_client_and_repository(transport_path)
3349
client.add_expected_call(
3350
'Repository.insert_stream_1.19', ('quack/', ''),
3352
client.add_expected_call(
3353
'Repository.insert_stream_1.19', ('quack/', ''),
3355
self.checkInsertEmptyStream(repo, client)
3357
def test_locked_repo_with_no_lock_token(self):
3358
transport_path = 'quack'
3359
repo, client = self.setup_fake_client_and_repository(transport_path)
3360
client.add_expected_call(
3361
'Repository.lock_write', ('quack/', ''),
3362
'success', ('ok', ''))
3363
client.add_expected_call(
3364
'Repository.insert_stream_1.19', ('quack/', ''),
3366
client.add_expected_call(
3367
'Repository.insert_stream_1.19', ('quack/', ''),
3370
self.checkInsertEmptyStream(repo, client)
3372
def test_locked_repo_with_lock_token(self):
3373
transport_path = 'quack'
3374
repo, client = self.setup_fake_client_and_repository(transport_path)
3375
client.add_expected_call(
3376
'Repository.lock_write', ('quack/', ''),
3377
'success', ('ok', 'a token'))
3378
client.add_expected_call(
3379
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3381
client.add_expected_call(
3382
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3385
self.checkInsertEmptyStream(repo, client)
3388
class TestRepositoryTarball(TestRemoteRepository):
3390
# This is a canned tarball reponse we can validate against
3392
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3393
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3394
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3395
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3396
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3397
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3398
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3399
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3400
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3401
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3402
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3403
'nWQ7QH/F3JFOFCQ0aSPfA='
3406
def test_repository_tarball(self):
3407
# Test that Repository.tarball generates the right operations
3408
transport_path = 'repo'
3409
expected_calls = [('call_expecting_body', 'Repository.tarball',
3410
('repo/', 'bz2',),),
3412
repo, client = self.setup_fake_client_and_repository(transport_path)
3413
client.add_success_response_with_body(self.tarball_content, 'ok')
3414
# Now actually ask for the tarball
3415
tarball_file = repo._get_tarball('bz2')
3417
self.assertEqual(expected_calls, client._calls)
3418
self.assertEqual(self.tarball_content, tarball_file.read())
3420
tarball_file.close()
3423
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3424
"""RemoteRepository.copy_content_into optimizations"""
3426
def test_copy_content_remote_to_local(self):
3427
self.transport_server = test_server.SmartTCPServer_for_testing
3428
src_repo = self.make_repository('repo1')
3429
src_repo = repository.Repository.open(self.get_url('repo1'))
3430
# At the moment the tarball-based copy_content_into can't write back
3431
# into a smart server. It would be good if it could upload the
3432
# tarball; once that works we'd have to create repositories of
3433
# different formats. -- mbp 20070410
3434
dest_url = self.get_vfs_only_url('repo2')
3435
dest_bzrdir = BzrDir.create(dest_url)
3436
dest_repo = dest_bzrdir.create_repository()
3437
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3438
self.assertTrue(isinstance(src_repo, RemoteRepository))
3439
src_repo.copy_content_into(dest_repo)
3442
class _StubRealPackRepository(object):
3444
def __init__(self, calls):
3446
self._pack_collection = _StubPackCollection(calls)
3448
def start_write_group(self):
3449
self.calls.append(('start_write_group',))
3451
def is_in_write_group(self):
3454
def refresh_data(self):
3455
self.calls.append(('pack collection reload_pack_names',))
3458
class _StubPackCollection(object):
3460
def __init__(self, calls):
3464
self.calls.append(('pack collection autopack',))
3467
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3468
"""Tests for RemoteRepository.autopack implementation."""
3471
"""When the server returns 'ok' and there's no _real_repository, then
3472
nothing else happens: the autopack method is done.
3474
transport_path = 'quack'
3475
repo, client = self.setup_fake_client_and_repository(transport_path)
3476
client.add_expected_call(
3477
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3479
self.assertFinished(client)
3481
def test_ok_with_real_repo(self):
3482
"""When the server returns 'ok' and there is a _real_repository, then
3483
the _real_repository's reload_pack_name's method will be called.
3485
transport_path = 'quack'
3486
repo, client = self.setup_fake_client_and_repository(transport_path)
3487
client.add_expected_call(
3488
'PackRepository.autopack', ('quack/',),
3490
repo._real_repository = _StubRealPackRepository(client._calls)
3493
[('call', 'PackRepository.autopack', ('quack/',)),
3494
('pack collection reload_pack_names',)],
3497
def test_backwards_compatibility(self):
3498
"""If the server does not recognise the PackRepository.autopack verb,
3499
fallback to the real_repository's implementation.
3501
transport_path = 'quack'
3502
repo, client = self.setup_fake_client_and_repository(transport_path)
3503
client.add_unknown_method_response('PackRepository.autopack')
3504
def stub_ensure_real():
3505
client._calls.append(('_ensure_real',))
3506
repo._real_repository = _StubRealPackRepository(client._calls)
3507
repo._ensure_real = stub_ensure_real
3510
[('call', 'PackRepository.autopack', ('quack/',)),
3512
('pack collection autopack',)],
3515
def test_oom_error_reporting(self):
3516
"""An out-of-memory condition on the server is reported clearly"""
3517
transport_path = 'quack'
3518
repo, client = self.setup_fake_client_and_repository(transport_path)
3519
client.add_expected_call(
3520
'PackRepository.autopack', ('quack/',),
3521
'error', ('MemoryError',))
3522
err = self.assertRaises(errors.BzrError, repo.autopack)
3523
self.assertContainsRe(str(err), "^remote server out of mem")
3526
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3527
"""Base class for unit tests for bzrlib.remote._translate_error."""
3529
def translateTuple(self, error_tuple, **context):
3530
"""Call _translate_error with an ErrorFromSmartServer built from the
3533
:param error_tuple: A tuple of a smart server response, as would be
3534
passed to an ErrorFromSmartServer.
3535
:kwargs context: context items to call _translate_error with.
3537
:returns: The error raised by _translate_error.
3539
# Raise the ErrorFromSmartServer before passing it as an argument,
3540
# because _translate_error may need to re-raise it with a bare 'raise'
3542
server_error = errors.ErrorFromSmartServer(error_tuple)
3543
translated_error = self.translateErrorFromSmartServer(
3544
server_error, **context)
3545
return translated_error
3547
def translateErrorFromSmartServer(self, error_object, **context):
3548
"""Like translateTuple, but takes an already constructed
3549
ErrorFromSmartServer rather than a tuple.
3553
except errors.ErrorFromSmartServer, server_error:
3554
translated_error = self.assertRaises(
3555
errors.BzrError, remote._translate_error, server_error,
3557
return translated_error
3560
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3561
"""Unit tests for bzrlib.remote._translate_error.
3563
Given an ErrorFromSmartServer (which has an error tuple from a smart
3564
server) and some context, _translate_error raises more specific errors from
3567
This test case covers the cases where _translate_error succeeds in
3568
translating an ErrorFromSmartServer to something better. See
3569
TestErrorTranslationRobustness for other cases.
3572
def test_NoSuchRevision(self):
3573
branch = self.make_branch('')
3575
translated_error = self.translateTuple(
3576
('NoSuchRevision', revid), branch=branch)
3577
expected_error = errors.NoSuchRevision(branch, revid)
3578
self.assertEqual(expected_error, translated_error)
3580
def test_nosuchrevision(self):
3581
repository = self.make_repository('')
3583
translated_error = self.translateTuple(
3584
('nosuchrevision', revid), repository=repository)
3585
expected_error = errors.NoSuchRevision(repository, revid)
3586
self.assertEqual(expected_error, translated_error)
3588
def test_nobranch(self):
3589
bzrdir = self.make_bzrdir('')
3590
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3591
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3592
self.assertEqual(expected_error, translated_error)
3594
def test_nobranch_one_arg(self):
3595
bzrdir = self.make_bzrdir('')
3596
translated_error = self.translateTuple(
3597
('nobranch', 'extra detail'), bzrdir=bzrdir)
3598
expected_error = errors.NotBranchError(
3599
path=bzrdir.root_transport.base,
3600
detail='extra detail')
3601
self.assertEqual(expected_error, translated_error)
3603
def test_norepository(self):
3604
bzrdir = self.make_bzrdir('')
3605
translated_error = self.translateTuple(('norepository',),
3607
expected_error = errors.NoRepositoryPresent(bzrdir)
3608
self.assertEqual(expected_error, translated_error)
3610
def test_LockContention(self):
3611
translated_error = self.translateTuple(('LockContention',))
3612
expected_error = errors.LockContention('(remote lock)')
3613
self.assertEqual(expected_error, translated_error)
3615
def test_UnlockableTransport(self):
3616
bzrdir = self.make_bzrdir('')
3617
translated_error = self.translateTuple(
3618
('UnlockableTransport',), bzrdir=bzrdir)
3619
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3620
self.assertEqual(expected_error, translated_error)
3622
def test_LockFailed(self):
3623
lock = 'str() of a server lock'
3624
why = 'str() of why'
3625
translated_error = self.translateTuple(('LockFailed', lock, why))
3626
expected_error = errors.LockFailed(lock, why)
3627
self.assertEqual(expected_error, translated_error)
3629
def test_TokenMismatch(self):
3630
token = 'a lock token'
3631
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3632
expected_error = errors.TokenMismatch(token, '(remote token)')
3633
self.assertEqual(expected_error, translated_error)
3635
def test_Diverged(self):
3636
branch = self.make_branch('a')
3637
other_branch = self.make_branch('b')
3638
translated_error = self.translateTuple(
3639
('Diverged',), branch=branch, other_branch=other_branch)
3640
expected_error = errors.DivergedBranches(branch, other_branch)
3641
self.assertEqual(expected_error, translated_error)
3643
def test_NotStacked(self):
3644
branch = self.make_branch('')
3645
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3646
expected_error = errors.NotStacked(branch)
3647
self.assertEqual(expected_error, translated_error)
3649
def test_ReadError_no_args(self):
3651
translated_error = self.translateTuple(('ReadError',), path=path)
3652
expected_error = errors.ReadError(path)
3653
self.assertEqual(expected_error, translated_error)
3655
def test_ReadError(self):
3657
translated_error = self.translateTuple(('ReadError', path))
3658
expected_error = errors.ReadError(path)
3659
self.assertEqual(expected_error, translated_error)
3661
def test_IncompatibleRepositories(self):
3662
translated_error = self.translateTuple(('IncompatibleRepositories',
3663
"repo1", "repo2", "details here"))
3664
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3666
self.assertEqual(expected_error, translated_error)
3668
def test_PermissionDenied_no_args(self):
3670
translated_error = self.translateTuple(('PermissionDenied',),
3672
expected_error = errors.PermissionDenied(path)
3673
self.assertEqual(expected_error, translated_error)
3675
def test_PermissionDenied_one_arg(self):
3677
translated_error = self.translateTuple(('PermissionDenied', path))
3678
expected_error = errors.PermissionDenied(path)
3679
self.assertEqual(expected_error, translated_error)
3681
def test_PermissionDenied_one_arg_and_context(self):
3682
"""Given a choice between a path from the local context and a path on
3683
the wire, _translate_error prefers the path from the local context.
3685
local_path = 'local path'
3686
remote_path = 'remote path'
3687
translated_error = self.translateTuple(
3688
('PermissionDenied', remote_path), path=local_path)
3689
expected_error = errors.PermissionDenied(local_path)
3690
self.assertEqual(expected_error, translated_error)
3692
def test_PermissionDenied_two_args(self):
3694
extra = 'a string with extra info'
3695
translated_error = self.translateTuple(
3696
('PermissionDenied', path, extra))
3697
expected_error = errors.PermissionDenied(path, extra)
3698
self.assertEqual(expected_error, translated_error)
3700
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3702
def test_NoSuchFile_context_path(self):
3703
local_path = "local path"
3704
translated_error = self.translateTuple(('ReadError', "remote path"),
3706
expected_error = errors.ReadError(local_path)
3707
self.assertEqual(expected_error, translated_error)
3709
def test_NoSuchFile_without_context(self):
3710
remote_path = "remote path"
3711
translated_error = self.translateTuple(('ReadError', remote_path))
3712
expected_error = errors.ReadError(remote_path)
3713
self.assertEqual(expected_error, translated_error)
3715
def test_ReadOnlyError(self):
3716
translated_error = self.translateTuple(('ReadOnlyError',))
3717
expected_error = errors.TransportNotPossible("readonly transport")
3718
self.assertEqual(expected_error, translated_error)
3720
def test_MemoryError(self):
3721
translated_error = self.translateTuple(('MemoryError',))
3722
self.assertStartsWith(str(translated_error),
3723
"remote server out of memory")
3725
def test_generic_IndexError_no_classname(self):
3726
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3727
translated_error = self.translateErrorFromSmartServer(err)
3728
expected_error = errors.UnknownErrorFromSmartServer(err)
3729
self.assertEqual(expected_error, translated_error)
3731
# GZ 2011-03-02: TODO test generic non-ascii error string
3733
def test_generic_KeyError(self):
3734
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3735
translated_error = self.translateErrorFromSmartServer(err)
3736
expected_error = errors.UnknownErrorFromSmartServer(err)
3737
self.assertEqual(expected_error, translated_error)
3740
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3741
"""Unit tests for bzrlib.remote._translate_error's robustness.
3743
TestErrorTranslationSuccess is for cases where _translate_error can
3744
translate successfully. This class about how _translate_err behaves when
3745
it fails to translate: it re-raises the original error.
3748
def test_unrecognised_server_error(self):
3749
"""If the error code from the server is not recognised, the original
3750
ErrorFromSmartServer is propagated unmodified.
3752
error_tuple = ('An unknown error tuple',)
3753
server_error = errors.ErrorFromSmartServer(error_tuple)
3754
translated_error = self.translateErrorFromSmartServer(server_error)
3755
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3756
self.assertEqual(expected_error, translated_error)
3758
def test_context_missing_a_key(self):
3759
"""In case of a bug in the client, or perhaps an unexpected response
3760
from a server, _translate_error returns the original error tuple from
3761
the server and mutters a warning.
3763
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3764
# in the context dict. So let's give it an empty context dict instead
3765
# to exercise its error recovery.
3767
error_tuple = ('NoSuchRevision', 'revid')
3768
server_error = errors.ErrorFromSmartServer(error_tuple)
3769
translated_error = self.translateErrorFromSmartServer(server_error)
3770
self.assertEqual(server_error, translated_error)
3771
# In addition to re-raising ErrorFromSmartServer, some debug info has
3772
# been muttered to the log file for developer to look at.
3773
self.assertContainsRe(
3775
"Missing key 'branch' in context")
3777
def test_path_missing(self):
3778
"""Some translations (PermissionDenied, ReadError) can determine the
3779
'path' variable from either the wire or the local context. If neither
3780
has it, then an error is raised.
3782
error_tuple = ('ReadError',)
3783
server_error = errors.ErrorFromSmartServer(error_tuple)
3784
translated_error = self.translateErrorFromSmartServer(server_error)
3785
self.assertEqual(server_error, translated_error)
3786
# In addition to re-raising ErrorFromSmartServer, some debug info has
3787
# been muttered to the log file for developer to look at.
3788
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3791
class TestStacking(tests.TestCaseWithTransport):
3792
"""Tests for operations on stacked remote repositories.
3794
The underlying format type must support stacking.
3797
def test_access_stacked_remote(self):
3798
# based on <http://launchpad.net/bugs/261315>
3799
# make a branch stacked on another repository containing an empty
3800
# revision, then open it over hpss - we should be able to see that
3802
base_transport = self.get_transport()
3803
base_builder = self.make_branch_builder('base', format='1.9')
3804
base_builder.start_series()
3805
base_revid = base_builder.build_snapshot('rev-id', None,
3806
[('add', ('', None, 'directory', None))],
3808
base_builder.finish_series()
3809
stacked_branch = self.make_branch('stacked', format='1.9')
3810
stacked_branch.set_stacked_on_url('../base')
3811
# start a server looking at this
3812
smart_server = test_server.SmartTCPServer_for_testing()
3813
self.start_server(smart_server)
3814
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3815
# can get its branch and repository
3816
remote_branch = remote_bzrdir.open_branch()
3817
remote_repo = remote_branch.repository
3818
remote_repo.lock_read()
3820
# it should have an appropriate fallback repository, which should also
3821
# be a RemoteRepository
3822
self.assertLength(1, remote_repo._fallback_repositories)
3823
self.assertIsInstance(remote_repo._fallback_repositories[0],
3825
# and it has the revision committed to the underlying repository;
3826
# these have varying implementations so we try several of them
3827
self.assertTrue(remote_repo.has_revisions([base_revid]))
3828
self.assertTrue(remote_repo.has_revision(base_revid))
3829
self.assertEqual(remote_repo.get_revision(base_revid).message,
3832
remote_repo.unlock()
3834
def prepare_stacked_remote_branch(self):
3835
"""Get stacked_upon and stacked branches with content in each."""
3836
self.setup_smart_server_with_call_log()
3837
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3838
tree1.commit('rev1', rev_id='rev1')
3839
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3840
).open_workingtree()
3841
local_tree = tree2.branch.create_checkout('local')
3842
local_tree.commit('local changes make me feel good.')
3843
branch2 = Branch.open(self.get_url('tree2'))
3845
self.addCleanup(branch2.unlock)
3846
return tree1.branch, branch2
3848
def test_stacked_get_parent_map(self):
3849
# the public implementation of get_parent_map obeys stacking
3850
_, branch = self.prepare_stacked_remote_branch()
3851
repo = branch.repository
3852
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3854
def test_unstacked_get_parent_map(self):
3855
# _unstacked_provider.get_parent_map ignores stacking
3856
_, branch = self.prepare_stacked_remote_branch()
3857
provider = branch.repository._unstacked_provider
3858
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3860
def fetch_stream_to_rev_order(self, stream):
3862
for kind, substream in stream:
3863
if not kind == 'revisions':
3866
for content in substream:
3867
result.append(content.key[-1])
3870
def get_ordered_revs(self, format, order, branch_factory=None):
3871
"""Get a list of the revisions in a stream to format format.
3873
:param format: The format of the target.
3874
:param order: the order that target should have requested.
3875
:param branch_factory: A callable to create a trunk and stacked branch
3876
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3877
:result: The revision ids in the stream, in the order seen,
3878
the topological order of revisions in the source.
3880
unordered_format = bzrdir.format_registry.get(format)()
3881
target_repository_format = unordered_format.repository_format
3883
self.assertEqual(order, target_repository_format._fetch_order)
3884
if branch_factory is None:
3885
branch_factory = self.prepare_stacked_remote_branch
3886
_, stacked = branch_factory()
3887
source = stacked.repository._get_source(target_repository_format)
3888
tip = stacked.last_revision()
3889
stacked.repository._ensure_real()
3890
graph = stacked.repository.get_graph()
3891
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3892
if r != NULL_REVISION]
3894
search = _mod_graph.PendingAncestryResult([tip], stacked.repository)
3895
self.reset_smart_call_log()
3896
stream = source.get_stream(search)
3897
# We trust that if a revision is in the stream the rest of the new
3898
# content for it is too, as per our main fetch tests; here we are
3899
# checking that the revisions are actually included at all, and their
3901
return self.fetch_stream_to_rev_order(stream), revs
3903
def test_stacked_get_stream_unordered(self):
3904
# Repository._get_source.get_stream() from a stacked repository with
3905
# unordered yields the full data from both stacked and stacked upon
3907
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3908
self.assertEqual(set(expected_revs), set(rev_ord))
3909
# Getting unordered results should have made a streaming data request
3910
# from the server, then one from the backing branch.
3911
self.assertLength(2, self.hpss_calls)
3913
def test_stacked_on_stacked_get_stream_unordered(self):
3914
# Repository._get_source.get_stream() from a stacked repository which
3915
# is itself stacked yields the full data from all three sources.
3916
def make_stacked_stacked():
3917
_, stacked = self.prepare_stacked_remote_branch()
3918
tree = stacked.bzrdir.sprout('tree3', stacked=True
3919
).open_workingtree()
3920
local_tree = tree.branch.create_checkout('local-tree3')
3921
local_tree.commit('more local changes are better')
3922
branch = Branch.open(self.get_url('tree3'))
3924
self.addCleanup(branch.unlock)
3926
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3927
branch_factory=make_stacked_stacked)
3928
self.assertEqual(set(expected_revs), set(rev_ord))
3929
# Getting unordered results should have made a streaming data request
3930
# from the server, and one from each backing repo
3931
self.assertLength(3, self.hpss_calls)
3933
def test_stacked_get_stream_topological(self):
3934
# Repository._get_source.get_stream() from a stacked repository with
3935
# topological sorting yields the full data from both stacked and
3936
# stacked upon sources in topological order.
3937
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3938
self.assertEqual(expected_revs, rev_ord)
3939
# Getting topological sort requires VFS calls still - one of which is
3940
# pushing up from the bound branch.
3941
self.assertLength(14, self.hpss_calls)
3943
def test_stacked_get_stream_groupcompress(self):
3944
# Repository._get_source.get_stream() from a stacked repository with
3945
# groupcompress sorting yields the full data from both stacked and
3946
# stacked upon sources in groupcompress order.
3947
raise tests.TestSkipped('No groupcompress ordered format available')
3948
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3949
self.assertEqual(expected_revs, reversed(rev_ord))
3950
# Getting unordered results should have made a streaming data request
3951
# from the backing branch, and one from the stacked on branch.
3952
self.assertLength(2, self.hpss_calls)
3954
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3955
# When pulling some fixed amount of content that is more than the
3956
# source has (because some is coming from a fallback branch, no error
3957
# should be received. This was reported as bug 360791.
3958
# Need three branches: a trunk, a stacked branch, and a preexisting
3959
# branch pulling content from stacked and trunk.
3960
self.setup_smart_server_with_call_log()
3961
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3962
r1 = trunk.commit('start')
3963
stacked_branch = trunk.branch.create_clone_on_transport(
3964
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3965
local = self.make_branch('local', format='1.9-rich-root')
3966
local.repository.fetch(stacked_branch.repository,
3967
stacked_branch.last_revision())
3970
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3973
super(TestRemoteBranchEffort, self).setUp()
3974
# Create a smart server that publishes whatever the backing VFS server
3976
self.smart_server = test_server.SmartTCPServer_for_testing()
3977
self.start_server(self.smart_server, self.get_server())
3978
# Log all HPSS calls into self.hpss_calls.
3979
_SmartClient.hooks.install_named_hook(
3980
'call', self.capture_hpss_call, None)
3981
self.hpss_calls = []
3983
def capture_hpss_call(self, params):
3984
self.hpss_calls.append(params.method)
3986
def test_copy_content_into_avoids_revision_history(self):
3987
local = self.make_branch('local')
3988
builder = self.make_branch_builder('remote')
3989
builder.build_commit(message="Commit.")
3990
remote_branch_url = self.smart_server.get_url() + 'remote'
3991
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3992
local.repository.fetch(remote_branch.repository)
3993
self.hpss_calls = []
3994
remote_branch.copy_content_into(local)
3995
self.assertFalse('Branch.revision_history' in self.hpss_calls)
3997
def test_fetch_everything_needs_just_one_call(self):
3998
local = self.make_branch('local')
3999
builder = self.make_branch_builder('remote')
4000
builder.build_commit(message="Commit.")
4001
remote_branch_url = self.smart_server.get_url() + 'remote'
4002
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4003
self.hpss_calls = []
4004
local.repository.fetch(
4005
remote_branch.repository,
4006
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
4007
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4009
def override_verb(self, verb_name, verb):
4010
request_handlers = request.request_handlers
4011
orig_verb = request_handlers.get(verb_name)
4012
orig_info = request_handlers.get_info(verb_name)
4013
request_handlers.register(verb_name, verb, override_existing=True)
4014
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4015
override_existing=True, info=orig_info)
4017
def test_fetch_everything_backwards_compat(self):
4018
"""Can fetch with EverythingResult even with pre 2.4 servers.
4020
Pre-2.4 do not support 'everything' searches with the
4021
Repository.get_stream_1.19 verb.
4024
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4025
"""A version of the Repository.get_stream_1.19 verb patched to
4026
reject 'everything' searches the way 2.3 and earlier do.
4028
def recreate_search(self, repository, search_bytes,
4029
discard_excess=False):
4030
verb_log.append(search_bytes.split('\n', 1)[0])
4031
if search_bytes == 'everything':
4033
request.FailedSmartServerResponse(('BadSearch',)))
4034
return super(OldGetStreamVerb,
4035
self).recreate_search(repository, search_bytes,
4036
discard_excess=discard_excess)
4037
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4038
local = self.make_branch('local')
4039
builder = self.make_branch_builder('remote')
4040
builder.build_commit(message="Commit.")
4041
remote_branch_url = self.smart_server.get_url() + 'remote'
4042
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4043
self.hpss_calls = []
4044
local.repository.fetch(
4045
remote_branch.repository,
4046
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
4047
# make sure the overridden verb was used
4048
self.assertLength(1, verb_log)
4049
# more than one HPSS call is needed, but because it's a VFS callback
4050
# its hard to predict exactly how many.
4051
self.assertTrue(len(self.hpss_calls) > 1)
4054
class TestUpdateBoundBranchWithModifiedBoundLocation(
4055
tests.TestCaseWithTransport):
4056
"""Ensure correct handling of bound_location modifications.
4058
This is tested against a smart server as http://pad.lv/786980 was about a
4059
ReadOnlyError (write attempt during a read-only transaction) which can only
4060
happen in this context.
4064
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4065
self.transport_server = test_server.SmartTCPServer_for_testing
4067
def make_master_and_checkout(self, master_name, checkout_name):
4068
# Create the master branch and its associated checkout
4069
self.master = self.make_branch_and_tree(master_name)
4070
self.checkout = self.master.branch.create_checkout(checkout_name)
4071
# Modify the master branch so there is something to update
4072
self.master.commit('add stuff')
4073
self.last_revid = self.master.commit('even more stuff')
4074
self.bound_location = self.checkout.branch.get_bound_location()
4076
def assertUpdateSucceeds(self, new_location):
4077
self.checkout.branch.set_bound_location(new_location)
4078
self.checkout.update()
4079
self.assertEquals(self.last_revid, self.checkout.last_revision())
4081
def test_without_final_slash(self):
4082
self.make_master_and_checkout('master', 'checkout')
4083
# For unclear reasons some users have a bound_location without a final
4084
# '/', simulate that by forcing such a value
4085
self.assertEndsWith(self.bound_location, '/')
4086
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4088
def test_plus_sign(self):
4089
self.make_master_and_checkout('+master', 'checkout')
4090
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4092
def test_tilda(self):
4093
# Embed ~ in the middle of the path just to avoid any $HOME
4095
self.make_master_and_checkout('mas~ter', 'checkout')
4096
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4099
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4101
def test_no_context(self):
4102
class OutOfCoffee(errors.BzrError):
4103
"""A dummy exception for testing."""
4105
def __init__(self, urgency):
4106
self.urgency = urgency
4107
remote.no_context_error_translators.register("OutOfCoffee",
4108
lambda err: OutOfCoffee(err.error_args[0]))
4109
transport = MemoryTransport()
4110
client = FakeClient(transport.base)
4111
client.add_expected_call(
4112
'Branch.get_stacked_on_url', ('quack/',),
4113
'error', ('NotStacked',))
4114
client.add_expected_call(
4115
'Branch.last_revision_info',
4117
'error', ('OutOfCoffee', 'low'))
4118
transport.mkdir('quack')
4119
transport = transport.clone('quack')
4120
branch = self.make_remote_branch(transport, client)
4121
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4122
self.assertFinished(client)
4124
def test_with_context(self):
4125
class OutOfTea(errors.BzrError):
4126
def __init__(self, branch, urgency):
4127
self.branch = branch
4128
self.urgency = urgency
4129
remote.error_translators.register("OutOfTea",
4130
lambda err, find, path: OutOfTea(err.error_args[0],
4132
transport = MemoryTransport()
4133
client = FakeClient(transport.base)
4134
client.add_expected_call(
4135
'Branch.get_stacked_on_url', ('quack/',),
4136
'error', ('NotStacked',))
4137
client.add_expected_call(
4138
'Branch.last_revision_info',
4140
'error', ('OutOfTea', 'low'))
4141
transport.mkdir('quack')
4142
transport = transport.clone('quack')
4143
branch = self.make_remote_branch(transport, client)
4144
self.assertRaises(OutOfTea, branch.last_revision_info)
4145
self.assertFinished(client)
4148
class TestRepositoryPack(TestRemoteRepository):
4150
def test_pack(self):
4151
transport_path = 'quack'
4152
repo, client = self.setup_fake_client_and_repository(transport_path)
4153
client.add_expected_call(
4154
'Repository.lock_write', ('quack/', ''),
4155
'success', ('ok', 'token'))
4156
client.add_expected_call(
4157
'Repository.pack', ('quack/', 'token', 'False'),
4158
'success', ('ok',), )
4159
client.add_expected_call(
4160
'Repository.unlock', ('quack/', 'token'),
4161
'success', ('ok', ))
4164
def test_pack_with_hint(self):
4165
transport_path = 'quack'
4166
repo, client = self.setup_fake_client_and_repository(transport_path)
4167
client.add_expected_call(
4168
'Repository.lock_write', ('quack/', ''),
4169
'success', ('ok', 'token'))
4170
client.add_expected_call(
4171
'Repository.pack', ('quack/', 'token', 'False'),
4172
'success', ('ok',), )
4173
client.add_expected_call(
4174
'Repository.unlock', ('quack/', 'token', 'False'),
4175
'success', ('ok', ))
4176
repo.pack(['hinta', 'hintb'])