1
# Copyright (C) 2006-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for remote bzrdir/branch/repo/etc
19
These are proxy objects which act on remote objects by sending messages
20
through a smart client. The proxies are to be created when attempting to open
21
the object given a transport that supports smartserver rpc operations.
23
These tests correspond to tests.test_smart, which exercises the server side.
27
from cStringIO import StringIO
47
from bzrlib.branch import Branch
48
from bzrlib.bzrdir import (
53
from bzrlib.chk_serializer import chk_bencode_serializer
54
from bzrlib.remote import (
60
RemoteRepositoryFormat,
62
from bzrlib.repofmt import groupcompress_repo, knitpack_repo
63
from bzrlib.revision import (
67
from bzrlib.smart import medium, request
68
from bzrlib.smart.client import _SmartClient
69
from bzrlib.smart.repository import (
70
SmartServerRepositoryGetParentMap,
71
SmartServerRepositoryGetStream_1_19,
73
from bzrlib.symbol_versioning import deprecated_in
74
from bzrlib.tests import (
77
from bzrlib.tests.scenarios import load_tests_apply_scenarios
78
from bzrlib.transport.memory import MemoryTransport
79
from bzrlib.transport.remote import (
86
load_tests = load_tests_apply_scenarios
89
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
93
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
95
{'transport_server': test_server.SmartTCPServer_for_testing})]
99
super(BasicRemoteObjectTests, self).setUp()
100
self.transport = self.get_transport()
101
# make a branch that can be opened over the smart transport
102
self.local_wt = BzrDir.create_standalone_workingtree('.')
103
self.addCleanup(self.transport.disconnect)
105
def test_create_remote_bzrdir(self):
106
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
107
self.assertIsInstance(b, BzrDir)
109
def test_open_remote_branch(self):
110
# open a standalone branch in the working directory
111
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
112
branch = b.open_branch()
113
self.assertIsInstance(branch, Branch)
115
def test_remote_repository(self):
116
b = BzrDir.open_from_transport(self.transport)
117
repo = b.open_repository()
118
revid = u'\xc823123123'.encode('utf8')
119
self.assertFalse(repo.has_revision(revid))
120
self.local_wt.commit(message='test commit', rev_id=revid)
121
self.assertTrue(repo.has_revision(revid))
123
def test_remote_branch_revision_history(self):
124
b = BzrDir.open_from_transport(self.transport).open_branch()
126
self.applyDeprecated(deprecated_in((2, 5, 0)), b.revision_history))
127
r1 = self.local_wt.commit('1st commit')
128
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
129
self.assertEqual([r1, r2],
130
self.applyDeprecated(deprecated_in((2, 5, 0)), b.revision_history))
132
def test_find_correct_format(self):
133
"""Should open a RemoteBzrDir over a RemoteTransport"""
134
fmt = BzrDirFormat.find_format(self.transport)
135
self.assertTrue(bzrdir.RemoteBzrProber
136
in controldir.ControlDirFormat._server_probers)
137
self.assertIsInstance(fmt, RemoteBzrDirFormat)
139
def test_open_detected_smart_format(self):
140
fmt = BzrDirFormat.find_format(self.transport)
141
d = fmt.open(self.transport)
142
self.assertIsInstance(d, BzrDir)
144
def test_remote_branch_repr(self):
145
b = BzrDir.open_from_transport(self.transport).open_branch()
146
self.assertStartsWith(str(b), 'RemoteBranch(')
148
def test_remote_bzrdir_repr(self):
149
b = BzrDir.open_from_transport(self.transport)
150
self.assertStartsWith(str(b), 'RemoteBzrDir(')
152
def test_remote_branch_format_supports_stacking(self):
154
self.make_branch('unstackable', format='pack-0.92')
155
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
156
self.assertFalse(b._format.supports_stacking())
157
self.make_branch('stackable', format='1.9')
158
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
159
self.assertTrue(b._format.supports_stacking())
161
def test_remote_repo_format_supports_external_references(self):
163
bd = self.make_bzrdir('unstackable', format='pack-0.92')
164
r = bd.create_repository()
165
self.assertFalse(r._format.supports_external_lookups)
166
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
167
self.assertFalse(r._format.supports_external_lookups)
168
bd = self.make_bzrdir('stackable', format='1.9')
169
r = bd.create_repository()
170
self.assertTrue(r._format.supports_external_lookups)
171
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
172
self.assertTrue(r._format.supports_external_lookups)
174
def test_remote_branch_set_append_revisions_only(self):
175
# Make a format 1.9 branch, which supports append_revisions_only
176
branch = self.make_branch('branch', format='1.9')
177
config = branch.get_config()
178
branch.set_append_revisions_only(True)
180
'True', config.get_user_option('append_revisions_only'))
181
branch.set_append_revisions_only(False)
183
'False', config.get_user_option('append_revisions_only'))
185
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
186
branch = self.make_branch('branch', format='knit')
187
config = branch.get_config()
189
errors.UpgradeRequired, branch.set_append_revisions_only, True)
192
class FakeProtocol(object):
193
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
195
def __init__(self, body, fake_client):
197
self._body_buffer = None
198
self._fake_client = fake_client
200
def read_body_bytes(self, count=-1):
201
if self._body_buffer is None:
202
self._body_buffer = StringIO(self.body)
203
bytes = self._body_buffer.read(count)
204
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
205
self._fake_client.expecting_body = False
208
def cancel_read_body(self):
209
self._fake_client.expecting_body = False
211
def read_streamed_body(self):
215
class FakeClient(_SmartClient):
216
"""Lookalike for _SmartClient allowing testing."""
218
def __init__(self, fake_medium_base='fake base'):
219
"""Create a FakeClient."""
222
self.expecting_body = False
223
# if non-None, this is the list of expected calls, with only the
224
# method name and arguments included. the body might be hard to
225
# compute so is not included. If a call is None, that call can
227
self._expected_calls = None
228
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
230
def add_expected_call(self, call_name, call_args, response_type,
231
response_args, response_body=None):
232
if self._expected_calls is None:
233
self._expected_calls = []
234
self._expected_calls.append((call_name, call_args))
235
self.responses.append((response_type, response_args, response_body))
237
def add_success_response(self, *args):
238
self.responses.append(('success', args, None))
240
def add_success_response_with_body(self, body, *args):
241
self.responses.append(('success', args, body))
242
if self._expected_calls is not None:
243
self._expected_calls.append(None)
245
def add_error_response(self, *args):
246
self.responses.append(('error', args))
248
def add_unknown_method_response(self, verb):
249
self.responses.append(('unknown', verb))
251
def finished_test(self):
252
if self._expected_calls:
253
raise AssertionError("%r finished but was still expecting %r"
254
% (self, self._expected_calls[0]))
256
def _get_next_response(self):
258
response_tuple = self.responses.pop(0)
259
except IndexError, e:
260
raise AssertionError("%r didn't expect any more calls"
262
if response_tuple[0] == 'unknown':
263
raise errors.UnknownSmartMethod(response_tuple[1])
264
elif response_tuple[0] == 'error':
265
raise errors.ErrorFromSmartServer(response_tuple[1])
266
return response_tuple
268
def _check_call(self, method, args):
269
if self._expected_calls is None:
270
# the test should be updated to say what it expects
273
next_call = self._expected_calls.pop(0)
275
raise AssertionError("%r didn't expect any more calls "
277
% (self, method, args,))
278
if next_call is None:
280
if method != next_call[0] or args != next_call[1]:
281
raise AssertionError("%r expected %r%r "
283
% (self, next_call[0], next_call[1], method, args,))
285
def call(self, method, *args):
286
self._check_call(method, args)
287
self._calls.append(('call', method, args))
288
return self._get_next_response()[1]
290
def call_expecting_body(self, method, *args):
291
self._check_call(method, args)
292
self._calls.append(('call_expecting_body', method, args))
293
result = self._get_next_response()
294
self.expecting_body = True
295
return result[1], FakeProtocol(result[2], self)
297
def call_with_body_bytes(self, method, args, body):
298
self._check_call(method, args)
299
self._calls.append(('call_with_body_bytes', method, args, body))
300
result = self._get_next_response()
301
return result[1], FakeProtocol(result[2], self)
303
def call_with_body_bytes_expecting_body(self, method, args, body):
304
self._check_call(method, args)
305
self._calls.append(('call_with_body_bytes_expecting_body', method,
307
result = self._get_next_response()
308
self.expecting_body = True
309
return result[1], FakeProtocol(result[2], self)
311
def call_with_body_stream(self, args, stream):
312
# Explicitly consume the stream before checking for an error, because
313
# that's what happens a real medium.
314
stream = list(stream)
315
self._check_call(args[0], args[1:])
316
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
317
result = self._get_next_response()
318
# The second value returned from call_with_body_stream is supposed to
319
# be a response_handler object, but so far no tests depend on that.
320
response_handler = None
321
return result[1], response_handler
324
class FakeMedium(medium.SmartClientMedium):
326
def __init__(self, client_calls, base):
327
medium.SmartClientMedium.__init__(self, base)
328
self._client_calls = client_calls
330
def disconnect(self):
331
self._client_calls.append(('disconnect medium',))
334
class TestVfsHas(tests.TestCase):
336
def test_unicode_path(self):
337
client = FakeClient('/')
338
client.add_success_response('yes',)
339
transport = RemoteTransport('bzr://localhost/', _client=client)
340
filename = u'/hell\u00d8'.encode('utf8')
341
result = transport.has(filename)
343
[('call', 'has', (filename,))],
345
self.assertTrue(result)
348
class TestRemote(tests.TestCaseWithMemoryTransport):
350
def get_branch_format(self):
351
reference_bzrdir_format = bzrdir.format_registry.get('default')()
352
return reference_bzrdir_format.get_branch_format()
354
def get_repo_format(self):
355
reference_bzrdir_format = bzrdir.format_registry.get('default')()
356
return reference_bzrdir_format.repository_format
358
def assertFinished(self, fake_client):
359
"""Assert that all of a FakeClient's expected calls have occurred."""
360
fake_client.finished_test()
363
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
364
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
366
def assertRemotePath(self, expected, client_base, transport_base):
367
"""Assert that the result of
368
SmartClientMedium.remote_path_from_transport is the expected value for
369
a given client_base and transport_base.
371
client_medium = medium.SmartClientMedium(client_base)
372
t = transport.get_transport(transport_base)
373
result = client_medium.remote_path_from_transport(t)
374
self.assertEqual(expected, result)
376
def test_remote_path_from_transport(self):
377
"""SmartClientMedium.remote_path_from_transport calculates a URL for
378
the given transport relative to the root of the client base URL.
380
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
381
self.assertRemotePath(
382
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
384
def assertRemotePathHTTP(self, expected, transport_base, relpath):
385
"""Assert that the result of
386
HttpTransportBase.remote_path_from_transport is the expected value for
387
a given transport_base and relpath of that transport. (Note that
388
HttpTransportBase is a subclass of SmartClientMedium)
390
base_transport = transport.get_transport(transport_base)
391
client_medium = base_transport.get_smart_medium()
392
cloned_transport = base_transport.clone(relpath)
393
result = client_medium.remote_path_from_transport(cloned_transport)
394
self.assertEqual(expected, result)
396
def test_remote_path_from_transport_http(self):
397
"""Remote paths for HTTP transports are calculated differently to other
398
transports. They are just relative to the client base, not the root
399
directory of the host.
401
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
402
self.assertRemotePathHTTP(
403
'../xyz/', scheme + '//host/path', '../xyz/')
404
self.assertRemotePathHTTP(
405
'xyz/', scheme + '//host/path', 'xyz/')
408
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
409
"""Tests for the behaviour of client_medium.remote_is_at_least."""
411
def test_initially_unlimited(self):
412
"""A fresh medium assumes that the remote side supports all
415
client_medium = medium.SmartClientMedium('dummy base')
416
self.assertFalse(client_medium._is_remote_before((99, 99)))
418
def test__remember_remote_is_before(self):
419
"""Calling _remember_remote_is_before ratchets down the known remote
422
client_medium = medium.SmartClientMedium('dummy base')
423
# Mark the remote side as being less than 1.6. The remote side may
425
client_medium._remember_remote_is_before((1, 6))
426
self.assertTrue(client_medium._is_remote_before((1, 6)))
427
self.assertFalse(client_medium._is_remote_before((1, 5)))
428
# Calling _remember_remote_is_before again with a lower value works.
429
client_medium._remember_remote_is_before((1, 5))
430
self.assertTrue(client_medium._is_remote_before((1, 5)))
431
# If you call _remember_remote_is_before with a higher value it logs a
432
# warning, and continues to remember the lower value.
433
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
434
client_medium._remember_remote_is_before((1, 9))
435
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
436
self.assertTrue(client_medium._is_remote_before((1, 5)))
439
class TestBzrDirCloningMetaDir(TestRemote):
441
def test_backwards_compat(self):
442
self.setup_smart_server_with_call_log()
443
a_dir = self.make_bzrdir('.')
444
self.reset_smart_call_log()
445
verb = 'BzrDir.cloning_metadir'
446
self.disable_verb(verb)
447
format = a_dir.cloning_metadir()
448
call_count = len([call for call in self.hpss_calls if
449
call.call.method == verb])
450
self.assertEqual(1, call_count)
452
def test_branch_reference(self):
453
transport = self.get_transport('quack')
454
referenced = self.make_branch('referenced')
455
expected = referenced.bzrdir.cloning_metadir()
456
client = FakeClient(transport.base)
457
client.add_expected_call(
458
'BzrDir.cloning_metadir', ('quack/', 'False'),
459
'error', ('BranchReference',)),
460
client.add_expected_call(
461
'BzrDir.open_branchV3', ('quack/',),
462
'success', ('ref', self.get_url('referenced'))),
463
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
465
result = a_bzrdir.cloning_metadir()
466
# We should have got a control dir matching the referenced branch.
467
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
468
self.assertEqual(expected._repository_format, result._repository_format)
469
self.assertEqual(expected._branch_format, result._branch_format)
470
self.assertFinished(client)
472
def test_current_server(self):
473
transport = self.get_transport('.')
474
transport = transport.clone('quack')
475
self.make_bzrdir('quack')
476
client = FakeClient(transport.base)
477
reference_bzrdir_format = bzrdir.format_registry.get('default')()
478
control_name = reference_bzrdir_format.network_name()
479
client.add_expected_call(
480
'BzrDir.cloning_metadir', ('quack/', 'False'),
481
'success', (control_name, '', ('branch', ''))),
482
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
484
result = a_bzrdir.cloning_metadir()
485
# We should have got a reference control dir with default branch and
486
# repository formats.
487
# This pokes a little, just to be sure.
488
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
489
self.assertEqual(None, result._repository_format)
490
self.assertEqual(None, result._branch_format)
491
self.assertFinished(client)
493
def test_unknown(self):
494
transport = self.get_transport('quack')
495
referenced = self.make_branch('referenced')
496
expected = referenced.bzrdir.cloning_metadir()
497
client = FakeClient(transport.base)
498
client.add_expected_call(
499
'BzrDir.cloning_metadir', ('quack/', 'False'),
500
'success', ('unknown', 'unknown', ('branch', ''))),
501
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
503
self.assertRaises(errors.UnknownFormatError, a_bzrdir.cloning_metadir)
506
class TestBzrDirDestroyBranch(TestRemote):
508
def test_destroy_default(self):
509
transport = self.get_transport('quack')
510
referenced = self.make_branch('referenced')
511
client = FakeClient(transport.base)
512
client.add_expected_call(
513
'BzrDir.destroy_branch', ('quack/', ),
515
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
517
a_bzrdir.destroy_branch()
518
self.assertFinished(client)
520
def test_destroy_named(self):
521
transport = self.get_transport('quack')
522
referenced = self.make_branch('referenced')
523
client = FakeClient(transport.base)
524
client.add_expected_call(
525
'BzrDir.destroy_branch', ('quack/', "foo"),
527
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
529
a_bzrdir.destroy_branch("foo")
530
self.assertFinished(client)
533
class TestBzrDirHasWorkingTree(TestRemote):
535
def test_has_workingtree(self):
536
transport = self.get_transport('quack')
537
client = FakeClient(transport.base)
538
client.add_expected_call(
539
'BzrDir.has_workingtree', ('quack/',),
540
'success', ('yes',)),
541
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
543
self.assertTrue(a_bzrdir.has_workingtree())
544
self.assertFinished(client)
546
def test_no_workingtree(self):
547
transport = self.get_transport('quack')
548
client = FakeClient(transport.base)
549
client.add_expected_call(
550
'BzrDir.has_workingtree', ('quack/',),
552
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
554
self.assertFalse(a_bzrdir.has_workingtree())
555
self.assertFinished(client)
558
class TestBzrDirDestroyRepository(TestRemote):
560
def test_destroy_repository(self):
561
transport = self.get_transport('quack')
562
client = FakeClient(transport.base)
563
client.add_expected_call(
564
'BzrDir.destroy_repository', ('quack/',),
566
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
568
a_bzrdir.destroy_repository()
569
self.assertFinished(client)
572
class TestBzrDirOpen(TestRemote):
574
def make_fake_client_and_transport(self, path='quack'):
575
transport = MemoryTransport()
576
transport.mkdir(path)
577
transport = transport.clone(path)
578
client = FakeClient(transport.base)
579
return client, transport
581
def test_absent(self):
582
client, transport = self.make_fake_client_and_transport()
583
client.add_expected_call(
584
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
585
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
586
RemoteBzrDirFormat(), _client=client, _force_probe=True)
587
self.assertFinished(client)
589
def test_present_without_workingtree(self):
590
client, transport = self.make_fake_client_and_transport()
591
client.add_expected_call(
592
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
593
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
594
_client=client, _force_probe=True)
595
self.assertIsInstance(bd, RemoteBzrDir)
596
self.assertFalse(bd.has_workingtree())
597
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
598
self.assertFinished(client)
600
def test_present_with_workingtree(self):
601
client, transport = self.make_fake_client_and_transport()
602
client.add_expected_call(
603
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
604
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
605
_client=client, _force_probe=True)
606
self.assertIsInstance(bd, RemoteBzrDir)
607
self.assertTrue(bd.has_workingtree())
608
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
609
self.assertFinished(client)
611
def test_backwards_compat(self):
612
client, transport = self.make_fake_client_and_transport()
613
client.add_expected_call(
614
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
615
client.add_expected_call(
616
'BzrDir.open', ('quack/',), 'success', ('yes',))
617
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
618
_client=client, _force_probe=True)
619
self.assertIsInstance(bd, RemoteBzrDir)
620
self.assertFinished(client)
622
def test_backwards_compat_hpss_v2(self):
623
client, transport = self.make_fake_client_and_transport()
624
# Monkey-patch fake client to simulate real-world behaviour with v2
625
# server: upon first RPC call detect the protocol version, and because
626
# the version is 2 also do _remember_remote_is_before((1, 6)) before
627
# continuing with the RPC.
628
orig_check_call = client._check_call
629
def check_call(method, args):
630
client._medium._protocol_version = 2
631
client._medium._remember_remote_is_before((1, 6))
632
client._check_call = orig_check_call
633
client._check_call(method, args)
634
client._check_call = check_call
635
client.add_expected_call(
636
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
637
client.add_expected_call(
638
'BzrDir.open', ('quack/',), 'success', ('yes',))
639
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
640
_client=client, _force_probe=True)
641
self.assertIsInstance(bd, RemoteBzrDir)
642
self.assertFinished(client)
645
class TestBzrDirOpenBranch(TestRemote):
647
def test_backwards_compat(self):
648
self.setup_smart_server_with_call_log()
649
self.make_branch('.')
650
a_dir = BzrDir.open(self.get_url('.'))
651
self.reset_smart_call_log()
652
verb = 'BzrDir.open_branchV3'
653
self.disable_verb(verb)
654
format = a_dir.open_branch()
655
call_count = len([call for call in self.hpss_calls if
656
call.call.method == verb])
657
self.assertEqual(1, call_count)
659
def test_branch_present(self):
660
reference_format = self.get_repo_format()
661
network_name = reference_format.network_name()
662
branch_network_name = self.get_branch_format().network_name()
663
transport = MemoryTransport()
664
transport.mkdir('quack')
665
transport = transport.clone('quack')
666
client = FakeClient(transport.base)
667
client.add_expected_call(
668
'BzrDir.open_branchV3', ('quack/',),
669
'success', ('branch', branch_network_name))
670
client.add_expected_call(
671
'BzrDir.find_repositoryV3', ('quack/',),
672
'success', ('ok', '', 'no', 'no', 'no', network_name))
673
client.add_expected_call(
674
'Branch.get_stacked_on_url', ('quack/',),
675
'error', ('NotStacked',))
676
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
678
result = bzrdir.open_branch()
679
self.assertIsInstance(result, RemoteBranch)
680
self.assertEqual(bzrdir, result.bzrdir)
681
self.assertFinished(client)
683
def test_branch_missing(self):
684
transport = MemoryTransport()
685
transport.mkdir('quack')
686
transport = transport.clone('quack')
687
client = FakeClient(transport.base)
688
client.add_error_response('nobranch')
689
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
691
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
693
[('call', 'BzrDir.open_branchV3', ('quack/',))],
696
def test__get_tree_branch(self):
697
# _get_tree_branch is a form of open_branch, but it should only ask for
698
# branch opening, not any other network requests.
700
def open_branch(name=None, possible_transports=None):
701
calls.append("Called")
703
transport = MemoryTransport()
704
# no requests on the network - catches other api calls being made.
705
client = FakeClient(transport.base)
706
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
708
# patch the open_branch call to record that it was called.
709
bzrdir.open_branch = open_branch
710
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
711
self.assertEqual(["Called"], calls)
712
self.assertEqual([], client._calls)
714
def test_url_quoting_of_path(self):
715
# Relpaths on the wire should not be URL-escaped. So "~" should be
716
# transmitted as "~", not "%7E".
717
transport = RemoteTCPTransport('bzr://localhost/~hello/')
718
client = FakeClient(transport.base)
719
reference_format = self.get_repo_format()
720
network_name = reference_format.network_name()
721
branch_network_name = self.get_branch_format().network_name()
722
client.add_expected_call(
723
'BzrDir.open_branchV3', ('~hello/',),
724
'success', ('branch', branch_network_name))
725
client.add_expected_call(
726
'BzrDir.find_repositoryV3', ('~hello/',),
727
'success', ('ok', '', 'no', 'no', 'no', network_name))
728
client.add_expected_call(
729
'Branch.get_stacked_on_url', ('~hello/',),
730
'error', ('NotStacked',))
731
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
733
result = bzrdir.open_branch()
734
self.assertFinished(client)
736
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
737
reference_format = self.get_repo_format()
738
network_name = reference_format.network_name()
739
transport = MemoryTransport()
740
transport.mkdir('quack')
741
transport = transport.clone('quack')
743
rich_response = 'yes'
747
subtree_response = 'yes'
749
subtree_response = 'no'
750
client = FakeClient(transport.base)
751
client.add_success_response(
752
'ok', '', rich_response, subtree_response, external_lookup,
754
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
756
result = bzrdir.open_repository()
758
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
760
self.assertIsInstance(result, RemoteRepository)
761
self.assertEqual(bzrdir, result.bzrdir)
762
self.assertEqual(rich_root, result._format.rich_root_data)
763
self.assertEqual(subtrees, result._format.supports_tree_reference)
765
def test_open_repository_sets_format_attributes(self):
766
self.check_open_repository(True, True)
767
self.check_open_repository(False, True)
768
self.check_open_repository(True, False)
769
self.check_open_repository(False, False)
770
self.check_open_repository(False, False, 'yes')
772
def test_old_server(self):
773
"""RemoteBzrDirFormat should fail to probe if the server version is too
776
self.assertRaises(errors.NotBranchError,
777
RemoteBzrProber.probe_transport, OldServerTransport())
780
class TestBzrDirCreateBranch(TestRemote):
782
def test_backwards_compat(self):
783
self.setup_smart_server_with_call_log()
784
repo = self.make_repository('.')
785
self.reset_smart_call_log()
786
self.disable_verb('BzrDir.create_branch')
787
branch = repo.bzrdir.create_branch()
788
create_branch_call_count = len([call for call in self.hpss_calls if
789
call.call.method == 'BzrDir.create_branch'])
790
self.assertEqual(1, create_branch_call_count)
792
def test_current_server(self):
793
transport = self.get_transport('.')
794
transport = transport.clone('quack')
795
self.make_repository('quack')
796
client = FakeClient(transport.base)
797
reference_bzrdir_format = bzrdir.format_registry.get('default')()
798
reference_format = reference_bzrdir_format.get_branch_format()
799
network_name = reference_format.network_name()
800
reference_repo_fmt = reference_bzrdir_format.repository_format
801
reference_repo_name = reference_repo_fmt.network_name()
802
client.add_expected_call(
803
'BzrDir.create_branch', ('quack/', network_name),
804
'success', ('ok', network_name, '', 'no', 'no', 'yes',
805
reference_repo_name))
806
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
808
branch = a_bzrdir.create_branch()
809
# We should have got a remote branch
810
self.assertIsInstance(branch, remote.RemoteBranch)
811
# its format should have the settings from the response
812
format = branch._format
813
self.assertEqual(network_name, format.network_name())
815
def test_already_open_repo_and_reused_medium(self):
816
"""Bug 726584: create_branch(..., repository=repo) should work
817
regardless of what the smart medium's base URL is.
819
self.transport_server = test_server.SmartTCPServer_for_testing
820
transport = self.get_transport('.')
821
repo = self.make_repository('quack')
822
# Client's medium rooted a transport root (not at the bzrdir)
823
client = FakeClient(transport.base)
824
transport = transport.clone('quack')
825
reference_bzrdir_format = bzrdir.format_registry.get('default')()
826
reference_format = reference_bzrdir_format.get_branch_format()
827
network_name = reference_format.network_name()
828
reference_repo_fmt = reference_bzrdir_format.repository_format
829
reference_repo_name = reference_repo_fmt.network_name()
830
client.add_expected_call(
831
'BzrDir.create_branch', ('extra/quack/', network_name),
832
'success', ('ok', network_name, '', 'no', 'no', 'yes',
833
reference_repo_name))
834
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
836
branch = a_bzrdir.create_branch(repository=repo)
837
# We should have got a remote branch
838
self.assertIsInstance(branch, remote.RemoteBranch)
839
# its format should have the settings from the response
840
format = branch._format
841
self.assertEqual(network_name, format.network_name())
844
class TestBzrDirCreateRepository(TestRemote):
846
def test_backwards_compat(self):
847
self.setup_smart_server_with_call_log()
848
bzrdir = self.make_bzrdir('.')
849
self.reset_smart_call_log()
850
self.disable_verb('BzrDir.create_repository')
851
repo = bzrdir.create_repository()
852
create_repo_call_count = len([call for call in self.hpss_calls if
853
call.call.method == 'BzrDir.create_repository'])
854
self.assertEqual(1, create_repo_call_count)
856
def test_current_server(self):
857
transport = self.get_transport('.')
858
transport = transport.clone('quack')
859
self.make_bzrdir('quack')
860
client = FakeClient(transport.base)
861
reference_bzrdir_format = bzrdir.format_registry.get('default')()
862
reference_format = reference_bzrdir_format.repository_format
863
network_name = reference_format.network_name()
864
client.add_expected_call(
865
'BzrDir.create_repository', ('quack/',
866
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
868
'success', ('ok', 'yes', 'yes', 'yes', network_name))
869
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
871
repo = a_bzrdir.create_repository()
872
# We should have got a remote repository
873
self.assertIsInstance(repo, remote.RemoteRepository)
874
# its format should have the settings from the response
875
format = repo._format
876
self.assertTrue(format.rich_root_data)
877
self.assertTrue(format.supports_tree_reference)
878
self.assertTrue(format.supports_external_lookups)
879
self.assertEqual(network_name, format.network_name())
882
class TestBzrDirOpenRepository(TestRemote):
884
def test_backwards_compat_1_2_3(self):
885
# fallback all the way to the first version.
886
reference_format = self.get_repo_format()
887
network_name = reference_format.network_name()
888
server_url = 'bzr://example.com/'
889
self.permit_url(server_url)
890
client = FakeClient(server_url)
891
client.add_unknown_method_response('BzrDir.find_repositoryV3')
892
client.add_unknown_method_response('BzrDir.find_repositoryV2')
893
client.add_success_response('ok', '', 'no', 'no')
894
# A real repository instance will be created to determine the network
896
client.add_success_response_with_body(
897
"Bazaar-NG meta directory, format 1\n", 'ok')
898
client.add_success_response_with_body(
899
reference_format.get_format_string(), 'ok')
900
# PackRepository wants to do a stat
901
client.add_success_response('stat', '0', '65535')
902
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
904
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
906
repo = bzrdir.open_repository()
908
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
909
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
910
('call', 'BzrDir.find_repository', ('quack/',)),
911
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
912
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
913
('call', 'stat', ('/quack/.bzr/repository',)),
916
self.assertEqual(network_name, repo._format.network_name())
918
def test_backwards_compat_2(self):
919
# fallback to find_repositoryV2
920
reference_format = self.get_repo_format()
921
network_name = reference_format.network_name()
922
server_url = 'bzr://example.com/'
923
self.permit_url(server_url)
924
client = FakeClient(server_url)
925
client.add_unknown_method_response('BzrDir.find_repositoryV3')
926
client.add_success_response('ok', '', 'no', 'no', 'no')
927
# A real repository instance will be created to determine the network
929
client.add_success_response_with_body(
930
"Bazaar-NG meta directory, format 1\n", 'ok')
931
client.add_success_response_with_body(
932
reference_format.get_format_string(), 'ok')
933
# PackRepository wants to do a stat
934
client.add_success_response('stat', '0', '65535')
935
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
937
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
939
repo = bzrdir.open_repository()
941
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
942
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
943
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
944
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
945
('call', 'stat', ('/quack/.bzr/repository',)),
948
self.assertEqual(network_name, repo._format.network_name())
950
def test_current_server(self):
951
reference_format = self.get_repo_format()
952
network_name = reference_format.network_name()
953
transport = MemoryTransport()
954
transport.mkdir('quack')
955
transport = transport.clone('quack')
956
client = FakeClient(transport.base)
957
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
958
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
960
repo = bzrdir.open_repository()
962
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
964
self.assertEqual(network_name, repo._format.network_name())
967
class TestBzrDirFormatInitializeEx(TestRemote):
969
def test_success(self):
970
"""Simple test for typical successful call."""
971
fmt = RemoteBzrDirFormat()
972
default_format_name = BzrDirFormat.get_default_format().network_name()
973
transport = self.get_transport()
974
client = FakeClient(transport.base)
975
client.add_expected_call(
976
'BzrDirFormat.initialize_ex_1.16',
977
(default_format_name, 'path', 'False', 'False', 'False', '',
978
'', '', '', 'False'),
980
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
981
'bzrdir fmt', 'False', '', '', 'repo lock token'))
982
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
983
# it's currently hard to test that without supplying a real remote
984
# transport connected to a real server.
985
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
986
transport, False, False, False, None, None, None, None, False)
987
self.assertFinished(client)
989
def test_error(self):
990
"""Error responses are translated, e.g. 'PermissionDenied' raises the
991
corresponding error from the client.
993
fmt = RemoteBzrDirFormat()
994
default_format_name = BzrDirFormat.get_default_format().network_name()
995
transport = self.get_transport()
996
client = FakeClient(transport.base)
997
client.add_expected_call(
998
'BzrDirFormat.initialize_ex_1.16',
999
(default_format_name, 'path', 'False', 'False', 'False', '',
1000
'', '', '', 'False'),
1002
('PermissionDenied', 'path', 'extra info'))
1003
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1004
# it's currently hard to test that without supplying a real remote
1005
# transport connected to a real server.
1006
err = self.assertRaises(errors.PermissionDenied,
1007
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1008
False, False, False, None, None, None, None, False)
1009
self.assertEqual('path', err.path)
1010
self.assertEqual(': extra info', err.extra)
1011
self.assertFinished(client)
1013
def test_error_from_real_server(self):
1014
"""Integration test for error translation."""
1015
transport = self.make_smart_server('foo')
1016
transport = transport.clone('no-such-path')
1017
fmt = RemoteBzrDirFormat()
1018
err = self.assertRaises(errors.NoSuchFile,
1019
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1022
class OldSmartClient(object):
1023
"""A fake smart client for test_old_version that just returns a version one
1024
response to the 'hello' (query version) command.
1027
def get_request(self):
1028
input_file = StringIO('ok\x011\n')
1029
output_file = StringIO()
1030
client_medium = medium.SmartSimplePipesClientMedium(
1031
input_file, output_file)
1032
return medium.SmartClientStreamMediumRequest(client_medium)
1034
def protocol_version(self):
1038
class OldServerTransport(object):
1039
"""A fake transport for test_old_server that reports it's smart server
1040
protocol version as version one.
1046
def get_smart_client(self):
1047
return OldSmartClient()
1050
class RemoteBzrDirTestCase(TestRemote):
1052
def make_remote_bzrdir(self, transport, client):
1053
"""Make a RemotebzrDir using 'client' as the _client."""
1054
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1058
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1060
def lock_remote_branch(self, branch):
1061
"""Trick a RemoteBranch into thinking it is locked."""
1062
branch._lock_mode = 'w'
1063
branch._lock_count = 2
1064
branch._lock_token = 'branch token'
1065
branch._repo_lock_token = 'repo token'
1066
branch.repository._lock_mode = 'w'
1067
branch.repository._lock_count = 2
1068
branch.repository._lock_token = 'repo token'
1070
def make_remote_branch(self, transport, client):
1071
"""Make a RemoteBranch using 'client' as its _SmartClient.
1073
A RemoteBzrDir and RemoteRepository will also be created to fill out
1074
the RemoteBranch, albeit with stub values for some of their attributes.
1076
# we do not want bzrdir to make any remote calls, so use False as its
1077
# _client. If it tries to make a remote call, this will fail
1079
bzrdir = self.make_remote_bzrdir(transport, False)
1080
repo = RemoteRepository(bzrdir, None, _client=client)
1081
branch_format = self.get_branch_format()
1082
format = RemoteBranchFormat(network_name=branch_format.network_name())
1083
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1086
class TestBranchBreakLock(RemoteBranchTestCase):
1088
def test_break_lock(self):
1089
transport_path = 'quack'
1090
transport = MemoryTransport()
1091
client = FakeClient(transport.base)
1092
client.add_expected_call(
1093
'Branch.get_stacked_on_url', ('quack/',),
1094
'error', ('NotStacked',))
1095
client.add_expected_call(
1096
'Branch.break_lock', ('quack/',),
1098
transport.mkdir('quack')
1099
transport = transport.clone('quack')
1100
branch = self.make_remote_branch(transport, client)
1102
self.assertFinished(client)
1105
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1107
def test_get_physical_lock_status_yes(self):
1108
transport = MemoryTransport()
1109
client = FakeClient(transport.base)
1110
client.add_expected_call(
1111
'Branch.get_stacked_on_url', ('quack/',),
1112
'error', ('NotStacked',))
1113
client.add_expected_call(
1114
'Branch.get_physical_lock_status', ('quack/',),
1115
'success', ('yes',))
1116
transport.mkdir('quack')
1117
transport = transport.clone('quack')
1118
branch = self.make_remote_branch(transport, client)
1119
result = branch.get_physical_lock_status()
1120
self.assertFinished(client)
1121
self.assertEqual(True, result)
1123
def test_get_physical_lock_status_no(self):
1124
transport = MemoryTransport()
1125
client = FakeClient(transport.base)
1126
client.add_expected_call(
1127
'Branch.get_stacked_on_url', ('quack/',),
1128
'error', ('NotStacked',))
1129
client.add_expected_call(
1130
'Branch.get_physical_lock_status', ('quack/',),
1132
transport.mkdir('quack')
1133
transport = transport.clone('quack')
1134
branch = self.make_remote_branch(transport, client)
1135
result = branch.get_physical_lock_status()
1136
self.assertFinished(client)
1137
self.assertEqual(False, result)
1140
class TestBranchGetParent(RemoteBranchTestCase):
1142
def test_no_parent(self):
1143
# in an empty branch we decode the response properly
1144
transport = MemoryTransport()
1145
client = FakeClient(transport.base)
1146
client.add_expected_call(
1147
'Branch.get_stacked_on_url', ('quack/',),
1148
'error', ('NotStacked',))
1149
client.add_expected_call(
1150
'Branch.get_parent', ('quack/',),
1152
transport.mkdir('quack')
1153
transport = transport.clone('quack')
1154
branch = self.make_remote_branch(transport, client)
1155
result = branch.get_parent()
1156
self.assertFinished(client)
1157
self.assertEqual(None, result)
1159
def test_parent_relative(self):
1160
transport = MemoryTransport()
1161
client = FakeClient(transport.base)
1162
client.add_expected_call(
1163
'Branch.get_stacked_on_url', ('kwaak/',),
1164
'error', ('NotStacked',))
1165
client.add_expected_call(
1166
'Branch.get_parent', ('kwaak/',),
1167
'success', ('../foo/',))
1168
transport.mkdir('kwaak')
1169
transport = transport.clone('kwaak')
1170
branch = self.make_remote_branch(transport, client)
1171
result = branch.get_parent()
1172
self.assertEqual(transport.clone('../foo').base, result)
1174
def test_parent_absolute(self):
1175
transport = MemoryTransport()
1176
client = FakeClient(transport.base)
1177
client.add_expected_call(
1178
'Branch.get_stacked_on_url', ('kwaak/',),
1179
'error', ('NotStacked',))
1180
client.add_expected_call(
1181
'Branch.get_parent', ('kwaak/',),
1182
'success', ('http://foo/',))
1183
transport.mkdir('kwaak')
1184
transport = transport.clone('kwaak')
1185
branch = self.make_remote_branch(transport, client)
1186
result = branch.get_parent()
1187
self.assertEqual('http://foo/', result)
1188
self.assertFinished(client)
1191
class TestBranchSetParentLocation(RemoteBranchTestCase):
1193
def test_no_parent(self):
1194
# We call the verb when setting parent to None
1195
transport = MemoryTransport()
1196
client = FakeClient(transport.base)
1197
client.add_expected_call(
1198
'Branch.get_stacked_on_url', ('quack/',),
1199
'error', ('NotStacked',))
1200
client.add_expected_call(
1201
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1203
transport.mkdir('quack')
1204
transport = transport.clone('quack')
1205
branch = self.make_remote_branch(transport, client)
1206
branch._lock_token = 'b'
1207
branch._repo_lock_token = 'r'
1208
branch._set_parent_location(None)
1209
self.assertFinished(client)
1211
def test_parent(self):
1212
transport = MemoryTransport()
1213
client = FakeClient(transport.base)
1214
client.add_expected_call(
1215
'Branch.get_stacked_on_url', ('kwaak/',),
1216
'error', ('NotStacked',))
1217
client.add_expected_call(
1218
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1220
transport.mkdir('kwaak')
1221
transport = transport.clone('kwaak')
1222
branch = self.make_remote_branch(transport, client)
1223
branch._lock_token = 'b'
1224
branch._repo_lock_token = 'r'
1225
branch._set_parent_location('foo')
1226
self.assertFinished(client)
1228
def test_backwards_compat(self):
1229
self.setup_smart_server_with_call_log()
1230
branch = self.make_branch('.')
1231
self.reset_smart_call_log()
1232
verb = 'Branch.set_parent_location'
1233
self.disable_verb(verb)
1234
branch.set_parent('http://foo/')
1235
self.assertLength(12, self.hpss_calls)
1238
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1240
def test_backwards_compat(self):
1241
self.setup_smart_server_with_call_log()
1242
branch = self.make_branch('.')
1243
self.reset_smart_call_log()
1244
verb = 'Branch.get_tags_bytes'
1245
self.disable_verb(verb)
1246
branch.tags.get_tag_dict()
1247
call_count = len([call for call in self.hpss_calls if
1248
call.call.method == verb])
1249
self.assertEqual(1, call_count)
1251
def test_trivial(self):
1252
transport = MemoryTransport()
1253
client = FakeClient(transport.base)
1254
client.add_expected_call(
1255
'Branch.get_stacked_on_url', ('quack/',),
1256
'error', ('NotStacked',))
1257
client.add_expected_call(
1258
'Branch.get_tags_bytes', ('quack/',),
1260
transport.mkdir('quack')
1261
transport = transport.clone('quack')
1262
branch = self.make_remote_branch(transport, client)
1263
result = branch.tags.get_tag_dict()
1264
self.assertFinished(client)
1265
self.assertEqual({}, result)
1268
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1270
def test_trivial(self):
1271
transport = MemoryTransport()
1272
client = FakeClient(transport.base)
1273
client.add_expected_call(
1274
'Branch.get_stacked_on_url', ('quack/',),
1275
'error', ('NotStacked',))
1276
client.add_expected_call(
1277
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1279
transport.mkdir('quack')
1280
transport = transport.clone('quack')
1281
branch = self.make_remote_branch(transport, client)
1282
self.lock_remote_branch(branch)
1283
branch._set_tags_bytes('tags bytes')
1284
self.assertFinished(client)
1285
self.assertEqual('tags bytes', client._calls[-1][-1])
1287
def test_backwards_compatible(self):
1288
transport = MemoryTransport()
1289
client = FakeClient(transport.base)
1290
client.add_expected_call(
1291
'Branch.get_stacked_on_url', ('quack/',),
1292
'error', ('NotStacked',))
1293
client.add_expected_call(
1294
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1295
'unknown', ('Branch.set_tags_bytes',))
1296
transport.mkdir('quack')
1297
transport = transport.clone('quack')
1298
branch = self.make_remote_branch(transport, client)
1299
self.lock_remote_branch(branch)
1300
class StubRealBranch(object):
1303
def _set_tags_bytes(self, bytes):
1304
self.calls.append(('set_tags_bytes', bytes))
1305
real_branch = StubRealBranch()
1306
branch._real_branch = real_branch
1307
branch._set_tags_bytes('tags bytes')
1308
# Call a second time, to exercise the 'remote version already inferred'
1310
branch._set_tags_bytes('tags bytes')
1311
self.assertFinished(client)
1313
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1316
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1318
def test_uses_last_revision_info_and_tags_by_default(self):
1319
transport = MemoryTransport()
1320
client = FakeClient(transport.base)
1321
client.add_expected_call(
1322
'Branch.get_stacked_on_url', ('quack/',),
1323
'error', ('NotStacked',))
1324
client.add_expected_call(
1325
'Branch.last_revision_info', ('quack/',),
1326
'success', ('ok', '1', 'rev-tip'))
1327
client.add_expected_call(
1328
'Branch.get_config_file', ('quack/',),
1329
'success', ('ok',), '')
1330
transport.mkdir('quack')
1331
transport = transport.clone('quack')
1332
branch = self.make_remote_branch(transport, client)
1333
result = branch.heads_to_fetch()
1334
self.assertFinished(client)
1335
self.assertEqual((set(['rev-tip']), set()), result)
1337
def test_uses_last_revision_info_and_tags_when_set(self):
1338
transport = MemoryTransport()
1339
client = FakeClient(transport.base)
1340
client.add_expected_call(
1341
'Branch.get_stacked_on_url', ('quack/',),
1342
'error', ('NotStacked',))
1343
client.add_expected_call(
1344
'Branch.last_revision_info', ('quack/',),
1345
'success', ('ok', '1', 'rev-tip'))
1346
client.add_expected_call(
1347
'Branch.get_config_file', ('quack/',),
1348
'success', ('ok',), 'branch.fetch_tags = True')
1349
# XXX: this will break if the default format's serialization of tags
1350
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1351
client.add_expected_call(
1352
'Branch.get_tags_bytes', ('quack/',),
1353
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1354
transport.mkdir('quack')
1355
transport = transport.clone('quack')
1356
branch = self.make_remote_branch(transport, client)
1357
result = branch.heads_to_fetch()
1358
self.assertFinished(client)
1360
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1362
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1363
transport = MemoryTransport()
1364
client = FakeClient(transport.base)
1365
client.add_expected_call(
1366
'Branch.get_stacked_on_url', ('quack/',),
1367
'error', ('NotStacked',))
1368
client.add_expected_call(
1369
'Branch.heads_to_fetch', ('quack/',),
1370
'success', (['tip'], ['tagged-1', 'tagged-2']))
1371
transport.mkdir('quack')
1372
transport = transport.clone('quack')
1373
branch = self.make_remote_branch(transport, client)
1374
branch._format._use_default_local_heads_to_fetch = lambda: False
1375
result = branch.heads_to_fetch()
1376
self.assertFinished(client)
1377
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1379
def make_branch_with_tags(self):
1380
self.setup_smart_server_with_call_log()
1381
# Make a branch with a single revision.
1382
builder = self.make_branch_builder('foo')
1383
builder.start_series()
1384
builder.build_snapshot('tip', None, [
1385
('add', ('', 'root-id', 'directory', ''))])
1386
builder.finish_series()
1387
branch = builder.get_branch()
1388
# Add two tags to that branch
1389
branch.tags.set_tag('tag-1', 'rev-1')
1390
branch.tags.set_tag('tag-2', 'rev-2')
1393
def test_backwards_compatible(self):
1394
branch = self.make_branch_with_tags()
1395
c = branch.get_config()
1396
c.set_user_option('branch.fetch_tags', 'True')
1397
self.addCleanup(branch.lock_read().unlock)
1398
# Disable the heads_to_fetch verb
1399
verb = 'Branch.heads_to_fetch'
1400
self.disable_verb(verb)
1401
self.reset_smart_call_log()
1402
result = branch.heads_to_fetch()
1403
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1405
['Branch.last_revision_info', 'Branch.get_config_file',
1406
'Branch.get_tags_bytes'],
1407
[call.call.method for call in self.hpss_calls])
1409
def test_backwards_compatible_no_tags(self):
1410
branch = self.make_branch_with_tags()
1411
c = branch.get_config()
1412
c.set_user_option('branch.fetch_tags', 'False')
1413
self.addCleanup(branch.lock_read().unlock)
1414
# Disable the heads_to_fetch verb
1415
verb = 'Branch.heads_to_fetch'
1416
self.disable_verb(verb)
1417
self.reset_smart_call_log()
1418
result = branch.heads_to_fetch()
1419
self.assertEqual((set(['tip']), set()), result)
1421
['Branch.last_revision_info', 'Branch.get_config_file'],
1422
[call.call.method for call in self.hpss_calls])
1425
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1427
def test_empty_branch(self):
1428
# in an empty branch we decode the response properly
1429
transport = MemoryTransport()
1430
client = FakeClient(transport.base)
1431
client.add_expected_call(
1432
'Branch.get_stacked_on_url', ('quack/',),
1433
'error', ('NotStacked',))
1434
client.add_expected_call(
1435
'Branch.last_revision_info', ('quack/',),
1436
'success', ('ok', '0', 'null:'))
1437
transport.mkdir('quack')
1438
transport = transport.clone('quack')
1439
branch = self.make_remote_branch(transport, client)
1440
result = branch.last_revision_info()
1441
self.assertFinished(client)
1442
self.assertEqual((0, NULL_REVISION), result)
1444
def test_non_empty_branch(self):
1445
# in a non-empty branch we also decode the response properly
1446
revid = u'\xc8'.encode('utf8')
1447
transport = MemoryTransport()
1448
client = FakeClient(transport.base)
1449
client.add_expected_call(
1450
'Branch.get_stacked_on_url', ('kwaak/',),
1451
'error', ('NotStacked',))
1452
client.add_expected_call(
1453
'Branch.last_revision_info', ('kwaak/',),
1454
'success', ('ok', '2', revid))
1455
transport.mkdir('kwaak')
1456
transport = transport.clone('kwaak')
1457
branch = self.make_remote_branch(transport, client)
1458
result = branch.last_revision_info()
1459
self.assertEqual((2, revid), result)
1462
class TestBranch_get_stacked_on_url(TestRemote):
1463
"""Test Branch._get_stacked_on_url rpc"""
1465
def test_get_stacked_on_invalid_url(self):
1466
# test that asking for a stacked on url the server can't access works.
1467
# This isn't perfect, but then as we're in the same process there
1468
# really isn't anything we can do to be 100% sure that the server
1469
# doesn't just open in - this test probably needs to be rewritten using
1470
# a spawn()ed server.
1471
stacked_branch = self.make_branch('stacked', format='1.9')
1472
memory_branch = self.make_branch('base', format='1.9')
1473
vfs_url = self.get_vfs_only_url('base')
1474
stacked_branch.set_stacked_on_url(vfs_url)
1475
transport = stacked_branch.bzrdir.root_transport
1476
client = FakeClient(transport.base)
1477
client.add_expected_call(
1478
'Branch.get_stacked_on_url', ('stacked/',),
1479
'success', ('ok', vfs_url))
1480
# XXX: Multiple calls are bad, this second call documents what is
1482
client.add_expected_call(
1483
'Branch.get_stacked_on_url', ('stacked/',),
1484
'success', ('ok', vfs_url))
1485
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1487
repo_fmt = remote.RemoteRepositoryFormat()
1488
repo_fmt._custom_format = stacked_branch.repository._format
1489
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1491
result = branch.get_stacked_on_url()
1492
self.assertEqual(vfs_url, result)
1494
def test_backwards_compatible(self):
1495
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1496
base_branch = self.make_branch('base', format='1.6')
1497
stacked_branch = self.make_branch('stacked', format='1.6')
1498
stacked_branch.set_stacked_on_url('../base')
1499
client = FakeClient(self.get_url())
1500
branch_network_name = self.get_branch_format().network_name()
1501
client.add_expected_call(
1502
'BzrDir.open_branchV3', ('stacked/',),
1503
'success', ('branch', branch_network_name))
1504
client.add_expected_call(
1505
'BzrDir.find_repositoryV3', ('stacked/',),
1506
'success', ('ok', '', 'no', 'no', 'yes',
1507
stacked_branch.repository._format.network_name()))
1508
# called twice, once from constructor and then again by us
1509
client.add_expected_call(
1510
'Branch.get_stacked_on_url', ('stacked/',),
1511
'unknown', ('Branch.get_stacked_on_url',))
1512
client.add_expected_call(
1513
'Branch.get_stacked_on_url', ('stacked/',),
1514
'unknown', ('Branch.get_stacked_on_url',))
1515
# this will also do vfs access, but that goes direct to the transport
1516
# and isn't seen by the FakeClient.
1517
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1518
RemoteBzrDirFormat(), _client=client)
1519
branch = bzrdir.open_branch()
1520
result = branch.get_stacked_on_url()
1521
self.assertEqual('../base', result)
1522
self.assertFinished(client)
1523
# it's in the fallback list both for the RemoteRepository and its vfs
1525
self.assertEqual(1, len(branch.repository._fallback_repositories))
1527
len(branch.repository._real_repository._fallback_repositories))
1529
def test_get_stacked_on_real_branch(self):
1530
base_branch = self.make_branch('base')
1531
stacked_branch = self.make_branch('stacked')
1532
stacked_branch.set_stacked_on_url('../base')
1533
reference_format = self.get_repo_format()
1534
network_name = reference_format.network_name()
1535
client = FakeClient(self.get_url())
1536
branch_network_name = self.get_branch_format().network_name()
1537
client.add_expected_call(
1538
'BzrDir.open_branchV3', ('stacked/',),
1539
'success', ('branch', branch_network_name))
1540
client.add_expected_call(
1541
'BzrDir.find_repositoryV3', ('stacked/',),
1542
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1543
# called twice, once from constructor and then again by us
1544
client.add_expected_call(
1545
'Branch.get_stacked_on_url', ('stacked/',),
1546
'success', ('ok', '../base'))
1547
client.add_expected_call(
1548
'Branch.get_stacked_on_url', ('stacked/',),
1549
'success', ('ok', '../base'))
1550
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1551
RemoteBzrDirFormat(), _client=client)
1552
branch = bzrdir.open_branch()
1553
result = branch.get_stacked_on_url()
1554
self.assertEqual('../base', result)
1555
self.assertFinished(client)
1556
# it's in the fallback list both for the RemoteRepository.
1557
self.assertEqual(1, len(branch.repository._fallback_repositories))
1558
# And we haven't had to construct a real repository.
1559
self.assertEqual(None, branch.repository._real_repository)
1562
class TestBranchSetLastRevision(RemoteBranchTestCase):
1564
def test_set_empty(self):
1565
# _set_last_revision_info('null:') is translated to calling
1566
# Branch.set_last_revision(path, '') on the wire.
1567
transport = MemoryTransport()
1568
transport.mkdir('branch')
1569
transport = transport.clone('branch')
1571
client = FakeClient(transport.base)
1572
client.add_expected_call(
1573
'Branch.get_stacked_on_url', ('branch/',),
1574
'error', ('NotStacked',))
1575
client.add_expected_call(
1576
'Branch.lock_write', ('branch/', '', ''),
1577
'success', ('ok', 'branch token', 'repo token'))
1578
client.add_expected_call(
1579
'Branch.last_revision_info',
1581
'success', ('ok', '0', 'null:'))
1582
client.add_expected_call(
1583
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1585
client.add_expected_call(
1586
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1588
branch = self.make_remote_branch(transport, client)
1590
result = branch._set_last_revision(NULL_REVISION)
1592
self.assertEqual(None, result)
1593
self.assertFinished(client)
1595
def test_set_nonempty(self):
1596
# set_last_revision_info(N, rev-idN) is translated to calling
1597
# Branch.set_last_revision(path, rev-idN) on the wire.
1598
transport = MemoryTransport()
1599
transport.mkdir('branch')
1600
transport = transport.clone('branch')
1602
client = FakeClient(transport.base)
1603
client.add_expected_call(
1604
'Branch.get_stacked_on_url', ('branch/',),
1605
'error', ('NotStacked',))
1606
client.add_expected_call(
1607
'Branch.lock_write', ('branch/', '', ''),
1608
'success', ('ok', 'branch token', 'repo token'))
1609
client.add_expected_call(
1610
'Branch.last_revision_info',
1612
'success', ('ok', '0', 'null:'))
1614
encoded_body = bz2.compress('\n'.join(lines))
1615
client.add_success_response_with_body(encoded_body, 'ok')
1616
client.add_expected_call(
1617
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1619
client.add_expected_call(
1620
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1622
branch = self.make_remote_branch(transport, client)
1623
# Lock the branch, reset the record of remote calls.
1625
result = branch._set_last_revision('rev-id2')
1627
self.assertEqual(None, result)
1628
self.assertFinished(client)
1630
def test_no_such_revision(self):
1631
transport = MemoryTransport()
1632
transport.mkdir('branch')
1633
transport = transport.clone('branch')
1634
# A response of 'NoSuchRevision' is translated into an exception.
1635
client = FakeClient(transport.base)
1636
client.add_expected_call(
1637
'Branch.get_stacked_on_url', ('branch/',),
1638
'error', ('NotStacked',))
1639
client.add_expected_call(
1640
'Branch.lock_write', ('branch/', '', ''),
1641
'success', ('ok', 'branch token', 'repo token'))
1642
client.add_expected_call(
1643
'Branch.last_revision_info',
1645
'success', ('ok', '0', 'null:'))
1646
# get_graph calls to construct the revision history, for the set_rh
1649
encoded_body = bz2.compress('\n'.join(lines))
1650
client.add_success_response_with_body(encoded_body, 'ok')
1651
client.add_expected_call(
1652
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1653
'error', ('NoSuchRevision', 'rev-id'))
1654
client.add_expected_call(
1655
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1658
branch = self.make_remote_branch(transport, client)
1661
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1663
self.assertFinished(client)
1665
def test_tip_change_rejected(self):
1666
"""TipChangeRejected responses cause a TipChangeRejected exception to
1669
transport = MemoryTransport()
1670
transport.mkdir('branch')
1671
transport = transport.clone('branch')
1672
client = FakeClient(transport.base)
1673
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1674
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1675
client.add_expected_call(
1676
'Branch.get_stacked_on_url', ('branch/',),
1677
'error', ('NotStacked',))
1678
client.add_expected_call(
1679
'Branch.lock_write', ('branch/', '', ''),
1680
'success', ('ok', 'branch token', 'repo token'))
1681
client.add_expected_call(
1682
'Branch.last_revision_info',
1684
'success', ('ok', '0', 'null:'))
1686
encoded_body = bz2.compress('\n'.join(lines))
1687
client.add_success_response_with_body(encoded_body, 'ok')
1688
client.add_expected_call(
1689
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1690
'error', ('TipChangeRejected', rejection_msg_utf8))
1691
client.add_expected_call(
1692
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1694
branch = self.make_remote_branch(transport, client)
1696
# The 'TipChangeRejected' error response triggered by calling
1697
# set_last_revision_info causes a TipChangeRejected exception.
1698
err = self.assertRaises(
1699
errors.TipChangeRejected,
1700
branch._set_last_revision, 'rev-id')
1701
# The UTF-8 message from the response has been decoded into a unicode
1703
self.assertIsInstance(err.msg, unicode)
1704
self.assertEqual(rejection_msg_unicode, err.msg)
1706
self.assertFinished(client)
1709
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1711
def test_set_last_revision_info(self):
1712
# set_last_revision_info(num, 'rev-id') is translated to calling
1713
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1714
transport = MemoryTransport()
1715
transport.mkdir('branch')
1716
transport = transport.clone('branch')
1717
client = FakeClient(transport.base)
1718
# get_stacked_on_url
1719
client.add_error_response('NotStacked')
1721
client.add_success_response('ok', 'branch token', 'repo token')
1722
# query the current revision
1723
client.add_success_response('ok', '0', 'null:')
1725
client.add_success_response('ok')
1727
client.add_success_response('ok')
1729
branch = self.make_remote_branch(transport, client)
1730
# Lock the branch, reset the record of remote calls.
1733
result = branch.set_last_revision_info(1234, 'a-revision-id')
1735
[('call', 'Branch.last_revision_info', ('branch/',)),
1736
('call', 'Branch.set_last_revision_info',
1737
('branch/', 'branch token', 'repo token',
1738
'1234', 'a-revision-id'))],
1740
self.assertEqual(None, result)
1742
def test_no_such_revision(self):
1743
# A response of 'NoSuchRevision' is translated into an exception.
1744
transport = MemoryTransport()
1745
transport.mkdir('branch')
1746
transport = transport.clone('branch')
1747
client = FakeClient(transport.base)
1748
# get_stacked_on_url
1749
client.add_error_response('NotStacked')
1751
client.add_success_response('ok', 'branch token', 'repo token')
1753
client.add_error_response('NoSuchRevision', 'revid')
1755
client.add_success_response('ok')
1757
branch = self.make_remote_branch(transport, client)
1758
# Lock the branch, reset the record of remote calls.
1763
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1766
def test_backwards_compatibility(self):
1767
"""If the server does not support the Branch.set_last_revision_info
1768
verb (which is new in 1.4), then the client falls back to VFS methods.
1770
# This test is a little messy. Unlike most tests in this file, it
1771
# doesn't purely test what a Remote* object sends over the wire, and
1772
# how it reacts to responses from the wire. It instead relies partly
1773
# on asserting that the RemoteBranch will call
1774
# self._real_branch.set_last_revision_info(...).
1776
# First, set up our RemoteBranch with a FakeClient that raises
1777
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1778
transport = MemoryTransport()
1779
transport.mkdir('branch')
1780
transport = transport.clone('branch')
1781
client = FakeClient(transport.base)
1782
client.add_expected_call(
1783
'Branch.get_stacked_on_url', ('branch/',),
1784
'error', ('NotStacked',))
1785
client.add_expected_call(
1786
'Branch.last_revision_info',
1788
'success', ('ok', '0', 'null:'))
1789
client.add_expected_call(
1790
'Branch.set_last_revision_info',
1791
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1792
'unknown', 'Branch.set_last_revision_info')
1794
branch = self.make_remote_branch(transport, client)
1795
class StubRealBranch(object):
1798
def set_last_revision_info(self, revno, revision_id):
1800
('set_last_revision_info', revno, revision_id))
1801
def _clear_cached_state(self):
1803
real_branch = StubRealBranch()
1804
branch._real_branch = real_branch
1805
self.lock_remote_branch(branch)
1807
# Call set_last_revision_info, and verify it behaved as expected.
1808
result = branch.set_last_revision_info(1234, 'a-revision-id')
1810
[('set_last_revision_info', 1234, 'a-revision-id')],
1812
self.assertFinished(client)
1814
def test_unexpected_error(self):
1815
# If the server sends an error the client doesn't understand, it gets
1816
# turned into an UnknownErrorFromSmartServer, which is presented as a
1817
# non-internal error to the user.
1818
transport = MemoryTransport()
1819
transport.mkdir('branch')
1820
transport = transport.clone('branch')
1821
client = FakeClient(transport.base)
1822
# get_stacked_on_url
1823
client.add_error_response('NotStacked')
1825
client.add_success_response('ok', 'branch token', 'repo token')
1827
client.add_error_response('UnexpectedError')
1829
client.add_success_response('ok')
1831
branch = self.make_remote_branch(transport, client)
1832
# Lock the branch, reset the record of remote calls.
1836
err = self.assertRaises(
1837
errors.UnknownErrorFromSmartServer,
1838
branch.set_last_revision_info, 123, 'revid')
1839
self.assertEqual(('UnexpectedError',), err.error_tuple)
1842
def test_tip_change_rejected(self):
1843
"""TipChangeRejected responses cause a TipChangeRejected exception to
1846
transport = MemoryTransport()
1847
transport.mkdir('branch')
1848
transport = transport.clone('branch')
1849
client = FakeClient(transport.base)
1850
# get_stacked_on_url
1851
client.add_error_response('NotStacked')
1853
client.add_success_response('ok', 'branch token', 'repo token')
1855
client.add_error_response('TipChangeRejected', 'rejection message')
1857
client.add_success_response('ok')
1859
branch = self.make_remote_branch(transport, client)
1860
# Lock the branch, reset the record of remote calls.
1862
self.addCleanup(branch.unlock)
1865
# The 'TipChangeRejected' error response triggered by calling
1866
# set_last_revision_info causes a TipChangeRejected exception.
1867
err = self.assertRaises(
1868
errors.TipChangeRejected,
1869
branch.set_last_revision_info, 123, 'revid')
1870
self.assertEqual('rejection message', err.msg)
1873
class TestBranchGetSetConfig(RemoteBranchTestCase):
1875
def test_get_branch_conf(self):
1876
# in an empty branch we decode the response properly
1877
client = FakeClient()
1878
client.add_expected_call(
1879
'Branch.get_stacked_on_url', ('memory:///',),
1880
'error', ('NotStacked',),)
1881
client.add_success_response_with_body('# config file body', 'ok')
1882
transport = MemoryTransport()
1883
branch = self.make_remote_branch(transport, client)
1884
config = branch.get_config()
1885
config.has_explicit_nickname()
1887
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1888
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1891
def test_get_multi_line_branch_conf(self):
1892
# Make sure that multiple-line branch.conf files are supported
1894
# https://bugs.launchpad.net/bzr/+bug/354075
1895
client = FakeClient()
1896
client.add_expected_call(
1897
'Branch.get_stacked_on_url', ('memory:///',),
1898
'error', ('NotStacked',),)
1899
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1900
transport = MemoryTransport()
1901
branch = self.make_remote_branch(transport, client)
1902
config = branch.get_config()
1903
self.assertEqual(u'2', config.get_user_option('b'))
1905
def test_set_option(self):
1906
client = FakeClient()
1907
client.add_expected_call(
1908
'Branch.get_stacked_on_url', ('memory:///',),
1909
'error', ('NotStacked',),)
1910
client.add_expected_call(
1911
'Branch.lock_write', ('memory:///', '', ''),
1912
'success', ('ok', 'branch token', 'repo token'))
1913
client.add_expected_call(
1914
'Branch.set_config_option', ('memory:///', 'branch token',
1915
'repo token', 'foo', 'bar', ''),
1917
client.add_expected_call(
1918
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1920
transport = MemoryTransport()
1921
branch = self.make_remote_branch(transport, client)
1923
config = branch._get_config()
1924
config.set_option('foo', 'bar')
1926
self.assertFinished(client)
1928
def test_set_option_with_dict(self):
1929
client = FakeClient()
1930
client.add_expected_call(
1931
'Branch.get_stacked_on_url', ('memory:///',),
1932
'error', ('NotStacked',),)
1933
client.add_expected_call(
1934
'Branch.lock_write', ('memory:///', '', ''),
1935
'success', ('ok', 'branch token', 'repo token'))
1936
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1937
client.add_expected_call(
1938
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1939
'repo token', encoded_dict_value, 'foo', ''),
1941
client.add_expected_call(
1942
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1944
transport = MemoryTransport()
1945
branch = self.make_remote_branch(transport, client)
1947
config = branch._get_config()
1949
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1952
self.assertFinished(client)
1954
def test_backwards_compat_set_option(self):
1955
self.setup_smart_server_with_call_log()
1956
branch = self.make_branch('.')
1957
verb = 'Branch.set_config_option'
1958
self.disable_verb(verb)
1960
self.addCleanup(branch.unlock)
1961
self.reset_smart_call_log()
1962
branch._get_config().set_option('value', 'name')
1963
self.assertLength(10, self.hpss_calls)
1964
self.assertEqual('value', branch._get_config().get_option('name'))
1966
def test_backwards_compat_set_option_with_dict(self):
1967
self.setup_smart_server_with_call_log()
1968
branch = self.make_branch('.')
1969
verb = 'Branch.set_config_option_dict'
1970
self.disable_verb(verb)
1972
self.addCleanup(branch.unlock)
1973
self.reset_smart_call_log()
1974
config = branch._get_config()
1975
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
1976
config.set_option(value_dict, 'name')
1977
self.assertLength(10, self.hpss_calls)
1978
self.assertEqual(value_dict, branch._get_config().get_option('name'))
1981
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
1983
def test_get_branch_conf(self):
1984
# in an empty branch we decode the response properly
1985
client = FakeClient()
1986
client.add_expected_call(
1987
'Branch.get_stacked_on_url', ('memory:///',),
1988
'error', ('NotStacked',),)
1989
client.add_success_response_with_body('# config file body', 'ok')
1990
transport = MemoryTransport()
1991
branch = self.make_remote_branch(transport, client)
1992
config = branch.get_config_stack()
1994
config.get("log_format")
1996
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1997
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2000
def test_set_branch_conf(self):
2001
client = FakeClient()
2002
client.add_expected_call(
2003
'Branch.get_stacked_on_url', ('memory:///',),
2004
'error', ('NotStacked',),)
2005
client.add_expected_call(
2006
'Branch.lock_write', ('memory:///', '', ''),
2007
'success', ('ok', 'branch token', 'repo token'))
2008
client.add_expected_call(
2009
'Branch.get_config_file', ('memory:///', ),
2010
'success', ('ok', ), "# line 1\n")
2011
client.add_expected_call(
2012
'Branch.put_config_file', ('memory:///', 'branch token',
2015
client.add_expected_call(
2016
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2018
transport = MemoryTransport()
2019
branch = self.make_remote_branch(transport, client)
2021
config = branch.get_config_stack()
2022
config.set('email', 'The Dude <lebowski@example.com>')
2024
self.assertFinished(client)
2026
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2027
('call', 'Branch.lock_write', ('memory:///', '', '')),
2028
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2029
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2030
('memory:///', 'branch token', 'repo token'),
2031
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2032
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2036
class TestBranchLockWrite(RemoteBranchTestCase):
2038
def test_lock_write_unlockable(self):
2039
transport = MemoryTransport()
2040
client = FakeClient(transport.base)
2041
client.add_expected_call(
2042
'Branch.get_stacked_on_url', ('quack/',),
2043
'error', ('NotStacked',),)
2044
client.add_expected_call(
2045
'Branch.lock_write', ('quack/', '', ''),
2046
'error', ('UnlockableTransport',))
2047
transport.mkdir('quack')
2048
transport = transport.clone('quack')
2049
branch = self.make_remote_branch(transport, client)
2050
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2051
self.assertFinished(client)
2054
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2056
def test_simple(self):
2057
transport = MemoryTransport()
2058
client = FakeClient(transport.base)
2059
client.add_expected_call(
2060
'Branch.get_stacked_on_url', ('quack/',),
2061
'error', ('NotStacked',),)
2062
client.add_expected_call(
2063
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2064
'success', ('ok', '0',),)
2065
client.add_expected_call(
2066
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2067
'error', ('NoSuchRevision', 'unknown',),)
2068
transport.mkdir('quack')
2069
transport = transport.clone('quack')
2070
branch = self.make_remote_branch(transport, client)
2071
self.assertEquals(0, branch.revision_id_to_revno('null:'))
2072
self.assertRaises(errors.NoSuchRevision,
2073
branch.revision_id_to_revno, 'unknown')
2074
self.assertFinished(client)
2076
def test_dotted(self):
2077
transport = MemoryTransport()
2078
client = FakeClient(transport.base)
2079
client.add_expected_call(
2080
'Branch.get_stacked_on_url', ('quack/',),
2081
'error', ('NotStacked',),)
2082
client.add_expected_call(
2083
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2084
'success', ('ok', '0',),)
2085
client.add_expected_call(
2086
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2087
'error', ('NoSuchRevision', 'unknown',),)
2088
transport.mkdir('quack')
2089
transport = transport.clone('quack')
2090
branch = self.make_remote_branch(transport, client)
2091
self.assertEquals((0, ), branch.revision_id_to_dotted_revno('null:'))
2092
self.assertRaises(errors.NoSuchRevision,
2093
branch.revision_id_to_dotted_revno, 'unknown')
2094
self.assertFinished(client)
2096
def test_dotted_no_smart_verb(self):
2097
self.setup_smart_server_with_call_log()
2098
branch = self.make_branch('.')
2099
self.disable_verb('Branch.revision_id_to_revno')
2100
self.reset_smart_call_log()
2101
self.assertEquals((0, ),
2102
branch.revision_id_to_dotted_revno('null:'))
2103
self.assertLength(7, self.hpss_calls)
2106
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2108
def test__get_config(self):
2109
client = FakeClient()
2110
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2111
transport = MemoryTransport()
2112
bzrdir = self.make_remote_bzrdir(transport, client)
2113
config = bzrdir.get_config()
2114
self.assertEqual('/', config.get_default_stack_on())
2116
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2119
def test_set_option_uses_vfs(self):
2120
self.setup_smart_server_with_call_log()
2121
bzrdir = self.make_bzrdir('.')
2122
self.reset_smart_call_log()
2123
config = bzrdir.get_config()
2124
config.set_default_stack_on('/')
2125
self.assertLength(3, self.hpss_calls)
2127
def test_backwards_compat_get_option(self):
2128
self.setup_smart_server_with_call_log()
2129
bzrdir = self.make_bzrdir('.')
2130
verb = 'BzrDir.get_config_file'
2131
self.disable_verb(verb)
2132
self.reset_smart_call_log()
2133
self.assertEqual(None,
2134
bzrdir._get_config().get_option('default_stack_on'))
2135
self.assertLength(3, self.hpss_calls)
2138
class TestTransportIsReadonly(tests.TestCase):
2140
def test_true(self):
2141
client = FakeClient()
2142
client.add_success_response('yes')
2143
transport = RemoteTransport('bzr://example.com/', medium=False,
2145
self.assertEqual(True, transport.is_readonly())
2147
[('call', 'Transport.is_readonly', ())],
2150
def test_false(self):
2151
client = FakeClient()
2152
client.add_success_response('no')
2153
transport = RemoteTransport('bzr://example.com/', medium=False,
2155
self.assertEqual(False, transport.is_readonly())
2157
[('call', 'Transport.is_readonly', ())],
2160
def test_error_from_old_server(self):
2161
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2163
Clients should treat it as a "no" response, because is_readonly is only
2164
advisory anyway (a transport could be read-write, but then the
2165
underlying filesystem could be readonly anyway).
2167
client = FakeClient()
2168
client.add_unknown_method_response('Transport.is_readonly')
2169
transport = RemoteTransport('bzr://example.com/', medium=False,
2171
self.assertEqual(False, transport.is_readonly())
2173
[('call', 'Transport.is_readonly', ())],
2177
class TestTransportMkdir(tests.TestCase):
2179
def test_permissiondenied(self):
2180
client = FakeClient()
2181
client.add_error_response('PermissionDenied', 'remote path', 'extra')
2182
transport = RemoteTransport('bzr://example.com/', medium=False,
2184
exc = self.assertRaises(
2185
errors.PermissionDenied, transport.mkdir, 'client path')
2186
expected_error = errors.PermissionDenied('/client path', 'extra')
2187
self.assertEqual(expected_error, exc)
2190
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2192
def test_defaults_to_none(self):
2193
t = RemoteSSHTransport('bzr+ssh://example.com')
2194
self.assertIs(None, t._get_credentials()[0])
2196
def test_uses_authentication_config(self):
2197
conf = config.AuthenticationConfig()
2198
conf._get_config().update(
2199
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2202
t = RemoteSSHTransport('bzr+ssh://example.com')
2203
self.assertEqual('bar', t._get_credentials()[0])
2206
class TestRemoteRepository(TestRemote):
2207
"""Base for testing RemoteRepository protocol usage.
2209
These tests contain frozen requests and responses. We want any changes to
2210
what is sent or expected to be require a thoughtful update to these tests
2211
because they might break compatibility with different-versioned servers.
2214
def setup_fake_client_and_repository(self, transport_path):
2215
"""Create the fake client and repository for testing with.
2217
There's no real server here; we just have canned responses sent
2220
:param transport_path: Path below the root of the MemoryTransport
2221
where the repository will be created.
2223
transport = MemoryTransport()
2224
transport.mkdir(transport_path)
2225
client = FakeClient(transport.base)
2226
transport = transport.clone(transport_path)
2227
# we do not want bzrdir to make any remote calls
2228
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2230
repo = RemoteRepository(bzrdir, None, _client=client)
2234
def remoted_description(format):
2235
return 'Remote: ' + format.get_format_description()
2238
class TestBranchFormat(tests.TestCase):
2240
def test_get_format_description(self):
2241
remote_format = RemoteBranchFormat()
2242
real_format = branch.format_registry.get_default()
2243
remote_format._network_name = real_format.network_name()
2244
self.assertEqual(remoted_description(real_format),
2245
remote_format.get_format_description())
2248
class TestRepositoryFormat(TestRemoteRepository):
2250
def test_fast_delta(self):
2251
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2252
true_format = RemoteRepositoryFormat()
2253
true_format._network_name = true_name
2254
self.assertEqual(True, true_format.fast_deltas)
2255
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2256
false_format = RemoteRepositoryFormat()
2257
false_format._network_name = false_name
2258
self.assertEqual(False, false_format.fast_deltas)
2260
def test_get_format_description(self):
2261
remote_repo_format = RemoteRepositoryFormat()
2262
real_format = repository.format_registry.get_default()
2263
remote_repo_format._network_name = real_format.network_name()
2264
self.assertEqual(remoted_description(real_format),
2265
remote_repo_format.get_format_description())
2268
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2270
def test_empty(self):
2271
transport_path = 'quack'
2272
repo, client = self.setup_fake_client_and_repository(transport_path)
2273
client.add_success_response_with_body('', 'ok')
2274
self.assertEquals([], repo.all_revision_ids())
2276
[('call_expecting_body', 'Repository.all_revision_ids',
2280
def test_with_some_content(self):
2281
transport_path = 'quack'
2282
repo, client = self.setup_fake_client_and_repository(transport_path)
2283
client.add_success_response_with_body(
2284
'rev1\nrev2\nanotherrev\n', 'ok')
2285
self.assertEquals(["rev1", "rev2", "anotherrev"],
2286
repo.all_revision_ids())
2288
[('call_expecting_body', 'Repository.all_revision_ids',
2293
class TestRepositoryGatherStats(TestRemoteRepository):
2295
def test_revid_none(self):
2296
# ('ok',), body with revisions and size
2297
transport_path = 'quack'
2298
repo, client = self.setup_fake_client_and_repository(transport_path)
2299
client.add_success_response_with_body(
2300
'revisions: 2\nsize: 18\n', 'ok')
2301
result = repo.gather_stats(None)
2303
[('call_expecting_body', 'Repository.gather_stats',
2304
('quack/','','no'))],
2306
self.assertEqual({'revisions': 2, 'size': 18}, result)
2308
def test_revid_no_committers(self):
2309
# ('ok',), body without committers
2310
body = ('firstrev: 123456.300 3600\n'
2311
'latestrev: 654231.400 0\n'
2314
transport_path = 'quick'
2315
revid = u'\xc8'.encode('utf8')
2316
repo, client = self.setup_fake_client_and_repository(transport_path)
2317
client.add_success_response_with_body(body, 'ok')
2318
result = repo.gather_stats(revid)
2320
[('call_expecting_body', 'Repository.gather_stats',
2321
('quick/', revid, 'no'))],
2323
self.assertEqual({'revisions': 2, 'size': 18,
2324
'firstrev': (123456.300, 3600),
2325
'latestrev': (654231.400, 0),},
2328
def test_revid_with_committers(self):
2329
# ('ok',), body with committers
2330
body = ('committers: 128\n'
2331
'firstrev: 123456.300 3600\n'
2332
'latestrev: 654231.400 0\n'
2335
transport_path = 'buick'
2336
revid = u'\xc8'.encode('utf8')
2337
repo, client = self.setup_fake_client_and_repository(transport_path)
2338
client.add_success_response_with_body(body, 'ok')
2339
result = repo.gather_stats(revid, True)
2341
[('call_expecting_body', 'Repository.gather_stats',
2342
('buick/', revid, 'yes'))],
2344
self.assertEqual({'revisions': 2, 'size': 18,
2346
'firstrev': (123456.300, 3600),
2347
'latestrev': (654231.400, 0),},
2351
class TestRepositoryBreakLock(TestRemoteRepository):
2353
def test_break_lock(self):
2354
transport_path = 'quack'
2355
repo, client = self.setup_fake_client_and_repository(transport_path)
2356
client.add_success_response('ok')
2359
[('call', 'Repository.break_lock', ('quack/',))],
2363
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2365
def test_get_serializer_format(self):
2366
transport_path = 'hill'
2367
repo, client = self.setup_fake_client_and_repository(transport_path)
2368
client.add_success_response('ok', '7')
2369
self.assertEquals('7', repo.get_serializer_format())
2371
[('call', 'VersionedFileRepository.get_serializer_format',
2376
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2378
def test_text(self):
2379
# ('ok',), body with signature text
2380
transport_path = 'quack'
2381
repo, client = self.setup_fake_client_and_repository(transport_path)
2382
client.add_success_response_with_body(
2384
self.assertEquals("THETEXT", repo.get_signature_text("revid"))
2386
[('call_expecting_body', 'Repository.get_revision_signature_text',
2387
('quack/', 'revid'))],
2390
def test_no_signature(self):
2391
transport_path = 'quick'
2392
repo, client = self.setup_fake_client_and_repository(transport_path)
2393
client.add_error_response('nosuchrevision', 'unknown')
2394
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2397
[('call_expecting_body', 'Repository.get_revision_signature_text',
2398
('quick/', 'unknown'))],
2402
class TestRepositoryGetGraph(TestRemoteRepository):
2404
def test_get_graph(self):
2405
# get_graph returns a graph with a custom parents provider.
2406
transport_path = 'quack'
2407
repo, client = self.setup_fake_client_and_repository(transport_path)
2408
graph = repo.get_graph()
2409
self.assertNotEqual(graph._parents_provider, repo)
2412
class TestRepositoryAddSignatureText(TestRemoteRepository):
2414
def test_add_signature_text(self):
2415
transport_path = 'quack'
2416
repo, client = self.setup_fake_client_and_repository(transport_path)
2417
client.add_expected_call(
2418
'Repository.lock_write', ('quack/', ''),
2419
'success', ('ok', 'a token'))
2420
client.add_expected_call(
2421
'Repository.start_write_group', ('quack/', 'a token'),
2422
'success', ('ok', ('token1', )))
2423
client.add_expected_call(
2424
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2426
'success', ('ok', ), None)
2428
repo.start_write_group()
2430
repo.add_signature_text("rev1", "every bloody emperor"))
2432
('call_with_body_bytes_expecting_body',
2433
'Repository.add_signature_text',
2434
('quack/', 'a token', 'rev1', 'token1'),
2435
'every bloody emperor'),
2439
class TestRepositoryGetParentMap(TestRemoteRepository):
2441
def test_get_parent_map_caching(self):
2442
# get_parent_map returns from cache until unlock()
2443
# setup a reponse with two revisions
2444
r1 = u'\u0e33'.encode('utf8')
2445
r2 = u'\u0dab'.encode('utf8')
2446
lines = [' '.join([r2, r1]), r1]
2447
encoded_body = bz2.compress('\n'.join(lines))
2449
transport_path = 'quack'
2450
repo, client = self.setup_fake_client_and_repository(transport_path)
2451
client.add_success_response_with_body(encoded_body, 'ok')
2452
client.add_success_response_with_body(encoded_body, 'ok')
2454
graph = repo.get_graph()
2455
parents = graph.get_parent_map([r2])
2456
self.assertEqual({r2: (r1,)}, parents)
2457
# locking and unlocking deeper should not reset
2460
parents = graph.get_parent_map([r1])
2461
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2463
[('call_with_body_bytes_expecting_body',
2464
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2468
# now we call again, and it should use the second response.
2470
graph = repo.get_graph()
2471
parents = graph.get_parent_map([r1])
2472
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2474
[('call_with_body_bytes_expecting_body',
2475
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2477
('call_with_body_bytes_expecting_body',
2478
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2484
def test_get_parent_map_reconnects_if_unknown_method(self):
2485
transport_path = 'quack'
2486
rev_id = 'revision-id'
2487
repo, client = self.setup_fake_client_and_repository(transport_path)
2488
client.add_unknown_method_response('Repository.get_parent_map')
2489
client.add_success_response_with_body(rev_id, 'ok')
2490
self.assertFalse(client._medium._is_remote_before((1, 2)))
2491
parents = repo.get_parent_map([rev_id])
2493
[('call_with_body_bytes_expecting_body',
2494
'Repository.get_parent_map',
2495
('quack/', 'include-missing:', rev_id), '\n\n0'),
2496
('disconnect medium',),
2497
('call_expecting_body', 'Repository.get_revision_graph',
2500
# The medium is now marked as being connected to an older server
2501
self.assertTrue(client._medium._is_remote_before((1, 2)))
2502
self.assertEqual({rev_id: ('null:',)}, parents)
2504
def test_get_parent_map_fallback_parentless_node(self):
2505
"""get_parent_map falls back to get_revision_graph on old servers. The
2506
results from get_revision_graph are tweaked to match the get_parent_map
2509
Specifically, a {key: ()} result from get_revision_graph means "no
2510
parents" for that key, which in get_parent_map results should be
2511
represented as {key: ('null:',)}.
2513
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2515
rev_id = 'revision-id'
2516
transport_path = 'quack'
2517
repo, client = self.setup_fake_client_and_repository(transport_path)
2518
client.add_success_response_with_body(rev_id, 'ok')
2519
client._medium._remember_remote_is_before((1, 2))
2520
parents = repo.get_parent_map([rev_id])
2522
[('call_expecting_body', 'Repository.get_revision_graph',
2525
self.assertEqual({rev_id: ('null:',)}, parents)
2527
def test_get_parent_map_unexpected_response(self):
2528
repo, client = self.setup_fake_client_and_repository('path')
2529
client.add_success_response('something unexpected!')
2531
errors.UnexpectedSmartServerResponse,
2532
repo.get_parent_map, ['a-revision-id'])
2534
def test_get_parent_map_negative_caches_missing_keys(self):
2535
self.setup_smart_server_with_call_log()
2536
repo = self.make_repository('foo')
2537
self.assertIsInstance(repo, RemoteRepository)
2539
self.addCleanup(repo.unlock)
2540
self.reset_smart_call_log()
2541
graph = repo.get_graph()
2542
self.assertEqual({},
2543
graph.get_parent_map(['some-missing', 'other-missing']))
2544
self.assertLength(1, self.hpss_calls)
2545
# No call if we repeat this
2546
self.reset_smart_call_log()
2547
graph = repo.get_graph()
2548
self.assertEqual({},
2549
graph.get_parent_map(['some-missing', 'other-missing']))
2550
self.assertLength(0, self.hpss_calls)
2551
# Asking for more unknown keys makes a request.
2552
self.reset_smart_call_log()
2553
graph = repo.get_graph()
2554
self.assertEqual({},
2555
graph.get_parent_map(['some-missing', 'other-missing',
2557
self.assertLength(1, self.hpss_calls)
2559
def disableExtraResults(self):
2560
self.overrideAttr(SmartServerRepositoryGetParentMap,
2561
'no_extra_results', True)
2563
def test_null_cached_missing_and_stop_key(self):
2564
self.setup_smart_server_with_call_log()
2565
# Make a branch with a single revision.
2566
builder = self.make_branch_builder('foo')
2567
builder.start_series()
2568
builder.build_snapshot('first', None, [
2569
('add', ('', 'root-id', 'directory', ''))])
2570
builder.finish_series()
2571
branch = builder.get_branch()
2572
repo = branch.repository
2573
self.assertIsInstance(repo, RemoteRepository)
2574
# Stop the server from sending extra results.
2575
self.disableExtraResults()
2577
self.addCleanup(repo.unlock)
2578
self.reset_smart_call_log()
2579
graph = repo.get_graph()
2580
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2581
# 'first' it will be a candidate for the stop_keys of subsequent
2582
# requests, and because 'null:' was queried but not returned it will be
2583
# cached as missing.
2584
self.assertEqual({'first': ('null:',)},
2585
graph.get_parent_map(['first', 'null:']))
2586
# Now query for another key. This request will pass along a recipe of
2587
# start and stop keys describing the already cached results, and this
2588
# recipe's revision count must be correct (or else it will trigger an
2589
# error from the server).
2590
self.assertEqual({}, graph.get_parent_map(['another-key']))
2591
# This assertion guards against disableExtraResults silently failing to
2592
# work, thus invalidating the test.
2593
self.assertLength(2, self.hpss_calls)
2595
def test_get_parent_map_gets_ghosts_from_result(self):
2596
# asking for a revision should negatively cache close ghosts in its
2598
self.setup_smart_server_with_call_log()
2599
tree = self.make_branch_and_memory_tree('foo')
2602
builder = treebuilder.TreeBuilder()
2603
builder.start_tree(tree)
2605
builder.finish_tree()
2606
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2607
rev_id = tree.commit('')
2611
self.addCleanup(tree.unlock)
2612
repo = tree.branch.repository
2613
self.assertIsInstance(repo, RemoteRepository)
2615
repo.get_parent_map([rev_id])
2616
self.reset_smart_call_log()
2617
# Now asking for rev_id's ghost parent should not make calls
2618
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2619
self.assertLength(0, self.hpss_calls)
2621
def test_exposes_get_cached_parent_map(self):
2622
"""RemoteRepository exposes get_cached_parent_map from
2625
r1 = u'\u0e33'.encode('utf8')
2626
r2 = u'\u0dab'.encode('utf8')
2627
lines = [' '.join([r2, r1]), r1]
2628
encoded_body = bz2.compress('\n'.join(lines))
2630
transport_path = 'quack'
2631
repo, client = self.setup_fake_client_and_repository(transport_path)
2632
client.add_success_response_with_body(encoded_body, 'ok')
2634
# get_cached_parent_map should *not* trigger an RPC
2635
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2636
self.assertEqual([], client._calls)
2637
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2638
self.assertEqual({r1: (NULL_REVISION,)},
2639
repo.get_cached_parent_map([r1]))
2641
[('call_with_body_bytes_expecting_body',
2642
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2648
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2650
def test_allows_new_revisions(self):
2651
"""get_parent_map's results can be updated by commit."""
2652
smart_server = test_server.SmartTCPServer_for_testing()
2653
self.start_server(smart_server)
2654
self.make_branch('branch')
2655
branch = Branch.open(smart_server.get_url() + '/branch')
2656
tree = branch.create_checkout('tree', lightweight=True)
2658
self.addCleanup(tree.unlock)
2659
graph = tree.branch.repository.get_graph()
2660
# This provides an opportunity for the missing rev-id to be cached.
2661
self.assertEqual({}, graph.get_parent_map(['rev1']))
2662
tree.commit('message', rev_id='rev1')
2663
graph = tree.branch.repository.get_graph()
2664
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2667
class TestRepositoryGetRevisions(TestRemoteRepository):
2669
def test_hpss_missing_revision(self):
2670
transport_path = 'quack'
2671
repo, client = self.setup_fake_client_and_repository(transport_path)
2672
client.add_success_response_with_body(
2674
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2675
['somerev1', 'anotherrev2'])
2677
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2678
('quack/', ), "somerev1\nanotherrev2")],
2681
def test_hpss_get_single_revision(self):
2682
transport_path = 'quack'
2683
repo, client = self.setup_fake_client_and_repository(transport_path)
2684
somerev1 = Revision("somerev1")
2685
somerev1.committer = "Joe Committer <joe@example.com>"
2686
somerev1.timestamp = 1321828927
2687
somerev1.timezone = -60
2688
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2689
somerev1.message = "Message"
2690
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2692
# Split up body into two bits to make sure the zlib compression object
2693
# gets data fed twice.
2694
client.add_success_response_with_body(
2695
[body[:10], body[10:]], 'ok', '10')
2696
revs = repo.get_revisions(['somerev1'])
2697
self.assertEquals(revs, [somerev1])
2699
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2700
('quack/', ), "somerev1")],
2704
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2706
def test_null_revision(self):
2707
# a null revision has the predictable result {}, we should have no wire
2708
# traffic when calling it with this argument
2709
transport_path = 'empty'
2710
repo, client = self.setup_fake_client_and_repository(transport_path)
2711
client.add_success_response('notused')
2712
# actual RemoteRepository.get_revision_graph is gone, but there's an
2713
# equivalent private method for testing
2714
result = repo._get_revision_graph(NULL_REVISION)
2715
self.assertEqual([], client._calls)
2716
self.assertEqual({}, result)
2718
def test_none_revision(self):
2719
# with none we want the entire graph
2720
r1 = u'\u0e33'.encode('utf8')
2721
r2 = u'\u0dab'.encode('utf8')
2722
lines = [' '.join([r2, r1]), r1]
2723
encoded_body = '\n'.join(lines)
2725
transport_path = 'sinhala'
2726
repo, client = self.setup_fake_client_and_repository(transport_path)
2727
client.add_success_response_with_body(encoded_body, 'ok')
2728
# actual RemoteRepository.get_revision_graph is gone, but there's an
2729
# equivalent private method for testing
2730
result = repo._get_revision_graph(None)
2732
[('call_expecting_body', 'Repository.get_revision_graph',
2735
self.assertEqual({r1: (), r2: (r1, )}, result)
2737
def test_specific_revision(self):
2738
# with a specific revision we want the graph for that
2739
# with none we want the entire graph
2740
r11 = u'\u0e33'.encode('utf8')
2741
r12 = u'\xc9'.encode('utf8')
2742
r2 = u'\u0dab'.encode('utf8')
2743
lines = [' '.join([r2, r11, r12]), r11, r12]
2744
encoded_body = '\n'.join(lines)
2746
transport_path = 'sinhala'
2747
repo, client = self.setup_fake_client_and_repository(transport_path)
2748
client.add_success_response_with_body(encoded_body, 'ok')
2749
result = repo._get_revision_graph(r2)
2751
[('call_expecting_body', 'Repository.get_revision_graph',
2754
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2756
def test_no_such_revision(self):
2758
transport_path = 'sinhala'
2759
repo, client = self.setup_fake_client_and_repository(transport_path)
2760
client.add_error_response('nosuchrevision', revid)
2761
# also check that the right revision is reported in the error
2762
self.assertRaises(errors.NoSuchRevision,
2763
repo._get_revision_graph, revid)
2765
[('call_expecting_body', 'Repository.get_revision_graph',
2766
('sinhala/', revid))],
2769
def test_unexpected_error(self):
2771
transport_path = 'sinhala'
2772
repo, client = self.setup_fake_client_and_repository(transport_path)
2773
client.add_error_response('AnUnexpectedError')
2774
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2775
repo._get_revision_graph, revid)
2776
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2779
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2782
repo, client = self.setup_fake_client_and_repository('quack')
2783
client.add_expected_call(
2784
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2785
'success', ('ok', 'rev-five'))
2786
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2787
self.assertEqual((True, 'rev-five'), result)
2788
self.assertFinished(client)
2790
def test_history_incomplete(self):
2791
repo, client = self.setup_fake_client_and_repository('quack')
2792
client.add_expected_call(
2793
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2794
'success', ('history-incomplete', 10, 'rev-ten'))
2795
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2796
self.assertEqual((False, (10, 'rev-ten')), result)
2797
self.assertFinished(client)
2799
def test_history_incomplete_with_fallback(self):
2800
"""A 'history-incomplete' response causes the fallback repository to be
2801
queried too, if one is set.
2803
# Make a repo with a fallback repo, both using a FakeClient.
2804
format = remote.response_tuple_to_repo_format(
2805
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2806
repo, client = self.setup_fake_client_and_repository('quack')
2807
repo._format = format
2808
fallback_repo, ignored = self.setup_fake_client_and_repository(
2810
fallback_repo._client = client
2811
fallback_repo._format = format
2812
repo.add_fallback_repository(fallback_repo)
2813
# First the client should ask the primary repo
2814
client.add_expected_call(
2815
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2816
'success', ('history-incomplete', 2, 'rev-two'))
2817
# Then it should ask the fallback, using revno/revid from the
2818
# history-incomplete response as the known revno/revid.
2819
client.add_expected_call(
2820
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2821
'success', ('ok', 'rev-one'))
2822
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2823
self.assertEqual((True, 'rev-one'), result)
2824
self.assertFinished(client)
2826
def test_nosuchrevision(self):
2827
# 'nosuchrevision' is returned when the known-revid is not found in the
2828
# remote repo. The client translates that response to NoSuchRevision.
2829
repo, client = self.setup_fake_client_and_repository('quack')
2830
client.add_expected_call(
2831
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2832
'error', ('nosuchrevision', 'rev-foo'))
2834
errors.NoSuchRevision,
2835
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2836
self.assertFinished(client)
2838
def test_branch_fallback_locking(self):
2839
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2840
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2841
will be invoked, which will fail if the repo is unlocked.
2843
self.setup_smart_server_with_call_log()
2844
tree = self.make_branch_and_memory_tree('.')
2847
rev1 = tree.commit('First')
2848
rev2 = tree.commit('Second')
2850
branch = tree.branch
2851
self.assertFalse(branch.is_locked())
2852
self.reset_smart_call_log()
2853
verb = 'Repository.get_rev_id_for_revno'
2854
self.disable_verb(verb)
2855
self.assertEqual(rev1, branch.get_rev_id(1))
2856
self.assertLength(1, [call for call in self.hpss_calls if
2857
call.call.method == verb])
2860
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2862
def test_has_signature_for_revision_id(self):
2863
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2864
transport_path = 'quack'
2865
repo, client = self.setup_fake_client_and_repository(transport_path)
2866
client.add_success_response('yes')
2867
result = repo.has_signature_for_revision_id('A')
2869
[('call', 'Repository.has_signature_for_revision_id',
2872
self.assertEqual(True, result)
2874
def test_is_not_shared(self):
2875
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2876
transport_path = 'qwack'
2877
repo, client = self.setup_fake_client_and_repository(transport_path)
2878
client.add_success_response('no')
2879
result = repo.has_signature_for_revision_id('A')
2881
[('call', 'Repository.has_signature_for_revision_id',
2884
self.assertEqual(False, result)
2887
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2889
def test_get_physical_lock_status_yes(self):
2890
transport_path = 'qwack'
2891
repo, client = self.setup_fake_client_and_repository(transport_path)
2892
client.add_success_response('yes')
2893
result = repo.get_physical_lock_status()
2895
[('call', 'Repository.get_physical_lock_status',
2898
self.assertEqual(True, result)
2900
def test_get_physical_lock_status_no(self):
2901
transport_path = 'qwack'
2902
repo, client = self.setup_fake_client_and_repository(transport_path)
2903
client.add_success_response('no')
2904
result = repo.get_physical_lock_status()
2906
[('call', 'Repository.get_physical_lock_status',
2909
self.assertEqual(False, result)
2912
class TestRepositoryIsShared(TestRemoteRepository):
2914
def test_is_shared(self):
2915
# ('yes', ) for Repository.is_shared -> 'True'.
2916
transport_path = 'quack'
2917
repo, client = self.setup_fake_client_and_repository(transport_path)
2918
client.add_success_response('yes')
2919
result = repo.is_shared()
2921
[('call', 'Repository.is_shared', ('quack/',))],
2923
self.assertEqual(True, result)
2925
def test_is_not_shared(self):
2926
# ('no', ) for Repository.is_shared -> 'False'.
2927
transport_path = 'qwack'
2928
repo, client = self.setup_fake_client_and_repository(transport_path)
2929
client.add_success_response('no')
2930
result = repo.is_shared()
2932
[('call', 'Repository.is_shared', ('qwack/',))],
2934
self.assertEqual(False, result)
2937
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
2939
def test_make_working_trees(self):
2940
# ('yes', ) for Repository.make_working_trees -> 'True'.
2941
transport_path = 'quack'
2942
repo, client = self.setup_fake_client_and_repository(transport_path)
2943
client.add_success_response('yes')
2944
result = repo.make_working_trees()
2946
[('call', 'Repository.make_working_trees', ('quack/',))],
2948
self.assertEqual(True, result)
2950
def test_no_working_trees(self):
2951
# ('no', ) for Repository.make_working_trees -> 'False'.
2952
transport_path = 'qwack'
2953
repo, client = self.setup_fake_client_and_repository(transport_path)
2954
client.add_success_response('no')
2955
result = repo.make_working_trees()
2957
[('call', 'Repository.make_working_trees', ('qwack/',))],
2959
self.assertEqual(False, result)
2962
class TestRepositoryLockWrite(TestRemoteRepository):
2964
def test_lock_write(self):
2965
transport_path = 'quack'
2966
repo, client = self.setup_fake_client_and_repository(transport_path)
2967
client.add_success_response('ok', 'a token')
2968
token = repo.lock_write().repository_token
2970
[('call', 'Repository.lock_write', ('quack/', ''))],
2972
self.assertEqual('a token', token)
2974
def test_lock_write_already_locked(self):
2975
transport_path = 'quack'
2976
repo, client = self.setup_fake_client_and_repository(transport_path)
2977
client.add_error_response('LockContention')
2978
self.assertRaises(errors.LockContention, repo.lock_write)
2980
[('call', 'Repository.lock_write', ('quack/', ''))],
2983
def test_lock_write_unlockable(self):
2984
transport_path = 'quack'
2985
repo, client = self.setup_fake_client_and_repository(transport_path)
2986
client.add_error_response('UnlockableTransport')
2987
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2989
[('call', 'Repository.lock_write', ('quack/', ''))],
2993
class TestRepositoryWriteGroups(TestRemoteRepository):
2995
def test_start_write_group(self):
2996
transport_path = 'quack'
2997
repo, client = self.setup_fake_client_and_repository(transport_path)
2998
client.add_expected_call(
2999
'Repository.lock_write', ('quack/', ''),
3000
'success', ('ok', 'a token'))
3001
client.add_expected_call(
3002
'Repository.start_write_group', ('quack/', 'a token'),
3003
'success', ('ok', ('token1', )))
3005
repo.start_write_group()
3007
def test_start_write_group_unsuspendable(self):
3008
# Some repositories do not support suspending write
3009
# groups. For those, fall back to the "real" repository.
3010
transport_path = 'quack'
3011
repo, client = self.setup_fake_client_and_repository(transport_path)
3012
def stub_ensure_real():
3013
client._calls.append(('_ensure_real',))
3014
repo._real_repository = _StubRealPackRepository(client._calls)
3015
repo._ensure_real = stub_ensure_real
3016
client.add_expected_call(
3017
'Repository.lock_write', ('quack/', ''),
3018
'success', ('ok', 'a token'))
3019
client.add_expected_call(
3020
'Repository.start_write_group', ('quack/', 'a token'),
3021
'error', ('UnsuspendableWriteGroup',))
3023
repo.start_write_group()
3024
self.assertEquals(client._calls[-2:], [
3026
('start_write_group',)])
3028
def test_commit_write_group(self):
3029
transport_path = 'quack'
3030
repo, client = self.setup_fake_client_and_repository(transport_path)
3031
client.add_expected_call(
3032
'Repository.lock_write', ('quack/', ''),
3033
'success', ('ok', 'a token'))
3034
client.add_expected_call(
3035
'Repository.start_write_group', ('quack/', 'a token'),
3036
'success', ('ok', ['token1']))
3037
client.add_expected_call(
3038
'Repository.commit_write_group', ('quack/', 'a token', ['token1']),
3041
repo.start_write_group()
3042
repo.commit_write_group()
3044
def test_abort_write_group(self):
3045
transport_path = 'quack'
3046
repo, client = self.setup_fake_client_and_repository(transport_path)
3047
client.add_expected_call(
3048
'Repository.lock_write', ('quack/', ''),
3049
'success', ('ok', 'a token'))
3050
client.add_expected_call(
3051
'Repository.start_write_group', ('quack/', 'a token'),
3052
'success', ('ok', ['token1']))
3053
client.add_expected_call(
3054
'Repository.abort_write_group', ('quack/', 'a token', ['token1']),
3057
repo.start_write_group()
3058
repo.abort_write_group(False)
3060
def test_suspend_write_group(self):
3061
transport_path = 'quack'
3062
repo, client = self.setup_fake_client_and_repository(transport_path)
3063
self.assertEquals([], repo.suspend_write_group())
3065
def test_resume_write_group(self):
3066
transport_path = 'quack'
3067
repo, client = self.setup_fake_client_and_repository(transport_path)
3068
client.add_expected_call(
3069
'Repository.lock_write', ('quack/', ''),
3070
'success', ('ok', 'a token'))
3071
client.add_expected_call(
3072
'Repository.check_write_group', ('quack/', 'a token', ['token1']),
3075
repo.resume_write_group(['token1'])
3078
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3080
def test_backwards_compat(self):
3081
self.setup_smart_server_with_call_log()
3082
repo = self.make_repository('.')
3083
self.reset_smart_call_log()
3084
verb = 'Repository.set_make_working_trees'
3085
self.disable_verb(verb)
3086
repo.set_make_working_trees(True)
3087
call_count = len([call for call in self.hpss_calls if
3088
call.call.method == verb])
3089
self.assertEqual(1, call_count)
3091
def test_current(self):
3092
transport_path = 'quack'
3093
repo, client = self.setup_fake_client_and_repository(transport_path)
3094
client.add_expected_call(
3095
'Repository.set_make_working_trees', ('quack/', 'True'),
3097
client.add_expected_call(
3098
'Repository.set_make_working_trees', ('quack/', 'False'),
3100
repo.set_make_working_trees(True)
3101
repo.set_make_working_trees(False)
3104
class TestRepositoryUnlock(TestRemoteRepository):
3106
def test_unlock(self):
3107
transport_path = 'quack'
3108
repo, client = self.setup_fake_client_and_repository(transport_path)
3109
client.add_success_response('ok', 'a token')
3110
client.add_success_response('ok')
3114
[('call', 'Repository.lock_write', ('quack/', '')),
3115
('call', 'Repository.unlock', ('quack/', 'a token'))],
3118
def test_unlock_wrong_token(self):
3119
# If somehow the token is wrong, unlock will raise TokenMismatch.
3120
transport_path = 'quack'
3121
repo, client = self.setup_fake_client_and_repository(transport_path)
3122
client.add_success_response('ok', 'a token')
3123
client.add_error_response('TokenMismatch')
3125
self.assertRaises(errors.TokenMismatch, repo.unlock)
3128
class TestRepositoryHasRevision(TestRemoteRepository):
3130
def test_none(self):
3131
# repo.has_revision(None) should not cause any traffic.
3132
transport_path = 'quack'
3133
repo, client = self.setup_fake_client_and_repository(transport_path)
3135
# The null revision is always there, so has_revision(None) == True.
3136
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3138
# The remote repo shouldn't be accessed.
3139
self.assertEqual([], client._calls)
3142
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3143
"""Test Repository.iter_file_bytes."""
3145
def test_single(self):
3146
transport_path = 'quack'
3147
repo, client = self.setup_fake_client_and_repository(transport_path)
3148
client.add_expected_call(
3149
'Repository.iter_files_bytes', ('quack/', ),
3150
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3151
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3152
"somerev", "myid")]):
3153
self.assertEquals("myid", identifier)
3154
self.assertEquals("".join(byte_stream), "mydata" * 10)
3156
def test_missing(self):
3157
transport_path = 'quack'
3158
repo, client = self.setup_fake_client_and_repository(transport_path)
3159
client.add_expected_call(
3160
'Repository.iter_files_bytes',
3162
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3163
iter(["absent\0somefile\0somerev\n"]))
3164
self.assertRaises(errors.RevisionNotPresent, list,
3165
repo.iter_files_bytes(
3166
[("somefile", "somerev", "myid")]))
3169
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3170
"""Base class for Repository.insert_stream and .insert_stream_1.19
3174
def checkInsertEmptyStream(self, repo, client):
3175
"""Insert an empty stream, checking the result.
3177
This checks that there are no resume_tokens or missing_keys, and that
3178
the client is finished.
3180
sink = repo._get_sink()
3181
fmt = repository.format_registry.get_default()
3182
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3183
self.assertEqual([], resume_tokens)
3184
self.assertEqual(set(), missing_keys)
3185
self.assertFinished(client)
3188
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3189
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3192
This test case is very similar to TestRepositoryInsertStream_1_19.
3196
TestRemoteRepository.setUp(self)
3197
self.disable_verb('Repository.insert_stream_1.19')
3199
def test_unlocked_repo(self):
3200
transport_path = 'quack'
3201
repo, client = self.setup_fake_client_and_repository(transport_path)
3202
client.add_expected_call(
3203
'Repository.insert_stream_1.19', ('quack/', ''),
3204
'unknown', ('Repository.insert_stream_1.19',))
3205
client.add_expected_call(
3206
'Repository.insert_stream', ('quack/', ''),
3208
client.add_expected_call(
3209
'Repository.insert_stream', ('quack/', ''),
3211
self.checkInsertEmptyStream(repo, client)
3213
def test_locked_repo_with_no_lock_token(self):
3214
transport_path = 'quack'
3215
repo, client = self.setup_fake_client_and_repository(transport_path)
3216
client.add_expected_call(
3217
'Repository.lock_write', ('quack/', ''),
3218
'success', ('ok', ''))
3219
client.add_expected_call(
3220
'Repository.insert_stream_1.19', ('quack/', ''),
3221
'unknown', ('Repository.insert_stream_1.19',))
3222
client.add_expected_call(
3223
'Repository.insert_stream', ('quack/', ''),
3225
client.add_expected_call(
3226
'Repository.insert_stream', ('quack/', ''),
3229
self.checkInsertEmptyStream(repo, client)
3231
def test_locked_repo_with_lock_token(self):
3232
transport_path = 'quack'
3233
repo, client = self.setup_fake_client_and_repository(transport_path)
3234
client.add_expected_call(
3235
'Repository.lock_write', ('quack/', ''),
3236
'success', ('ok', 'a token'))
3237
client.add_expected_call(
3238
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3239
'unknown', ('Repository.insert_stream_1.19',))
3240
client.add_expected_call(
3241
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3243
client.add_expected_call(
3244
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3247
self.checkInsertEmptyStream(repo, client)
3249
def test_stream_with_inventory_deltas(self):
3250
"""'inventory-deltas' substreams cannot be sent to the
3251
Repository.insert_stream verb, because not all servers that implement
3252
that verb will accept them. So when one is encountered the RemoteSink
3253
immediately stops using that verb and falls back to VFS insert_stream.
3255
transport_path = 'quack'
3256
repo, client = self.setup_fake_client_and_repository(transport_path)
3257
client.add_expected_call(
3258
'Repository.insert_stream_1.19', ('quack/', ''),
3259
'unknown', ('Repository.insert_stream_1.19',))
3260
client.add_expected_call(
3261
'Repository.insert_stream', ('quack/', ''),
3263
client.add_expected_call(
3264
'Repository.insert_stream', ('quack/', ''),
3266
# Create a fake real repository for insert_stream to fall back on, so
3267
# that we can directly see the records the RemoteSink passes to the
3272
def insert_stream(self, stream, src_format, resume_tokens):
3273
for substream_kind, substream in stream:
3274
self.records.append(
3275
(substream_kind, [record.key for record in substream]))
3276
return ['fake tokens'], ['fake missing keys']
3277
fake_real_sink = FakeRealSink()
3278
class FakeRealRepository:
3279
def _get_sink(self):
3280
return fake_real_sink
3281
def is_in_write_group(self):
3283
def refresh_data(self):
3285
repo._real_repository = FakeRealRepository()
3286
sink = repo._get_sink()
3287
fmt = repository.format_registry.get_default()
3288
stream = self.make_stream_with_inv_deltas(fmt)
3289
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3290
# Every record from the first inventory delta should have been sent to
3292
expected_records = [
3293
('inventory-deltas', [('rev2',), ('rev3',)]),
3294
('texts', [('some-rev', 'some-file')])]
3295
self.assertEqual(expected_records, fake_real_sink.records)
3296
# The return values from the real sink's insert_stream are propagated
3297
# back to the original caller.
3298
self.assertEqual(['fake tokens'], resume_tokens)
3299
self.assertEqual(['fake missing keys'], missing_keys)
3300
self.assertFinished(client)
3302
def make_stream_with_inv_deltas(self, fmt):
3303
"""Make a simple stream with an inventory delta followed by more
3304
records and more substreams to test that all records and substreams
3305
from that point on are used.
3307
This sends, in order:
3308
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3310
* texts substream: (some-rev, some-file)
3312
# Define a stream using generators so that it isn't rewindable.
3313
inv = inventory.Inventory(revision_id='rev1')
3314
inv.root.revision = 'rev1'
3315
def stream_with_inv_delta():
3316
yield ('inventories', inventories_substream())
3317
yield ('inventory-deltas', inventory_delta_substream())
3319
versionedfile.FulltextContentFactory(
3320
('some-rev', 'some-file'), (), None, 'content')])
3321
def inventories_substream():
3322
# An empty inventory fulltext. This will be streamed normally.
3323
text = fmt._serializer.write_inventory_to_string(inv)
3324
yield versionedfile.FulltextContentFactory(
3325
('rev1',), (), None, text)
3326
def inventory_delta_substream():
3327
# An inventory delta. This can't be streamed via this verb, so it
3328
# will trigger a fallback to VFS insert_stream.
3329
entry = inv.make_entry(
3330
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3331
entry.revision = 'ghost'
3332
delta = [(None, 'newdir', 'newdir-id', entry)]
3333
serializer = inventory_delta.InventoryDeltaSerializer(
3334
versioned_root=True, tree_references=False)
3335
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3336
yield versionedfile.ChunkedContentFactory(
3337
('rev2',), (('rev1',)), None, lines)
3339
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3340
yield versionedfile.ChunkedContentFactory(
3341
('rev3',), (('rev1',)), None, lines)
3342
return stream_with_inv_delta()
3345
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3347
def test_unlocked_repo(self):
3348
transport_path = 'quack'
3349
repo, client = self.setup_fake_client_and_repository(transport_path)
3350
client.add_expected_call(
3351
'Repository.insert_stream_1.19', ('quack/', ''),
3353
client.add_expected_call(
3354
'Repository.insert_stream_1.19', ('quack/', ''),
3356
self.checkInsertEmptyStream(repo, client)
3358
def test_locked_repo_with_no_lock_token(self):
3359
transport_path = 'quack'
3360
repo, client = self.setup_fake_client_and_repository(transport_path)
3361
client.add_expected_call(
3362
'Repository.lock_write', ('quack/', ''),
3363
'success', ('ok', ''))
3364
client.add_expected_call(
3365
'Repository.insert_stream_1.19', ('quack/', ''),
3367
client.add_expected_call(
3368
'Repository.insert_stream_1.19', ('quack/', ''),
3371
self.checkInsertEmptyStream(repo, client)
3373
def test_locked_repo_with_lock_token(self):
3374
transport_path = 'quack'
3375
repo, client = self.setup_fake_client_and_repository(transport_path)
3376
client.add_expected_call(
3377
'Repository.lock_write', ('quack/', ''),
3378
'success', ('ok', 'a token'))
3379
client.add_expected_call(
3380
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3382
client.add_expected_call(
3383
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3386
self.checkInsertEmptyStream(repo, client)
3389
class TestRepositoryTarball(TestRemoteRepository):
3391
# This is a canned tarball reponse we can validate against
3393
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3394
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3395
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3396
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3397
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3398
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3399
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3400
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3401
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3402
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3403
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3404
'nWQ7QH/F3JFOFCQ0aSPfA='
3407
def test_repository_tarball(self):
3408
# Test that Repository.tarball generates the right operations
3409
transport_path = 'repo'
3410
expected_calls = [('call_expecting_body', 'Repository.tarball',
3411
('repo/', 'bz2',),),
3413
repo, client = self.setup_fake_client_and_repository(transport_path)
3414
client.add_success_response_with_body(self.tarball_content, 'ok')
3415
# Now actually ask for the tarball
3416
tarball_file = repo._get_tarball('bz2')
3418
self.assertEqual(expected_calls, client._calls)
3419
self.assertEqual(self.tarball_content, tarball_file.read())
3421
tarball_file.close()
3424
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3425
"""RemoteRepository.copy_content_into optimizations"""
3427
def test_copy_content_remote_to_local(self):
3428
self.transport_server = test_server.SmartTCPServer_for_testing
3429
src_repo = self.make_repository('repo1')
3430
src_repo = repository.Repository.open(self.get_url('repo1'))
3431
# At the moment the tarball-based copy_content_into can't write back
3432
# into a smart server. It would be good if it could upload the
3433
# tarball; once that works we'd have to create repositories of
3434
# different formats. -- mbp 20070410
3435
dest_url = self.get_vfs_only_url('repo2')
3436
dest_bzrdir = BzrDir.create(dest_url)
3437
dest_repo = dest_bzrdir.create_repository()
3438
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3439
self.assertTrue(isinstance(src_repo, RemoteRepository))
3440
src_repo.copy_content_into(dest_repo)
3443
class _StubRealPackRepository(object):
3445
def __init__(self, calls):
3447
self._pack_collection = _StubPackCollection(calls)
3449
def start_write_group(self):
3450
self.calls.append(('start_write_group',))
3452
def is_in_write_group(self):
3455
def refresh_data(self):
3456
self.calls.append(('pack collection reload_pack_names',))
3459
class _StubPackCollection(object):
3461
def __init__(self, calls):
3465
self.calls.append(('pack collection autopack',))
3468
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3469
"""Tests for RemoteRepository.autopack implementation."""
3472
"""When the server returns 'ok' and there's no _real_repository, then
3473
nothing else happens: the autopack method is done.
3475
transport_path = 'quack'
3476
repo, client = self.setup_fake_client_and_repository(transport_path)
3477
client.add_expected_call(
3478
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3480
self.assertFinished(client)
3482
def test_ok_with_real_repo(self):
3483
"""When the server returns 'ok' and there is a _real_repository, then
3484
the _real_repository's reload_pack_name's method will be called.
3486
transport_path = 'quack'
3487
repo, client = self.setup_fake_client_and_repository(transport_path)
3488
client.add_expected_call(
3489
'PackRepository.autopack', ('quack/',),
3491
repo._real_repository = _StubRealPackRepository(client._calls)
3494
[('call', 'PackRepository.autopack', ('quack/',)),
3495
('pack collection reload_pack_names',)],
3498
def test_backwards_compatibility(self):
3499
"""If the server does not recognise the PackRepository.autopack verb,
3500
fallback to the real_repository's implementation.
3502
transport_path = 'quack'
3503
repo, client = self.setup_fake_client_and_repository(transport_path)
3504
client.add_unknown_method_response('PackRepository.autopack')
3505
def stub_ensure_real():
3506
client._calls.append(('_ensure_real',))
3507
repo._real_repository = _StubRealPackRepository(client._calls)
3508
repo._ensure_real = stub_ensure_real
3511
[('call', 'PackRepository.autopack', ('quack/',)),
3513
('pack collection autopack',)],
3516
def test_oom_error_reporting(self):
3517
"""An out-of-memory condition on the server is reported clearly"""
3518
transport_path = 'quack'
3519
repo, client = self.setup_fake_client_and_repository(transport_path)
3520
client.add_expected_call(
3521
'PackRepository.autopack', ('quack/',),
3522
'error', ('MemoryError',))
3523
err = self.assertRaises(errors.BzrError, repo.autopack)
3524
self.assertContainsRe(str(err), "^remote server out of mem")
3527
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3528
"""Base class for unit tests for bzrlib.remote._translate_error."""
3530
def translateTuple(self, error_tuple, **context):
3531
"""Call _translate_error with an ErrorFromSmartServer built from the
3534
:param error_tuple: A tuple of a smart server response, as would be
3535
passed to an ErrorFromSmartServer.
3536
:kwargs context: context items to call _translate_error with.
3538
:returns: The error raised by _translate_error.
3540
# Raise the ErrorFromSmartServer before passing it as an argument,
3541
# because _translate_error may need to re-raise it with a bare 'raise'
3543
server_error = errors.ErrorFromSmartServer(error_tuple)
3544
translated_error = self.translateErrorFromSmartServer(
3545
server_error, **context)
3546
return translated_error
3548
def translateErrorFromSmartServer(self, error_object, **context):
3549
"""Like translateTuple, but takes an already constructed
3550
ErrorFromSmartServer rather than a tuple.
3554
except errors.ErrorFromSmartServer, server_error:
3555
translated_error = self.assertRaises(
3556
errors.BzrError, remote._translate_error, server_error,
3558
return translated_error
3561
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3562
"""Unit tests for bzrlib.remote._translate_error.
3564
Given an ErrorFromSmartServer (which has an error tuple from a smart
3565
server) and some context, _translate_error raises more specific errors from
3568
This test case covers the cases where _translate_error succeeds in
3569
translating an ErrorFromSmartServer to something better. See
3570
TestErrorTranslationRobustness for other cases.
3573
def test_NoSuchRevision(self):
3574
branch = self.make_branch('')
3576
translated_error = self.translateTuple(
3577
('NoSuchRevision', revid), branch=branch)
3578
expected_error = errors.NoSuchRevision(branch, revid)
3579
self.assertEqual(expected_error, translated_error)
3581
def test_nosuchrevision(self):
3582
repository = self.make_repository('')
3584
translated_error = self.translateTuple(
3585
('nosuchrevision', revid), repository=repository)
3586
expected_error = errors.NoSuchRevision(repository, revid)
3587
self.assertEqual(expected_error, translated_error)
3589
def test_nobranch(self):
3590
bzrdir = self.make_bzrdir('')
3591
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3592
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3593
self.assertEqual(expected_error, translated_error)
3595
def test_nobranch_one_arg(self):
3596
bzrdir = self.make_bzrdir('')
3597
translated_error = self.translateTuple(
3598
('nobranch', 'extra detail'), bzrdir=bzrdir)
3599
expected_error = errors.NotBranchError(
3600
path=bzrdir.root_transport.base,
3601
detail='extra detail')
3602
self.assertEqual(expected_error, translated_error)
3604
def test_norepository(self):
3605
bzrdir = self.make_bzrdir('')
3606
translated_error = self.translateTuple(('norepository',),
3608
expected_error = errors.NoRepositoryPresent(bzrdir)
3609
self.assertEqual(expected_error, translated_error)
3611
def test_LockContention(self):
3612
translated_error = self.translateTuple(('LockContention',))
3613
expected_error = errors.LockContention('(remote lock)')
3614
self.assertEqual(expected_error, translated_error)
3616
def test_UnlockableTransport(self):
3617
bzrdir = self.make_bzrdir('')
3618
translated_error = self.translateTuple(
3619
('UnlockableTransport',), bzrdir=bzrdir)
3620
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3621
self.assertEqual(expected_error, translated_error)
3623
def test_LockFailed(self):
3624
lock = 'str() of a server lock'
3625
why = 'str() of why'
3626
translated_error = self.translateTuple(('LockFailed', lock, why))
3627
expected_error = errors.LockFailed(lock, why)
3628
self.assertEqual(expected_error, translated_error)
3630
def test_TokenMismatch(self):
3631
token = 'a lock token'
3632
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3633
expected_error = errors.TokenMismatch(token, '(remote token)')
3634
self.assertEqual(expected_error, translated_error)
3636
def test_Diverged(self):
3637
branch = self.make_branch('a')
3638
other_branch = self.make_branch('b')
3639
translated_error = self.translateTuple(
3640
('Diverged',), branch=branch, other_branch=other_branch)
3641
expected_error = errors.DivergedBranches(branch, other_branch)
3642
self.assertEqual(expected_error, translated_error)
3644
def test_NotStacked(self):
3645
branch = self.make_branch('')
3646
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3647
expected_error = errors.NotStacked(branch)
3648
self.assertEqual(expected_error, translated_error)
3650
def test_ReadError_no_args(self):
3652
translated_error = self.translateTuple(('ReadError',), path=path)
3653
expected_error = errors.ReadError(path)
3654
self.assertEqual(expected_error, translated_error)
3656
def test_ReadError(self):
3658
translated_error = self.translateTuple(('ReadError', path))
3659
expected_error = errors.ReadError(path)
3660
self.assertEqual(expected_error, translated_error)
3662
def test_IncompatibleRepositories(self):
3663
translated_error = self.translateTuple(('IncompatibleRepositories',
3664
"repo1", "repo2", "details here"))
3665
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3667
self.assertEqual(expected_error, translated_error)
3669
def test_PermissionDenied_no_args(self):
3671
translated_error = self.translateTuple(('PermissionDenied',),
3673
expected_error = errors.PermissionDenied(path)
3674
self.assertEqual(expected_error, translated_error)
3676
def test_PermissionDenied_one_arg(self):
3678
translated_error = self.translateTuple(('PermissionDenied', path))
3679
expected_error = errors.PermissionDenied(path)
3680
self.assertEqual(expected_error, translated_error)
3682
def test_PermissionDenied_one_arg_and_context(self):
3683
"""Given a choice between a path from the local context and a path on
3684
the wire, _translate_error prefers the path from the local context.
3686
local_path = 'local path'
3687
remote_path = 'remote path'
3688
translated_error = self.translateTuple(
3689
('PermissionDenied', remote_path), path=local_path)
3690
expected_error = errors.PermissionDenied(local_path)
3691
self.assertEqual(expected_error, translated_error)
3693
def test_PermissionDenied_two_args(self):
3695
extra = 'a string with extra info'
3696
translated_error = self.translateTuple(
3697
('PermissionDenied', path, extra))
3698
expected_error = errors.PermissionDenied(path, extra)
3699
self.assertEqual(expected_error, translated_error)
3701
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3703
def test_NoSuchFile_context_path(self):
3704
local_path = "local path"
3705
translated_error = self.translateTuple(('ReadError', "remote path"),
3707
expected_error = errors.ReadError(local_path)
3708
self.assertEqual(expected_error, translated_error)
3710
def test_NoSuchFile_without_context(self):
3711
remote_path = "remote path"
3712
translated_error = self.translateTuple(('ReadError', remote_path))
3713
expected_error = errors.ReadError(remote_path)
3714
self.assertEqual(expected_error, translated_error)
3716
def test_ReadOnlyError(self):
3717
translated_error = self.translateTuple(('ReadOnlyError',))
3718
expected_error = errors.TransportNotPossible("readonly transport")
3719
self.assertEqual(expected_error, translated_error)
3721
def test_MemoryError(self):
3722
translated_error = self.translateTuple(('MemoryError',))
3723
self.assertStartsWith(str(translated_error),
3724
"remote server out of memory")
3726
def test_generic_IndexError_no_classname(self):
3727
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3728
translated_error = self.translateErrorFromSmartServer(err)
3729
expected_error = errors.UnknownErrorFromSmartServer(err)
3730
self.assertEqual(expected_error, translated_error)
3732
# GZ 2011-03-02: TODO test generic non-ascii error string
3734
def test_generic_KeyError(self):
3735
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3736
translated_error = self.translateErrorFromSmartServer(err)
3737
expected_error = errors.UnknownErrorFromSmartServer(err)
3738
self.assertEqual(expected_error, translated_error)
3741
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3742
"""Unit tests for bzrlib.remote._translate_error's robustness.
3744
TestErrorTranslationSuccess is for cases where _translate_error can
3745
translate successfully. This class about how _translate_err behaves when
3746
it fails to translate: it re-raises the original error.
3749
def test_unrecognised_server_error(self):
3750
"""If the error code from the server is not recognised, the original
3751
ErrorFromSmartServer is propagated unmodified.
3753
error_tuple = ('An unknown error tuple',)
3754
server_error = errors.ErrorFromSmartServer(error_tuple)
3755
translated_error = self.translateErrorFromSmartServer(server_error)
3756
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3757
self.assertEqual(expected_error, translated_error)
3759
def test_context_missing_a_key(self):
3760
"""In case of a bug in the client, or perhaps an unexpected response
3761
from a server, _translate_error returns the original error tuple from
3762
the server and mutters a warning.
3764
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3765
# in the context dict. So let's give it an empty context dict instead
3766
# to exercise its error recovery.
3768
error_tuple = ('NoSuchRevision', 'revid')
3769
server_error = errors.ErrorFromSmartServer(error_tuple)
3770
translated_error = self.translateErrorFromSmartServer(server_error)
3771
self.assertEqual(server_error, translated_error)
3772
# In addition to re-raising ErrorFromSmartServer, some debug info has
3773
# been muttered to the log file for developer to look at.
3774
self.assertContainsRe(
3776
"Missing key 'branch' in context")
3778
def test_path_missing(self):
3779
"""Some translations (PermissionDenied, ReadError) can determine the
3780
'path' variable from either the wire or the local context. If neither
3781
has it, then an error is raised.
3783
error_tuple = ('ReadError',)
3784
server_error = errors.ErrorFromSmartServer(error_tuple)
3785
translated_error = self.translateErrorFromSmartServer(server_error)
3786
self.assertEqual(server_error, translated_error)
3787
# In addition to re-raising ErrorFromSmartServer, some debug info has
3788
# been muttered to the log file for developer to look at.
3789
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3792
class TestStacking(tests.TestCaseWithTransport):
3793
"""Tests for operations on stacked remote repositories.
3795
The underlying format type must support stacking.
3798
def test_access_stacked_remote(self):
3799
# based on <http://launchpad.net/bugs/261315>
3800
# make a branch stacked on another repository containing an empty
3801
# revision, then open it over hpss - we should be able to see that
3803
base_transport = self.get_transport()
3804
base_builder = self.make_branch_builder('base', format='1.9')
3805
base_builder.start_series()
3806
base_revid = base_builder.build_snapshot('rev-id', None,
3807
[('add', ('', None, 'directory', None))],
3809
base_builder.finish_series()
3810
stacked_branch = self.make_branch('stacked', format='1.9')
3811
stacked_branch.set_stacked_on_url('../base')
3812
# start a server looking at this
3813
smart_server = test_server.SmartTCPServer_for_testing()
3814
self.start_server(smart_server)
3815
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3816
# can get its branch and repository
3817
remote_branch = remote_bzrdir.open_branch()
3818
remote_repo = remote_branch.repository
3819
remote_repo.lock_read()
3821
# it should have an appropriate fallback repository, which should also
3822
# be a RemoteRepository
3823
self.assertLength(1, remote_repo._fallback_repositories)
3824
self.assertIsInstance(remote_repo._fallback_repositories[0],
3826
# and it has the revision committed to the underlying repository;
3827
# these have varying implementations so we try several of them
3828
self.assertTrue(remote_repo.has_revisions([base_revid]))
3829
self.assertTrue(remote_repo.has_revision(base_revid))
3830
self.assertEqual(remote_repo.get_revision(base_revid).message,
3833
remote_repo.unlock()
3835
def prepare_stacked_remote_branch(self):
3836
"""Get stacked_upon and stacked branches with content in each."""
3837
self.setup_smart_server_with_call_log()
3838
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3839
tree1.commit('rev1', rev_id='rev1')
3840
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3841
).open_workingtree()
3842
local_tree = tree2.branch.create_checkout('local')
3843
local_tree.commit('local changes make me feel good.')
3844
branch2 = Branch.open(self.get_url('tree2'))
3846
self.addCleanup(branch2.unlock)
3847
return tree1.branch, branch2
3849
def test_stacked_get_parent_map(self):
3850
# the public implementation of get_parent_map obeys stacking
3851
_, branch = self.prepare_stacked_remote_branch()
3852
repo = branch.repository
3853
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3855
def test_unstacked_get_parent_map(self):
3856
# _unstacked_provider.get_parent_map ignores stacking
3857
_, branch = self.prepare_stacked_remote_branch()
3858
provider = branch.repository._unstacked_provider
3859
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3861
def fetch_stream_to_rev_order(self, stream):
3863
for kind, substream in stream:
3864
if not kind == 'revisions':
3867
for content in substream:
3868
result.append(content.key[-1])
3871
def get_ordered_revs(self, format, order, branch_factory=None):
3872
"""Get a list of the revisions in a stream to format format.
3874
:param format: The format of the target.
3875
:param order: the order that target should have requested.
3876
:param branch_factory: A callable to create a trunk and stacked branch
3877
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3878
:result: The revision ids in the stream, in the order seen,
3879
the topological order of revisions in the source.
3881
unordered_format = bzrdir.format_registry.get(format)()
3882
target_repository_format = unordered_format.repository_format
3884
self.assertEqual(order, target_repository_format._fetch_order)
3885
if branch_factory is None:
3886
branch_factory = self.prepare_stacked_remote_branch
3887
_, stacked = branch_factory()
3888
source = stacked.repository._get_source(target_repository_format)
3889
tip = stacked.last_revision()
3890
stacked.repository._ensure_real()
3891
graph = stacked.repository.get_graph()
3892
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3893
if r != NULL_REVISION]
3895
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3896
self.reset_smart_call_log()
3897
stream = source.get_stream(search)
3898
# We trust that if a revision is in the stream the rest of the new
3899
# content for it is too, as per our main fetch tests; here we are
3900
# checking that the revisions are actually included at all, and their
3902
return self.fetch_stream_to_rev_order(stream), revs
3904
def test_stacked_get_stream_unordered(self):
3905
# Repository._get_source.get_stream() from a stacked repository with
3906
# unordered yields the full data from both stacked and stacked upon
3908
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3909
self.assertEqual(set(expected_revs), set(rev_ord))
3910
# Getting unordered results should have made a streaming data request
3911
# from the server, then one from the backing branch.
3912
self.assertLength(2, self.hpss_calls)
3914
def test_stacked_on_stacked_get_stream_unordered(self):
3915
# Repository._get_source.get_stream() from a stacked repository which
3916
# is itself stacked yields the full data from all three sources.
3917
def make_stacked_stacked():
3918
_, stacked = self.prepare_stacked_remote_branch()
3919
tree = stacked.bzrdir.sprout('tree3', stacked=True
3920
).open_workingtree()
3921
local_tree = tree.branch.create_checkout('local-tree3')
3922
local_tree.commit('more local changes are better')
3923
branch = Branch.open(self.get_url('tree3'))
3925
self.addCleanup(branch.unlock)
3927
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3928
branch_factory=make_stacked_stacked)
3929
self.assertEqual(set(expected_revs), set(rev_ord))
3930
# Getting unordered results should have made a streaming data request
3931
# from the server, and one from each backing repo
3932
self.assertLength(3, self.hpss_calls)
3934
def test_stacked_get_stream_topological(self):
3935
# Repository._get_source.get_stream() from a stacked repository with
3936
# topological sorting yields the full data from both stacked and
3937
# stacked upon sources in topological order.
3938
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3939
self.assertEqual(expected_revs, rev_ord)
3940
# Getting topological sort requires VFS calls still - one of which is
3941
# pushing up from the bound branch.
3942
self.assertLength(14, self.hpss_calls)
3944
def test_stacked_get_stream_groupcompress(self):
3945
# Repository._get_source.get_stream() from a stacked repository with
3946
# groupcompress sorting yields the full data from both stacked and
3947
# stacked upon sources in groupcompress order.
3948
raise tests.TestSkipped('No groupcompress ordered format available')
3949
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3950
self.assertEqual(expected_revs, reversed(rev_ord))
3951
# Getting unordered results should have made a streaming data request
3952
# from the backing branch, and one from the stacked on branch.
3953
self.assertLength(2, self.hpss_calls)
3955
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3956
# When pulling some fixed amount of content that is more than the
3957
# source has (because some is coming from a fallback branch, no error
3958
# should be received. This was reported as bug 360791.
3959
# Need three branches: a trunk, a stacked branch, and a preexisting
3960
# branch pulling content from stacked and trunk.
3961
self.setup_smart_server_with_call_log()
3962
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3963
r1 = trunk.commit('start')
3964
stacked_branch = trunk.branch.create_clone_on_transport(
3965
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3966
local = self.make_branch('local', format='1.9-rich-root')
3967
local.repository.fetch(stacked_branch.repository,
3968
stacked_branch.last_revision())
3971
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3974
super(TestRemoteBranchEffort, self).setUp()
3975
# Create a smart server that publishes whatever the backing VFS server
3977
self.smart_server = test_server.SmartTCPServer_for_testing()
3978
self.start_server(self.smart_server, self.get_server())
3979
# Log all HPSS calls into self.hpss_calls.
3980
_SmartClient.hooks.install_named_hook(
3981
'call', self.capture_hpss_call, None)
3982
self.hpss_calls = []
3984
def capture_hpss_call(self, params):
3985
self.hpss_calls.append(params.method)
3987
def test_copy_content_into_avoids_revision_history(self):
3988
local = self.make_branch('local')
3989
builder = self.make_branch_builder('remote')
3990
builder.build_commit(message="Commit.")
3991
remote_branch_url = self.smart_server.get_url() + 'remote'
3992
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3993
local.repository.fetch(remote_branch.repository)
3994
self.hpss_calls = []
3995
remote_branch.copy_content_into(local)
3996
self.assertFalse('Branch.revision_history' in self.hpss_calls)
3998
def test_fetch_everything_needs_just_one_call(self):
3999
local = self.make_branch('local')
4000
builder = self.make_branch_builder('remote')
4001
builder.build_commit(message="Commit.")
4002
remote_branch_url = self.smart_server.get_url() + 'remote'
4003
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4004
self.hpss_calls = []
4005
local.repository.fetch(
4006
remote_branch.repository,
4007
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4008
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4010
def override_verb(self, verb_name, verb):
4011
request_handlers = request.request_handlers
4012
orig_verb = request_handlers.get(verb_name)
4013
orig_info = request_handlers.get_info(verb_name)
4014
request_handlers.register(verb_name, verb, override_existing=True)
4015
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4016
override_existing=True, info=orig_info)
4018
def test_fetch_everything_backwards_compat(self):
4019
"""Can fetch with EverythingResult even with pre 2.4 servers.
4021
Pre-2.4 do not support 'everything' searches with the
4022
Repository.get_stream_1.19 verb.
4025
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4026
"""A version of the Repository.get_stream_1.19 verb patched to
4027
reject 'everything' searches the way 2.3 and earlier do.
4029
def recreate_search(self, repository, search_bytes,
4030
discard_excess=False):
4031
verb_log.append(search_bytes.split('\n', 1)[0])
4032
if search_bytes == 'everything':
4034
request.FailedSmartServerResponse(('BadSearch',)))
4035
return super(OldGetStreamVerb,
4036
self).recreate_search(repository, search_bytes,
4037
discard_excess=discard_excess)
4038
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4039
local = self.make_branch('local')
4040
builder = self.make_branch_builder('remote')
4041
builder.build_commit(message="Commit.")
4042
remote_branch_url = self.smart_server.get_url() + 'remote'
4043
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4044
self.hpss_calls = []
4045
local.repository.fetch(
4046
remote_branch.repository,
4047
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4048
# make sure the overridden verb was used
4049
self.assertLength(1, verb_log)
4050
# more than one HPSS call is needed, but because it's a VFS callback
4051
# its hard to predict exactly how many.
4052
self.assertTrue(len(self.hpss_calls) > 1)
4055
class TestUpdateBoundBranchWithModifiedBoundLocation(
4056
tests.TestCaseWithTransport):
4057
"""Ensure correct handling of bound_location modifications.
4059
This is tested against a smart server as http://pad.lv/786980 was about a
4060
ReadOnlyError (write attempt during a read-only transaction) which can only
4061
happen in this context.
4065
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4066
self.transport_server = test_server.SmartTCPServer_for_testing
4068
def make_master_and_checkout(self, master_name, checkout_name):
4069
# Create the master branch and its associated checkout
4070
self.master = self.make_branch_and_tree(master_name)
4071
self.checkout = self.master.branch.create_checkout(checkout_name)
4072
# Modify the master branch so there is something to update
4073
self.master.commit('add stuff')
4074
self.last_revid = self.master.commit('even more stuff')
4075
self.bound_location = self.checkout.branch.get_bound_location()
4077
def assertUpdateSucceeds(self, new_location):
4078
self.checkout.branch.set_bound_location(new_location)
4079
self.checkout.update()
4080
self.assertEquals(self.last_revid, self.checkout.last_revision())
4082
def test_without_final_slash(self):
4083
self.make_master_and_checkout('master', 'checkout')
4084
# For unclear reasons some users have a bound_location without a final
4085
# '/', simulate that by forcing such a value
4086
self.assertEndsWith(self.bound_location, '/')
4087
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4089
def test_plus_sign(self):
4090
self.make_master_and_checkout('+master', 'checkout')
4091
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4093
def test_tilda(self):
4094
# Embed ~ in the middle of the path just to avoid any $HOME
4096
self.make_master_and_checkout('mas~ter', 'checkout')
4097
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4100
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4102
def test_no_context(self):
4103
class OutOfCoffee(errors.BzrError):
4104
"""A dummy exception for testing."""
4106
def __init__(self, urgency):
4107
self.urgency = urgency
4108
remote.no_context_error_translators.register("OutOfCoffee",
4109
lambda err: OutOfCoffee(err.error_args[0]))
4110
transport = MemoryTransport()
4111
client = FakeClient(transport.base)
4112
client.add_expected_call(
4113
'Branch.get_stacked_on_url', ('quack/',),
4114
'error', ('NotStacked',))
4115
client.add_expected_call(
4116
'Branch.last_revision_info',
4118
'error', ('OutOfCoffee', 'low'))
4119
transport.mkdir('quack')
4120
transport = transport.clone('quack')
4121
branch = self.make_remote_branch(transport, client)
4122
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4123
self.assertFinished(client)
4125
def test_with_context(self):
4126
class OutOfTea(errors.BzrError):
4127
def __init__(self, branch, urgency):
4128
self.branch = branch
4129
self.urgency = urgency
4130
remote.error_translators.register("OutOfTea",
4131
lambda err, find, path: OutOfTea(err.error_args[0],
4133
transport = MemoryTransport()
4134
client = FakeClient(transport.base)
4135
client.add_expected_call(
4136
'Branch.get_stacked_on_url', ('quack/',),
4137
'error', ('NotStacked',))
4138
client.add_expected_call(
4139
'Branch.last_revision_info',
4141
'error', ('OutOfTea', 'low'))
4142
transport.mkdir('quack')
4143
transport = transport.clone('quack')
4144
branch = self.make_remote_branch(transport, client)
4145
self.assertRaises(OutOfTea, branch.last_revision_info)
4146
self.assertFinished(client)
4149
class TestRepositoryPack(TestRemoteRepository):
4151
def test_pack(self):
4152
transport_path = 'quack'
4153
repo, client = self.setup_fake_client_and_repository(transport_path)
4154
client.add_expected_call(
4155
'Repository.lock_write', ('quack/', ''),
4156
'success', ('ok', 'token'))
4157
client.add_expected_call(
4158
'Repository.pack', ('quack/', 'token', 'False'),
4159
'success', ('ok',), )
4160
client.add_expected_call(
4161
'Repository.unlock', ('quack/', 'token'),
4162
'success', ('ok', ))
4165
def test_pack_with_hint(self):
4166
transport_path = 'quack'
4167
repo, client = self.setup_fake_client_and_repository(transport_path)
4168
client.add_expected_call(
4169
'Repository.lock_write', ('quack/', ''),
4170
'success', ('ok', 'token'))
4171
client.add_expected_call(
4172
'Repository.pack', ('quack/', 'token', 'False'),
4173
'success', ('ok',), )
4174
client.add_expected_call(
4175
'Repository.unlock', ('quack/', 'token', 'False'),
4176
'success', ('ok', ))
4177
repo.pack(['hinta', 'hintb'])