1
# Copyright (C) 2006, 2007, 2008 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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
41
from bzrlib.branch import Branch
42
from bzrlib.bzrdir import BzrDir, BzrDirFormat
43
from bzrlib.remote import (
50
from bzrlib.revision import NULL_REVISION
51
from bzrlib.smart import server, medium
52
from bzrlib.smart.client import _SmartClient
53
from bzrlib.symbol_versioning import one_four
54
from bzrlib.tests import (
56
split_suite_by_condition,
59
from bzrlib.transport import get_transport, http
60
from bzrlib.transport.memory import MemoryTransport
61
from bzrlib.transport.remote import (
67
def load_tests(standard_tests, module, loader):
68
to_adapt, result = split_suite_by_condition(
69
standard_tests, condition_isinstance(BasicRemoteObjectTests))
70
smart_server_version_scenarios = [
72
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
74
{'transport_server': server.SmartTCPServer_for_testing})]
75
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
78
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
81
super(BasicRemoteObjectTests, self).setUp()
82
self.transport = self.get_transport()
83
# make a branch that can be opened over the smart transport
84
self.local_wt = BzrDir.create_standalone_workingtree('.')
87
self.transport.disconnect()
88
tests.TestCaseWithTransport.tearDown(self)
90
def test_create_remote_bzrdir(self):
91
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
92
self.assertIsInstance(b, BzrDir)
94
def test_open_remote_branch(self):
95
# open a standalone branch in the working directory
96
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
97
branch = b.open_branch()
98
self.assertIsInstance(branch, Branch)
100
def test_remote_repository(self):
101
b = BzrDir.open_from_transport(self.transport)
102
repo = b.open_repository()
103
revid = u'\xc823123123'.encode('utf8')
104
self.assertFalse(repo.has_revision(revid))
105
self.local_wt.commit(message='test commit', rev_id=revid)
106
self.assertTrue(repo.has_revision(revid))
108
def test_remote_branch_revision_history(self):
109
b = BzrDir.open_from_transport(self.transport).open_branch()
110
self.assertEqual([], b.revision_history())
111
r1 = self.local_wt.commit('1st commit')
112
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
113
self.assertEqual([r1, r2], b.revision_history())
115
def test_find_correct_format(self):
116
"""Should open a RemoteBzrDir over a RemoteTransport"""
117
fmt = BzrDirFormat.find_format(self.transport)
118
self.assertTrue(RemoteBzrDirFormat
119
in BzrDirFormat._control_server_formats)
120
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
122
def test_open_detected_smart_format(self):
123
fmt = BzrDirFormat.find_format(self.transport)
124
d = fmt.open(self.transport)
125
self.assertIsInstance(d, BzrDir)
127
def test_remote_branch_repr(self):
128
b = BzrDir.open_from_transport(self.transport).open_branch()
129
self.assertStartsWith(str(b), 'RemoteBranch(')
131
def test_remote_branch_format_supports_stacking(self):
133
self.make_branch('unstackable', format='pack-0.92')
134
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
135
self.assertFalse(b._format.supports_stacking())
136
self.make_branch('stackable', format='1.9')
137
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
138
self.assertTrue(b._format.supports_stacking())
140
def test_remote_repo_format_supports_external_references(self):
142
bd = self.make_bzrdir('unstackable', format='pack-0.92')
143
r = bd.create_repository()
144
self.assertFalse(r._format.supports_external_lookups)
145
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
146
self.assertFalse(r._format.supports_external_lookups)
147
bd = self.make_bzrdir('stackable', format='1.9')
148
r = bd.create_repository()
149
self.assertTrue(r._format.supports_external_lookups)
150
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
151
self.assertTrue(r._format.supports_external_lookups)
154
class FakeProtocol(object):
155
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
157
def __init__(self, body, fake_client):
159
self._body_buffer = None
160
self._fake_client = fake_client
162
def read_body_bytes(self, count=-1):
163
if self._body_buffer is None:
164
self._body_buffer = StringIO(self.body)
165
bytes = self._body_buffer.read(count)
166
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
167
self._fake_client.expecting_body = False
170
def cancel_read_body(self):
171
self._fake_client.expecting_body = False
173
def read_streamed_body(self):
177
class FakeClient(_SmartClient):
178
"""Lookalike for _SmartClient allowing testing."""
180
def __init__(self, fake_medium_base='fake base'):
181
"""Create a FakeClient."""
184
self.expecting_body = False
185
# if non-None, this is the list of expected calls, with only the
186
# method name and arguments included. the body might be hard to
187
# compute so is not included. If a call is None, that call can
189
self._expected_calls = None
190
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
192
def add_expected_call(self, call_name, call_args, response_type,
193
response_args, response_body=None):
194
if self._expected_calls is None:
195
self._expected_calls = []
196
self._expected_calls.append((call_name, call_args))
197
self.responses.append((response_type, response_args, response_body))
199
def add_success_response(self, *args):
200
self.responses.append(('success', args, None))
202
def add_success_response_with_body(self, body, *args):
203
self.responses.append(('success', args, body))
204
if self._expected_calls is not None:
205
self._expected_calls.append(None)
207
def add_error_response(self, *args):
208
self.responses.append(('error', args))
210
def add_unknown_method_response(self, verb):
211
self.responses.append(('unknown', verb))
213
def finished_test(self):
214
if self._expected_calls:
215
raise AssertionError("%r finished but was still expecting %r"
216
% (self, self._expected_calls[0]))
218
def _get_next_response(self):
220
response_tuple = self.responses.pop(0)
221
except IndexError, e:
222
raise AssertionError("%r didn't expect any more calls"
224
if response_tuple[0] == 'unknown':
225
raise errors.UnknownSmartMethod(response_tuple[1])
226
elif response_tuple[0] == 'error':
227
raise errors.ErrorFromSmartServer(response_tuple[1])
228
return response_tuple
230
def _check_call(self, method, args):
231
if self._expected_calls is None:
232
# the test should be updated to say what it expects
235
next_call = self._expected_calls.pop(0)
237
raise AssertionError("%r didn't expect any more calls "
239
% (self, method, args,))
240
if next_call is None:
242
if method != next_call[0] or args != next_call[1]:
243
raise AssertionError("%r expected %r%r "
245
% (self, next_call[0], next_call[1], method, args,))
247
def call(self, method, *args):
248
self._check_call(method, args)
249
self._calls.append(('call', method, args))
250
return self._get_next_response()[1]
252
def call_expecting_body(self, method, *args):
253
self._check_call(method, args)
254
self._calls.append(('call_expecting_body', method, args))
255
result = self._get_next_response()
256
self.expecting_body = True
257
return result[1], FakeProtocol(result[2], self)
259
def call_with_body_bytes_expecting_body(self, method, args, body):
260
self._check_call(method, args)
261
self._calls.append(('call_with_body_bytes_expecting_body', method,
263
result = self._get_next_response()
264
self.expecting_body = True
265
return result[1], FakeProtocol(result[2], self)
267
def call_with_body_stream(self, args, stream):
268
# Explicitly consume the stream before checking for an error, because
269
# that's what happens a real medium.
270
stream = list(stream)
271
self._check_call(args[0], args[1:])
272
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
273
return self._get_next_response()[1]
276
class FakeMedium(medium.SmartClientMedium):
278
def __init__(self, client_calls, base):
279
medium.SmartClientMedium.__init__(self, base)
280
self._client_calls = client_calls
282
def disconnect(self):
283
self._client_calls.append(('disconnect medium',))
286
class TestVfsHas(tests.TestCase):
288
def test_unicode_path(self):
289
client = FakeClient('/')
290
client.add_success_response('yes',)
291
transport = RemoteTransport('bzr://localhost/', _client=client)
292
filename = u'/hell\u00d8'.encode('utf8')
293
result = transport.has(filename)
295
[('call', 'has', (filename,))],
297
self.assertTrue(result)
300
class TestRemote(tests.TestCaseWithMemoryTransport):
302
def get_branch_format(self):
303
reference_bzrdir_format = bzrdir.format_registry.get('default')()
304
return reference_bzrdir_format.get_branch_format()
306
def get_repo_format(self):
307
reference_bzrdir_format = bzrdir.format_registry.get('default')()
308
return reference_bzrdir_format.repository_format
310
def disable_verb(self, verb):
311
"""Disable a verb for one test."""
312
request_handlers = smart.request.request_handlers
313
orig_method = request_handlers.get(verb)
314
request_handlers.remove(verb)
316
request_handlers.register(verb, orig_method)
317
self.addCleanup(restoreVerb)
320
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
321
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
323
def assertRemotePath(self, expected, client_base, transport_base):
324
"""Assert that the result of
325
SmartClientMedium.remote_path_from_transport is the expected value for
326
a given client_base and transport_base.
328
client_medium = medium.SmartClientMedium(client_base)
329
transport = get_transport(transport_base)
330
result = client_medium.remote_path_from_transport(transport)
331
self.assertEqual(expected, result)
333
def test_remote_path_from_transport(self):
334
"""SmartClientMedium.remote_path_from_transport calculates a URL for
335
the given transport relative to the root of the client base URL.
337
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
338
self.assertRemotePath(
339
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
341
def assertRemotePathHTTP(self, expected, transport_base, relpath):
342
"""Assert that the result of
343
HttpTransportBase.remote_path_from_transport is the expected value for
344
a given transport_base and relpath of that transport. (Note that
345
HttpTransportBase is a subclass of SmartClientMedium)
347
base_transport = get_transport(transport_base)
348
client_medium = base_transport.get_smart_medium()
349
cloned_transport = base_transport.clone(relpath)
350
result = client_medium.remote_path_from_transport(cloned_transport)
351
self.assertEqual(expected, result)
353
def test_remote_path_from_transport_http(self):
354
"""Remote paths for HTTP transports are calculated differently to other
355
transports. They are just relative to the client base, not the root
356
directory of the host.
358
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
359
self.assertRemotePathHTTP(
360
'../xyz/', scheme + '//host/path', '../xyz/')
361
self.assertRemotePathHTTP(
362
'xyz/', scheme + '//host/path', 'xyz/')
365
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
366
"""Tests for the behaviour of client_medium.remote_is_at_least."""
368
def test_initially_unlimited(self):
369
"""A fresh medium assumes that the remote side supports all
372
client_medium = medium.SmartClientMedium('dummy base')
373
self.assertFalse(client_medium._is_remote_before((99, 99)))
375
def test__remember_remote_is_before(self):
376
"""Calling _remember_remote_is_before ratchets down the known remote
379
client_medium = medium.SmartClientMedium('dummy base')
380
# Mark the remote side as being less than 1.6. The remote side may
382
client_medium._remember_remote_is_before((1, 6))
383
self.assertTrue(client_medium._is_remote_before((1, 6)))
384
self.assertFalse(client_medium._is_remote_before((1, 5)))
385
# Calling _remember_remote_is_before again with a lower value works.
386
client_medium._remember_remote_is_before((1, 5))
387
self.assertTrue(client_medium._is_remote_before((1, 5)))
388
# You cannot call _remember_remote_is_before with a larger value.
390
AssertionError, client_medium._remember_remote_is_before, (1, 9))
393
class TestBzrDirCloningMetaDir(TestRemote):
395
def test_backwards_compat(self):
396
self.setup_smart_server_with_call_log()
397
a_dir = self.make_bzrdir('.')
398
self.reset_smart_call_log()
399
verb = 'BzrDir.cloning_metadir'
400
self.disable_verb(verb)
401
format = a_dir.cloning_metadir()
402
call_count = len([call for call in self.hpss_calls if
403
call.call.method == verb])
404
self.assertEqual(1, call_count)
406
def test_current_server(self):
407
transport = self.get_transport('.')
408
transport = transport.clone('quack')
409
self.make_bzrdir('quack')
410
client = FakeClient(transport.base)
411
reference_bzrdir_format = bzrdir.format_registry.get('default')()
412
control_name = reference_bzrdir_format.network_name()
413
client.add_expected_call(
414
'BzrDir.cloning_metadir', ('quack/', 'False'),
415
'success', (control_name, '', ('branch', ''))),
416
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
418
result = a_bzrdir.cloning_metadir()
419
# We should have got a reference control dir with default branch and
420
# repository formats.
421
# This pokes a little, just to be sure.
422
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
423
self.assertEqual(None, result._repository_format)
424
self.assertEqual(None, result._branch_format)
425
client.finished_test()
428
class TestBzrDirOpenBranch(TestRemote):
430
def test_backwards_compat(self):
431
self.setup_smart_server_with_call_log()
432
self.make_branch('.')
433
a_dir = BzrDir.open(self.get_url('.'))
434
self.reset_smart_call_log()
435
verb = 'BzrDir.open_branchV2'
436
self.disable_verb(verb)
437
format = a_dir.open_branch()
438
call_count = len([call for call in self.hpss_calls if
439
call.call.method == verb])
440
self.assertEqual(1, call_count)
442
def test_branch_present(self):
443
reference_format = self.get_repo_format()
444
network_name = reference_format.network_name()
445
branch_network_name = self.get_branch_format().network_name()
446
transport = MemoryTransport()
447
transport.mkdir('quack')
448
transport = transport.clone('quack')
449
client = FakeClient(transport.base)
450
client.add_expected_call(
451
'BzrDir.open_branchV2', ('quack/',),
452
'success', ('branch', branch_network_name))
453
client.add_expected_call(
454
'BzrDir.find_repositoryV3', ('quack/',),
455
'success', ('ok', '', 'no', 'no', 'no', network_name))
456
client.add_expected_call(
457
'Branch.get_stacked_on_url', ('quack/',),
458
'error', ('NotStacked',))
459
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
461
result = bzrdir.open_branch()
462
self.assertIsInstance(result, RemoteBranch)
463
self.assertEqual(bzrdir, result.bzrdir)
464
client.finished_test()
466
def test_branch_missing(self):
467
transport = MemoryTransport()
468
transport.mkdir('quack')
469
transport = transport.clone('quack')
470
client = FakeClient(transport.base)
471
client.add_error_response('nobranch')
472
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
474
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
476
[('call', 'BzrDir.open_branchV2', ('quack/',))],
479
def test__get_tree_branch(self):
480
# _get_tree_branch is a form of open_branch, but it should only ask for
481
# branch opening, not any other network requests.
484
calls.append("Called")
486
transport = MemoryTransport()
487
# no requests on the network - catches other api calls being made.
488
client = FakeClient(transport.base)
489
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
491
# patch the open_branch call to record that it was called.
492
bzrdir.open_branch = open_branch
493
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
494
self.assertEqual(["Called"], calls)
495
self.assertEqual([], client._calls)
497
def test_url_quoting_of_path(self):
498
# Relpaths on the wire should not be URL-escaped. So "~" should be
499
# transmitted as "~", not "%7E".
500
transport = RemoteTCPTransport('bzr://localhost/~hello/')
501
client = FakeClient(transport.base)
502
reference_format = self.get_repo_format()
503
network_name = reference_format.network_name()
504
branch_network_name = self.get_branch_format().network_name()
505
client.add_expected_call(
506
'BzrDir.open_branchV2', ('~hello/',),
507
'success', ('branch', branch_network_name))
508
client.add_expected_call(
509
'BzrDir.find_repositoryV3', ('~hello/',),
510
'success', ('ok', '', 'no', 'no', 'no', network_name))
511
client.add_expected_call(
512
'Branch.get_stacked_on_url', ('~hello/',),
513
'error', ('NotStacked',))
514
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
516
result = bzrdir.open_branch()
517
client.finished_test()
519
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
520
reference_format = self.get_repo_format()
521
network_name = reference_format.network_name()
522
transport = MemoryTransport()
523
transport.mkdir('quack')
524
transport = transport.clone('quack')
526
rich_response = 'yes'
530
subtree_response = 'yes'
532
subtree_response = 'no'
533
client = FakeClient(transport.base)
534
client.add_success_response(
535
'ok', '', rich_response, subtree_response, external_lookup,
537
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
539
result = bzrdir.open_repository()
541
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
543
self.assertIsInstance(result, RemoteRepository)
544
self.assertEqual(bzrdir, result.bzrdir)
545
self.assertEqual(rich_root, result._format.rich_root_data)
546
self.assertEqual(subtrees, result._format.supports_tree_reference)
548
def test_open_repository_sets_format_attributes(self):
549
self.check_open_repository(True, True)
550
self.check_open_repository(False, True)
551
self.check_open_repository(True, False)
552
self.check_open_repository(False, False)
553
self.check_open_repository(False, False, 'yes')
555
def test_old_server(self):
556
"""RemoteBzrDirFormat should fail to probe if the server version is too
559
self.assertRaises(errors.NotBranchError,
560
RemoteBzrDirFormat.probe_transport, OldServerTransport())
563
class TestBzrDirCreateBranch(TestRemote):
565
def test_backwards_compat(self):
566
self.setup_smart_server_with_call_log()
567
repo = self.make_repository('.')
568
self.reset_smart_call_log()
569
self.disable_verb('BzrDir.create_branch')
570
branch = repo.bzrdir.create_branch()
571
create_branch_call_count = len([call for call in self.hpss_calls if
572
call.call.method == 'BzrDir.create_branch'])
573
self.assertEqual(1, create_branch_call_count)
575
def test_current_server(self):
576
transport = self.get_transport('.')
577
transport = transport.clone('quack')
578
self.make_repository('quack')
579
client = FakeClient(transport.base)
580
reference_bzrdir_format = bzrdir.format_registry.get('default')()
581
reference_format = reference_bzrdir_format.get_branch_format()
582
network_name = reference_format.network_name()
583
reference_repo_fmt = reference_bzrdir_format.repository_format
584
reference_repo_name = reference_repo_fmt.network_name()
585
client.add_expected_call(
586
'BzrDir.create_branch', ('quack/', network_name),
587
'success', ('ok', network_name, '', 'no', 'no', 'yes',
588
reference_repo_name))
589
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
591
branch = a_bzrdir.create_branch()
592
# We should have got a remote branch
593
self.assertIsInstance(branch, remote.RemoteBranch)
594
# its format should have the settings from the response
595
format = branch._format
596
self.assertEqual(network_name, format.network_name())
599
class TestBzrDirCreateRepository(TestRemote):
601
def test_backwards_compat(self):
602
self.setup_smart_server_with_call_log()
603
bzrdir = self.make_bzrdir('.')
604
self.reset_smart_call_log()
605
self.disable_verb('BzrDir.create_repository')
606
repo = bzrdir.create_repository()
607
create_repo_call_count = len([call for call in self.hpss_calls if
608
call.call.method == 'BzrDir.create_repository'])
609
self.assertEqual(1, create_repo_call_count)
611
def test_current_server(self):
612
transport = self.get_transport('.')
613
transport = transport.clone('quack')
614
self.make_bzrdir('quack')
615
client = FakeClient(transport.base)
616
reference_bzrdir_format = bzrdir.format_registry.get('default')()
617
reference_format = reference_bzrdir_format.repository_format
618
network_name = reference_format.network_name()
619
client.add_expected_call(
620
'BzrDir.create_repository', ('quack/',
621
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
622
'success', ('ok', 'no', 'no', 'no', network_name))
623
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
625
repo = a_bzrdir.create_repository()
626
# We should have got a remote repository
627
self.assertIsInstance(repo, remote.RemoteRepository)
628
# its format should have the settings from the response
629
format = repo._format
630
self.assertFalse(format.rich_root_data)
631
self.assertFalse(format.supports_tree_reference)
632
self.assertFalse(format.supports_external_lookups)
633
self.assertEqual(network_name, format.network_name())
636
class TestBzrDirOpenRepository(TestRemote):
638
def test_backwards_compat_1_2_3(self):
639
# fallback all the way to the first version.
640
reference_format = self.get_repo_format()
641
network_name = reference_format.network_name()
642
client = FakeClient('bzr://example.com/')
643
client.add_unknown_method_response('BzrDir.find_repositoryV3')
644
client.add_unknown_method_response('BzrDir.find_repositoryV2')
645
client.add_success_response('ok', '', 'no', 'no')
646
# A real repository instance will be created to determine the network
648
client.add_success_response_with_body(
649
"Bazaar-NG meta directory, format 1\n", 'ok')
650
client.add_success_response_with_body(
651
reference_format.get_format_string(), 'ok')
652
# PackRepository wants to do a stat
653
client.add_success_response('stat', '0', '65535')
654
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
656
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
658
repo = bzrdir.open_repository()
660
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
661
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
662
('call', 'BzrDir.find_repository', ('quack/',)),
663
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
664
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
665
('call', 'stat', ('/quack/.bzr/repository',)),
668
self.assertEqual(network_name, repo._format.network_name())
670
def test_backwards_compat_2(self):
671
# fallback to find_repositoryV2
672
reference_format = self.get_repo_format()
673
network_name = reference_format.network_name()
674
client = FakeClient('bzr://example.com/')
675
client.add_unknown_method_response('BzrDir.find_repositoryV3')
676
client.add_success_response('ok', '', 'no', 'no', 'no')
677
# A real repository instance will be created to determine the network
679
client.add_success_response_with_body(
680
"Bazaar-NG meta directory, format 1\n", 'ok')
681
client.add_success_response_with_body(
682
reference_format.get_format_string(), 'ok')
683
# PackRepository wants to do a stat
684
client.add_success_response('stat', '0', '65535')
685
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
687
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
689
repo = bzrdir.open_repository()
691
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
692
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
693
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
694
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
695
('call', 'stat', ('/quack/.bzr/repository',)),
698
self.assertEqual(network_name, repo._format.network_name())
700
def test_current_server(self):
701
reference_format = self.get_repo_format()
702
network_name = reference_format.network_name()
703
transport = MemoryTransport()
704
transport.mkdir('quack')
705
transport = transport.clone('quack')
706
client = FakeClient(transport.base)
707
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
708
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
710
repo = bzrdir.open_repository()
712
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
714
self.assertEqual(network_name, repo._format.network_name())
717
class OldSmartClient(object):
718
"""A fake smart client for test_old_version that just returns a version one
719
response to the 'hello' (query version) command.
722
def get_request(self):
723
input_file = StringIO('ok\x011\n')
724
output_file = StringIO()
725
client_medium = medium.SmartSimplePipesClientMedium(
726
input_file, output_file)
727
return medium.SmartClientStreamMediumRequest(client_medium)
729
def protocol_version(self):
733
class OldServerTransport(object):
734
"""A fake transport for test_old_server that reports it's smart server
735
protocol version as version one.
741
def get_smart_client(self):
742
return OldSmartClient()
745
class RemoteBranchTestCase(TestRemote):
747
def make_remote_branch(self, transport, client):
748
"""Make a RemoteBranch using 'client' as its _SmartClient.
750
A RemoteBzrDir and RemoteRepository will also be created to fill out
751
the RemoteBranch, albeit with stub values for some of their attributes.
753
# we do not want bzrdir to make any remote calls, so use False as its
754
# _client. If it tries to make a remote call, this will fail
756
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
758
repo = RemoteRepository(bzrdir, None, _client=client)
759
branch_format = self.get_branch_format()
760
format = RemoteBranchFormat(network_name=branch_format.network_name())
761
return RemoteBranch(bzrdir, repo, _client=client, format=format)
764
class TestBranchGetParent(RemoteBranchTestCase):
766
def test_no_parent(self):
767
# in an empty branch we decode the response properly
768
transport = MemoryTransport()
769
client = FakeClient(transport.base)
770
client.add_expected_call(
771
'Branch.get_stacked_on_url', ('quack/',),
772
'error', ('NotStacked',))
773
client.add_expected_call(
774
'Branch.get_parent', ('quack/',),
776
transport.mkdir('quack')
777
transport = transport.clone('quack')
778
branch = self.make_remote_branch(transport, client)
779
result = branch.get_parent()
780
client.finished_test()
781
self.assertEqual(None, result)
783
def test_parent_relative(self):
784
transport = MemoryTransport()
785
client = FakeClient(transport.base)
786
client.add_expected_call(
787
'Branch.get_stacked_on_url', ('kwaak/',),
788
'error', ('NotStacked',))
789
client.add_expected_call(
790
'Branch.get_parent', ('kwaak/',),
791
'success', ('../foo/',))
792
transport.mkdir('kwaak')
793
transport = transport.clone('kwaak')
794
branch = self.make_remote_branch(transport, client)
795
result = branch.get_parent()
796
self.assertEqual(transport.clone('../foo').base, result)
798
def test_parent_absolute(self):
799
transport = MemoryTransport()
800
client = FakeClient(transport.base)
801
client.add_expected_call(
802
'Branch.get_stacked_on_url', ('kwaak/',),
803
'error', ('NotStacked',))
804
client.add_expected_call(
805
'Branch.get_parent', ('kwaak/',),
806
'success', ('http://foo/',))
807
transport.mkdir('kwaak')
808
transport = transport.clone('kwaak')
809
branch = self.make_remote_branch(transport, client)
810
result = branch.get_parent()
811
self.assertEqual('http://foo/', result)
814
class TestBranchGetTagsBytes(RemoteBranchTestCase):
816
def test_backwards_compat(self):
817
self.setup_smart_server_with_call_log()
818
branch = self.make_branch('.')
819
self.reset_smart_call_log()
820
verb = 'Branch.get_tags_bytes'
821
self.disable_verb(verb)
822
branch.tags.get_tag_dict()
823
call_count = len([call for call in self.hpss_calls if
824
call.call.method == verb])
825
self.assertEqual(1, call_count)
827
def test_trivial(self):
828
transport = MemoryTransport()
829
client = FakeClient(transport.base)
830
client.add_expected_call(
831
'Branch.get_stacked_on_url', ('quack/',),
832
'error', ('NotStacked',))
833
client.add_expected_call(
834
'Branch.get_tags_bytes', ('quack/',),
836
transport.mkdir('quack')
837
transport = transport.clone('quack')
838
branch = self.make_remote_branch(transport, client)
839
result = branch.tags.get_tag_dict()
840
client.finished_test()
841
self.assertEqual({}, result)
844
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
846
def test_empty_branch(self):
847
# in an empty branch we decode the response properly
848
transport = MemoryTransport()
849
client = FakeClient(transport.base)
850
client.add_expected_call(
851
'Branch.get_stacked_on_url', ('quack/',),
852
'error', ('NotStacked',))
853
client.add_expected_call(
854
'Branch.last_revision_info', ('quack/',),
855
'success', ('ok', '0', 'null:'))
856
transport.mkdir('quack')
857
transport = transport.clone('quack')
858
branch = self.make_remote_branch(transport, client)
859
result = branch.last_revision_info()
860
client.finished_test()
861
self.assertEqual((0, NULL_REVISION), result)
863
def test_non_empty_branch(self):
864
# in a non-empty branch we also decode the response properly
865
revid = u'\xc8'.encode('utf8')
866
transport = MemoryTransport()
867
client = FakeClient(transport.base)
868
client.add_expected_call(
869
'Branch.get_stacked_on_url', ('kwaak/',),
870
'error', ('NotStacked',))
871
client.add_expected_call(
872
'Branch.last_revision_info', ('kwaak/',),
873
'success', ('ok', '2', revid))
874
transport.mkdir('kwaak')
875
transport = transport.clone('kwaak')
876
branch = self.make_remote_branch(transport, client)
877
result = branch.last_revision_info()
878
self.assertEqual((2, revid), result)
881
class TestBranch_get_stacked_on_url(TestRemote):
882
"""Test Branch._get_stacked_on_url rpc"""
884
def test_get_stacked_on_invalid_url(self):
885
# test that asking for a stacked on url the server can't access works.
886
# This isn't perfect, but then as we're in the same process there
887
# really isn't anything we can do to be 100% sure that the server
888
# doesn't just open in - this test probably needs to be rewritten using
889
# a spawn()ed server.
890
stacked_branch = self.make_branch('stacked', format='1.9')
891
memory_branch = self.make_branch('base', format='1.9')
892
vfs_url = self.get_vfs_only_url('base')
893
stacked_branch.set_stacked_on_url(vfs_url)
894
transport = stacked_branch.bzrdir.root_transport
895
client = FakeClient(transport.base)
896
client.add_expected_call(
897
'Branch.get_stacked_on_url', ('stacked/',),
898
'success', ('ok', vfs_url))
899
# XXX: Multiple calls are bad, this second call documents what is
901
client.add_expected_call(
902
'Branch.get_stacked_on_url', ('stacked/',),
903
'success', ('ok', vfs_url))
904
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
906
repo_fmt = remote.RemoteRepositoryFormat()
907
repo_fmt._custom_format = stacked_branch.repository._format
908
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
910
result = branch.get_stacked_on_url()
911
self.assertEqual(vfs_url, result)
913
def test_backwards_compatible(self):
914
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
915
base_branch = self.make_branch('base', format='1.6')
916
stacked_branch = self.make_branch('stacked', format='1.6')
917
stacked_branch.set_stacked_on_url('../base')
918
client = FakeClient(self.get_url())
919
branch_network_name = self.get_branch_format().network_name()
920
client.add_expected_call(
921
'BzrDir.open_branchV2', ('stacked/',),
922
'success', ('branch', branch_network_name))
923
client.add_expected_call(
924
'BzrDir.find_repositoryV3', ('stacked/',),
925
'success', ('ok', '', 'no', 'no', 'yes',
926
stacked_branch.repository._format.network_name()))
927
# called twice, once from constructor and then again by us
928
client.add_expected_call(
929
'Branch.get_stacked_on_url', ('stacked/',),
930
'unknown', ('Branch.get_stacked_on_url',))
931
client.add_expected_call(
932
'Branch.get_stacked_on_url', ('stacked/',),
933
'unknown', ('Branch.get_stacked_on_url',))
934
# this will also do vfs access, but that goes direct to the transport
935
# and isn't seen by the FakeClient.
936
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
937
remote.RemoteBzrDirFormat(), _client=client)
938
branch = bzrdir.open_branch()
939
result = branch.get_stacked_on_url()
940
self.assertEqual('../base', result)
941
client.finished_test()
942
# it's in the fallback list both for the RemoteRepository and its vfs
944
self.assertEqual(1, len(branch.repository._fallback_repositories))
946
len(branch.repository._real_repository._fallback_repositories))
948
def test_get_stacked_on_real_branch(self):
949
base_branch = self.make_branch('base', format='1.6')
950
stacked_branch = self.make_branch('stacked', format='1.6')
951
stacked_branch.set_stacked_on_url('../base')
952
reference_format = self.get_repo_format()
953
network_name = reference_format.network_name()
954
client = FakeClient(self.get_url())
955
branch_network_name = self.get_branch_format().network_name()
956
client.add_expected_call(
957
'BzrDir.open_branchV2', ('stacked/',),
958
'success', ('branch', branch_network_name))
959
client.add_expected_call(
960
'BzrDir.find_repositoryV3', ('stacked/',),
961
'success', ('ok', '', 'no', 'no', 'yes', network_name))
962
# called twice, once from constructor and then again by us
963
client.add_expected_call(
964
'Branch.get_stacked_on_url', ('stacked/',),
965
'success', ('ok', '../base'))
966
client.add_expected_call(
967
'Branch.get_stacked_on_url', ('stacked/',),
968
'success', ('ok', '../base'))
969
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
970
remote.RemoteBzrDirFormat(), _client=client)
971
branch = bzrdir.open_branch()
972
result = branch.get_stacked_on_url()
973
self.assertEqual('../base', result)
974
client.finished_test()
975
# it's in the fallback list both for the RemoteRepository and its vfs
977
self.assertEqual(1, len(branch.repository._fallback_repositories))
979
len(branch.repository._real_repository._fallback_repositories))
982
class TestBranchSetLastRevision(RemoteBranchTestCase):
984
def test_set_empty(self):
985
# set_revision_history([]) is translated to calling
986
# Branch.set_last_revision(path, '') on the wire.
987
transport = MemoryTransport()
988
transport.mkdir('branch')
989
transport = transport.clone('branch')
991
client = FakeClient(transport.base)
992
client.add_expected_call(
993
'Branch.get_stacked_on_url', ('branch/',),
994
'error', ('NotStacked',))
995
client.add_expected_call(
996
'Branch.lock_write', ('branch/', '', ''),
997
'success', ('ok', 'branch token', 'repo token'))
998
client.add_expected_call(
999
'Branch.last_revision_info',
1001
'success', ('ok', '0', 'null:'))
1002
client.add_expected_call(
1003
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1005
client.add_expected_call(
1006
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1008
branch = self.make_remote_branch(transport, client)
1009
# This is a hack to work around the problem that RemoteBranch currently
1010
# unnecessarily invokes _ensure_real upon a call to lock_write.
1011
branch._ensure_real = lambda: None
1013
result = branch.set_revision_history([])
1015
self.assertEqual(None, result)
1016
client.finished_test()
1018
def test_set_nonempty(self):
1019
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1020
# Branch.set_last_revision(path, rev-idN) on the wire.
1021
transport = MemoryTransport()
1022
transport.mkdir('branch')
1023
transport = transport.clone('branch')
1025
client = FakeClient(transport.base)
1026
client.add_expected_call(
1027
'Branch.get_stacked_on_url', ('branch/',),
1028
'error', ('NotStacked',))
1029
client.add_expected_call(
1030
'Branch.lock_write', ('branch/', '', ''),
1031
'success', ('ok', 'branch token', 'repo token'))
1032
client.add_expected_call(
1033
'Branch.last_revision_info',
1035
'success', ('ok', '0', 'null:'))
1037
encoded_body = bz2.compress('\n'.join(lines))
1038
client.add_success_response_with_body(encoded_body, 'ok')
1039
client.add_expected_call(
1040
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1042
client.add_expected_call(
1043
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1045
branch = self.make_remote_branch(transport, client)
1046
# This is a hack to work around the problem that RemoteBranch currently
1047
# unnecessarily invokes _ensure_real upon a call to lock_write.
1048
branch._ensure_real = lambda: None
1049
# Lock the branch, reset the record of remote calls.
1051
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1053
self.assertEqual(None, result)
1054
client.finished_test()
1056
def test_no_such_revision(self):
1057
transport = MemoryTransport()
1058
transport.mkdir('branch')
1059
transport = transport.clone('branch')
1060
# A response of 'NoSuchRevision' is translated into an exception.
1061
client = FakeClient(transport.base)
1062
client.add_expected_call(
1063
'Branch.get_stacked_on_url', ('branch/',),
1064
'error', ('NotStacked',))
1065
client.add_expected_call(
1066
'Branch.lock_write', ('branch/', '', ''),
1067
'success', ('ok', 'branch token', 'repo token'))
1068
client.add_expected_call(
1069
'Branch.last_revision_info',
1071
'success', ('ok', '0', 'null:'))
1072
# get_graph calls to construct the revision history, for the set_rh
1075
encoded_body = bz2.compress('\n'.join(lines))
1076
client.add_success_response_with_body(encoded_body, 'ok')
1077
client.add_expected_call(
1078
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1079
'error', ('NoSuchRevision', 'rev-id'))
1080
client.add_expected_call(
1081
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1084
branch = self.make_remote_branch(transport, client)
1087
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1089
client.finished_test()
1091
def test_tip_change_rejected(self):
1092
"""TipChangeRejected responses cause a TipChangeRejected exception to
1095
transport = MemoryTransport()
1096
transport.mkdir('branch')
1097
transport = transport.clone('branch')
1098
client = FakeClient(transport.base)
1099
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1100
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1101
client.add_expected_call(
1102
'Branch.get_stacked_on_url', ('branch/',),
1103
'error', ('NotStacked',))
1104
client.add_expected_call(
1105
'Branch.lock_write', ('branch/', '', ''),
1106
'success', ('ok', 'branch token', 'repo token'))
1107
client.add_expected_call(
1108
'Branch.last_revision_info',
1110
'success', ('ok', '0', 'null:'))
1112
encoded_body = bz2.compress('\n'.join(lines))
1113
client.add_success_response_with_body(encoded_body, 'ok')
1114
client.add_expected_call(
1115
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1116
'error', ('TipChangeRejected', rejection_msg_utf8))
1117
client.add_expected_call(
1118
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1120
branch = self.make_remote_branch(transport, client)
1121
branch._ensure_real = lambda: None
1123
# The 'TipChangeRejected' error response triggered by calling
1124
# set_revision_history causes a TipChangeRejected exception.
1125
err = self.assertRaises(
1126
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1127
# The UTF-8 message from the response has been decoded into a unicode
1129
self.assertIsInstance(err.msg, unicode)
1130
self.assertEqual(rejection_msg_unicode, err.msg)
1132
client.finished_test()
1135
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1137
def test_set_last_revision_info(self):
1138
# set_last_revision_info(num, 'rev-id') is translated to calling
1139
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1140
transport = MemoryTransport()
1141
transport.mkdir('branch')
1142
transport = transport.clone('branch')
1143
client = FakeClient(transport.base)
1144
# get_stacked_on_url
1145
client.add_error_response('NotStacked')
1147
client.add_success_response('ok', 'branch token', 'repo token')
1148
# query the current revision
1149
client.add_success_response('ok', '0', 'null:')
1151
client.add_success_response('ok')
1153
client.add_success_response('ok')
1155
branch = self.make_remote_branch(transport, client)
1156
# Lock the branch, reset the record of remote calls.
1159
result = branch.set_last_revision_info(1234, 'a-revision-id')
1161
[('call', 'Branch.last_revision_info', ('branch/',)),
1162
('call', 'Branch.set_last_revision_info',
1163
('branch/', 'branch token', 'repo token',
1164
'1234', 'a-revision-id'))],
1166
self.assertEqual(None, result)
1168
def test_no_such_revision(self):
1169
# A response of 'NoSuchRevision' is translated into an exception.
1170
transport = MemoryTransport()
1171
transport.mkdir('branch')
1172
transport = transport.clone('branch')
1173
client = FakeClient(transport.base)
1174
# get_stacked_on_url
1175
client.add_error_response('NotStacked')
1177
client.add_success_response('ok', 'branch token', 'repo token')
1179
client.add_error_response('NoSuchRevision', 'revid')
1181
client.add_success_response('ok')
1183
branch = self.make_remote_branch(transport, client)
1184
# Lock the branch, reset the record of remote calls.
1189
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1192
def lock_remote_branch(self, branch):
1193
"""Trick a RemoteBranch into thinking it is locked."""
1194
branch._lock_mode = 'w'
1195
branch._lock_count = 2
1196
branch._lock_token = 'branch token'
1197
branch._repo_lock_token = 'repo token'
1198
branch.repository._lock_mode = 'w'
1199
branch.repository._lock_count = 2
1200
branch.repository._lock_token = 'repo token'
1202
def test_backwards_compatibility(self):
1203
"""If the server does not support the Branch.set_last_revision_info
1204
verb (which is new in 1.4), then the client falls back to VFS methods.
1206
# This test is a little messy. Unlike most tests in this file, it
1207
# doesn't purely test what a Remote* object sends over the wire, and
1208
# how it reacts to responses from the wire. It instead relies partly
1209
# on asserting that the RemoteBranch will call
1210
# self._real_branch.set_last_revision_info(...).
1212
# First, set up our RemoteBranch with a FakeClient that raises
1213
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1214
transport = MemoryTransport()
1215
transport.mkdir('branch')
1216
transport = transport.clone('branch')
1217
client = FakeClient(transport.base)
1218
client.add_expected_call(
1219
'Branch.get_stacked_on_url', ('branch/',),
1220
'error', ('NotStacked',))
1221
client.add_expected_call(
1222
'Branch.last_revision_info',
1224
'success', ('ok', '0', 'null:'))
1225
client.add_expected_call(
1226
'Branch.set_last_revision_info',
1227
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1228
'unknown', 'Branch.set_last_revision_info')
1230
branch = self.make_remote_branch(transport, client)
1231
class StubRealBranch(object):
1234
def set_last_revision_info(self, revno, revision_id):
1236
('set_last_revision_info', revno, revision_id))
1237
def _clear_cached_state(self):
1239
real_branch = StubRealBranch()
1240
branch._real_branch = real_branch
1241
self.lock_remote_branch(branch)
1243
# Call set_last_revision_info, and verify it behaved as expected.
1244
result = branch.set_last_revision_info(1234, 'a-revision-id')
1246
[('set_last_revision_info', 1234, 'a-revision-id')],
1248
client.finished_test()
1250
def test_unexpected_error(self):
1251
# If the server sends an error the client doesn't understand, it gets
1252
# turned into an UnknownErrorFromSmartServer, which is presented as a
1253
# non-internal error to the user.
1254
transport = MemoryTransport()
1255
transport.mkdir('branch')
1256
transport = transport.clone('branch')
1257
client = FakeClient(transport.base)
1258
# get_stacked_on_url
1259
client.add_error_response('NotStacked')
1261
client.add_success_response('ok', 'branch token', 'repo token')
1263
client.add_error_response('UnexpectedError')
1265
client.add_success_response('ok')
1267
branch = self.make_remote_branch(transport, client)
1268
# Lock the branch, reset the record of remote calls.
1272
err = self.assertRaises(
1273
errors.UnknownErrorFromSmartServer,
1274
branch.set_last_revision_info, 123, 'revid')
1275
self.assertEqual(('UnexpectedError',), err.error_tuple)
1278
def test_tip_change_rejected(self):
1279
"""TipChangeRejected responses cause a TipChangeRejected exception to
1282
transport = MemoryTransport()
1283
transport.mkdir('branch')
1284
transport = transport.clone('branch')
1285
client = FakeClient(transport.base)
1286
# get_stacked_on_url
1287
client.add_error_response('NotStacked')
1289
client.add_success_response('ok', 'branch token', 'repo token')
1291
client.add_error_response('TipChangeRejected', 'rejection message')
1293
client.add_success_response('ok')
1295
branch = self.make_remote_branch(transport, client)
1296
# Lock the branch, reset the record of remote calls.
1298
self.addCleanup(branch.unlock)
1301
# The 'TipChangeRejected' error response triggered by calling
1302
# set_last_revision_info causes a TipChangeRejected exception.
1303
err = self.assertRaises(
1304
errors.TipChangeRejected,
1305
branch.set_last_revision_info, 123, 'revid')
1306
self.assertEqual('rejection message', err.msg)
1309
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1310
"""Getting the branch configuration should use an abstract method not vfs.
1313
def test_get_branch_conf(self):
1314
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1315
## # We should see that branch.get_config() does a single rpc to get the
1316
## # remote configuration file, abstracting away where that is stored on
1317
## # the server. However at the moment it always falls back to using the
1318
## # vfs, and this would need some changes in config.py.
1320
## # in an empty branch we decode the response properly
1321
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1322
## # we need to make a real branch because the remote_branch.control_files
1323
## # will trigger _ensure_real.
1324
## branch = self.make_branch('quack')
1325
## transport = branch.bzrdir.root_transport
1326
## # we do not want bzrdir to make any remote calls
1327
## bzrdir = RemoteBzrDir(transport, _client=False)
1328
## branch = RemoteBranch(bzrdir, None, _client=client)
1329
## config = branch.get_config()
1330
## self.assertEqual(
1331
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1335
class TestBranchLockWrite(RemoteBranchTestCase):
1337
def test_lock_write_unlockable(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.lock_write', ('quack/', '', ''),
1345
'error', ('UnlockableTransport',))
1346
transport.mkdir('quack')
1347
transport = transport.clone('quack')
1348
branch = self.make_remote_branch(transport, client)
1349
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1350
client.finished_test()
1353
class TestTransportIsReadonly(tests.TestCase):
1355
def test_true(self):
1356
client = FakeClient()
1357
client.add_success_response('yes')
1358
transport = RemoteTransport('bzr://example.com/', medium=False,
1360
self.assertEqual(True, transport.is_readonly())
1362
[('call', 'Transport.is_readonly', ())],
1365
def test_false(self):
1366
client = FakeClient()
1367
client.add_success_response('no')
1368
transport = RemoteTransport('bzr://example.com/', medium=False,
1370
self.assertEqual(False, transport.is_readonly())
1372
[('call', 'Transport.is_readonly', ())],
1375
def test_error_from_old_server(self):
1376
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1378
Clients should treat it as a "no" response, because is_readonly is only
1379
advisory anyway (a transport could be read-write, but then the
1380
underlying filesystem could be readonly anyway).
1382
client = FakeClient()
1383
client.add_unknown_method_response('Transport.is_readonly')
1384
transport = RemoteTransport('bzr://example.com/', medium=False,
1386
self.assertEqual(False, transport.is_readonly())
1388
[('call', 'Transport.is_readonly', ())],
1392
class TestTransportMkdir(tests.TestCase):
1394
def test_permissiondenied(self):
1395
client = FakeClient()
1396
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1397
transport = RemoteTransport('bzr://example.com/', medium=False,
1399
exc = self.assertRaises(
1400
errors.PermissionDenied, transport.mkdir, 'client path')
1401
expected_error = errors.PermissionDenied('/client path', 'extra')
1402
self.assertEqual(expected_error, exc)
1405
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1407
def test_defaults_to_none(self):
1408
t = RemoteSSHTransport('bzr+ssh://example.com')
1409
self.assertIs(None, t._get_credentials()[0])
1411
def test_uses_authentication_config(self):
1412
conf = config.AuthenticationConfig()
1413
conf._get_config().update(
1414
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1417
t = RemoteSSHTransport('bzr+ssh://example.com')
1418
self.assertEqual('bar', t._get_credentials()[0])
1421
class TestRemoteRepository(TestRemote):
1422
"""Base for testing RemoteRepository protocol usage.
1424
These tests contain frozen requests and responses. We want any changes to
1425
what is sent or expected to be require a thoughtful update to these tests
1426
because they might break compatibility with different-versioned servers.
1429
def setup_fake_client_and_repository(self, transport_path):
1430
"""Create the fake client and repository for testing with.
1432
There's no real server here; we just have canned responses sent
1435
:param transport_path: Path below the root of the MemoryTransport
1436
where the repository will be created.
1438
transport = MemoryTransport()
1439
transport.mkdir(transport_path)
1440
client = FakeClient(transport.base)
1441
transport = transport.clone(transport_path)
1442
# we do not want bzrdir to make any remote calls
1443
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1445
repo = RemoteRepository(bzrdir, None, _client=client)
1449
class TestRepositoryGatherStats(TestRemoteRepository):
1451
def test_revid_none(self):
1452
# ('ok',), body with revisions and size
1453
transport_path = 'quack'
1454
repo, client = self.setup_fake_client_and_repository(transport_path)
1455
client.add_success_response_with_body(
1456
'revisions: 2\nsize: 18\n', 'ok')
1457
result = repo.gather_stats(None)
1459
[('call_expecting_body', 'Repository.gather_stats',
1460
('quack/','','no'))],
1462
self.assertEqual({'revisions': 2, 'size': 18}, result)
1464
def test_revid_no_committers(self):
1465
# ('ok',), body without committers
1466
body = ('firstrev: 123456.300 3600\n'
1467
'latestrev: 654231.400 0\n'
1470
transport_path = 'quick'
1471
revid = u'\xc8'.encode('utf8')
1472
repo, client = self.setup_fake_client_and_repository(transport_path)
1473
client.add_success_response_with_body(body, 'ok')
1474
result = repo.gather_stats(revid)
1476
[('call_expecting_body', 'Repository.gather_stats',
1477
('quick/', revid, 'no'))],
1479
self.assertEqual({'revisions': 2, 'size': 18,
1480
'firstrev': (123456.300, 3600),
1481
'latestrev': (654231.400, 0),},
1484
def test_revid_with_committers(self):
1485
# ('ok',), body with committers
1486
body = ('committers: 128\n'
1487
'firstrev: 123456.300 3600\n'
1488
'latestrev: 654231.400 0\n'
1491
transport_path = 'buick'
1492
revid = u'\xc8'.encode('utf8')
1493
repo, client = self.setup_fake_client_and_repository(transport_path)
1494
client.add_success_response_with_body(body, 'ok')
1495
result = repo.gather_stats(revid, True)
1497
[('call_expecting_body', 'Repository.gather_stats',
1498
('buick/', revid, 'yes'))],
1500
self.assertEqual({'revisions': 2, 'size': 18,
1502
'firstrev': (123456.300, 3600),
1503
'latestrev': (654231.400, 0),},
1507
class TestRepositoryGetGraph(TestRemoteRepository):
1509
def test_get_graph(self):
1510
# get_graph returns a graph with a custom parents provider.
1511
transport_path = 'quack'
1512
repo, client = self.setup_fake_client_and_repository(transport_path)
1513
graph = repo.get_graph()
1514
self.assertNotEqual(graph._parents_provider, repo)
1517
class TestRepositoryGetParentMap(TestRemoteRepository):
1519
def test_get_parent_map_caching(self):
1520
# get_parent_map returns from cache until unlock()
1521
# setup a reponse with two revisions
1522
r1 = u'\u0e33'.encode('utf8')
1523
r2 = u'\u0dab'.encode('utf8')
1524
lines = [' '.join([r2, r1]), r1]
1525
encoded_body = bz2.compress('\n'.join(lines))
1527
transport_path = 'quack'
1528
repo, client = self.setup_fake_client_and_repository(transport_path)
1529
client.add_success_response_with_body(encoded_body, 'ok')
1530
client.add_success_response_with_body(encoded_body, 'ok')
1532
graph = repo.get_graph()
1533
parents = graph.get_parent_map([r2])
1534
self.assertEqual({r2: (r1,)}, parents)
1535
# locking and unlocking deeper should not reset
1538
parents = graph.get_parent_map([r1])
1539
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1541
[('call_with_body_bytes_expecting_body',
1542
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
1545
# now we call again, and it should use the second response.
1547
graph = repo.get_graph()
1548
parents = graph.get_parent_map([r1])
1549
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1551
[('call_with_body_bytes_expecting_body',
1552
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
1553
('call_with_body_bytes_expecting_body',
1554
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
1559
def test_get_parent_map_reconnects_if_unknown_method(self):
1560
transport_path = 'quack'
1561
repo, client = self.setup_fake_client_and_repository(transport_path)
1562
client.add_unknown_method_response('Repository,get_parent_map')
1563
client.add_success_response_with_body('', 'ok')
1564
self.assertFalse(client._medium._is_remote_before((1, 2)))
1565
rev_id = 'revision-id'
1566
expected_deprecations = [
1567
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1569
parents = self.callDeprecated(
1570
expected_deprecations, repo.get_parent_map, [rev_id])
1572
[('call_with_body_bytes_expecting_body',
1573
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
1574
('disconnect medium',),
1575
('call_expecting_body', 'Repository.get_revision_graph',
1578
# The medium is now marked as being connected to an older server
1579
self.assertTrue(client._medium._is_remote_before((1, 2)))
1581
def test_get_parent_map_fallback_parentless_node(self):
1582
"""get_parent_map falls back to get_revision_graph on old servers. The
1583
results from get_revision_graph are tweaked to match the get_parent_map
1586
Specifically, a {key: ()} result from get_revision_graph means "no
1587
parents" for that key, which in get_parent_map results should be
1588
represented as {key: ('null:',)}.
1590
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1592
rev_id = 'revision-id'
1593
transport_path = 'quack'
1594
repo, client = self.setup_fake_client_and_repository(transport_path)
1595
client.add_success_response_with_body(rev_id, 'ok')
1596
client._medium._remember_remote_is_before((1, 2))
1597
expected_deprecations = [
1598
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1600
parents = self.callDeprecated(
1601
expected_deprecations, repo.get_parent_map, [rev_id])
1603
[('call_expecting_body', 'Repository.get_revision_graph',
1606
self.assertEqual({rev_id: ('null:',)}, parents)
1608
def test_get_parent_map_unexpected_response(self):
1609
repo, client = self.setup_fake_client_and_repository('path')
1610
client.add_success_response('something unexpected!')
1612
errors.UnexpectedSmartServerResponse,
1613
repo.get_parent_map, ['a-revision-id'])
1616
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1618
def test_allows_new_revisions(self):
1619
"""get_parent_map's results can be updated by commit."""
1620
smart_server = server.SmartTCPServer_for_testing()
1621
smart_server.setUp()
1622
self.addCleanup(smart_server.tearDown)
1623
self.make_branch('branch')
1624
branch = Branch.open(smart_server.get_url() + '/branch')
1625
tree = branch.create_checkout('tree', lightweight=True)
1627
self.addCleanup(tree.unlock)
1628
graph = tree.branch.repository.get_graph()
1629
# This provides an opportunity for the missing rev-id to be cached.
1630
self.assertEqual({}, graph.get_parent_map(['rev1']))
1631
tree.commit('message', rev_id='rev1')
1632
graph = tree.branch.repository.get_graph()
1633
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1636
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1638
def test_null_revision(self):
1639
# a null revision has the predictable result {}, we should have no wire
1640
# traffic when calling it with this argument
1641
transport_path = 'empty'
1642
repo, client = self.setup_fake_client_and_repository(transport_path)
1643
client.add_success_response('notused')
1644
result = self.applyDeprecated(one_four, repo.get_revision_graph,
1646
self.assertEqual([], client._calls)
1647
self.assertEqual({}, result)
1649
def test_none_revision(self):
1650
# with none we want the entire graph
1651
r1 = u'\u0e33'.encode('utf8')
1652
r2 = u'\u0dab'.encode('utf8')
1653
lines = [' '.join([r2, r1]), r1]
1654
encoded_body = '\n'.join(lines)
1656
transport_path = 'sinhala'
1657
repo, client = self.setup_fake_client_and_repository(transport_path)
1658
client.add_success_response_with_body(encoded_body, 'ok')
1659
result = self.applyDeprecated(one_four, repo.get_revision_graph)
1661
[('call_expecting_body', 'Repository.get_revision_graph',
1664
self.assertEqual({r1: (), r2: (r1, )}, result)
1666
def test_specific_revision(self):
1667
# with a specific revision we want the graph for that
1668
# with none we want the entire graph
1669
r11 = u'\u0e33'.encode('utf8')
1670
r12 = u'\xc9'.encode('utf8')
1671
r2 = u'\u0dab'.encode('utf8')
1672
lines = [' '.join([r2, r11, r12]), r11, r12]
1673
encoded_body = '\n'.join(lines)
1675
transport_path = 'sinhala'
1676
repo, client = self.setup_fake_client_and_repository(transport_path)
1677
client.add_success_response_with_body(encoded_body, 'ok')
1678
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
1680
[('call_expecting_body', 'Repository.get_revision_graph',
1683
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1685
def test_no_such_revision(self):
1687
transport_path = 'sinhala'
1688
repo, client = self.setup_fake_client_and_repository(transport_path)
1689
client.add_error_response('nosuchrevision', revid)
1690
# also check that the right revision is reported in the error
1691
self.assertRaises(errors.NoSuchRevision,
1692
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1694
[('call_expecting_body', 'Repository.get_revision_graph',
1695
('sinhala/', revid))],
1698
def test_unexpected_error(self):
1700
transport_path = 'sinhala'
1701
repo, client = self.setup_fake_client_and_repository(transport_path)
1702
client.add_error_response('AnUnexpectedError')
1703
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1704
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1705
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1708
class TestRepositoryIsShared(TestRemoteRepository):
1710
def test_is_shared(self):
1711
# ('yes', ) for Repository.is_shared -> 'True'.
1712
transport_path = 'quack'
1713
repo, client = self.setup_fake_client_and_repository(transport_path)
1714
client.add_success_response('yes')
1715
result = repo.is_shared()
1717
[('call', 'Repository.is_shared', ('quack/',))],
1719
self.assertEqual(True, result)
1721
def test_is_not_shared(self):
1722
# ('no', ) for Repository.is_shared -> 'False'.
1723
transport_path = 'qwack'
1724
repo, client = self.setup_fake_client_and_repository(transport_path)
1725
client.add_success_response('no')
1726
result = repo.is_shared()
1728
[('call', 'Repository.is_shared', ('qwack/',))],
1730
self.assertEqual(False, result)
1733
class TestRepositoryLockWrite(TestRemoteRepository):
1735
def test_lock_write(self):
1736
transport_path = 'quack'
1737
repo, client = self.setup_fake_client_and_repository(transport_path)
1738
client.add_success_response('ok', 'a token')
1739
result = repo.lock_write()
1741
[('call', 'Repository.lock_write', ('quack/', ''))],
1743
self.assertEqual('a token', result)
1745
def test_lock_write_already_locked(self):
1746
transport_path = 'quack'
1747
repo, client = self.setup_fake_client_and_repository(transport_path)
1748
client.add_error_response('LockContention')
1749
self.assertRaises(errors.LockContention, repo.lock_write)
1751
[('call', 'Repository.lock_write', ('quack/', ''))],
1754
def test_lock_write_unlockable(self):
1755
transport_path = 'quack'
1756
repo, client = self.setup_fake_client_and_repository(transport_path)
1757
client.add_error_response('UnlockableTransport')
1758
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1760
[('call', 'Repository.lock_write', ('quack/', ''))],
1764
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1766
def test_backwards_compat(self):
1767
self.setup_smart_server_with_call_log()
1768
repo = self.make_repository('.')
1769
self.reset_smart_call_log()
1770
verb = 'Repository.set_make_working_trees'
1771
self.disable_verb(verb)
1772
repo.set_make_working_trees(True)
1773
call_count = len([call for call in self.hpss_calls if
1774
call.call.method == verb])
1775
self.assertEqual(1, call_count)
1777
def test_current(self):
1778
transport_path = 'quack'
1779
repo, client = self.setup_fake_client_and_repository(transport_path)
1780
client.add_expected_call(
1781
'Repository.set_make_working_trees', ('quack/', 'True'),
1783
client.add_expected_call(
1784
'Repository.set_make_working_trees', ('quack/', 'False'),
1786
repo.set_make_working_trees(True)
1787
repo.set_make_working_trees(False)
1790
class TestRepositoryUnlock(TestRemoteRepository):
1792
def test_unlock(self):
1793
transport_path = 'quack'
1794
repo, client = self.setup_fake_client_and_repository(transport_path)
1795
client.add_success_response('ok', 'a token')
1796
client.add_success_response('ok')
1800
[('call', 'Repository.lock_write', ('quack/', '')),
1801
('call', 'Repository.unlock', ('quack/', 'a token'))],
1804
def test_unlock_wrong_token(self):
1805
# If somehow the token is wrong, unlock will raise TokenMismatch.
1806
transport_path = 'quack'
1807
repo, client = self.setup_fake_client_and_repository(transport_path)
1808
client.add_success_response('ok', 'a token')
1809
client.add_error_response('TokenMismatch')
1811
self.assertRaises(errors.TokenMismatch, repo.unlock)
1814
class TestRepositoryHasRevision(TestRemoteRepository):
1816
def test_none(self):
1817
# repo.has_revision(None) should not cause any traffic.
1818
transport_path = 'quack'
1819
repo, client = self.setup_fake_client_and_repository(transport_path)
1821
# The null revision is always there, so has_revision(None) == True.
1822
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1824
# The remote repo shouldn't be accessed.
1825
self.assertEqual([], client._calls)
1828
class TestRepositoryTarball(TestRemoteRepository):
1830
# This is a canned tarball reponse we can validate against
1832
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1833
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1834
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1835
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1836
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1837
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1838
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1839
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1840
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1841
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1842
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1843
'nWQ7QH/F3JFOFCQ0aSPfA='
1846
def test_repository_tarball(self):
1847
# Test that Repository.tarball generates the right operations
1848
transport_path = 'repo'
1849
expected_calls = [('call_expecting_body', 'Repository.tarball',
1850
('repo/', 'bz2',),),
1852
repo, client = self.setup_fake_client_and_repository(transport_path)
1853
client.add_success_response_with_body(self.tarball_content, 'ok')
1854
# Now actually ask for the tarball
1855
tarball_file = repo._get_tarball('bz2')
1857
self.assertEqual(expected_calls, client._calls)
1858
self.assertEqual(self.tarball_content, tarball_file.read())
1860
tarball_file.close()
1863
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
1864
"""RemoteRepository.copy_content_into optimizations"""
1866
def test_copy_content_remote_to_local(self):
1867
self.transport_server = server.SmartTCPServer_for_testing
1868
src_repo = self.make_repository('repo1')
1869
src_repo = repository.Repository.open(self.get_url('repo1'))
1870
# At the moment the tarball-based copy_content_into can't write back
1871
# into a smart server. It would be good if it could upload the
1872
# tarball; once that works we'd have to create repositories of
1873
# different formats. -- mbp 20070410
1874
dest_url = self.get_vfs_only_url('repo2')
1875
dest_bzrdir = BzrDir.create(dest_url)
1876
dest_repo = dest_bzrdir.create_repository()
1877
self.assertFalse(isinstance(dest_repo, RemoteRepository))
1878
self.assertTrue(isinstance(src_repo, RemoteRepository))
1879
src_repo.copy_content_into(dest_repo)
1882
class _StubRealPackRepository(object):
1884
def __init__(self, calls):
1885
self._pack_collection = _StubPackCollection(calls)
1888
class _StubPackCollection(object):
1890
def __init__(self, calls):
1894
self.calls.append(('pack collection autopack',))
1896
def reload_pack_names(self):
1897
self.calls.append(('pack collection reload_pack_names',))
1900
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1901
"""Tests for RemoteRepository.autopack implementation."""
1904
"""When the server returns 'ok' and there's no _real_repository, then
1905
nothing else happens: the autopack method is done.
1907
transport_path = 'quack'
1908
repo, client = self.setup_fake_client_and_repository(transport_path)
1909
client.add_expected_call(
1910
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
1912
client.finished_test()
1914
def test_ok_with_real_repo(self):
1915
"""When the server returns 'ok' and there is a _real_repository, then
1916
the _real_repository's reload_pack_name's method will be called.
1918
transport_path = 'quack'
1919
repo, client = self.setup_fake_client_and_repository(transport_path)
1920
client.add_expected_call(
1921
'PackRepository.autopack', ('quack/',),
1923
repo._real_repository = _StubRealPackRepository(client._calls)
1926
[('call', 'PackRepository.autopack', ('quack/',)),
1927
('pack collection reload_pack_names',)],
1930
def test_backwards_compatibility(self):
1931
"""If the server does not recognise the PackRepository.autopack verb,
1932
fallback to the real_repository's implementation.
1934
transport_path = 'quack'
1935
repo, client = self.setup_fake_client_and_repository(transport_path)
1936
client.add_unknown_method_response('PackRepository.autopack')
1937
def stub_ensure_real():
1938
client._calls.append(('_ensure_real',))
1939
repo._real_repository = _StubRealPackRepository(client._calls)
1940
repo._ensure_real = stub_ensure_real
1943
[('call', 'PackRepository.autopack', ('quack/',)),
1945
('pack collection autopack',)],
1949
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
1950
"""Base class for unit tests for bzrlib.remote._translate_error."""
1952
def translateTuple(self, error_tuple, **context):
1953
"""Call _translate_error with an ErrorFromSmartServer built from the
1956
:param error_tuple: A tuple of a smart server response, as would be
1957
passed to an ErrorFromSmartServer.
1958
:kwargs context: context items to call _translate_error with.
1960
:returns: The error raised by _translate_error.
1962
# Raise the ErrorFromSmartServer before passing it as an argument,
1963
# because _translate_error may need to re-raise it with a bare 'raise'
1965
server_error = errors.ErrorFromSmartServer(error_tuple)
1966
translated_error = self.translateErrorFromSmartServer(
1967
server_error, **context)
1968
return translated_error
1970
def translateErrorFromSmartServer(self, error_object, **context):
1971
"""Like translateTuple, but takes an already constructed
1972
ErrorFromSmartServer rather than a tuple.
1976
except errors.ErrorFromSmartServer, server_error:
1977
translated_error = self.assertRaises(
1978
errors.BzrError, remote._translate_error, server_error,
1980
return translated_error
1983
class TestErrorTranslationSuccess(TestErrorTranslationBase):
1984
"""Unit tests for bzrlib.remote._translate_error.
1986
Given an ErrorFromSmartServer (which has an error tuple from a smart
1987
server) and some context, _translate_error raises more specific errors from
1990
This test case covers the cases where _translate_error succeeds in
1991
translating an ErrorFromSmartServer to something better. See
1992
TestErrorTranslationRobustness for other cases.
1995
def test_NoSuchRevision(self):
1996
branch = self.make_branch('')
1998
translated_error = self.translateTuple(
1999
('NoSuchRevision', revid), branch=branch)
2000
expected_error = errors.NoSuchRevision(branch, revid)
2001
self.assertEqual(expected_error, translated_error)
2003
def test_nosuchrevision(self):
2004
repository = self.make_repository('')
2006
translated_error = self.translateTuple(
2007
('nosuchrevision', revid), repository=repository)
2008
expected_error = errors.NoSuchRevision(repository, revid)
2009
self.assertEqual(expected_error, translated_error)
2011
def test_nobranch(self):
2012
bzrdir = self.make_bzrdir('')
2013
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2014
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2015
self.assertEqual(expected_error, translated_error)
2017
def test_LockContention(self):
2018
translated_error = self.translateTuple(('LockContention',))
2019
expected_error = errors.LockContention('(remote lock)')
2020
self.assertEqual(expected_error, translated_error)
2022
def test_UnlockableTransport(self):
2023
bzrdir = self.make_bzrdir('')
2024
translated_error = self.translateTuple(
2025
('UnlockableTransport',), bzrdir=bzrdir)
2026
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2027
self.assertEqual(expected_error, translated_error)
2029
def test_LockFailed(self):
2030
lock = 'str() of a server lock'
2031
why = 'str() of why'
2032
translated_error = self.translateTuple(('LockFailed', lock, why))
2033
expected_error = errors.LockFailed(lock, why)
2034
self.assertEqual(expected_error, translated_error)
2036
def test_TokenMismatch(self):
2037
token = 'a lock token'
2038
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2039
expected_error = errors.TokenMismatch(token, '(remote token)')
2040
self.assertEqual(expected_error, translated_error)
2042
def test_Diverged(self):
2043
branch = self.make_branch('a')
2044
other_branch = self.make_branch('b')
2045
translated_error = self.translateTuple(
2046
('Diverged',), branch=branch, other_branch=other_branch)
2047
expected_error = errors.DivergedBranches(branch, other_branch)
2048
self.assertEqual(expected_error, translated_error)
2050
def test_ReadError_no_args(self):
2052
translated_error = self.translateTuple(('ReadError',), path=path)
2053
expected_error = errors.ReadError(path)
2054
self.assertEqual(expected_error, translated_error)
2056
def test_ReadError(self):
2058
translated_error = self.translateTuple(('ReadError', path))
2059
expected_error = errors.ReadError(path)
2060
self.assertEqual(expected_error, translated_error)
2062
def test_PermissionDenied_no_args(self):
2064
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2065
expected_error = errors.PermissionDenied(path)
2066
self.assertEqual(expected_error, translated_error)
2068
def test_PermissionDenied_one_arg(self):
2070
translated_error = self.translateTuple(('PermissionDenied', path))
2071
expected_error = errors.PermissionDenied(path)
2072
self.assertEqual(expected_error, translated_error)
2074
def test_PermissionDenied_one_arg_and_context(self):
2075
"""Given a choice between a path from the local context and a path on
2076
the wire, _translate_error prefers the path from the local context.
2078
local_path = 'local path'
2079
remote_path = 'remote path'
2080
translated_error = self.translateTuple(
2081
('PermissionDenied', remote_path), path=local_path)
2082
expected_error = errors.PermissionDenied(local_path)
2083
self.assertEqual(expected_error, translated_error)
2085
def test_PermissionDenied_two_args(self):
2087
extra = 'a string with extra info'
2088
translated_error = self.translateTuple(
2089
('PermissionDenied', path, extra))
2090
expected_error = errors.PermissionDenied(path, extra)
2091
self.assertEqual(expected_error, translated_error)
2094
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2095
"""Unit tests for bzrlib.remote._translate_error's robustness.
2097
TestErrorTranslationSuccess is for cases where _translate_error can
2098
translate successfully. This class about how _translate_err behaves when
2099
it fails to translate: it re-raises the original error.
2102
def test_unrecognised_server_error(self):
2103
"""If the error code from the server is not recognised, the original
2104
ErrorFromSmartServer is propagated unmodified.
2106
error_tuple = ('An unknown error tuple',)
2107
server_error = errors.ErrorFromSmartServer(error_tuple)
2108
translated_error = self.translateErrorFromSmartServer(server_error)
2109
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2110
self.assertEqual(expected_error, translated_error)
2112
def test_context_missing_a_key(self):
2113
"""In case of a bug in the client, or perhaps an unexpected response
2114
from a server, _translate_error returns the original error tuple from
2115
the server and mutters a warning.
2117
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2118
# in the context dict. So let's give it an empty context dict instead
2119
# to exercise its error recovery.
2121
error_tuple = ('NoSuchRevision', 'revid')
2122
server_error = errors.ErrorFromSmartServer(error_tuple)
2123
translated_error = self.translateErrorFromSmartServer(server_error)
2124
self.assertEqual(server_error, translated_error)
2125
# In addition to re-raising ErrorFromSmartServer, some debug info has
2126
# been muttered to the log file for developer to look at.
2127
self.assertContainsRe(
2128
self._get_log(keep_log_file=True),
2129
"Missing key 'branch' in context")
2131
def test_path_missing(self):
2132
"""Some translations (PermissionDenied, ReadError) can determine the
2133
'path' variable from either the wire or the local context. If neither
2134
has it, then an error is raised.
2136
error_tuple = ('ReadError',)
2137
server_error = errors.ErrorFromSmartServer(error_tuple)
2138
translated_error = self.translateErrorFromSmartServer(server_error)
2139
self.assertEqual(server_error, translated_error)
2140
# In addition to re-raising ErrorFromSmartServer, some debug info has
2141
# been muttered to the log file for developer to look at.
2142
self.assertContainsRe(
2143
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2146
class TestStacking(tests.TestCaseWithTransport):
2147
"""Tests for operations on stacked remote repositories.
2149
The underlying format type must support stacking.
2152
def test_access_stacked_remote(self):
2153
# based on <http://launchpad.net/bugs/261315>
2154
# make a branch stacked on another repository containing an empty
2155
# revision, then open it over hpss - we should be able to see that
2157
base_transport = self.get_transport()
2158
base_builder = self.make_branch_builder('base', format='1.6')
2159
base_builder.start_series()
2160
base_revid = base_builder.build_snapshot('rev-id', None,
2161
[('add', ('', None, 'directory', None))],
2163
base_builder.finish_series()
2164
stacked_branch = self.make_branch('stacked', format='1.6')
2165
stacked_branch.set_stacked_on_url('../base')
2166
# start a server looking at this
2167
smart_server = server.SmartTCPServer_for_testing()
2168
smart_server.setUp()
2169
self.addCleanup(smart_server.tearDown)
2170
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2171
# can get its branch and repository
2172
remote_branch = remote_bzrdir.open_branch()
2173
remote_repo = remote_branch.repository
2174
remote_repo.lock_read()
2176
# it should have an appropriate fallback repository, which should also
2177
# be a RemoteRepository
2178
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2179
self.assertIsInstance(remote_repo._fallback_repositories[0],
2181
# and it has the revision committed to the underlying repository;
2182
# these have varying implementations so we try several of them
2183
self.assertTrue(remote_repo.has_revisions([base_revid]))
2184
self.assertTrue(remote_repo.has_revision(base_revid))
2185
self.assertEqual(remote_repo.get_revision(base_revid).message,
2188
remote_repo.unlock()
2190
def prepare_stacked_remote_branch(self):
2191
smart_server = server.SmartTCPServer_for_testing()
2192
smart_server.setUp()
2193
self.addCleanup(smart_server.tearDown)
2194
tree1 = self.make_branch_and_tree('tree1')
2195
tree1.commit('rev1', rev_id='rev1')
2196
tree2 = self.make_branch_and_tree('tree2', format='1.6')
2197
tree2.branch.set_stacked_on_url(tree1.branch.base)
2198
branch2 = Branch.open(smart_server.get_url() + '/tree2')
2200
self.addCleanup(branch2.unlock)
2203
def test_stacked_get_parent_map(self):
2204
# the public implementation of get_parent_map obeys stacking
2205
branch = self.prepare_stacked_remote_branch()
2206
repo = branch.repository
2207
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2209
def test_unstacked_get_parent_map(self):
2210
# _unstacked_provider.get_parent_map ignores stacking
2211
branch = self.prepare_stacked_remote_branch()
2212
provider = branch.repository._unstacked_provider
2213
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2216
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2219
super(TestRemoteBranchEffort, self).setUp()
2220
# Create a smart server that publishes whatever the backing VFS server
2222
self.smart_server = server.SmartTCPServer_for_testing()
2223
self.smart_server.setUp(self.get_server())
2224
self.addCleanup(self.smart_server.tearDown)
2225
# Log all HPSS calls into self.hpss_calls.
2226
_SmartClient.hooks.install_named_hook(
2227
'call', self.capture_hpss_call, None)
2228
self.hpss_calls = []
2230
def capture_hpss_call(self, params):
2231
self.hpss_calls.append(params.method)
2233
def test_copy_content_into_avoids_revision_history(self):
2234
local = self.make_branch('local')
2235
remote_backing_tree = self.make_branch_and_tree('remote')
2236
remote_backing_tree.commit("Commit.")
2237
remote_branch_url = self.smart_server.get_url() + 'remote'
2238
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2239
local.repository.fetch(remote_branch.repository)
2240
self.hpss_calls = []
2241
remote_branch.copy_content_into(local)
2242
self.assertFalse('Branch.revision_history' in self.hpss_calls)