1
# Copyright (C) 2006, 2007, 2008, 2009 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
42
from bzrlib.branch import Branch
43
from bzrlib.bzrdir import BzrDir, BzrDirFormat
44
from bzrlib.remote import (
50
RemoteRepositoryFormat,
52
from bzrlib.repofmt import pack_repo
53
from bzrlib.revision import NULL_REVISION
54
from bzrlib.smart import server, medium
55
from bzrlib.smart.client import _SmartClient
56
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
57
from bzrlib.tests import (
59
split_suite_by_condition,
62
from bzrlib.transport import get_transport, http
63
from bzrlib.transport.memory import MemoryTransport
64
from bzrlib.transport.remote import (
70
def load_tests(standard_tests, module, loader):
71
to_adapt, result = split_suite_by_condition(
72
standard_tests, condition_isinstance(BasicRemoteObjectTests))
73
smart_server_version_scenarios = [
75
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
77
{'transport_server': server.SmartTCPServer_for_testing})]
78
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
81
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
84
super(BasicRemoteObjectTests, self).setUp()
85
self.transport = self.get_transport()
86
# make a branch that can be opened over the smart transport
87
self.local_wt = BzrDir.create_standalone_workingtree('.')
90
self.transport.disconnect()
91
tests.TestCaseWithTransport.tearDown(self)
93
def test_create_remote_bzrdir(self):
94
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
95
self.assertIsInstance(b, BzrDir)
97
def test_open_remote_branch(self):
98
# open a standalone branch in the working directory
99
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
100
branch = b.open_branch()
101
self.assertIsInstance(branch, Branch)
103
def test_remote_repository(self):
104
b = BzrDir.open_from_transport(self.transport)
105
repo = b.open_repository()
106
revid = u'\xc823123123'.encode('utf8')
107
self.assertFalse(repo.has_revision(revid))
108
self.local_wt.commit(message='test commit', rev_id=revid)
109
self.assertTrue(repo.has_revision(revid))
111
def test_remote_branch_revision_history(self):
112
b = BzrDir.open_from_transport(self.transport).open_branch()
113
self.assertEqual([], b.revision_history())
114
r1 = self.local_wt.commit('1st commit')
115
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
116
self.assertEqual([r1, r2], b.revision_history())
118
def test_find_correct_format(self):
119
"""Should open a RemoteBzrDir over a RemoteTransport"""
120
fmt = BzrDirFormat.find_format(self.transport)
121
self.assertTrue(RemoteBzrDirFormat
122
in BzrDirFormat._control_server_formats)
123
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
125
def test_open_detected_smart_format(self):
126
fmt = BzrDirFormat.find_format(self.transport)
127
d = fmt.open(self.transport)
128
self.assertIsInstance(d, BzrDir)
130
def test_remote_branch_repr(self):
131
b = BzrDir.open_from_transport(self.transport).open_branch()
132
self.assertStartsWith(str(b), 'RemoteBranch(')
134
def test_remote_branch_format_supports_stacking(self):
136
self.make_branch('unstackable', format='pack-0.92')
137
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
138
self.assertFalse(b._format.supports_stacking())
139
self.make_branch('stackable', format='1.9')
140
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
141
self.assertTrue(b._format.supports_stacking())
143
def test_remote_repo_format_supports_external_references(self):
145
bd = self.make_bzrdir('unstackable', format='pack-0.92')
146
r = bd.create_repository()
147
self.assertFalse(r._format.supports_external_lookups)
148
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
149
self.assertFalse(r._format.supports_external_lookups)
150
bd = self.make_bzrdir('stackable', format='1.9')
151
r = bd.create_repository()
152
self.assertTrue(r._format.supports_external_lookups)
153
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
154
self.assertTrue(r._format.supports_external_lookups)
157
class FakeProtocol(object):
158
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
160
def __init__(self, body, fake_client):
162
self._body_buffer = None
163
self._fake_client = fake_client
165
def read_body_bytes(self, count=-1):
166
if self._body_buffer is None:
167
self._body_buffer = StringIO(self.body)
168
bytes = self._body_buffer.read(count)
169
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
170
self._fake_client.expecting_body = False
173
def cancel_read_body(self):
174
self._fake_client.expecting_body = False
176
def read_streamed_body(self):
180
class FakeClient(_SmartClient):
181
"""Lookalike for _SmartClient allowing testing."""
183
def __init__(self, fake_medium_base='fake base'):
184
"""Create a FakeClient."""
187
self.expecting_body = False
188
# if non-None, this is the list of expected calls, with only the
189
# method name and arguments included. the body might be hard to
190
# compute so is not included. If a call is None, that call can
192
self._expected_calls = None
193
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
195
def add_expected_call(self, call_name, call_args, response_type,
196
response_args, response_body=None):
197
if self._expected_calls is None:
198
self._expected_calls = []
199
self._expected_calls.append((call_name, call_args))
200
self.responses.append((response_type, response_args, response_body))
202
def add_success_response(self, *args):
203
self.responses.append(('success', args, None))
205
def add_success_response_with_body(self, body, *args):
206
self.responses.append(('success', args, body))
207
if self._expected_calls is not None:
208
self._expected_calls.append(None)
210
def add_error_response(self, *args):
211
self.responses.append(('error', args))
213
def add_unknown_method_response(self, verb):
214
self.responses.append(('unknown', verb))
216
def finished_test(self):
217
if self._expected_calls:
218
raise AssertionError("%r finished but was still expecting %r"
219
% (self, self._expected_calls[0]))
221
def _get_next_response(self):
223
response_tuple = self.responses.pop(0)
224
except IndexError, e:
225
raise AssertionError("%r didn't expect any more calls"
227
if response_tuple[0] == 'unknown':
228
raise errors.UnknownSmartMethod(response_tuple[1])
229
elif response_tuple[0] == 'error':
230
raise errors.ErrorFromSmartServer(response_tuple[1])
231
return response_tuple
233
def _check_call(self, method, args):
234
if self._expected_calls is None:
235
# the test should be updated to say what it expects
238
next_call = self._expected_calls.pop(0)
240
raise AssertionError("%r didn't expect any more calls "
242
% (self, method, args,))
243
if next_call is None:
245
if method != next_call[0] or args != next_call[1]:
246
raise AssertionError("%r expected %r%r "
248
% (self, next_call[0], next_call[1], method, args,))
250
def call(self, method, *args):
251
self._check_call(method, args)
252
self._calls.append(('call', method, args))
253
return self._get_next_response()[1]
255
def call_expecting_body(self, method, *args):
256
self._check_call(method, args)
257
self._calls.append(('call_expecting_body', method, args))
258
result = self._get_next_response()
259
self.expecting_body = True
260
return result[1], FakeProtocol(result[2], self)
262
def call_with_body_bytes_expecting_body(self, method, args, body):
263
self._check_call(method, args)
264
self._calls.append(('call_with_body_bytes_expecting_body', method,
266
result = self._get_next_response()
267
self.expecting_body = True
268
return result[1], FakeProtocol(result[2], self)
270
def call_with_body_stream(self, args, stream):
271
# Explicitly consume the stream before checking for an error, because
272
# that's what happens a real medium.
273
stream = list(stream)
274
self._check_call(args[0], args[1:])
275
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
276
result = self._get_next_response()
277
# The second value returned from call_with_body_stream is supposed to
278
# be a response_handler object, but so far no tests depend on that.
279
response_handler = None
280
return result[1], response_handler
283
class FakeMedium(medium.SmartClientMedium):
285
def __init__(self, client_calls, base):
286
medium.SmartClientMedium.__init__(self, base)
287
self._client_calls = client_calls
289
def disconnect(self):
290
self._client_calls.append(('disconnect medium',))
293
class TestVfsHas(tests.TestCase):
295
def test_unicode_path(self):
296
client = FakeClient('/')
297
client.add_success_response('yes',)
298
transport = RemoteTransport('bzr://localhost/', _client=client)
299
filename = u'/hell\u00d8'.encode('utf8')
300
result = transport.has(filename)
302
[('call', 'has', (filename,))],
304
self.assertTrue(result)
307
class TestRemote(tests.TestCaseWithMemoryTransport):
309
def get_branch_format(self):
310
reference_bzrdir_format = bzrdir.format_registry.get('default')()
311
return reference_bzrdir_format.get_branch_format()
313
def get_repo_format(self):
314
reference_bzrdir_format = bzrdir.format_registry.get('default')()
315
return reference_bzrdir_format.repository_format
317
def disable_verb(self, verb):
318
"""Disable a verb for one test."""
319
request_handlers = smart.request.request_handlers
320
orig_method = request_handlers.get(verb)
321
request_handlers.remove(verb)
323
request_handlers.register(verb, orig_method)
324
self.addCleanup(restoreVerb)
327
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
328
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
330
def assertRemotePath(self, expected, client_base, transport_base):
331
"""Assert that the result of
332
SmartClientMedium.remote_path_from_transport is the expected value for
333
a given client_base and transport_base.
335
client_medium = medium.SmartClientMedium(client_base)
336
transport = get_transport(transport_base)
337
result = client_medium.remote_path_from_transport(transport)
338
self.assertEqual(expected, result)
340
def test_remote_path_from_transport(self):
341
"""SmartClientMedium.remote_path_from_transport calculates a URL for
342
the given transport relative to the root of the client base URL.
344
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
345
self.assertRemotePath(
346
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
348
def assertRemotePathHTTP(self, expected, transport_base, relpath):
349
"""Assert that the result of
350
HttpTransportBase.remote_path_from_transport is the expected value for
351
a given transport_base and relpath of that transport. (Note that
352
HttpTransportBase is a subclass of SmartClientMedium)
354
base_transport = get_transport(transport_base)
355
client_medium = base_transport.get_smart_medium()
356
cloned_transport = base_transport.clone(relpath)
357
result = client_medium.remote_path_from_transport(cloned_transport)
358
self.assertEqual(expected, result)
360
def test_remote_path_from_transport_http(self):
361
"""Remote paths for HTTP transports are calculated differently to other
362
transports. They are just relative to the client base, not the root
363
directory of the host.
365
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
366
self.assertRemotePathHTTP(
367
'../xyz/', scheme + '//host/path', '../xyz/')
368
self.assertRemotePathHTTP(
369
'xyz/', scheme + '//host/path', 'xyz/')
372
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
373
"""Tests for the behaviour of client_medium.remote_is_at_least."""
375
def test_initially_unlimited(self):
376
"""A fresh medium assumes that the remote side supports all
379
client_medium = medium.SmartClientMedium('dummy base')
380
self.assertFalse(client_medium._is_remote_before((99, 99)))
382
def test__remember_remote_is_before(self):
383
"""Calling _remember_remote_is_before ratchets down the known remote
386
client_medium = medium.SmartClientMedium('dummy base')
387
# Mark the remote side as being less than 1.6. The remote side may
389
client_medium._remember_remote_is_before((1, 6))
390
self.assertTrue(client_medium._is_remote_before((1, 6)))
391
self.assertFalse(client_medium._is_remote_before((1, 5)))
392
# Calling _remember_remote_is_before again with a lower value works.
393
client_medium._remember_remote_is_before((1, 5))
394
self.assertTrue(client_medium._is_remote_before((1, 5)))
395
# You cannot call _remember_remote_is_before with a larger value.
397
AssertionError, client_medium._remember_remote_is_before, (1, 9))
400
class TestBzrDirCloningMetaDir(TestRemote):
402
def test_backwards_compat(self):
403
self.setup_smart_server_with_call_log()
404
a_dir = self.make_bzrdir('.')
405
self.reset_smart_call_log()
406
verb = 'BzrDir.cloning_metadir'
407
self.disable_verb(verb)
408
format = a_dir.cloning_metadir()
409
call_count = len([call for call in self.hpss_calls if
410
call.call.method == verb])
411
self.assertEqual(1, call_count)
413
def test_branch_reference(self):
414
transport = self.get_transport('quack')
415
referenced = self.make_branch('referenced')
416
expected = referenced.bzrdir.cloning_metadir()
417
client = FakeClient(transport.base)
418
client.add_expected_call(
419
'BzrDir.cloning_metadir', ('quack/', 'False'),
420
'error', ('BranchReference',)),
421
client.add_expected_call(
422
'BzrDir.open_branchV2', ('quack/',),
423
'success', ('ref', self.get_url('referenced'))),
424
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
426
result = a_bzrdir.cloning_metadir()
427
# We should have got a control dir matching the referenced branch.
428
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
429
self.assertEqual(expected._repository_format, result._repository_format)
430
self.assertEqual(expected._branch_format, result._branch_format)
431
client.finished_test()
433
def test_current_server(self):
434
transport = self.get_transport('.')
435
transport = transport.clone('quack')
436
self.make_bzrdir('quack')
437
client = FakeClient(transport.base)
438
reference_bzrdir_format = bzrdir.format_registry.get('default')()
439
control_name = reference_bzrdir_format.network_name()
440
client.add_expected_call(
441
'BzrDir.cloning_metadir', ('quack/', 'False'),
442
'success', (control_name, '', ('branch', ''))),
443
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
445
result = a_bzrdir.cloning_metadir()
446
# We should have got a reference control dir with default branch and
447
# repository formats.
448
# This pokes a little, just to be sure.
449
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
450
self.assertEqual(None, result._repository_format)
451
self.assertEqual(None, result._branch_format)
452
client.finished_test()
455
class TestBzrDirOpenBranch(TestRemote):
457
def test_backwards_compat(self):
458
self.setup_smart_server_with_call_log()
459
self.make_branch('.')
460
a_dir = BzrDir.open(self.get_url('.'))
461
self.reset_smart_call_log()
462
verb = 'BzrDir.open_branchV2'
463
self.disable_verb(verb)
464
format = a_dir.open_branch()
465
call_count = len([call for call in self.hpss_calls if
466
call.call.method == verb])
467
self.assertEqual(1, call_count)
469
def test_branch_present(self):
470
reference_format = self.get_repo_format()
471
network_name = reference_format.network_name()
472
branch_network_name = self.get_branch_format().network_name()
473
transport = MemoryTransport()
474
transport.mkdir('quack')
475
transport = transport.clone('quack')
476
client = FakeClient(transport.base)
477
client.add_expected_call(
478
'BzrDir.open_branchV2', ('quack/',),
479
'success', ('branch', branch_network_name))
480
client.add_expected_call(
481
'BzrDir.find_repositoryV3', ('quack/',),
482
'success', ('ok', '', 'no', 'no', 'no', network_name))
483
client.add_expected_call(
484
'Branch.get_stacked_on_url', ('quack/',),
485
'error', ('NotStacked',))
486
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
488
result = bzrdir.open_branch()
489
self.assertIsInstance(result, RemoteBranch)
490
self.assertEqual(bzrdir, result.bzrdir)
491
client.finished_test()
493
def test_branch_missing(self):
494
transport = MemoryTransport()
495
transport.mkdir('quack')
496
transport = transport.clone('quack')
497
client = FakeClient(transport.base)
498
client.add_error_response('nobranch')
499
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
501
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
503
[('call', 'BzrDir.open_branchV2', ('quack/',))],
506
def test__get_tree_branch(self):
507
# _get_tree_branch is a form of open_branch, but it should only ask for
508
# branch opening, not any other network requests.
511
calls.append("Called")
513
transport = MemoryTransport()
514
# no requests on the network - catches other api calls being made.
515
client = FakeClient(transport.base)
516
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
518
# patch the open_branch call to record that it was called.
519
bzrdir.open_branch = open_branch
520
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
521
self.assertEqual(["Called"], calls)
522
self.assertEqual([], client._calls)
524
def test_url_quoting_of_path(self):
525
# Relpaths on the wire should not be URL-escaped. So "~" should be
526
# transmitted as "~", not "%7E".
527
transport = RemoteTCPTransport('bzr://localhost/~hello/')
528
client = FakeClient(transport.base)
529
reference_format = self.get_repo_format()
530
network_name = reference_format.network_name()
531
branch_network_name = self.get_branch_format().network_name()
532
client.add_expected_call(
533
'BzrDir.open_branchV2', ('~hello/',),
534
'success', ('branch', branch_network_name))
535
client.add_expected_call(
536
'BzrDir.find_repositoryV3', ('~hello/',),
537
'success', ('ok', '', 'no', 'no', 'no', network_name))
538
client.add_expected_call(
539
'Branch.get_stacked_on_url', ('~hello/',),
540
'error', ('NotStacked',))
541
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
543
result = bzrdir.open_branch()
544
client.finished_test()
546
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
547
reference_format = self.get_repo_format()
548
network_name = reference_format.network_name()
549
transport = MemoryTransport()
550
transport.mkdir('quack')
551
transport = transport.clone('quack')
553
rich_response = 'yes'
557
subtree_response = 'yes'
559
subtree_response = 'no'
560
client = FakeClient(transport.base)
561
client.add_success_response(
562
'ok', '', rich_response, subtree_response, external_lookup,
564
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
566
result = bzrdir.open_repository()
568
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
570
self.assertIsInstance(result, RemoteRepository)
571
self.assertEqual(bzrdir, result.bzrdir)
572
self.assertEqual(rich_root, result._format.rich_root_data)
573
self.assertEqual(subtrees, result._format.supports_tree_reference)
575
def test_open_repository_sets_format_attributes(self):
576
self.check_open_repository(True, True)
577
self.check_open_repository(False, True)
578
self.check_open_repository(True, False)
579
self.check_open_repository(False, False)
580
self.check_open_repository(False, False, 'yes')
582
def test_old_server(self):
583
"""RemoteBzrDirFormat should fail to probe if the server version is too
586
self.assertRaises(errors.NotBranchError,
587
RemoteBzrDirFormat.probe_transport, OldServerTransport())
590
class TestBzrDirCreateBranch(TestRemote):
592
def test_backwards_compat(self):
593
self.setup_smart_server_with_call_log()
594
repo = self.make_repository('.')
595
self.reset_smart_call_log()
596
self.disable_verb('BzrDir.create_branch')
597
branch = repo.bzrdir.create_branch()
598
create_branch_call_count = len([call for call in self.hpss_calls if
599
call.call.method == 'BzrDir.create_branch'])
600
self.assertEqual(1, create_branch_call_count)
602
def test_current_server(self):
603
transport = self.get_transport('.')
604
transport = transport.clone('quack')
605
self.make_repository('quack')
606
client = FakeClient(transport.base)
607
reference_bzrdir_format = bzrdir.format_registry.get('default')()
608
reference_format = reference_bzrdir_format.get_branch_format()
609
network_name = reference_format.network_name()
610
reference_repo_fmt = reference_bzrdir_format.repository_format
611
reference_repo_name = reference_repo_fmt.network_name()
612
client.add_expected_call(
613
'BzrDir.create_branch', ('quack/', network_name),
614
'success', ('ok', network_name, '', 'no', 'no', 'yes',
615
reference_repo_name))
616
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
618
branch = a_bzrdir.create_branch()
619
# We should have got a remote branch
620
self.assertIsInstance(branch, remote.RemoteBranch)
621
# its format should have the settings from the response
622
format = branch._format
623
self.assertEqual(network_name, format.network_name())
626
class TestBzrDirCreateRepository(TestRemote):
628
def test_backwards_compat(self):
629
self.setup_smart_server_with_call_log()
630
bzrdir = self.make_bzrdir('.')
631
self.reset_smart_call_log()
632
self.disable_verb('BzrDir.create_repository')
633
repo = bzrdir.create_repository()
634
create_repo_call_count = len([call for call in self.hpss_calls if
635
call.call.method == 'BzrDir.create_repository'])
636
self.assertEqual(1, create_repo_call_count)
638
def test_current_server(self):
639
transport = self.get_transport('.')
640
transport = transport.clone('quack')
641
self.make_bzrdir('quack')
642
client = FakeClient(transport.base)
643
reference_bzrdir_format = bzrdir.format_registry.get('default')()
644
reference_format = reference_bzrdir_format.repository_format
645
network_name = reference_format.network_name()
646
client.add_expected_call(
647
'BzrDir.create_repository', ('quack/',
648
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
649
'success', ('ok', 'no', 'no', 'no', network_name))
650
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
652
repo = a_bzrdir.create_repository()
653
# We should have got a remote repository
654
self.assertIsInstance(repo, remote.RemoteRepository)
655
# its format should have the settings from the response
656
format = repo._format
657
self.assertFalse(format.rich_root_data)
658
self.assertFalse(format.supports_tree_reference)
659
self.assertFalse(format.supports_external_lookups)
660
self.assertEqual(network_name, format.network_name())
663
class TestBzrDirOpenRepository(TestRemote):
665
def test_backwards_compat_1_2_3(self):
666
# fallback all the way to the first version.
667
reference_format = self.get_repo_format()
668
network_name = reference_format.network_name()
669
client = FakeClient('bzr://example.com/')
670
client.add_unknown_method_response('BzrDir.find_repositoryV3')
671
client.add_unknown_method_response('BzrDir.find_repositoryV2')
672
client.add_success_response('ok', '', 'no', 'no')
673
# A real repository instance will be created to determine the network
675
client.add_success_response_with_body(
676
"Bazaar-NG meta directory, format 1\n", 'ok')
677
client.add_success_response_with_body(
678
reference_format.get_format_string(), 'ok')
679
# PackRepository wants to do a stat
680
client.add_success_response('stat', '0', '65535')
681
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
683
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
685
repo = bzrdir.open_repository()
687
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
688
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
689
('call', 'BzrDir.find_repository', ('quack/',)),
690
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
691
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
692
('call', 'stat', ('/quack/.bzr/repository',)),
695
self.assertEqual(network_name, repo._format.network_name())
697
def test_backwards_compat_2(self):
698
# fallback to find_repositoryV2
699
reference_format = self.get_repo_format()
700
network_name = reference_format.network_name()
701
client = FakeClient('bzr://example.com/')
702
client.add_unknown_method_response('BzrDir.find_repositoryV3')
703
client.add_success_response('ok', '', 'no', 'no', 'no')
704
# A real repository instance will be created to determine the network
706
client.add_success_response_with_body(
707
"Bazaar-NG meta directory, format 1\n", 'ok')
708
client.add_success_response_with_body(
709
reference_format.get_format_string(), 'ok')
710
# PackRepository wants to do a stat
711
client.add_success_response('stat', '0', '65535')
712
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
714
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
716
repo = bzrdir.open_repository()
718
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
719
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
720
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
721
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
722
('call', 'stat', ('/quack/.bzr/repository',)),
725
self.assertEqual(network_name, repo._format.network_name())
727
def test_current_server(self):
728
reference_format = self.get_repo_format()
729
network_name = reference_format.network_name()
730
transport = MemoryTransport()
731
transport.mkdir('quack')
732
transport = transport.clone('quack')
733
client = FakeClient(transport.base)
734
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
735
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
737
repo = bzrdir.open_repository()
739
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
741
self.assertEqual(network_name, repo._format.network_name())
744
class OldSmartClient(object):
745
"""A fake smart client for test_old_version that just returns a version one
746
response to the 'hello' (query version) command.
749
def get_request(self):
750
input_file = StringIO('ok\x011\n')
751
output_file = StringIO()
752
client_medium = medium.SmartSimplePipesClientMedium(
753
input_file, output_file)
754
return medium.SmartClientStreamMediumRequest(client_medium)
756
def protocol_version(self):
760
class OldServerTransport(object):
761
"""A fake transport for test_old_server that reports it's smart server
762
protocol version as version one.
768
def get_smart_client(self):
769
return OldSmartClient()
772
class RemoteBranchTestCase(TestRemote):
774
def make_remote_branch(self, transport, client):
775
"""Make a RemoteBranch using 'client' as its _SmartClient.
777
A RemoteBzrDir and RemoteRepository will also be created to fill out
778
the RemoteBranch, albeit with stub values for some of their attributes.
780
# we do not want bzrdir to make any remote calls, so use False as its
781
# _client. If it tries to make a remote call, this will fail
783
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
785
repo = RemoteRepository(bzrdir, None, _client=client)
786
branch_format = self.get_branch_format()
787
format = RemoteBranchFormat(network_name=branch_format.network_name())
788
return RemoteBranch(bzrdir, repo, _client=client, format=format)
791
class TestBranchGetParent(RemoteBranchTestCase):
793
def test_no_parent(self):
794
# in an empty branch we decode the response properly
795
transport = MemoryTransport()
796
client = FakeClient(transport.base)
797
client.add_expected_call(
798
'Branch.get_stacked_on_url', ('quack/',),
799
'error', ('NotStacked',))
800
client.add_expected_call(
801
'Branch.get_parent', ('quack/',),
803
transport.mkdir('quack')
804
transport = transport.clone('quack')
805
branch = self.make_remote_branch(transport, client)
806
result = branch.get_parent()
807
client.finished_test()
808
self.assertEqual(None, result)
810
def test_parent_relative(self):
811
transport = MemoryTransport()
812
client = FakeClient(transport.base)
813
client.add_expected_call(
814
'Branch.get_stacked_on_url', ('kwaak/',),
815
'error', ('NotStacked',))
816
client.add_expected_call(
817
'Branch.get_parent', ('kwaak/',),
818
'success', ('../foo/',))
819
transport.mkdir('kwaak')
820
transport = transport.clone('kwaak')
821
branch = self.make_remote_branch(transport, client)
822
result = branch.get_parent()
823
self.assertEqual(transport.clone('../foo').base, result)
825
def test_parent_absolute(self):
826
transport = MemoryTransport()
827
client = FakeClient(transport.base)
828
client.add_expected_call(
829
'Branch.get_stacked_on_url', ('kwaak/',),
830
'error', ('NotStacked',))
831
client.add_expected_call(
832
'Branch.get_parent', ('kwaak/',),
833
'success', ('http://foo/',))
834
transport.mkdir('kwaak')
835
transport = transport.clone('kwaak')
836
branch = self.make_remote_branch(transport, client)
837
result = branch.get_parent()
838
self.assertEqual('http://foo/', result)
841
class TestBranchGetTagsBytes(RemoteBranchTestCase):
843
def test_backwards_compat(self):
844
self.setup_smart_server_with_call_log()
845
branch = self.make_branch('.')
846
self.reset_smart_call_log()
847
verb = 'Branch.get_tags_bytes'
848
self.disable_verb(verb)
849
branch.tags.get_tag_dict()
850
call_count = len([call for call in self.hpss_calls if
851
call.call.method == verb])
852
self.assertEqual(1, call_count)
854
def test_trivial(self):
855
transport = MemoryTransport()
856
client = FakeClient(transport.base)
857
client.add_expected_call(
858
'Branch.get_stacked_on_url', ('quack/',),
859
'error', ('NotStacked',))
860
client.add_expected_call(
861
'Branch.get_tags_bytes', ('quack/',),
863
transport.mkdir('quack')
864
transport = transport.clone('quack')
865
branch = self.make_remote_branch(transport, client)
866
result = branch.tags.get_tag_dict()
867
client.finished_test()
868
self.assertEqual({}, result)
871
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
873
def test_empty_branch(self):
874
# in an empty branch we decode the response properly
875
transport = MemoryTransport()
876
client = FakeClient(transport.base)
877
client.add_expected_call(
878
'Branch.get_stacked_on_url', ('quack/',),
879
'error', ('NotStacked',))
880
client.add_expected_call(
881
'Branch.last_revision_info', ('quack/',),
882
'success', ('ok', '0', 'null:'))
883
transport.mkdir('quack')
884
transport = transport.clone('quack')
885
branch = self.make_remote_branch(transport, client)
886
result = branch.last_revision_info()
887
client.finished_test()
888
self.assertEqual((0, NULL_REVISION), result)
890
def test_non_empty_branch(self):
891
# in a non-empty branch we also decode the response properly
892
revid = u'\xc8'.encode('utf8')
893
transport = MemoryTransport()
894
client = FakeClient(transport.base)
895
client.add_expected_call(
896
'Branch.get_stacked_on_url', ('kwaak/',),
897
'error', ('NotStacked',))
898
client.add_expected_call(
899
'Branch.last_revision_info', ('kwaak/',),
900
'success', ('ok', '2', revid))
901
transport.mkdir('kwaak')
902
transport = transport.clone('kwaak')
903
branch = self.make_remote_branch(transport, client)
904
result = branch.last_revision_info()
905
self.assertEqual((2, revid), result)
908
class TestBranch_get_stacked_on_url(TestRemote):
909
"""Test Branch._get_stacked_on_url rpc"""
911
def test_get_stacked_on_invalid_url(self):
912
# test that asking for a stacked on url the server can't access works.
913
# This isn't perfect, but then as we're in the same process there
914
# really isn't anything we can do to be 100% sure that the server
915
# doesn't just open in - this test probably needs to be rewritten using
916
# a spawn()ed server.
917
stacked_branch = self.make_branch('stacked', format='1.9')
918
memory_branch = self.make_branch('base', format='1.9')
919
vfs_url = self.get_vfs_only_url('base')
920
stacked_branch.set_stacked_on_url(vfs_url)
921
transport = stacked_branch.bzrdir.root_transport
922
client = FakeClient(transport.base)
923
client.add_expected_call(
924
'Branch.get_stacked_on_url', ('stacked/',),
925
'success', ('ok', vfs_url))
926
# XXX: Multiple calls are bad, this second call documents what is
928
client.add_expected_call(
929
'Branch.get_stacked_on_url', ('stacked/',),
930
'success', ('ok', vfs_url))
931
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
933
repo_fmt = remote.RemoteRepositoryFormat()
934
repo_fmt._custom_format = stacked_branch.repository._format
935
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
937
result = branch.get_stacked_on_url()
938
self.assertEqual(vfs_url, result)
940
def test_backwards_compatible(self):
941
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
942
base_branch = self.make_branch('base', format='1.6')
943
stacked_branch = self.make_branch('stacked', format='1.6')
944
stacked_branch.set_stacked_on_url('../base')
945
client = FakeClient(self.get_url())
946
branch_network_name = self.get_branch_format().network_name()
947
client.add_expected_call(
948
'BzrDir.open_branchV2', ('stacked/',),
949
'success', ('branch', branch_network_name))
950
client.add_expected_call(
951
'BzrDir.find_repositoryV3', ('stacked/',),
952
'success', ('ok', '', 'no', 'no', 'yes',
953
stacked_branch.repository._format.network_name()))
954
# called twice, once from constructor and then again by us
955
client.add_expected_call(
956
'Branch.get_stacked_on_url', ('stacked/',),
957
'unknown', ('Branch.get_stacked_on_url',))
958
client.add_expected_call(
959
'Branch.get_stacked_on_url', ('stacked/',),
960
'unknown', ('Branch.get_stacked_on_url',))
961
# this will also do vfs access, but that goes direct to the transport
962
# and isn't seen by the FakeClient.
963
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
964
remote.RemoteBzrDirFormat(), _client=client)
965
branch = bzrdir.open_branch()
966
result = branch.get_stacked_on_url()
967
self.assertEqual('../base', result)
968
client.finished_test()
969
# it's in the fallback list both for the RemoteRepository and its vfs
971
self.assertEqual(1, len(branch.repository._fallback_repositories))
973
len(branch.repository._real_repository._fallback_repositories))
975
def test_get_stacked_on_real_branch(self):
976
base_branch = self.make_branch('base', format='1.6')
977
stacked_branch = self.make_branch('stacked', format='1.6')
978
stacked_branch.set_stacked_on_url('../base')
979
reference_format = self.get_repo_format()
980
network_name = reference_format.network_name()
981
client = FakeClient(self.get_url())
982
branch_network_name = self.get_branch_format().network_name()
983
client.add_expected_call(
984
'BzrDir.open_branchV2', ('stacked/',),
985
'success', ('branch', branch_network_name))
986
client.add_expected_call(
987
'BzrDir.find_repositoryV3', ('stacked/',),
988
'success', ('ok', '', 'no', 'no', 'yes', network_name))
989
# called twice, once from constructor and then again by us
990
client.add_expected_call(
991
'Branch.get_stacked_on_url', ('stacked/',),
992
'success', ('ok', '../base'))
993
client.add_expected_call(
994
'Branch.get_stacked_on_url', ('stacked/',),
995
'success', ('ok', '../base'))
996
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
997
remote.RemoteBzrDirFormat(), _client=client)
998
branch = bzrdir.open_branch()
999
result = branch.get_stacked_on_url()
1000
self.assertEqual('../base', result)
1001
client.finished_test()
1002
# it's in the fallback list both for the RemoteRepository.
1003
self.assertEqual(1, len(branch.repository._fallback_repositories))
1004
# And we haven't had to construct a real repository.
1005
self.assertEqual(None, branch.repository._real_repository)
1008
class TestBranchSetLastRevision(RemoteBranchTestCase):
1010
def test_set_empty(self):
1011
# set_revision_history([]) is translated to calling
1012
# Branch.set_last_revision(path, '') on the wire.
1013
transport = MemoryTransport()
1014
transport.mkdir('branch')
1015
transport = transport.clone('branch')
1017
client = FakeClient(transport.base)
1018
client.add_expected_call(
1019
'Branch.get_stacked_on_url', ('branch/',),
1020
'error', ('NotStacked',))
1021
client.add_expected_call(
1022
'Branch.lock_write', ('branch/', '', ''),
1023
'success', ('ok', 'branch token', 'repo token'))
1024
client.add_expected_call(
1025
'Branch.last_revision_info',
1027
'success', ('ok', '0', 'null:'))
1028
client.add_expected_call(
1029
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1031
client.add_expected_call(
1032
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1034
branch = self.make_remote_branch(transport, client)
1035
# This is a hack to work around the problem that RemoteBranch currently
1036
# unnecessarily invokes _ensure_real upon a call to lock_write.
1037
branch._ensure_real = lambda: None
1039
result = branch.set_revision_history([])
1041
self.assertEqual(None, result)
1042
client.finished_test()
1044
def test_set_nonempty(self):
1045
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1046
# Branch.set_last_revision(path, rev-idN) on the wire.
1047
transport = MemoryTransport()
1048
transport.mkdir('branch')
1049
transport = transport.clone('branch')
1051
client = FakeClient(transport.base)
1052
client.add_expected_call(
1053
'Branch.get_stacked_on_url', ('branch/',),
1054
'error', ('NotStacked',))
1055
client.add_expected_call(
1056
'Branch.lock_write', ('branch/', '', ''),
1057
'success', ('ok', 'branch token', 'repo token'))
1058
client.add_expected_call(
1059
'Branch.last_revision_info',
1061
'success', ('ok', '0', 'null:'))
1063
encoded_body = bz2.compress('\n'.join(lines))
1064
client.add_success_response_with_body(encoded_body, 'ok')
1065
client.add_expected_call(
1066
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1068
client.add_expected_call(
1069
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1071
branch = self.make_remote_branch(transport, client)
1072
# This is a hack to work around the problem that RemoteBranch currently
1073
# unnecessarily invokes _ensure_real upon a call to lock_write.
1074
branch._ensure_real = lambda: None
1075
# Lock the branch, reset the record of remote calls.
1077
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1079
self.assertEqual(None, result)
1080
client.finished_test()
1082
def test_no_such_revision(self):
1083
transport = MemoryTransport()
1084
transport.mkdir('branch')
1085
transport = transport.clone('branch')
1086
# A response of 'NoSuchRevision' is translated into an exception.
1087
client = FakeClient(transport.base)
1088
client.add_expected_call(
1089
'Branch.get_stacked_on_url', ('branch/',),
1090
'error', ('NotStacked',))
1091
client.add_expected_call(
1092
'Branch.lock_write', ('branch/', '', ''),
1093
'success', ('ok', 'branch token', 'repo token'))
1094
client.add_expected_call(
1095
'Branch.last_revision_info',
1097
'success', ('ok', '0', 'null:'))
1098
# get_graph calls to construct the revision history, for the set_rh
1101
encoded_body = bz2.compress('\n'.join(lines))
1102
client.add_success_response_with_body(encoded_body, 'ok')
1103
client.add_expected_call(
1104
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1105
'error', ('NoSuchRevision', 'rev-id'))
1106
client.add_expected_call(
1107
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1110
branch = self.make_remote_branch(transport, client)
1113
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1115
client.finished_test()
1117
def test_tip_change_rejected(self):
1118
"""TipChangeRejected responses cause a TipChangeRejected exception to
1121
transport = MemoryTransport()
1122
transport.mkdir('branch')
1123
transport = transport.clone('branch')
1124
client = FakeClient(transport.base)
1125
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1126
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1127
client.add_expected_call(
1128
'Branch.get_stacked_on_url', ('branch/',),
1129
'error', ('NotStacked',))
1130
client.add_expected_call(
1131
'Branch.lock_write', ('branch/', '', ''),
1132
'success', ('ok', 'branch token', 'repo token'))
1133
client.add_expected_call(
1134
'Branch.last_revision_info',
1136
'success', ('ok', '0', 'null:'))
1138
encoded_body = bz2.compress('\n'.join(lines))
1139
client.add_success_response_with_body(encoded_body, 'ok')
1140
client.add_expected_call(
1141
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1142
'error', ('TipChangeRejected', rejection_msg_utf8))
1143
client.add_expected_call(
1144
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1146
branch = self.make_remote_branch(transport, client)
1147
branch._ensure_real = lambda: None
1149
# The 'TipChangeRejected' error response triggered by calling
1150
# set_revision_history causes a TipChangeRejected exception.
1151
err = self.assertRaises(
1152
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1153
# The UTF-8 message from the response has been decoded into a unicode
1155
self.assertIsInstance(err.msg, unicode)
1156
self.assertEqual(rejection_msg_unicode, err.msg)
1158
client.finished_test()
1161
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1163
def test_set_last_revision_info(self):
1164
# set_last_revision_info(num, 'rev-id') is translated to calling
1165
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1166
transport = MemoryTransport()
1167
transport.mkdir('branch')
1168
transport = transport.clone('branch')
1169
client = FakeClient(transport.base)
1170
# get_stacked_on_url
1171
client.add_error_response('NotStacked')
1173
client.add_success_response('ok', 'branch token', 'repo token')
1174
# query the current revision
1175
client.add_success_response('ok', '0', 'null:')
1177
client.add_success_response('ok')
1179
client.add_success_response('ok')
1181
branch = self.make_remote_branch(transport, client)
1182
# Lock the branch, reset the record of remote calls.
1185
result = branch.set_last_revision_info(1234, 'a-revision-id')
1187
[('call', 'Branch.last_revision_info', ('branch/',)),
1188
('call', 'Branch.set_last_revision_info',
1189
('branch/', 'branch token', 'repo token',
1190
'1234', 'a-revision-id'))],
1192
self.assertEqual(None, result)
1194
def test_no_such_revision(self):
1195
# A response of 'NoSuchRevision' is translated into an exception.
1196
transport = MemoryTransport()
1197
transport.mkdir('branch')
1198
transport = transport.clone('branch')
1199
client = FakeClient(transport.base)
1200
# get_stacked_on_url
1201
client.add_error_response('NotStacked')
1203
client.add_success_response('ok', 'branch token', 'repo token')
1205
client.add_error_response('NoSuchRevision', 'revid')
1207
client.add_success_response('ok')
1209
branch = self.make_remote_branch(transport, client)
1210
# Lock the branch, reset the record of remote calls.
1215
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1218
def lock_remote_branch(self, branch):
1219
"""Trick a RemoteBranch into thinking it is locked."""
1220
branch._lock_mode = 'w'
1221
branch._lock_count = 2
1222
branch._lock_token = 'branch token'
1223
branch._repo_lock_token = 'repo token'
1224
branch.repository._lock_mode = 'w'
1225
branch.repository._lock_count = 2
1226
branch.repository._lock_token = 'repo token'
1228
def test_backwards_compatibility(self):
1229
"""If the server does not support the Branch.set_last_revision_info
1230
verb (which is new in 1.4), then the client falls back to VFS methods.
1232
# This test is a little messy. Unlike most tests in this file, it
1233
# doesn't purely test what a Remote* object sends over the wire, and
1234
# how it reacts to responses from the wire. It instead relies partly
1235
# on asserting that the RemoteBranch will call
1236
# self._real_branch.set_last_revision_info(...).
1238
# First, set up our RemoteBranch with a FakeClient that raises
1239
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1240
transport = MemoryTransport()
1241
transport.mkdir('branch')
1242
transport = transport.clone('branch')
1243
client = FakeClient(transport.base)
1244
client.add_expected_call(
1245
'Branch.get_stacked_on_url', ('branch/',),
1246
'error', ('NotStacked',))
1247
client.add_expected_call(
1248
'Branch.last_revision_info',
1250
'success', ('ok', '0', 'null:'))
1251
client.add_expected_call(
1252
'Branch.set_last_revision_info',
1253
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1254
'unknown', 'Branch.set_last_revision_info')
1256
branch = self.make_remote_branch(transport, client)
1257
class StubRealBranch(object):
1260
def set_last_revision_info(self, revno, revision_id):
1262
('set_last_revision_info', revno, revision_id))
1263
def _clear_cached_state(self):
1265
real_branch = StubRealBranch()
1266
branch._real_branch = real_branch
1267
self.lock_remote_branch(branch)
1269
# Call set_last_revision_info, and verify it behaved as expected.
1270
result = branch.set_last_revision_info(1234, 'a-revision-id')
1272
[('set_last_revision_info', 1234, 'a-revision-id')],
1274
client.finished_test()
1276
def test_unexpected_error(self):
1277
# If the server sends an error the client doesn't understand, it gets
1278
# turned into an UnknownErrorFromSmartServer, which is presented as a
1279
# non-internal error to the user.
1280
transport = MemoryTransport()
1281
transport.mkdir('branch')
1282
transport = transport.clone('branch')
1283
client = FakeClient(transport.base)
1284
# get_stacked_on_url
1285
client.add_error_response('NotStacked')
1287
client.add_success_response('ok', 'branch token', 'repo token')
1289
client.add_error_response('UnexpectedError')
1291
client.add_success_response('ok')
1293
branch = self.make_remote_branch(transport, client)
1294
# Lock the branch, reset the record of remote calls.
1298
err = self.assertRaises(
1299
errors.UnknownErrorFromSmartServer,
1300
branch.set_last_revision_info, 123, 'revid')
1301
self.assertEqual(('UnexpectedError',), err.error_tuple)
1304
def test_tip_change_rejected(self):
1305
"""TipChangeRejected responses cause a TipChangeRejected exception to
1308
transport = MemoryTransport()
1309
transport.mkdir('branch')
1310
transport = transport.clone('branch')
1311
client = FakeClient(transport.base)
1312
# get_stacked_on_url
1313
client.add_error_response('NotStacked')
1315
client.add_success_response('ok', 'branch token', 'repo token')
1317
client.add_error_response('TipChangeRejected', 'rejection message')
1319
client.add_success_response('ok')
1321
branch = self.make_remote_branch(transport, client)
1322
# Lock the branch, reset the record of remote calls.
1324
self.addCleanup(branch.unlock)
1327
# The 'TipChangeRejected' error response triggered by calling
1328
# set_last_revision_info causes a TipChangeRejected exception.
1329
err = self.assertRaises(
1330
errors.TipChangeRejected,
1331
branch.set_last_revision_info, 123, 'revid')
1332
self.assertEqual('rejection message', err.msg)
1335
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1336
"""Getting the branch configuration should use an abstract method not vfs.
1339
def test_get_branch_conf(self):
1340
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1341
## # We should see that branch.get_config() does a single rpc to get the
1342
## # remote configuration file, abstracting away where that is stored on
1343
## # the server. However at the moment it always falls back to using the
1344
## # vfs, and this would need some changes in config.py.
1346
## # in an empty branch we decode the response properly
1347
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1348
## # we need to make a real branch because the remote_branch.control_files
1349
## # will trigger _ensure_real.
1350
## branch = self.make_branch('quack')
1351
## transport = branch.bzrdir.root_transport
1352
## # we do not want bzrdir to make any remote calls
1353
## bzrdir = RemoteBzrDir(transport, _client=False)
1354
## branch = RemoteBranch(bzrdir, None, _client=client)
1355
## config = branch.get_config()
1356
## self.assertEqual(
1357
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1361
class TestBranchLockWrite(RemoteBranchTestCase):
1363
def test_lock_write_unlockable(self):
1364
transport = MemoryTransport()
1365
client = FakeClient(transport.base)
1366
client.add_expected_call(
1367
'Branch.get_stacked_on_url', ('quack/',),
1368
'error', ('NotStacked',),)
1369
client.add_expected_call(
1370
'Branch.lock_write', ('quack/', '', ''),
1371
'error', ('UnlockableTransport',))
1372
transport.mkdir('quack')
1373
transport = transport.clone('quack')
1374
branch = self.make_remote_branch(transport, client)
1375
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1376
client.finished_test()
1379
class TestTransportIsReadonly(tests.TestCase):
1381
def test_true(self):
1382
client = FakeClient()
1383
client.add_success_response('yes')
1384
transport = RemoteTransport('bzr://example.com/', medium=False,
1386
self.assertEqual(True, transport.is_readonly())
1388
[('call', 'Transport.is_readonly', ())],
1391
def test_false(self):
1392
client = FakeClient()
1393
client.add_success_response('no')
1394
transport = RemoteTransport('bzr://example.com/', medium=False,
1396
self.assertEqual(False, transport.is_readonly())
1398
[('call', 'Transport.is_readonly', ())],
1401
def test_error_from_old_server(self):
1402
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1404
Clients should treat it as a "no" response, because is_readonly is only
1405
advisory anyway (a transport could be read-write, but then the
1406
underlying filesystem could be readonly anyway).
1408
client = FakeClient()
1409
client.add_unknown_method_response('Transport.is_readonly')
1410
transport = RemoteTransport('bzr://example.com/', medium=False,
1412
self.assertEqual(False, transport.is_readonly())
1414
[('call', 'Transport.is_readonly', ())],
1418
class TestTransportMkdir(tests.TestCase):
1420
def test_permissiondenied(self):
1421
client = FakeClient()
1422
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1423
transport = RemoteTransport('bzr://example.com/', medium=False,
1425
exc = self.assertRaises(
1426
errors.PermissionDenied, transport.mkdir, 'client path')
1427
expected_error = errors.PermissionDenied('/client path', 'extra')
1428
self.assertEqual(expected_error, exc)
1431
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1433
def test_defaults_to_none(self):
1434
t = RemoteSSHTransport('bzr+ssh://example.com')
1435
self.assertIs(None, t._get_credentials()[0])
1437
def test_uses_authentication_config(self):
1438
conf = config.AuthenticationConfig()
1439
conf._get_config().update(
1440
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1443
t = RemoteSSHTransport('bzr+ssh://example.com')
1444
self.assertEqual('bar', t._get_credentials()[0])
1447
class TestRemoteRepository(TestRemote):
1448
"""Base for testing RemoteRepository protocol usage.
1450
These tests contain frozen requests and responses. We want any changes to
1451
what is sent or expected to be require a thoughtful update to these tests
1452
because they might break compatibility with different-versioned servers.
1455
def setup_fake_client_and_repository(self, transport_path):
1456
"""Create the fake client and repository for testing with.
1458
There's no real server here; we just have canned responses sent
1461
:param transport_path: Path below the root of the MemoryTransport
1462
where the repository will be created.
1464
transport = MemoryTransport()
1465
transport.mkdir(transport_path)
1466
client = FakeClient(transport.base)
1467
transport = transport.clone(transport_path)
1468
# we do not want bzrdir to make any remote calls
1469
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1471
repo = RemoteRepository(bzrdir, None, _client=client)
1475
class TestRepositoryFormat(TestRemoteRepository):
1477
def test_fast_delta(self):
1478
true_name = pack_repo.RepositoryFormatPackDevelopment2().network_name()
1479
true_format = RemoteRepositoryFormat()
1480
true_format._network_name = true_name
1481
self.assertEqual(True, true_format.fast_deltas)
1482
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1483
false_format = RemoteRepositoryFormat()
1484
false_format._network_name = false_name
1485
self.assertEqual(False, false_format.fast_deltas)
1488
class TestRepositoryGatherStats(TestRemoteRepository):
1490
def test_revid_none(self):
1491
# ('ok',), body with revisions and size
1492
transport_path = 'quack'
1493
repo, client = self.setup_fake_client_and_repository(transport_path)
1494
client.add_success_response_with_body(
1495
'revisions: 2\nsize: 18\n', 'ok')
1496
result = repo.gather_stats(None)
1498
[('call_expecting_body', 'Repository.gather_stats',
1499
('quack/','','no'))],
1501
self.assertEqual({'revisions': 2, 'size': 18}, result)
1503
def test_revid_no_committers(self):
1504
# ('ok',), body without committers
1505
body = ('firstrev: 123456.300 3600\n'
1506
'latestrev: 654231.400 0\n'
1509
transport_path = 'quick'
1510
revid = u'\xc8'.encode('utf8')
1511
repo, client = self.setup_fake_client_and_repository(transport_path)
1512
client.add_success_response_with_body(body, 'ok')
1513
result = repo.gather_stats(revid)
1515
[('call_expecting_body', 'Repository.gather_stats',
1516
('quick/', revid, 'no'))],
1518
self.assertEqual({'revisions': 2, 'size': 18,
1519
'firstrev': (123456.300, 3600),
1520
'latestrev': (654231.400, 0),},
1523
def test_revid_with_committers(self):
1524
# ('ok',), body with committers
1525
body = ('committers: 128\n'
1526
'firstrev: 123456.300 3600\n'
1527
'latestrev: 654231.400 0\n'
1530
transport_path = 'buick'
1531
revid = u'\xc8'.encode('utf8')
1532
repo, client = self.setup_fake_client_and_repository(transport_path)
1533
client.add_success_response_with_body(body, 'ok')
1534
result = repo.gather_stats(revid, True)
1536
[('call_expecting_body', 'Repository.gather_stats',
1537
('buick/', revid, 'yes'))],
1539
self.assertEqual({'revisions': 2, 'size': 18,
1541
'firstrev': (123456.300, 3600),
1542
'latestrev': (654231.400, 0),},
1546
class TestRepositoryGetGraph(TestRemoteRepository):
1548
def test_get_graph(self):
1549
# get_graph returns a graph with a custom parents provider.
1550
transport_path = 'quack'
1551
repo, client = self.setup_fake_client_and_repository(transport_path)
1552
graph = repo.get_graph()
1553
self.assertNotEqual(graph._parents_provider, repo)
1556
class TestRepositoryGetParentMap(TestRemoteRepository):
1558
def test_get_parent_map_caching(self):
1559
# get_parent_map returns from cache until unlock()
1560
# setup a reponse with two revisions
1561
r1 = u'\u0e33'.encode('utf8')
1562
r2 = u'\u0dab'.encode('utf8')
1563
lines = [' '.join([r2, r1]), r1]
1564
encoded_body = bz2.compress('\n'.join(lines))
1566
transport_path = 'quack'
1567
repo, client = self.setup_fake_client_and_repository(transport_path)
1568
client.add_success_response_with_body(encoded_body, 'ok')
1569
client.add_success_response_with_body(encoded_body, 'ok')
1571
graph = repo.get_graph()
1572
parents = graph.get_parent_map([r2])
1573
self.assertEqual({r2: (r1,)}, parents)
1574
# locking and unlocking deeper should not reset
1577
parents = graph.get_parent_map([r1])
1578
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1580
[('call_with_body_bytes_expecting_body',
1581
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1585
# now we call again, and it should use the second response.
1587
graph = repo.get_graph()
1588
parents = graph.get_parent_map([r1])
1589
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1591
[('call_with_body_bytes_expecting_body',
1592
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1594
('call_with_body_bytes_expecting_body',
1595
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1601
def test_get_parent_map_reconnects_if_unknown_method(self):
1602
transport_path = 'quack'
1603
rev_id = 'revision-id'
1604
repo, client = self.setup_fake_client_and_repository(transport_path)
1605
client.add_unknown_method_response('Repository.get_parent_map')
1606
client.add_success_response_with_body(rev_id, 'ok')
1607
self.assertFalse(client._medium._is_remote_before((1, 2)))
1608
parents = repo.get_parent_map([rev_id])
1610
[('call_with_body_bytes_expecting_body',
1611
'Repository.get_parent_map', ('quack/', 'include-missing:',
1613
('disconnect medium',),
1614
('call_expecting_body', 'Repository.get_revision_graph',
1617
# The medium is now marked as being connected to an older server
1618
self.assertTrue(client._medium._is_remote_before((1, 2)))
1619
self.assertEqual({rev_id: ('null:',)}, parents)
1621
def test_get_parent_map_fallback_parentless_node(self):
1622
"""get_parent_map falls back to get_revision_graph on old servers. The
1623
results from get_revision_graph are tweaked to match the get_parent_map
1626
Specifically, a {key: ()} result from get_revision_graph means "no
1627
parents" for that key, which in get_parent_map results should be
1628
represented as {key: ('null:',)}.
1630
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1632
rev_id = 'revision-id'
1633
transport_path = 'quack'
1634
repo, client = self.setup_fake_client_and_repository(transport_path)
1635
client.add_success_response_with_body(rev_id, 'ok')
1636
client._medium._remember_remote_is_before((1, 2))
1637
parents = repo.get_parent_map([rev_id])
1639
[('call_expecting_body', 'Repository.get_revision_graph',
1642
self.assertEqual({rev_id: ('null:',)}, parents)
1644
def test_get_parent_map_unexpected_response(self):
1645
repo, client = self.setup_fake_client_and_repository('path')
1646
client.add_success_response('something unexpected!')
1648
errors.UnexpectedSmartServerResponse,
1649
repo.get_parent_map, ['a-revision-id'])
1651
def test_get_parent_map_negative_caches_missing_keys(self):
1652
self.setup_smart_server_with_call_log()
1653
repo = self.make_repository('foo')
1654
self.assertIsInstance(repo, RemoteRepository)
1656
self.addCleanup(repo.unlock)
1657
self.reset_smart_call_log()
1658
graph = repo.get_graph()
1659
self.assertEqual({},
1660
graph.get_parent_map(['some-missing', 'other-missing']))
1661
self.assertLength(1, self.hpss_calls)
1662
# No call if we repeat this
1663
self.reset_smart_call_log()
1664
graph = repo.get_graph()
1665
self.assertEqual({},
1666
graph.get_parent_map(['some-missing', 'other-missing']))
1667
self.assertLength(0, self.hpss_calls)
1668
# Asking for more unknown keys makes a request.
1669
self.reset_smart_call_log()
1670
graph = repo.get_graph()
1671
self.assertEqual({},
1672
graph.get_parent_map(['some-missing', 'other-missing',
1674
self.assertLength(1, self.hpss_calls)
1676
def disableExtraResults(self):
1677
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1678
SmartServerRepositoryGetParentMap.no_extra_results = True
1680
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1681
self.addCleanup(reset_values)
1683
def test_null_cached_missing_and_stop_key(self):
1684
self.setup_smart_server_with_call_log()
1685
# Make a branch with a single revision.
1686
builder = self.make_branch_builder('foo')
1687
builder.start_series()
1688
builder.build_snapshot('first', None, [
1689
('add', ('', 'root-id', 'directory', ''))])
1690
builder.finish_series()
1691
branch = builder.get_branch()
1692
repo = branch.repository
1693
self.assertIsInstance(repo, RemoteRepository)
1694
# Stop the server from sending extra results.
1695
self.disableExtraResults()
1697
self.addCleanup(repo.unlock)
1698
self.reset_smart_call_log()
1699
graph = repo.get_graph()
1700
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1701
# 'first' it will be a candidate for the stop_keys of subsequent
1702
# requests, and because 'null:' was queried but not returned it will be
1703
# cached as missing.
1704
self.assertEqual({'first': ('null:',)},
1705
graph.get_parent_map(['first', 'null:']))
1706
# Now query for another key. This request will pass along a recipe of
1707
# start and stop keys describing the already cached results, and this
1708
# recipe's revision count must be correct (or else it will trigger an
1709
# error from the server).
1710
self.assertEqual({}, graph.get_parent_map(['another-key']))
1711
# This assertion guards against disableExtraResults silently failing to
1712
# work, thus invalidating the test.
1713
self.assertLength(2, self.hpss_calls)
1715
def test_get_parent_map_gets_ghosts_from_result(self):
1716
# asking for a revision should negatively cache close ghosts in its
1718
self.setup_smart_server_with_call_log()
1719
tree = self.make_branch_and_memory_tree('foo')
1722
builder = treebuilder.TreeBuilder()
1723
builder.start_tree(tree)
1725
builder.finish_tree()
1726
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1727
rev_id = tree.commit('')
1731
self.addCleanup(tree.unlock)
1732
repo = tree.branch.repository
1733
self.assertIsInstance(repo, RemoteRepository)
1735
repo.get_parent_map([rev_id])
1736
self.reset_smart_call_log()
1737
# Now asking for rev_id's ghost parent should not make calls
1738
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1739
self.assertLength(0, self.hpss_calls)
1742
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1744
def test_allows_new_revisions(self):
1745
"""get_parent_map's results can be updated by commit."""
1746
smart_server = server.SmartTCPServer_for_testing()
1747
smart_server.setUp()
1748
self.addCleanup(smart_server.tearDown)
1749
self.make_branch('branch')
1750
branch = Branch.open(smart_server.get_url() + '/branch')
1751
tree = branch.create_checkout('tree', lightweight=True)
1753
self.addCleanup(tree.unlock)
1754
graph = tree.branch.repository.get_graph()
1755
# This provides an opportunity for the missing rev-id to be cached.
1756
self.assertEqual({}, graph.get_parent_map(['rev1']))
1757
tree.commit('message', rev_id='rev1')
1758
graph = tree.branch.repository.get_graph()
1759
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1762
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1764
def test_null_revision(self):
1765
# a null revision has the predictable result {}, we should have no wire
1766
# traffic when calling it with this argument
1767
transport_path = 'empty'
1768
repo, client = self.setup_fake_client_and_repository(transport_path)
1769
client.add_success_response('notused')
1770
# actual RemoteRepository.get_revision_graph is gone, but there's an
1771
# equivalent private method for testing
1772
result = repo._get_revision_graph(NULL_REVISION)
1773
self.assertEqual([], client._calls)
1774
self.assertEqual({}, result)
1776
def test_none_revision(self):
1777
# with none we want the entire graph
1778
r1 = u'\u0e33'.encode('utf8')
1779
r2 = u'\u0dab'.encode('utf8')
1780
lines = [' '.join([r2, r1]), r1]
1781
encoded_body = '\n'.join(lines)
1783
transport_path = 'sinhala'
1784
repo, client = self.setup_fake_client_and_repository(transport_path)
1785
client.add_success_response_with_body(encoded_body, 'ok')
1786
# actual RemoteRepository.get_revision_graph is gone, but there's an
1787
# equivalent private method for testing
1788
result = repo._get_revision_graph(None)
1790
[('call_expecting_body', 'Repository.get_revision_graph',
1793
self.assertEqual({r1: (), r2: (r1, )}, result)
1795
def test_specific_revision(self):
1796
# with a specific revision we want the graph for that
1797
# with none we want the entire graph
1798
r11 = u'\u0e33'.encode('utf8')
1799
r12 = u'\xc9'.encode('utf8')
1800
r2 = u'\u0dab'.encode('utf8')
1801
lines = [' '.join([r2, r11, r12]), r11, r12]
1802
encoded_body = '\n'.join(lines)
1804
transport_path = 'sinhala'
1805
repo, client = self.setup_fake_client_and_repository(transport_path)
1806
client.add_success_response_with_body(encoded_body, 'ok')
1807
result = repo._get_revision_graph(r2)
1809
[('call_expecting_body', 'Repository.get_revision_graph',
1812
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1814
def test_no_such_revision(self):
1816
transport_path = 'sinhala'
1817
repo, client = self.setup_fake_client_and_repository(transport_path)
1818
client.add_error_response('nosuchrevision', revid)
1819
# also check that the right revision is reported in the error
1820
self.assertRaises(errors.NoSuchRevision,
1821
repo._get_revision_graph, revid)
1823
[('call_expecting_body', 'Repository.get_revision_graph',
1824
('sinhala/', revid))],
1827
def test_unexpected_error(self):
1829
transport_path = 'sinhala'
1830
repo, client = self.setup_fake_client_and_repository(transport_path)
1831
client.add_error_response('AnUnexpectedError')
1832
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1833
repo._get_revision_graph, revid)
1834
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1837
class TestRepositoryIsShared(TestRemoteRepository):
1839
def test_is_shared(self):
1840
# ('yes', ) for Repository.is_shared -> 'True'.
1841
transport_path = 'quack'
1842
repo, client = self.setup_fake_client_and_repository(transport_path)
1843
client.add_success_response('yes')
1844
result = repo.is_shared()
1846
[('call', 'Repository.is_shared', ('quack/',))],
1848
self.assertEqual(True, result)
1850
def test_is_not_shared(self):
1851
# ('no', ) for Repository.is_shared -> 'False'.
1852
transport_path = 'qwack'
1853
repo, client = self.setup_fake_client_and_repository(transport_path)
1854
client.add_success_response('no')
1855
result = repo.is_shared()
1857
[('call', 'Repository.is_shared', ('qwack/',))],
1859
self.assertEqual(False, result)
1862
class TestRepositoryLockWrite(TestRemoteRepository):
1864
def test_lock_write(self):
1865
transport_path = 'quack'
1866
repo, client = self.setup_fake_client_and_repository(transport_path)
1867
client.add_success_response('ok', 'a token')
1868
result = repo.lock_write()
1870
[('call', 'Repository.lock_write', ('quack/', ''))],
1872
self.assertEqual('a token', result)
1874
def test_lock_write_already_locked(self):
1875
transport_path = 'quack'
1876
repo, client = self.setup_fake_client_and_repository(transport_path)
1877
client.add_error_response('LockContention')
1878
self.assertRaises(errors.LockContention, repo.lock_write)
1880
[('call', 'Repository.lock_write', ('quack/', ''))],
1883
def test_lock_write_unlockable(self):
1884
transport_path = 'quack'
1885
repo, client = self.setup_fake_client_and_repository(transport_path)
1886
client.add_error_response('UnlockableTransport')
1887
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1889
[('call', 'Repository.lock_write', ('quack/', ''))],
1893
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1895
def test_backwards_compat(self):
1896
self.setup_smart_server_with_call_log()
1897
repo = self.make_repository('.')
1898
self.reset_smart_call_log()
1899
verb = 'Repository.set_make_working_trees'
1900
self.disable_verb(verb)
1901
repo.set_make_working_trees(True)
1902
call_count = len([call for call in self.hpss_calls if
1903
call.call.method == verb])
1904
self.assertEqual(1, call_count)
1906
def test_current(self):
1907
transport_path = 'quack'
1908
repo, client = self.setup_fake_client_and_repository(transport_path)
1909
client.add_expected_call(
1910
'Repository.set_make_working_trees', ('quack/', 'True'),
1912
client.add_expected_call(
1913
'Repository.set_make_working_trees', ('quack/', 'False'),
1915
repo.set_make_working_trees(True)
1916
repo.set_make_working_trees(False)
1919
class TestRepositoryUnlock(TestRemoteRepository):
1921
def test_unlock(self):
1922
transport_path = 'quack'
1923
repo, client = self.setup_fake_client_and_repository(transport_path)
1924
client.add_success_response('ok', 'a token')
1925
client.add_success_response('ok')
1929
[('call', 'Repository.lock_write', ('quack/', '')),
1930
('call', 'Repository.unlock', ('quack/', 'a token'))],
1933
def test_unlock_wrong_token(self):
1934
# If somehow the token is wrong, unlock will raise TokenMismatch.
1935
transport_path = 'quack'
1936
repo, client = self.setup_fake_client_and_repository(transport_path)
1937
client.add_success_response('ok', 'a token')
1938
client.add_error_response('TokenMismatch')
1940
self.assertRaises(errors.TokenMismatch, repo.unlock)
1943
class TestRepositoryHasRevision(TestRemoteRepository):
1945
def test_none(self):
1946
# repo.has_revision(None) should not cause any traffic.
1947
transport_path = 'quack'
1948
repo, client = self.setup_fake_client_and_repository(transport_path)
1950
# The null revision is always there, so has_revision(None) == True.
1951
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1953
# The remote repo shouldn't be accessed.
1954
self.assertEqual([], client._calls)
1957
class TestRepositoryInsertStream(TestRemoteRepository):
1959
def test_unlocked_repo(self):
1960
transport_path = 'quack'
1961
repo, client = self.setup_fake_client_and_repository(transport_path)
1962
client.add_expected_call(
1963
'Repository.insert_stream', ('quack/', ''),
1965
client.add_expected_call(
1966
'Repository.insert_stream', ('quack/', ''),
1968
sink = repo._get_sink()
1969
fmt = repository.RepositoryFormat.get_default_format()
1970
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1971
self.assertEqual([], resume_tokens)
1972
self.assertEqual(set(), missing_keys)
1973
client.finished_test()
1975
def test_locked_repo_with_no_lock_token(self):
1976
transport_path = 'quack'
1977
repo, client = self.setup_fake_client_and_repository(transport_path)
1978
client.add_expected_call(
1979
'Repository.lock_write', ('quack/', ''),
1980
'success', ('ok', ''))
1981
client.add_expected_call(
1982
'Repository.insert_stream', ('quack/', ''),
1984
client.add_expected_call(
1985
'Repository.insert_stream', ('quack/', ''),
1988
sink = repo._get_sink()
1989
fmt = repository.RepositoryFormat.get_default_format()
1990
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1991
self.assertEqual([], resume_tokens)
1992
self.assertEqual(set(), missing_keys)
1993
client.finished_test()
1995
def test_locked_repo_with_lock_token(self):
1996
transport_path = 'quack'
1997
repo, client = self.setup_fake_client_and_repository(transport_path)
1998
client.add_expected_call(
1999
'Repository.lock_write', ('quack/', ''),
2000
'success', ('ok', 'a token'))
2001
client.add_expected_call(
2002
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2004
client.add_expected_call(
2005
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2008
sink = repo._get_sink()
2009
fmt = repository.RepositoryFormat.get_default_format()
2010
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2011
self.assertEqual([], resume_tokens)
2012
self.assertEqual(set(), missing_keys)
2013
client.finished_test()
2016
class TestRepositoryTarball(TestRemoteRepository):
2018
# This is a canned tarball reponse we can validate against
2020
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2021
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2022
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2023
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2024
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2025
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2026
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2027
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2028
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2029
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2030
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2031
'nWQ7QH/F3JFOFCQ0aSPfA='
2034
def test_repository_tarball(self):
2035
# Test that Repository.tarball generates the right operations
2036
transport_path = 'repo'
2037
expected_calls = [('call_expecting_body', 'Repository.tarball',
2038
('repo/', 'bz2',),),
2040
repo, client = self.setup_fake_client_and_repository(transport_path)
2041
client.add_success_response_with_body(self.tarball_content, 'ok')
2042
# Now actually ask for the tarball
2043
tarball_file = repo._get_tarball('bz2')
2045
self.assertEqual(expected_calls, client._calls)
2046
self.assertEqual(self.tarball_content, tarball_file.read())
2048
tarball_file.close()
2051
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2052
"""RemoteRepository.copy_content_into optimizations"""
2054
def test_copy_content_remote_to_local(self):
2055
self.transport_server = server.SmartTCPServer_for_testing
2056
src_repo = self.make_repository('repo1')
2057
src_repo = repository.Repository.open(self.get_url('repo1'))
2058
# At the moment the tarball-based copy_content_into can't write back
2059
# into a smart server. It would be good if it could upload the
2060
# tarball; once that works we'd have to create repositories of
2061
# different formats. -- mbp 20070410
2062
dest_url = self.get_vfs_only_url('repo2')
2063
dest_bzrdir = BzrDir.create(dest_url)
2064
dest_repo = dest_bzrdir.create_repository()
2065
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2066
self.assertTrue(isinstance(src_repo, RemoteRepository))
2067
src_repo.copy_content_into(dest_repo)
2070
class _StubRealPackRepository(object):
2072
def __init__(self, calls):
2074
self._pack_collection = _StubPackCollection(calls)
2076
def is_in_write_group(self):
2079
def refresh_data(self):
2080
self.calls.append(('pack collection reload_pack_names',))
2083
class _StubPackCollection(object):
2085
def __init__(self, calls):
2089
self.calls.append(('pack collection autopack',))
2092
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2093
"""Tests for RemoteRepository.autopack implementation."""
2096
"""When the server returns 'ok' and there's no _real_repository, then
2097
nothing else happens: the autopack method is done.
2099
transport_path = 'quack'
2100
repo, client = self.setup_fake_client_and_repository(transport_path)
2101
client.add_expected_call(
2102
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2104
client.finished_test()
2106
def test_ok_with_real_repo(self):
2107
"""When the server returns 'ok' and there is a _real_repository, then
2108
the _real_repository's reload_pack_name's method will be called.
2110
transport_path = 'quack'
2111
repo, client = self.setup_fake_client_and_repository(transport_path)
2112
client.add_expected_call(
2113
'PackRepository.autopack', ('quack/',),
2115
repo._real_repository = _StubRealPackRepository(client._calls)
2118
[('call', 'PackRepository.autopack', ('quack/',)),
2119
('pack collection reload_pack_names',)],
2122
def test_backwards_compatibility(self):
2123
"""If the server does not recognise the PackRepository.autopack verb,
2124
fallback to the real_repository's implementation.
2126
transport_path = 'quack'
2127
repo, client = self.setup_fake_client_and_repository(transport_path)
2128
client.add_unknown_method_response('PackRepository.autopack')
2129
def stub_ensure_real():
2130
client._calls.append(('_ensure_real',))
2131
repo._real_repository = _StubRealPackRepository(client._calls)
2132
repo._ensure_real = stub_ensure_real
2135
[('call', 'PackRepository.autopack', ('quack/',)),
2137
('pack collection autopack',)],
2141
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2142
"""Base class for unit tests for bzrlib.remote._translate_error."""
2144
def translateTuple(self, error_tuple, **context):
2145
"""Call _translate_error with an ErrorFromSmartServer built from the
2148
:param error_tuple: A tuple of a smart server response, as would be
2149
passed to an ErrorFromSmartServer.
2150
:kwargs context: context items to call _translate_error with.
2152
:returns: The error raised by _translate_error.
2154
# Raise the ErrorFromSmartServer before passing it as an argument,
2155
# because _translate_error may need to re-raise it with a bare 'raise'
2157
server_error = errors.ErrorFromSmartServer(error_tuple)
2158
translated_error = self.translateErrorFromSmartServer(
2159
server_error, **context)
2160
return translated_error
2162
def translateErrorFromSmartServer(self, error_object, **context):
2163
"""Like translateTuple, but takes an already constructed
2164
ErrorFromSmartServer rather than a tuple.
2168
except errors.ErrorFromSmartServer, server_error:
2169
translated_error = self.assertRaises(
2170
errors.BzrError, remote._translate_error, server_error,
2172
return translated_error
2175
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2176
"""Unit tests for bzrlib.remote._translate_error.
2178
Given an ErrorFromSmartServer (which has an error tuple from a smart
2179
server) and some context, _translate_error raises more specific errors from
2182
This test case covers the cases where _translate_error succeeds in
2183
translating an ErrorFromSmartServer to something better. See
2184
TestErrorTranslationRobustness for other cases.
2187
def test_NoSuchRevision(self):
2188
branch = self.make_branch('')
2190
translated_error = self.translateTuple(
2191
('NoSuchRevision', revid), branch=branch)
2192
expected_error = errors.NoSuchRevision(branch, revid)
2193
self.assertEqual(expected_error, translated_error)
2195
def test_nosuchrevision(self):
2196
repository = self.make_repository('')
2198
translated_error = self.translateTuple(
2199
('nosuchrevision', revid), repository=repository)
2200
expected_error = errors.NoSuchRevision(repository, revid)
2201
self.assertEqual(expected_error, translated_error)
2203
def test_nobranch(self):
2204
bzrdir = self.make_bzrdir('')
2205
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2206
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2207
self.assertEqual(expected_error, translated_error)
2209
def test_LockContention(self):
2210
translated_error = self.translateTuple(('LockContention',))
2211
expected_error = errors.LockContention('(remote lock)')
2212
self.assertEqual(expected_error, translated_error)
2214
def test_UnlockableTransport(self):
2215
bzrdir = self.make_bzrdir('')
2216
translated_error = self.translateTuple(
2217
('UnlockableTransport',), bzrdir=bzrdir)
2218
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2219
self.assertEqual(expected_error, translated_error)
2221
def test_LockFailed(self):
2222
lock = 'str() of a server lock'
2223
why = 'str() of why'
2224
translated_error = self.translateTuple(('LockFailed', lock, why))
2225
expected_error = errors.LockFailed(lock, why)
2226
self.assertEqual(expected_error, translated_error)
2228
def test_TokenMismatch(self):
2229
token = 'a lock token'
2230
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2231
expected_error = errors.TokenMismatch(token, '(remote token)')
2232
self.assertEqual(expected_error, translated_error)
2234
def test_Diverged(self):
2235
branch = self.make_branch('a')
2236
other_branch = self.make_branch('b')
2237
translated_error = self.translateTuple(
2238
('Diverged',), branch=branch, other_branch=other_branch)
2239
expected_error = errors.DivergedBranches(branch, other_branch)
2240
self.assertEqual(expected_error, translated_error)
2242
def test_ReadError_no_args(self):
2244
translated_error = self.translateTuple(('ReadError',), path=path)
2245
expected_error = errors.ReadError(path)
2246
self.assertEqual(expected_error, translated_error)
2248
def test_ReadError(self):
2250
translated_error = self.translateTuple(('ReadError', path))
2251
expected_error = errors.ReadError(path)
2252
self.assertEqual(expected_error, translated_error)
2254
def test_PermissionDenied_no_args(self):
2256
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2257
expected_error = errors.PermissionDenied(path)
2258
self.assertEqual(expected_error, translated_error)
2260
def test_PermissionDenied_one_arg(self):
2262
translated_error = self.translateTuple(('PermissionDenied', path))
2263
expected_error = errors.PermissionDenied(path)
2264
self.assertEqual(expected_error, translated_error)
2266
def test_PermissionDenied_one_arg_and_context(self):
2267
"""Given a choice between a path from the local context and a path on
2268
the wire, _translate_error prefers the path from the local context.
2270
local_path = 'local path'
2271
remote_path = 'remote path'
2272
translated_error = self.translateTuple(
2273
('PermissionDenied', remote_path), path=local_path)
2274
expected_error = errors.PermissionDenied(local_path)
2275
self.assertEqual(expected_error, translated_error)
2277
def test_PermissionDenied_two_args(self):
2279
extra = 'a string with extra info'
2280
translated_error = self.translateTuple(
2281
('PermissionDenied', path, extra))
2282
expected_error = errors.PermissionDenied(path, extra)
2283
self.assertEqual(expected_error, translated_error)
2286
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2287
"""Unit tests for bzrlib.remote._translate_error's robustness.
2289
TestErrorTranslationSuccess is for cases where _translate_error can
2290
translate successfully. This class about how _translate_err behaves when
2291
it fails to translate: it re-raises the original error.
2294
def test_unrecognised_server_error(self):
2295
"""If the error code from the server is not recognised, the original
2296
ErrorFromSmartServer is propagated unmodified.
2298
error_tuple = ('An unknown error tuple',)
2299
server_error = errors.ErrorFromSmartServer(error_tuple)
2300
translated_error = self.translateErrorFromSmartServer(server_error)
2301
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2302
self.assertEqual(expected_error, translated_error)
2304
def test_context_missing_a_key(self):
2305
"""In case of a bug in the client, or perhaps an unexpected response
2306
from a server, _translate_error returns the original error tuple from
2307
the server and mutters a warning.
2309
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2310
# in the context dict. So let's give it an empty context dict instead
2311
# to exercise its error recovery.
2313
error_tuple = ('NoSuchRevision', 'revid')
2314
server_error = errors.ErrorFromSmartServer(error_tuple)
2315
translated_error = self.translateErrorFromSmartServer(server_error)
2316
self.assertEqual(server_error, translated_error)
2317
# In addition to re-raising ErrorFromSmartServer, some debug info has
2318
# been muttered to the log file for developer to look at.
2319
self.assertContainsRe(
2320
self._get_log(keep_log_file=True),
2321
"Missing key 'branch' in context")
2323
def test_path_missing(self):
2324
"""Some translations (PermissionDenied, ReadError) can determine the
2325
'path' variable from either the wire or the local context. If neither
2326
has it, then an error is raised.
2328
error_tuple = ('ReadError',)
2329
server_error = errors.ErrorFromSmartServer(error_tuple)
2330
translated_error = self.translateErrorFromSmartServer(server_error)
2331
self.assertEqual(server_error, translated_error)
2332
# In addition to re-raising ErrorFromSmartServer, some debug info has
2333
# been muttered to the log file for developer to look at.
2334
self.assertContainsRe(
2335
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2338
class TestStacking(tests.TestCaseWithTransport):
2339
"""Tests for operations on stacked remote repositories.
2341
The underlying format type must support stacking.
2344
def test_access_stacked_remote(self):
2345
# based on <http://launchpad.net/bugs/261315>
2346
# make a branch stacked on another repository containing an empty
2347
# revision, then open it over hpss - we should be able to see that
2349
base_transport = self.get_transport()
2350
base_builder = self.make_branch_builder('base', format='1.9')
2351
base_builder.start_series()
2352
base_revid = base_builder.build_snapshot('rev-id', None,
2353
[('add', ('', None, 'directory', None))],
2355
base_builder.finish_series()
2356
stacked_branch = self.make_branch('stacked', format='1.9')
2357
stacked_branch.set_stacked_on_url('../base')
2358
# start a server looking at this
2359
smart_server = server.SmartTCPServer_for_testing()
2360
smart_server.setUp()
2361
self.addCleanup(smart_server.tearDown)
2362
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2363
# can get its branch and repository
2364
remote_branch = remote_bzrdir.open_branch()
2365
remote_repo = remote_branch.repository
2366
remote_repo.lock_read()
2368
# it should have an appropriate fallback repository, which should also
2369
# be a RemoteRepository
2370
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2371
self.assertIsInstance(remote_repo._fallback_repositories[0],
2373
# and it has the revision committed to the underlying repository;
2374
# these have varying implementations so we try several of them
2375
self.assertTrue(remote_repo.has_revisions([base_revid]))
2376
self.assertTrue(remote_repo.has_revision(base_revid))
2377
self.assertEqual(remote_repo.get_revision(base_revid).message,
2380
remote_repo.unlock()
2382
def prepare_stacked_remote_branch(self):
2383
"""Get stacked_upon and stacked branches with content in each."""
2384
self.setup_smart_server_with_call_log()
2385
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2386
tree1.commit('rev1', rev_id='rev1')
2387
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2388
).open_workingtree()
2389
tree2.commit('local changes make me feel good.')
2390
branch2 = Branch.open(self.get_url('tree2'))
2392
self.addCleanup(branch2.unlock)
2393
return tree1.branch, branch2
2395
def test_stacked_get_parent_map(self):
2396
# the public implementation of get_parent_map obeys stacking
2397
_, branch = self.prepare_stacked_remote_branch()
2398
repo = branch.repository
2399
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2401
def test_unstacked_get_parent_map(self):
2402
# _unstacked_provider.get_parent_map ignores stacking
2403
_, branch = self.prepare_stacked_remote_branch()
2404
provider = branch.repository._unstacked_provider
2405
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2407
def fetch_stream_to_rev_order(self, stream):
2409
for kind, substream in stream:
2410
if not kind == 'revisions':
2413
for content in substream:
2414
result.append(content.key[-1])
2417
def get_ordered_revs(self, format, order):
2418
"""Get a list of the revisions in a stream to format format.
2420
:param format: The format of the target.
2421
:param order: the order that target should have requested.
2422
:result: The revision ids in the stream, in the order seen,
2423
the topological order of revisions in the source.
2425
unordered_format = bzrdir.format_registry.get(format)()
2426
target_repository_format = unordered_format.repository_format
2428
self.assertEqual(order, target_repository_format._fetch_order)
2429
trunk, stacked = self.prepare_stacked_remote_branch()
2430
source = stacked.repository._get_source(target_repository_format)
2431
tip = stacked.last_revision()
2432
revs = stacked.repository.get_ancestry(tip)
2433
search = graph.PendingAncestryResult([tip], stacked.repository)
2434
self.reset_smart_call_log()
2435
stream = source.get_stream(search)
2438
# We trust that if a revision is in the stream the rest of the new
2439
# content for it is too, as per our main fetch tests; here we are
2440
# checking that the revisions are actually included at all, and their
2442
return self.fetch_stream_to_rev_order(stream), revs
2444
def test_stacked_get_stream_unordered(self):
2445
# Repository._get_source.get_stream() from a stacked repository with
2446
# unordered yields the full data from both stacked and stacked upon
2448
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2449
self.assertEqual(set(expected_revs), set(rev_ord))
2450
# Getting unordered results should have made a streaming data request
2451
# from the server, then one from the backing branch.
2452
self.assertLength(2, self.hpss_calls)
2454
def test_stacked_get_stream_topological(self):
2455
# Repository._get_source.get_stream() from a stacked repository with
2456
# topological sorting yields the full data from both stacked and
2457
# stacked upon sources in topological order.
2458
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2459
self.assertEqual(expected_revs, rev_ord)
2460
# Getting topological sort requires VFS calls still
2461
self.assertLength(12, self.hpss_calls)
2463
def test_stacked_get_stream_groupcompress(self):
2464
# Repository._get_source.get_stream() from a stacked repository with
2465
# groupcompress sorting yields the full data from both stacked and
2466
# stacked upon sources in groupcompress order.
2467
raise tests.TestSkipped('No groupcompress ordered format available')
2468
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2469
self.assertEqual(expected_revs, reversed(rev_ord))
2470
# Getting unordered results should have made a streaming data request
2471
# from the backing branch, and one from the stacked on branch.
2472
self.assertLength(2, self.hpss_calls)
2475
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2478
super(TestRemoteBranchEffort, self).setUp()
2479
# Create a smart server that publishes whatever the backing VFS server
2481
self.smart_server = server.SmartTCPServer_for_testing()
2482
self.smart_server.setUp(self.get_server())
2483
self.addCleanup(self.smart_server.tearDown)
2484
# Log all HPSS calls into self.hpss_calls.
2485
_SmartClient.hooks.install_named_hook(
2486
'call', self.capture_hpss_call, None)
2487
self.hpss_calls = []
2489
def capture_hpss_call(self, params):
2490
self.hpss_calls.append(params.method)
2492
def test_copy_content_into_avoids_revision_history(self):
2493
local = self.make_branch('local')
2494
remote_backing_tree = self.make_branch_and_tree('remote')
2495
remote_backing_tree.commit("Commit.")
2496
remote_branch_url = self.smart_server.get_url() + 'remote'
2497
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2498
local.repository.fetch(remote_branch.repository)
2499
self.hpss_calls = []
2500
remote_branch.copy_content_into(local)
2501
self.assertFalse('Branch.revision_history' in self.hpss_calls)