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.transport import get_transport, http
55
from bzrlib.transport.memory import MemoryTransport
56
from bzrlib.transport.remote import (
63
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
66
self.transport_server = server.SmartTCPServer_for_testing
67
super(BasicRemoteObjectTests, self).setUp()
68
self.transport = self.get_transport()
69
# make a branch that can be opened over the smart transport
70
self.local_wt = BzrDir.create_standalone_workingtree('.')
73
self.transport.disconnect()
74
tests.TestCaseWithTransport.tearDown(self)
76
def test_create_remote_bzrdir(self):
77
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
78
self.assertIsInstance(b, BzrDir)
80
def test_open_remote_branch(self):
81
# open a standalone branch in the working directory
82
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
83
branch = b.open_branch()
84
self.assertIsInstance(branch, Branch)
86
def test_remote_repository(self):
87
b = BzrDir.open_from_transport(self.transport)
88
repo = b.open_repository()
89
revid = u'\xc823123123'.encode('utf8')
90
self.assertFalse(repo.has_revision(revid))
91
self.local_wt.commit(message='test commit', rev_id=revid)
92
self.assertTrue(repo.has_revision(revid))
94
def test_remote_branch_revision_history(self):
95
b = BzrDir.open_from_transport(self.transport).open_branch()
96
self.assertEqual([], b.revision_history())
97
r1 = self.local_wt.commit('1st commit')
98
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
99
self.assertEqual([r1, r2], b.revision_history())
101
def test_find_correct_format(self):
102
"""Should open a RemoteBzrDir over a RemoteTransport"""
103
fmt = BzrDirFormat.find_format(self.transport)
104
self.assertTrue(RemoteBzrDirFormat
105
in BzrDirFormat._control_server_formats)
106
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
108
def test_open_detected_smart_format(self):
109
fmt = BzrDirFormat.find_format(self.transport)
110
d = fmt.open(self.transport)
111
self.assertIsInstance(d, BzrDir)
113
def test_remote_branch_repr(self):
114
b = BzrDir.open_from_transport(self.transport).open_branch()
115
self.assertStartsWith(str(b), 'RemoteBranch(')
117
def test_remote_branch_format_supports_stacking(self):
119
self.make_branch('unstackable', format='pack-0.92')
120
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
121
self.assertFalse(b._format.supports_stacking())
122
self.make_branch('stackable', format='1.9')
123
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
124
self.assertTrue(b._format.supports_stacking())
127
class FakeProtocol(object):
128
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
130
def __init__(self, body, fake_client):
132
self._body_buffer = None
133
self._fake_client = fake_client
135
def read_body_bytes(self, count=-1):
136
if self._body_buffer is None:
137
self._body_buffer = StringIO(self.body)
138
bytes = self._body_buffer.read(count)
139
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
140
self._fake_client.expecting_body = False
143
def cancel_read_body(self):
144
self._fake_client.expecting_body = False
146
def read_streamed_body(self):
150
class FakeClient(_SmartClient):
151
"""Lookalike for _SmartClient allowing testing."""
153
def __init__(self, fake_medium_base='fake base'):
154
"""Create a FakeClient."""
157
self.expecting_body = False
158
# if non-None, this is the list of expected calls, with only the
159
# method name and arguments included. the body might be hard to
160
# compute so is not included. If a call is None, that call can
162
self._expected_calls = None
163
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
165
def add_expected_call(self, call_name, call_args, response_type,
166
response_args, response_body=None):
167
if self._expected_calls is None:
168
self._expected_calls = []
169
self._expected_calls.append((call_name, call_args))
170
self.responses.append((response_type, response_args, response_body))
172
def add_success_response(self, *args):
173
self.responses.append(('success', args, None))
175
def add_success_response_with_body(self, body, *args):
176
self.responses.append(('success', args, body))
177
if self._expected_calls is not None:
178
self._expected_calls.append(None)
180
def add_error_response(self, *args):
181
self.responses.append(('error', args))
183
def add_unknown_method_response(self, verb):
184
self.responses.append(('unknown', verb))
186
def finished_test(self):
187
if self._expected_calls:
188
raise AssertionError("%r finished but was still expecting %r"
189
% (self, self._expected_calls[0]))
191
def _get_next_response(self):
193
response_tuple = self.responses.pop(0)
194
except IndexError, e:
195
raise AssertionError("%r didn't expect any more calls"
197
if response_tuple[0] == 'unknown':
198
raise errors.UnknownSmartMethod(response_tuple[1])
199
elif response_tuple[0] == 'error':
200
raise errors.ErrorFromSmartServer(response_tuple[1])
201
return response_tuple
203
def _check_call(self, method, args):
204
if self._expected_calls is None:
205
# the test should be updated to say what it expects
208
next_call = self._expected_calls.pop(0)
210
raise AssertionError("%r didn't expect any more calls "
212
% (self, method, args,))
213
if next_call is None:
215
if method != next_call[0] or args != next_call[1]:
216
raise AssertionError("%r expected %r%r "
218
% (self, next_call[0], next_call[1], method, args,))
220
def call(self, method, *args):
221
self._check_call(method, args)
222
self._calls.append(('call', method, args))
223
return self._get_next_response()[1]
225
def call_expecting_body(self, method, *args):
226
self._check_call(method, args)
227
self._calls.append(('call_expecting_body', method, args))
228
result = self._get_next_response()
229
self.expecting_body = True
230
return result[1], FakeProtocol(result[2], self)
232
def call_with_body_bytes_expecting_body(self, method, args, body):
233
self._check_call(method, args)
234
self._calls.append(('call_with_body_bytes_expecting_body', method,
236
result = self._get_next_response()
237
self.expecting_body = True
238
return result[1], FakeProtocol(result[2], self)
240
def call_with_body_stream(self, args, stream):
241
# Explicitly consume the stream before checking for an error, because
242
# that's what happens a real medium.
243
stream = list(stream)
244
self._check_call(args[0], args[1:])
245
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
246
return self._get_next_response()[1]
249
class FakeMedium(medium.SmartClientMedium):
251
def __init__(self, client_calls, base):
252
medium.SmartClientMedium.__init__(self, base)
253
self._client_calls = client_calls
255
def disconnect(self):
256
self._client_calls.append(('disconnect medium',))
259
class TestVfsHas(tests.TestCase):
261
def test_unicode_path(self):
262
client = FakeClient('/')
263
client.add_success_response('yes',)
264
transport = RemoteTransport('bzr://localhost/', _client=client)
265
filename = u'/hell\u00d8'.encode('utf8')
266
result = transport.has(filename)
268
[('call', 'has', (filename,))],
270
self.assertTrue(result)
273
class TestRemote(tests.TestCaseWithMemoryTransport):
275
def get_branch_format(self):
276
reference_bzrdir_format = bzrdir.format_registry.get('default')()
277
return reference_bzrdir_format.get_branch_format()
279
def get_repo_format(self):
280
reference_bzrdir_format = bzrdir.format_registry.get('default')()
281
return reference_bzrdir_format.repository_format
283
def disable_verb(self, verb):
284
"""Disable a verb for one test."""
285
request_handlers = smart.request.request_handlers
286
orig_method = request_handlers.get(verb)
287
request_handlers.remove(verb)
289
request_handlers.register(verb, orig_method)
290
self.addCleanup(restoreVerb)
293
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
294
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
296
def assertRemotePath(self, expected, client_base, transport_base):
297
"""Assert that the result of
298
SmartClientMedium.remote_path_from_transport is the expected value for
299
a given client_base and transport_base.
301
client_medium = medium.SmartClientMedium(client_base)
302
transport = get_transport(transport_base)
303
result = client_medium.remote_path_from_transport(transport)
304
self.assertEqual(expected, result)
306
def test_remote_path_from_transport(self):
307
"""SmartClientMedium.remote_path_from_transport calculates a URL for
308
the given transport relative to the root of the client base URL.
310
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
311
self.assertRemotePath(
312
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
314
def assertRemotePathHTTP(self, expected, transport_base, relpath):
315
"""Assert that the result of
316
HttpTransportBase.remote_path_from_transport is the expected value for
317
a given transport_base and relpath of that transport. (Note that
318
HttpTransportBase is a subclass of SmartClientMedium)
320
base_transport = get_transport(transport_base)
321
client_medium = base_transport.get_smart_medium()
322
cloned_transport = base_transport.clone(relpath)
323
result = client_medium.remote_path_from_transport(cloned_transport)
324
self.assertEqual(expected, result)
326
def test_remote_path_from_transport_http(self):
327
"""Remote paths for HTTP transports are calculated differently to other
328
transports. They are just relative to the client base, not the root
329
directory of the host.
331
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
332
self.assertRemotePathHTTP(
333
'../xyz/', scheme + '//host/path', '../xyz/')
334
self.assertRemotePathHTTP(
335
'xyz/', scheme + '//host/path', 'xyz/')
338
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
339
"""Tests for the behaviour of client_medium.remote_is_at_least."""
341
def test_initially_unlimited(self):
342
"""A fresh medium assumes that the remote side supports all
345
client_medium = medium.SmartClientMedium('dummy base')
346
self.assertFalse(client_medium._is_remote_before((99, 99)))
348
def test__remember_remote_is_before(self):
349
"""Calling _remember_remote_is_before ratchets down the known remote
352
client_medium = medium.SmartClientMedium('dummy base')
353
# Mark the remote side as being less than 1.6. The remote side may
355
client_medium._remember_remote_is_before((1, 6))
356
self.assertTrue(client_medium._is_remote_before((1, 6)))
357
self.assertFalse(client_medium._is_remote_before((1, 5)))
358
# Calling _remember_remote_is_before again with a lower value works.
359
client_medium._remember_remote_is_before((1, 5))
360
self.assertTrue(client_medium._is_remote_before((1, 5)))
361
# You cannot call _remember_remote_is_before with a larger value.
363
AssertionError, client_medium._remember_remote_is_before, (1, 9))
366
class TestBzrDirCloningMetaDir(TestRemote):
368
def test_backwards_compat(self):
369
self.setup_smart_server_with_call_log()
370
a_dir = self.make_bzrdir('.')
371
self.reset_smart_call_log()
372
verb = 'BzrDir.cloning_metadir'
373
self.disable_verb(verb)
374
format = a_dir.cloning_metadir()
375
call_count = len([call for call in self.hpss_calls if
376
call.call.method == verb])
377
self.assertEqual(1, call_count)
379
def test_current_server(self):
380
transport = self.get_transport('.')
381
transport = transport.clone('quack')
382
self.make_bzrdir('quack')
383
client = FakeClient(transport.base)
384
reference_bzrdir_format = bzrdir.format_registry.get('default')()
385
control_name = reference_bzrdir_format.network_name()
386
client.add_expected_call(
387
'BzrDir.cloning_metadir', ('quack/', 'False'),
388
'success', (control_name, '', ('branch', ''))),
389
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
391
result = a_bzrdir.cloning_metadir()
392
# We should have got a reference control dir with default branch and
393
# repository formats.
394
# This pokes a little, just to be sure.
395
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
396
self.assertEqual(None, result._repository_format)
397
self.assertEqual(None, result._branch_format)
398
client.finished_test()
401
class TestBzrDirOpenBranch(TestRemote):
403
def test_backwards_compat(self):
404
self.setup_smart_server_with_call_log()
405
self.make_branch('.')
406
a_dir = BzrDir.open(self.get_url('.'))
407
self.reset_smart_call_log()
408
verb = 'BzrDir.open_branchV2'
409
self.disable_verb(verb)
410
format = a_dir.open_branch()
411
call_count = len([call for call in self.hpss_calls if
412
call.call.method == verb])
413
self.assertEqual(1, call_count)
415
def test_branch_present(self):
416
reference_format = self.get_repo_format()
417
network_name = reference_format.network_name()
418
branch_network_name = self.get_branch_format().network_name()
419
transport = MemoryTransport()
420
transport.mkdir('quack')
421
transport = transport.clone('quack')
422
client = FakeClient(transport.base)
423
client.add_expected_call(
424
'BzrDir.open_branchV2', ('quack/',),
425
'success', ('branch', branch_network_name))
426
client.add_expected_call(
427
'BzrDir.find_repositoryV3', ('quack/',),
428
'success', ('ok', '', 'no', 'no', 'no', network_name))
429
client.add_expected_call(
430
'Branch.get_stacked_on_url', ('quack/',),
431
'error', ('NotStacked',))
432
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
434
result = bzrdir.open_branch()
435
self.assertIsInstance(result, RemoteBranch)
436
self.assertEqual(bzrdir, result.bzrdir)
437
client.finished_test()
439
def test_branch_missing(self):
440
transport = MemoryTransport()
441
transport.mkdir('quack')
442
transport = transport.clone('quack')
443
client = FakeClient(transport.base)
444
client.add_error_response('nobranch')
445
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
447
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
449
[('call', 'BzrDir.open_branchV2', ('quack/',))],
452
def test__get_tree_branch(self):
453
# _get_tree_branch is a form of open_branch, but it should only ask for
454
# branch opening, not any other network requests.
457
calls.append("Called")
459
transport = MemoryTransport()
460
# no requests on the network - catches other api calls being made.
461
client = FakeClient(transport.base)
462
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
464
# patch the open_branch call to record that it was called.
465
bzrdir.open_branch = open_branch
466
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
467
self.assertEqual(["Called"], calls)
468
self.assertEqual([], client._calls)
470
def test_url_quoting_of_path(self):
471
# Relpaths on the wire should not be URL-escaped. So "~" should be
472
# transmitted as "~", not "%7E".
473
transport = RemoteTCPTransport('bzr://localhost/~hello/')
474
client = FakeClient(transport.base)
475
reference_format = self.get_repo_format()
476
network_name = reference_format.network_name()
477
branch_network_name = self.get_branch_format().network_name()
478
client.add_expected_call(
479
'BzrDir.open_branchV2', ('~hello/',),
480
'success', ('branch', branch_network_name))
481
client.add_expected_call(
482
'BzrDir.find_repositoryV3', ('~hello/',),
483
'success', ('ok', '', 'no', 'no', 'no', network_name))
484
client.add_expected_call(
485
'Branch.get_stacked_on_url', ('~hello/',),
486
'error', ('NotStacked',))
487
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
489
result = bzrdir.open_branch()
490
client.finished_test()
492
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
493
reference_format = self.get_repo_format()
494
network_name = reference_format.network_name()
495
transport = MemoryTransport()
496
transport.mkdir('quack')
497
transport = transport.clone('quack')
499
rich_response = 'yes'
503
subtree_response = 'yes'
505
subtree_response = 'no'
506
client = FakeClient(transport.base)
507
client.add_success_response(
508
'ok', '', rich_response, subtree_response, external_lookup,
510
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
512
result = bzrdir.open_repository()
514
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
516
self.assertIsInstance(result, RemoteRepository)
517
self.assertEqual(bzrdir, result.bzrdir)
518
self.assertEqual(rich_root, result._format.rich_root_data)
519
self.assertEqual(subtrees, result._format.supports_tree_reference)
521
def test_open_repository_sets_format_attributes(self):
522
self.check_open_repository(True, True)
523
self.check_open_repository(False, True)
524
self.check_open_repository(True, False)
525
self.check_open_repository(False, False)
526
self.check_open_repository(False, False, 'yes')
528
def test_old_server(self):
529
"""RemoteBzrDirFormat should fail to probe if the server version is too
532
self.assertRaises(errors.NotBranchError,
533
RemoteBzrDirFormat.probe_transport, OldServerTransport())
536
class TestBzrDirCreateBranch(TestRemote):
538
def test_backwards_compat(self):
539
self.setup_smart_server_with_call_log()
540
repo = self.make_repository('.')
541
self.reset_smart_call_log()
542
self.disable_verb('BzrDir.create_branch')
543
branch = repo.bzrdir.create_branch()
544
create_branch_call_count = len([call for call in self.hpss_calls if
545
call.call.method == 'BzrDir.create_branch'])
546
self.assertEqual(1, create_branch_call_count)
548
def test_current_server(self):
549
transport = self.get_transport('.')
550
transport = transport.clone('quack')
551
self.make_repository('quack')
552
client = FakeClient(transport.base)
553
reference_bzrdir_format = bzrdir.format_registry.get('default')()
554
reference_format = reference_bzrdir_format.get_branch_format()
555
network_name = reference_format.network_name()
556
reference_repo_fmt = reference_bzrdir_format.repository_format
557
reference_repo_name = reference_repo_fmt.network_name()
558
client.add_expected_call(
559
'BzrDir.create_branch', ('quack/', network_name),
560
'success', ('ok', network_name, '', 'no', 'no', 'yes',
561
reference_repo_name))
562
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
564
branch = a_bzrdir.create_branch()
565
# We should have got a remote branch
566
self.assertIsInstance(branch, remote.RemoteBranch)
567
# its format should have the settings from the response
568
format = branch._format
569
self.assertEqual(network_name, format.network_name())
572
class TestBzrDirCreateRepository(TestRemote):
574
def test_backwards_compat(self):
575
self.setup_smart_server_with_call_log()
576
bzrdir = self.make_bzrdir('.')
577
self.reset_smart_call_log()
578
self.disable_verb('BzrDir.create_repository')
579
repo = bzrdir.create_repository()
580
create_repo_call_count = len([call for call in self.hpss_calls if
581
call.call.method == 'BzrDir.create_repository'])
582
self.assertEqual(1, create_repo_call_count)
584
def test_current_server(self):
585
transport = self.get_transport('.')
586
transport = transport.clone('quack')
587
self.make_bzrdir('quack')
588
client = FakeClient(transport.base)
589
reference_bzrdir_format = bzrdir.format_registry.get('default')()
590
reference_format = reference_bzrdir_format.repository_format
591
network_name = reference_format.network_name()
592
client.add_expected_call(
593
'BzrDir.create_repository', ('quack/',
594
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
595
'success', ('ok', 'no', 'no', 'no', network_name))
596
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
598
repo = a_bzrdir.create_repository()
599
# We should have got a remote repository
600
self.assertIsInstance(repo, remote.RemoteRepository)
601
# its format should have the settings from the response
602
format = repo._format
603
self.assertFalse(format.rich_root_data)
604
self.assertFalse(format.supports_tree_reference)
605
self.assertFalse(format.supports_external_lookups)
606
self.assertEqual(network_name, format.network_name())
609
class TestBzrDirOpenRepository(TestRemote):
611
def test_backwards_compat_1_2_3(self):
612
# fallback all the way to the first version.
613
reference_format = self.get_repo_format()
614
network_name = reference_format.network_name()
615
client = FakeClient('bzr://example.com/')
616
client.add_unknown_method_response('BzrDir.find_repositoryV3')
617
client.add_unknown_method_response('BzrDir.find_repositoryV2')
618
client.add_success_response('ok', '', 'no', 'no')
619
# A real repository instance will be created to determine the network
621
client.add_success_response_with_body(
622
"Bazaar-NG meta directory, format 1\n", 'ok')
623
client.add_success_response_with_body(
624
reference_format.get_format_string(), 'ok')
625
# PackRepository wants to do a stat
626
client.add_success_response('stat', '0', '65535')
627
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
629
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
631
repo = bzrdir.open_repository()
633
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
634
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
635
('call', 'BzrDir.find_repository', ('quack/',)),
636
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
637
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
638
('call', 'stat', ('/quack/.bzr/repository',)),
641
self.assertEqual(network_name, repo._format.network_name())
643
def test_backwards_compat_2(self):
644
# fallback to find_repositoryV2
645
reference_format = self.get_repo_format()
646
network_name = reference_format.network_name()
647
client = FakeClient('bzr://example.com/')
648
client.add_unknown_method_response('BzrDir.find_repositoryV3')
649
client.add_success_response('ok', '', 'no', 'no', 'no')
650
# A real repository instance will be created to determine the network
652
client.add_success_response_with_body(
653
"Bazaar-NG meta directory, format 1\n", 'ok')
654
client.add_success_response_with_body(
655
reference_format.get_format_string(), 'ok')
656
# PackRepository wants to do a stat
657
client.add_success_response('stat', '0', '65535')
658
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
660
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
662
repo = bzrdir.open_repository()
664
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
665
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
666
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
667
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
668
('call', 'stat', ('/quack/.bzr/repository',)),
671
self.assertEqual(network_name, repo._format.network_name())
673
def test_current_server(self):
674
reference_format = self.get_repo_format()
675
network_name = reference_format.network_name()
676
transport = MemoryTransport()
677
transport.mkdir('quack')
678
transport = transport.clone('quack')
679
client = FakeClient(transport.base)
680
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
681
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
683
repo = bzrdir.open_repository()
685
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
687
self.assertEqual(network_name, repo._format.network_name())
690
class OldSmartClient(object):
691
"""A fake smart client for test_old_version that just returns a version one
692
response to the 'hello' (query version) command.
695
def get_request(self):
696
input_file = StringIO('ok\x011\n')
697
output_file = StringIO()
698
client_medium = medium.SmartSimplePipesClientMedium(
699
input_file, output_file)
700
return medium.SmartClientStreamMediumRequest(client_medium)
702
def protocol_version(self):
706
class OldServerTransport(object):
707
"""A fake transport for test_old_server that reports it's smart server
708
protocol version as version one.
714
def get_smart_client(self):
715
return OldSmartClient()
718
class RemoteBranchTestCase(TestRemote):
720
def make_remote_branch(self, transport, client):
721
"""Make a RemoteBranch using 'client' as its _SmartClient.
723
A RemoteBzrDir and RemoteRepository will also be created to fill out
724
the RemoteBranch, albeit with stub values for some of their attributes.
726
# we do not want bzrdir to make any remote calls, so use False as its
727
# _client. If it tries to make a remote call, this will fail
729
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
731
repo = RemoteRepository(bzrdir, None, _client=client)
732
branch_format = self.get_branch_format()
733
format = RemoteBranchFormat(network_name=branch_format.network_name())
734
return RemoteBranch(bzrdir, repo, _client=client, format=format)
737
class TestBranchGetParent(RemoteBranchTestCase):
739
def test_no_parent(self):
740
# in an empty branch we decode the response properly
741
transport = MemoryTransport()
742
client = FakeClient(transport.base)
743
client.add_expected_call(
744
'Branch.get_stacked_on_url', ('quack/',),
745
'error', ('NotStacked',))
746
client.add_expected_call(
747
'Branch.get_parent', ('quack/',),
749
transport.mkdir('quack')
750
transport = transport.clone('quack')
751
branch = self.make_remote_branch(transport, client)
752
result = branch.get_parent()
753
client.finished_test()
754
self.assertEqual(None, result)
756
def test_parent_relative(self):
757
transport = MemoryTransport()
758
client = FakeClient(transport.base)
759
client.add_expected_call(
760
'Branch.get_stacked_on_url', ('kwaak/',),
761
'error', ('NotStacked',))
762
client.add_expected_call(
763
'Branch.get_parent', ('kwaak/',),
764
'success', ('../foo/',))
765
transport.mkdir('kwaak')
766
transport = transport.clone('kwaak')
767
branch = self.make_remote_branch(transport, client)
768
result = branch.get_parent()
769
self.assertEqual(transport.clone('../foo').base, result)
771
def test_parent_absolute(self):
772
transport = MemoryTransport()
773
client = FakeClient(transport.base)
774
client.add_expected_call(
775
'Branch.get_stacked_on_url', ('kwaak/',),
776
'error', ('NotStacked',))
777
client.add_expected_call(
778
'Branch.get_parent', ('kwaak/',),
779
'success', ('http://foo/',))
780
transport.mkdir('kwaak')
781
transport = transport.clone('kwaak')
782
branch = self.make_remote_branch(transport, client)
783
result = branch.get_parent()
784
self.assertEqual('http://foo/', result)
787
class TestBranchGetTagsBytes(RemoteBranchTestCase):
789
def test_backwards_compat(self):
790
self.setup_smart_server_with_call_log()
791
branch = self.make_branch('.')
792
self.reset_smart_call_log()
793
verb = 'Branch.get_tags_bytes'
794
self.disable_verb(verb)
795
branch.tags.get_tag_dict()
796
call_count = len([call for call in self.hpss_calls if
797
call.call.method == verb])
798
self.assertEqual(1, call_count)
800
def test_trivial(self):
801
transport = MemoryTransport()
802
client = FakeClient(transport.base)
803
client.add_expected_call(
804
'Branch.get_stacked_on_url', ('quack/',),
805
'error', ('NotStacked',))
806
client.add_expected_call(
807
'Branch.get_tags_bytes', ('quack/',),
809
transport.mkdir('quack')
810
transport = transport.clone('quack')
811
branch = self.make_remote_branch(transport, client)
812
result = branch.tags.get_tag_dict()
813
client.finished_test()
814
self.assertEqual({}, result)
817
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
819
def test_empty_branch(self):
820
# in an empty branch we decode the response properly
821
transport = MemoryTransport()
822
client = FakeClient(transport.base)
823
client.add_expected_call(
824
'Branch.get_stacked_on_url', ('quack/',),
825
'error', ('NotStacked',))
826
client.add_expected_call(
827
'Branch.last_revision_info', ('quack/',),
828
'success', ('ok', '0', 'null:'))
829
transport.mkdir('quack')
830
transport = transport.clone('quack')
831
branch = self.make_remote_branch(transport, client)
832
result = branch.last_revision_info()
833
client.finished_test()
834
self.assertEqual((0, NULL_REVISION), result)
836
def test_non_empty_branch(self):
837
# in a non-empty branch we also decode the response properly
838
revid = u'\xc8'.encode('utf8')
839
transport = MemoryTransport()
840
client = FakeClient(transport.base)
841
client.add_expected_call(
842
'Branch.get_stacked_on_url', ('kwaak/',),
843
'error', ('NotStacked',))
844
client.add_expected_call(
845
'Branch.last_revision_info', ('kwaak/',),
846
'success', ('ok', '2', revid))
847
transport.mkdir('kwaak')
848
transport = transport.clone('kwaak')
849
branch = self.make_remote_branch(transport, client)
850
result = branch.last_revision_info()
851
self.assertEqual((2, revid), result)
854
class TestBranch_get_stacked_on_url(TestRemote):
855
"""Test Branch._get_stacked_on_url rpc"""
857
def test_get_stacked_on_invalid_url(self):
858
# test that asking for a stacked on url the server can't access works.
859
# This isn't perfect, but then as we're in the same process there
860
# really isn't anything we can do to be 100% sure that the server
861
# doesn't just open in - this test probably needs to be rewritten using
862
# a spawn()ed server.
863
stacked_branch = self.make_branch('stacked', format='1.9')
864
memory_branch = self.make_branch('base', format='1.9')
865
vfs_url = self.get_vfs_only_url('base')
866
stacked_branch.set_stacked_on_url(vfs_url)
867
transport = stacked_branch.bzrdir.root_transport
868
client = FakeClient(transport.base)
869
client.add_expected_call(
870
'Branch.get_stacked_on_url', ('stacked/',),
871
'success', ('ok', vfs_url))
872
# XXX: Multiple calls are bad, this second call documents what is
874
client.add_expected_call(
875
'Branch.get_stacked_on_url', ('stacked/',),
876
'success', ('ok', vfs_url))
877
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
879
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, None),
881
result = branch.get_stacked_on_url()
882
self.assertEqual(vfs_url, result)
884
def test_backwards_compatible(self):
885
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
886
base_branch = self.make_branch('base', format='1.6')
887
stacked_branch = self.make_branch('stacked', format='1.6')
888
stacked_branch.set_stacked_on_url('../base')
889
client = FakeClient(self.get_url())
890
branch_network_name = self.get_branch_format().network_name()
891
client.add_expected_call(
892
'BzrDir.open_branchV2', ('stacked/',),
893
'success', ('branch', branch_network_name))
894
client.add_expected_call(
895
'BzrDir.find_repositoryV3', ('stacked/',),
896
'success', ('ok', '', 'no', 'no', 'no',
897
stacked_branch.repository._format.network_name()))
898
# called twice, once from constructor and then again by us
899
client.add_expected_call(
900
'Branch.get_stacked_on_url', ('stacked/',),
901
'unknown', ('Branch.get_stacked_on_url',))
902
client.add_expected_call(
903
'Branch.get_stacked_on_url', ('stacked/',),
904
'unknown', ('Branch.get_stacked_on_url',))
905
# this will also do vfs access, but that goes direct to the transport
906
# and isn't seen by the FakeClient.
907
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
908
remote.RemoteBzrDirFormat(), _client=client)
909
branch = bzrdir.open_branch()
910
result = branch.get_stacked_on_url()
911
self.assertEqual('../base', result)
912
client.finished_test()
913
# it's in the fallback list both for the RemoteRepository and its vfs
915
self.assertEqual(1, len(branch.repository._fallback_repositories))
917
len(branch.repository._real_repository._fallback_repositories))
919
def test_get_stacked_on_real_branch(self):
920
base_branch = self.make_branch('base', format='1.6')
921
stacked_branch = self.make_branch('stacked', format='1.6')
922
stacked_branch.set_stacked_on_url('../base')
923
reference_format = self.get_repo_format()
924
network_name = reference_format.network_name()
925
client = FakeClient(self.get_url())
926
branch_network_name = self.get_branch_format().network_name()
927
client.add_expected_call(
928
'BzrDir.open_branchV2', ('stacked/',),
929
'success', ('branch', branch_network_name))
930
client.add_expected_call(
931
'BzrDir.find_repositoryV3', ('stacked/',),
932
'success', ('ok', '', 'no', 'no', 'no', network_name))
933
# called twice, once from constructor and then again by us
934
client.add_expected_call(
935
'Branch.get_stacked_on_url', ('stacked/',),
936
'success', ('ok', '../base'))
937
client.add_expected_call(
938
'Branch.get_stacked_on_url', ('stacked/',),
939
'success', ('ok', '../base'))
940
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
941
remote.RemoteBzrDirFormat(), _client=client)
942
branch = bzrdir.open_branch()
943
result = branch.get_stacked_on_url()
944
self.assertEqual('../base', result)
945
client.finished_test()
946
# it's in the fallback list both for the RemoteRepository and its vfs
948
self.assertEqual(1, len(branch.repository._fallback_repositories))
950
len(branch.repository._real_repository._fallback_repositories))
953
class TestBranchSetLastRevision(RemoteBranchTestCase):
955
def test_set_empty(self):
956
# set_revision_history([]) is translated to calling
957
# Branch.set_last_revision(path, '') on the wire.
958
transport = MemoryTransport()
959
transport.mkdir('branch')
960
transport = transport.clone('branch')
962
client = FakeClient(transport.base)
963
client.add_expected_call(
964
'Branch.get_stacked_on_url', ('branch/',),
965
'error', ('NotStacked',))
966
client.add_expected_call(
967
'Branch.lock_write', ('branch/', '', ''),
968
'success', ('ok', 'branch token', 'repo token'))
969
client.add_expected_call(
970
'Branch.last_revision_info',
972
'success', ('ok', '0', 'null:'))
973
client.add_expected_call(
974
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
976
client.add_expected_call(
977
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
979
branch = self.make_remote_branch(transport, client)
980
# This is a hack to work around the problem that RemoteBranch currently
981
# unnecessarily invokes _ensure_real upon a call to lock_write.
982
branch._ensure_real = lambda: None
984
result = branch.set_revision_history([])
986
self.assertEqual(None, result)
987
client.finished_test()
989
def test_set_nonempty(self):
990
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
991
# Branch.set_last_revision(path, rev-idN) on the wire.
992
transport = MemoryTransport()
993
transport.mkdir('branch')
994
transport = transport.clone('branch')
996
client = FakeClient(transport.base)
997
client.add_expected_call(
998
'Branch.get_stacked_on_url', ('branch/',),
999
'error', ('NotStacked',))
1000
client.add_expected_call(
1001
'Branch.lock_write', ('branch/', '', ''),
1002
'success', ('ok', 'branch token', 'repo token'))
1003
client.add_expected_call(
1004
'Branch.last_revision_info',
1006
'success', ('ok', '0', 'null:'))
1008
encoded_body = bz2.compress('\n'.join(lines))
1009
client.add_success_response_with_body(encoded_body, 'ok')
1010
client.add_expected_call(
1011
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1013
client.add_expected_call(
1014
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1016
branch = self.make_remote_branch(transport, client)
1017
# This is a hack to work around the problem that RemoteBranch currently
1018
# unnecessarily invokes _ensure_real upon a call to lock_write.
1019
branch._ensure_real = lambda: None
1020
# Lock the branch, reset the record of remote calls.
1022
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1024
self.assertEqual(None, result)
1025
client.finished_test()
1027
def test_no_such_revision(self):
1028
transport = MemoryTransport()
1029
transport.mkdir('branch')
1030
transport = transport.clone('branch')
1031
# A response of 'NoSuchRevision' is translated into an exception.
1032
client = FakeClient(transport.base)
1033
client.add_expected_call(
1034
'Branch.get_stacked_on_url', ('branch/',),
1035
'error', ('NotStacked',))
1036
client.add_expected_call(
1037
'Branch.lock_write', ('branch/', '', ''),
1038
'success', ('ok', 'branch token', 'repo token'))
1039
client.add_expected_call(
1040
'Branch.last_revision_info',
1042
'success', ('ok', '0', 'null:'))
1043
# get_graph calls to construct the revision history, for the set_rh
1046
encoded_body = bz2.compress('\n'.join(lines))
1047
client.add_success_response_with_body(encoded_body, 'ok')
1048
client.add_expected_call(
1049
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1050
'error', ('NoSuchRevision', 'rev-id'))
1051
client.add_expected_call(
1052
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1055
branch = self.make_remote_branch(transport, client)
1058
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1060
client.finished_test()
1062
def test_tip_change_rejected(self):
1063
"""TipChangeRejected responses cause a TipChangeRejected exception to
1066
transport = MemoryTransport()
1067
transport.mkdir('branch')
1068
transport = transport.clone('branch')
1069
client = FakeClient(transport.base)
1070
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1071
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1072
client.add_expected_call(
1073
'Branch.get_stacked_on_url', ('branch/',),
1074
'error', ('NotStacked',))
1075
client.add_expected_call(
1076
'Branch.lock_write', ('branch/', '', ''),
1077
'success', ('ok', 'branch token', 'repo token'))
1078
client.add_expected_call(
1079
'Branch.last_revision_info',
1081
'success', ('ok', '0', 'null:'))
1083
encoded_body = bz2.compress('\n'.join(lines))
1084
client.add_success_response_with_body(encoded_body, 'ok')
1085
client.add_expected_call(
1086
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1087
'error', ('TipChangeRejected', rejection_msg_utf8))
1088
client.add_expected_call(
1089
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1091
branch = self.make_remote_branch(transport, client)
1092
branch._ensure_real = lambda: None
1094
# The 'TipChangeRejected' error response triggered by calling
1095
# set_revision_history causes a TipChangeRejected exception.
1096
err = self.assertRaises(
1097
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1098
# The UTF-8 message from the response has been decoded into a unicode
1100
self.assertIsInstance(err.msg, unicode)
1101
self.assertEqual(rejection_msg_unicode, err.msg)
1103
client.finished_test()
1106
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1108
def test_set_last_revision_info(self):
1109
# set_last_revision_info(num, 'rev-id') is translated to calling
1110
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1111
transport = MemoryTransport()
1112
transport.mkdir('branch')
1113
transport = transport.clone('branch')
1114
client = FakeClient(transport.base)
1115
# get_stacked_on_url
1116
client.add_error_response('NotStacked')
1118
client.add_success_response('ok', 'branch token', 'repo token')
1119
# query the current revision
1120
client.add_success_response('ok', '0', 'null:')
1122
client.add_success_response('ok')
1124
client.add_success_response('ok')
1126
branch = self.make_remote_branch(transport, client)
1127
# Lock the branch, reset the record of remote calls.
1130
result = branch.set_last_revision_info(1234, 'a-revision-id')
1132
[('call', 'Branch.last_revision_info', ('branch/',)),
1133
('call', 'Branch.set_last_revision_info',
1134
('branch/', 'branch token', 'repo token',
1135
'1234', 'a-revision-id'))],
1137
self.assertEqual(None, result)
1139
def test_no_such_revision(self):
1140
# A response of 'NoSuchRevision' is translated into an exception.
1141
transport = MemoryTransport()
1142
transport.mkdir('branch')
1143
transport = transport.clone('branch')
1144
client = FakeClient(transport.base)
1145
# get_stacked_on_url
1146
client.add_error_response('NotStacked')
1148
client.add_success_response('ok', 'branch token', 'repo token')
1150
client.add_error_response('NoSuchRevision', 'revid')
1152
client.add_success_response('ok')
1154
branch = self.make_remote_branch(transport, client)
1155
# Lock the branch, reset the record of remote calls.
1160
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1163
def lock_remote_branch(self, branch):
1164
"""Trick a RemoteBranch into thinking it is locked."""
1165
branch._lock_mode = 'w'
1166
branch._lock_count = 2
1167
branch._lock_token = 'branch token'
1168
branch._repo_lock_token = 'repo token'
1169
branch.repository._lock_mode = 'w'
1170
branch.repository._lock_count = 2
1171
branch.repository._lock_token = 'repo token'
1173
def test_backwards_compatibility(self):
1174
"""If the server does not support the Branch.set_last_revision_info
1175
verb (which is new in 1.4), then the client falls back to VFS methods.
1177
# This test is a little messy. Unlike most tests in this file, it
1178
# doesn't purely test what a Remote* object sends over the wire, and
1179
# how it reacts to responses from the wire. It instead relies partly
1180
# on asserting that the RemoteBranch will call
1181
# self._real_branch.set_last_revision_info(...).
1183
# First, set up our RemoteBranch with a FakeClient that raises
1184
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1185
transport = MemoryTransport()
1186
transport.mkdir('branch')
1187
transport = transport.clone('branch')
1188
client = FakeClient(transport.base)
1189
client.add_expected_call(
1190
'Branch.get_stacked_on_url', ('branch/',),
1191
'error', ('NotStacked',))
1192
client.add_expected_call(
1193
'Branch.last_revision_info',
1195
'success', ('ok', '0', 'null:'))
1196
client.add_expected_call(
1197
'Branch.set_last_revision_info',
1198
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1199
'unknown', 'Branch.set_last_revision_info')
1201
branch = self.make_remote_branch(transport, client)
1202
class StubRealBranch(object):
1205
def set_last_revision_info(self, revno, revision_id):
1207
('set_last_revision_info', revno, revision_id))
1208
def _clear_cached_state(self):
1210
real_branch = StubRealBranch()
1211
branch._real_branch = real_branch
1212
self.lock_remote_branch(branch)
1214
# Call set_last_revision_info, and verify it behaved as expected.
1215
result = branch.set_last_revision_info(1234, 'a-revision-id')
1217
[('set_last_revision_info', 1234, 'a-revision-id')],
1219
client.finished_test()
1221
def test_unexpected_error(self):
1222
# If the server sends an error the client doesn't understand, it gets
1223
# turned into an UnknownErrorFromSmartServer, which is presented as a
1224
# non-internal error to the user.
1225
transport = MemoryTransport()
1226
transport.mkdir('branch')
1227
transport = transport.clone('branch')
1228
client = FakeClient(transport.base)
1229
# get_stacked_on_url
1230
client.add_error_response('NotStacked')
1232
client.add_success_response('ok', 'branch token', 'repo token')
1234
client.add_error_response('UnexpectedError')
1236
client.add_success_response('ok')
1238
branch = self.make_remote_branch(transport, client)
1239
# Lock the branch, reset the record of remote calls.
1243
err = self.assertRaises(
1244
errors.UnknownErrorFromSmartServer,
1245
branch.set_last_revision_info, 123, 'revid')
1246
self.assertEqual(('UnexpectedError',), err.error_tuple)
1249
def test_tip_change_rejected(self):
1250
"""TipChangeRejected responses cause a TipChangeRejected exception to
1253
transport = MemoryTransport()
1254
transport.mkdir('branch')
1255
transport = transport.clone('branch')
1256
client = FakeClient(transport.base)
1257
# get_stacked_on_url
1258
client.add_error_response('NotStacked')
1260
client.add_success_response('ok', 'branch token', 'repo token')
1262
client.add_error_response('TipChangeRejected', 'rejection message')
1264
client.add_success_response('ok')
1266
branch = self.make_remote_branch(transport, client)
1267
# Lock the branch, reset the record of remote calls.
1269
self.addCleanup(branch.unlock)
1272
# The 'TipChangeRejected' error response triggered by calling
1273
# set_last_revision_info causes a TipChangeRejected exception.
1274
err = self.assertRaises(
1275
errors.TipChangeRejected,
1276
branch.set_last_revision_info, 123, 'revid')
1277
self.assertEqual('rejection message', err.msg)
1280
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1281
"""Getting the branch configuration should use an abstract method not vfs.
1284
def test_get_branch_conf(self):
1285
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1286
## # We should see that branch.get_config() does a single rpc to get the
1287
## # remote configuration file, abstracting away where that is stored on
1288
## # the server. However at the moment it always falls back to using the
1289
## # vfs, and this would need some changes in config.py.
1291
## # in an empty branch we decode the response properly
1292
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1293
## # we need to make a real branch because the remote_branch.control_files
1294
## # will trigger _ensure_real.
1295
## branch = self.make_branch('quack')
1296
## transport = branch.bzrdir.root_transport
1297
## # we do not want bzrdir to make any remote calls
1298
## bzrdir = RemoteBzrDir(transport, _client=False)
1299
## branch = RemoteBranch(bzrdir, None, _client=client)
1300
## config = branch.get_config()
1301
## self.assertEqual(
1302
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1306
class TestBranchLockWrite(RemoteBranchTestCase):
1308
def test_lock_write_unlockable(self):
1309
transport = MemoryTransport()
1310
client = FakeClient(transport.base)
1311
client.add_expected_call(
1312
'Branch.get_stacked_on_url', ('quack/',),
1313
'error', ('NotStacked',),)
1314
client.add_expected_call(
1315
'Branch.lock_write', ('quack/', '', ''),
1316
'error', ('UnlockableTransport',))
1317
transport.mkdir('quack')
1318
transport = transport.clone('quack')
1319
branch = self.make_remote_branch(transport, client)
1320
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1321
client.finished_test()
1324
class TestTransportIsReadonly(tests.TestCase):
1326
def test_true(self):
1327
client = FakeClient()
1328
client.add_success_response('yes')
1329
transport = RemoteTransport('bzr://example.com/', medium=False,
1331
self.assertEqual(True, transport.is_readonly())
1333
[('call', 'Transport.is_readonly', ())],
1336
def test_false(self):
1337
client = FakeClient()
1338
client.add_success_response('no')
1339
transport = RemoteTransport('bzr://example.com/', medium=False,
1341
self.assertEqual(False, transport.is_readonly())
1343
[('call', 'Transport.is_readonly', ())],
1346
def test_error_from_old_server(self):
1347
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1349
Clients should treat it as a "no" response, because is_readonly is only
1350
advisory anyway (a transport could be read-write, but then the
1351
underlying filesystem could be readonly anyway).
1353
client = FakeClient()
1354
client.add_unknown_method_response('Transport.is_readonly')
1355
transport = RemoteTransport('bzr://example.com/', medium=False,
1357
self.assertEqual(False, transport.is_readonly())
1359
[('call', 'Transport.is_readonly', ())],
1363
class TestTransportMkdir(tests.TestCase):
1365
def test_permissiondenied(self):
1366
client = FakeClient()
1367
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1368
transport = RemoteTransport('bzr://example.com/', medium=False,
1370
exc = self.assertRaises(
1371
errors.PermissionDenied, transport.mkdir, 'client path')
1372
expected_error = errors.PermissionDenied('/client path', 'extra')
1373
self.assertEqual(expected_error, exc)
1376
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1378
def test_defaults_to_none(self):
1379
t = RemoteSSHTransport('bzr+ssh://example.com')
1380
self.assertIs(None, t._get_credentials()[0])
1382
def test_uses_authentication_config(self):
1383
conf = config.AuthenticationConfig()
1384
conf._get_config().update(
1385
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1388
t = RemoteSSHTransport('bzr+ssh://example.com')
1389
self.assertEqual('bar', t._get_credentials()[0])
1392
class TestRemoteRepository(TestRemote):
1393
"""Base for testing RemoteRepository protocol usage.
1395
These tests contain frozen requests and responses. We want any changes to
1396
what is sent or expected to be require a thoughtful update to these tests
1397
because they might break compatibility with different-versioned servers.
1400
def setup_fake_client_and_repository(self, transport_path):
1401
"""Create the fake client and repository for testing with.
1403
There's no real server here; we just have canned responses sent
1406
:param transport_path: Path below the root of the MemoryTransport
1407
where the repository will be created.
1409
transport = MemoryTransport()
1410
transport.mkdir(transport_path)
1411
client = FakeClient(transport.base)
1412
transport = transport.clone(transport_path)
1413
# we do not want bzrdir to make any remote calls
1414
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1416
repo = RemoteRepository(bzrdir, None, _client=client)
1420
class TestRepositoryGatherStats(TestRemoteRepository):
1422
def test_revid_none(self):
1423
# ('ok',), body with revisions and size
1424
transport_path = 'quack'
1425
repo, client = self.setup_fake_client_and_repository(transport_path)
1426
client.add_success_response_with_body(
1427
'revisions: 2\nsize: 18\n', 'ok')
1428
result = repo.gather_stats(None)
1430
[('call_expecting_body', 'Repository.gather_stats',
1431
('quack/','','no'))],
1433
self.assertEqual({'revisions': 2, 'size': 18}, result)
1435
def test_revid_no_committers(self):
1436
# ('ok',), body without committers
1437
body = ('firstrev: 123456.300 3600\n'
1438
'latestrev: 654231.400 0\n'
1441
transport_path = 'quick'
1442
revid = u'\xc8'.encode('utf8')
1443
repo, client = self.setup_fake_client_and_repository(transport_path)
1444
client.add_success_response_with_body(body, 'ok')
1445
result = repo.gather_stats(revid)
1447
[('call_expecting_body', 'Repository.gather_stats',
1448
('quick/', revid, 'no'))],
1450
self.assertEqual({'revisions': 2, 'size': 18,
1451
'firstrev': (123456.300, 3600),
1452
'latestrev': (654231.400, 0),},
1455
def test_revid_with_committers(self):
1456
# ('ok',), body with committers
1457
body = ('committers: 128\n'
1458
'firstrev: 123456.300 3600\n'
1459
'latestrev: 654231.400 0\n'
1462
transport_path = 'buick'
1463
revid = u'\xc8'.encode('utf8')
1464
repo, client = self.setup_fake_client_and_repository(transport_path)
1465
client.add_success_response_with_body(body, 'ok')
1466
result = repo.gather_stats(revid, True)
1468
[('call_expecting_body', 'Repository.gather_stats',
1469
('buick/', revid, 'yes'))],
1471
self.assertEqual({'revisions': 2, 'size': 18,
1473
'firstrev': (123456.300, 3600),
1474
'latestrev': (654231.400, 0),},
1478
class TestRepositoryGetGraph(TestRemoteRepository):
1480
def test_get_graph(self):
1481
# get_graph returns a graph with a custom parents provider.
1482
transport_path = 'quack'
1483
repo, client = self.setup_fake_client_and_repository(transport_path)
1484
graph = repo.get_graph()
1485
self.assertNotEqual(graph._parents_provider, repo)
1488
class TestRepositoryGetParentMap(TestRemoteRepository):
1490
def test_get_parent_map_caching(self):
1491
# get_parent_map returns from cache until unlock()
1492
# setup a reponse with two revisions
1493
r1 = u'\u0e33'.encode('utf8')
1494
r2 = u'\u0dab'.encode('utf8')
1495
lines = [' '.join([r2, r1]), r1]
1496
encoded_body = bz2.compress('\n'.join(lines))
1498
transport_path = 'quack'
1499
repo, client = self.setup_fake_client_and_repository(transport_path)
1500
client.add_success_response_with_body(encoded_body, 'ok')
1501
client.add_success_response_with_body(encoded_body, 'ok')
1503
graph = repo.get_graph()
1504
parents = graph.get_parent_map([r2])
1505
self.assertEqual({r2: (r1,)}, parents)
1506
# locking and unlocking deeper should not reset
1509
parents = graph.get_parent_map([r1])
1510
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1512
[('call_with_body_bytes_expecting_body',
1513
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
1516
# now we call again, and it should use the second response.
1518
graph = repo.get_graph()
1519
parents = graph.get_parent_map([r1])
1520
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1522
[('call_with_body_bytes_expecting_body',
1523
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
1524
('call_with_body_bytes_expecting_body',
1525
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
1530
def test_get_parent_map_reconnects_if_unknown_method(self):
1531
transport_path = 'quack'
1532
repo, client = self.setup_fake_client_and_repository(transport_path)
1533
client.add_unknown_method_response('Repository,get_parent_map')
1534
client.add_success_response_with_body('', 'ok')
1535
self.assertFalse(client._medium._is_remote_before((1, 2)))
1536
rev_id = 'revision-id'
1537
expected_deprecations = [
1538
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1540
parents = self.callDeprecated(
1541
expected_deprecations, repo.get_parent_map, [rev_id])
1543
[('call_with_body_bytes_expecting_body',
1544
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
1545
('disconnect medium',),
1546
('call_expecting_body', 'Repository.get_revision_graph',
1549
# The medium is now marked as being connected to an older server
1550
self.assertTrue(client._medium._is_remote_before((1, 2)))
1552
def test_get_parent_map_fallback_parentless_node(self):
1553
"""get_parent_map falls back to get_revision_graph on old servers. The
1554
results from get_revision_graph are tweaked to match the get_parent_map
1557
Specifically, a {key: ()} result from get_revision_graph means "no
1558
parents" for that key, which in get_parent_map results should be
1559
represented as {key: ('null:',)}.
1561
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1563
rev_id = 'revision-id'
1564
transport_path = 'quack'
1565
repo, client = self.setup_fake_client_and_repository(transport_path)
1566
client.add_success_response_with_body(rev_id, 'ok')
1567
client._medium._remember_remote_is_before((1, 2))
1568
expected_deprecations = [
1569
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1571
parents = self.callDeprecated(
1572
expected_deprecations, repo.get_parent_map, [rev_id])
1574
[('call_expecting_body', 'Repository.get_revision_graph',
1577
self.assertEqual({rev_id: ('null:',)}, parents)
1579
def test_get_parent_map_unexpected_response(self):
1580
repo, client = self.setup_fake_client_and_repository('path')
1581
client.add_success_response('something unexpected!')
1583
errors.UnexpectedSmartServerResponse,
1584
repo.get_parent_map, ['a-revision-id'])
1587
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1589
def test_allows_new_revisions(self):
1590
"""get_parent_map's results can be updated by commit."""
1591
smart_server = server.SmartTCPServer_for_testing()
1592
smart_server.setUp()
1593
self.addCleanup(smart_server.tearDown)
1594
self.make_branch('branch')
1595
branch = Branch.open(smart_server.get_url() + '/branch')
1596
tree = branch.create_checkout('tree', lightweight=True)
1598
self.addCleanup(tree.unlock)
1599
graph = tree.branch.repository.get_graph()
1600
# This provides an opportunity for the missing rev-id to be cached.
1601
self.assertEqual({}, graph.get_parent_map(['rev1']))
1602
tree.commit('message', rev_id='rev1')
1603
graph = tree.branch.repository.get_graph()
1604
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1607
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1609
def test_null_revision(self):
1610
# a null revision has the predictable result {}, we should have no wire
1611
# traffic when calling it with this argument
1612
transport_path = 'empty'
1613
repo, client = self.setup_fake_client_and_repository(transport_path)
1614
client.add_success_response('notused')
1615
result = self.applyDeprecated(one_four, repo.get_revision_graph,
1617
self.assertEqual([], client._calls)
1618
self.assertEqual({}, result)
1620
def test_none_revision(self):
1621
# with none we want the entire graph
1622
r1 = u'\u0e33'.encode('utf8')
1623
r2 = u'\u0dab'.encode('utf8')
1624
lines = [' '.join([r2, r1]), r1]
1625
encoded_body = '\n'.join(lines)
1627
transport_path = 'sinhala'
1628
repo, client = self.setup_fake_client_and_repository(transport_path)
1629
client.add_success_response_with_body(encoded_body, 'ok')
1630
result = self.applyDeprecated(one_four, repo.get_revision_graph)
1632
[('call_expecting_body', 'Repository.get_revision_graph',
1635
self.assertEqual({r1: (), r2: (r1, )}, result)
1637
def test_specific_revision(self):
1638
# with a specific revision we want the graph for that
1639
# with none we want the entire graph
1640
r11 = u'\u0e33'.encode('utf8')
1641
r12 = u'\xc9'.encode('utf8')
1642
r2 = u'\u0dab'.encode('utf8')
1643
lines = [' '.join([r2, r11, r12]), r11, r12]
1644
encoded_body = '\n'.join(lines)
1646
transport_path = 'sinhala'
1647
repo, client = self.setup_fake_client_and_repository(transport_path)
1648
client.add_success_response_with_body(encoded_body, 'ok')
1649
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
1651
[('call_expecting_body', 'Repository.get_revision_graph',
1654
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1656
def test_no_such_revision(self):
1658
transport_path = 'sinhala'
1659
repo, client = self.setup_fake_client_and_repository(transport_path)
1660
client.add_error_response('nosuchrevision', revid)
1661
# also check that the right revision is reported in the error
1662
self.assertRaises(errors.NoSuchRevision,
1663
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1665
[('call_expecting_body', 'Repository.get_revision_graph',
1666
('sinhala/', revid))],
1669
def test_unexpected_error(self):
1671
transport_path = 'sinhala'
1672
repo, client = self.setup_fake_client_and_repository(transport_path)
1673
client.add_error_response('AnUnexpectedError')
1674
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1675
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1676
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1679
class TestRepositoryIsShared(TestRemoteRepository):
1681
def test_is_shared(self):
1682
# ('yes', ) for Repository.is_shared -> 'True'.
1683
transport_path = 'quack'
1684
repo, client = self.setup_fake_client_and_repository(transport_path)
1685
client.add_success_response('yes')
1686
result = repo.is_shared()
1688
[('call', 'Repository.is_shared', ('quack/',))],
1690
self.assertEqual(True, result)
1692
def test_is_not_shared(self):
1693
# ('no', ) for Repository.is_shared -> 'False'.
1694
transport_path = 'qwack'
1695
repo, client = self.setup_fake_client_and_repository(transport_path)
1696
client.add_success_response('no')
1697
result = repo.is_shared()
1699
[('call', 'Repository.is_shared', ('qwack/',))],
1701
self.assertEqual(False, result)
1704
class TestRepositoryLockWrite(TestRemoteRepository):
1706
def test_lock_write(self):
1707
transport_path = 'quack'
1708
repo, client = self.setup_fake_client_and_repository(transport_path)
1709
client.add_success_response('ok', 'a token')
1710
result = repo.lock_write()
1712
[('call', 'Repository.lock_write', ('quack/', ''))],
1714
self.assertEqual('a token', result)
1716
def test_lock_write_already_locked(self):
1717
transport_path = 'quack'
1718
repo, client = self.setup_fake_client_and_repository(transport_path)
1719
client.add_error_response('LockContention')
1720
self.assertRaises(errors.LockContention, repo.lock_write)
1722
[('call', 'Repository.lock_write', ('quack/', ''))],
1725
def test_lock_write_unlockable(self):
1726
transport_path = 'quack'
1727
repo, client = self.setup_fake_client_and_repository(transport_path)
1728
client.add_error_response('UnlockableTransport')
1729
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1731
[('call', 'Repository.lock_write', ('quack/', ''))],
1735
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1737
def test_backwards_compat(self):
1738
self.setup_smart_server_with_call_log()
1739
repo = self.make_repository('.')
1740
self.reset_smart_call_log()
1741
verb = 'Repository.set_make_working_trees'
1742
self.disable_verb(verb)
1743
repo.set_make_working_trees(True)
1744
call_count = len([call for call in self.hpss_calls if
1745
call.call.method == verb])
1746
self.assertEqual(1, call_count)
1748
def test_current(self):
1749
transport_path = 'quack'
1750
repo, client = self.setup_fake_client_and_repository(transport_path)
1751
client.add_expected_call(
1752
'Repository.set_make_working_trees', ('quack/', 'True'),
1754
client.add_expected_call(
1755
'Repository.set_make_working_trees', ('quack/', 'False'),
1757
repo.set_make_working_trees(True)
1758
repo.set_make_working_trees(False)
1761
class TestRepositoryUnlock(TestRemoteRepository):
1763
def test_unlock(self):
1764
transport_path = 'quack'
1765
repo, client = self.setup_fake_client_and_repository(transport_path)
1766
client.add_success_response('ok', 'a token')
1767
client.add_success_response('ok')
1771
[('call', 'Repository.lock_write', ('quack/', '')),
1772
('call', 'Repository.unlock', ('quack/', 'a token'))],
1775
def test_unlock_wrong_token(self):
1776
# If somehow the token is wrong, unlock will raise TokenMismatch.
1777
transport_path = 'quack'
1778
repo, client = self.setup_fake_client_and_repository(transport_path)
1779
client.add_success_response('ok', 'a token')
1780
client.add_error_response('TokenMismatch')
1782
self.assertRaises(errors.TokenMismatch, repo.unlock)
1785
class TestRepositoryHasRevision(TestRemoteRepository):
1787
def test_none(self):
1788
# repo.has_revision(None) should not cause any traffic.
1789
transport_path = 'quack'
1790
repo, client = self.setup_fake_client_and_repository(transport_path)
1792
# The null revision is always there, so has_revision(None) == True.
1793
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1795
# The remote repo shouldn't be accessed.
1796
self.assertEqual([], client._calls)
1799
class TestRepositoryTarball(TestRemoteRepository):
1801
# This is a canned tarball reponse we can validate against
1803
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1804
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1805
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1806
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1807
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1808
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1809
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1810
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1811
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1812
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1813
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1814
'nWQ7QH/F3JFOFCQ0aSPfA='
1817
def test_repository_tarball(self):
1818
# Test that Repository.tarball generates the right operations
1819
transport_path = 'repo'
1820
expected_calls = [('call_expecting_body', 'Repository.tarball',
1821
('repo/', 'bz2',),),
1823
repo, client = self.setup_fake_client_and_repository(transport_path)
1824
client.add_success_response_with_body(self.tarball_content, 'ok')
1825
# Now actually ask for the tarball
1826
tarball_file = repo._get_tarball('bz2')
1828
self.assertEqual(expected_calls, client._calls)
1829
self.assertEqual(self.tarball_content, tarball_file.read())
1831
tarball_file.close()
1834
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
1835
"""RemoteRepository.copy_content_into optimizations"""
1837
def test_copy_content_remote_to_local(self):
1838
self.transport_server = server.SmartTCPServer_for_testing
1839
src_repo = self.make_repository('repo1')
1840
src_repo = repository.Repository.open(self.get_url('repo1'))
1841
# At the moment the tarball-based copy_content_into can't write back
1842
# into a smart server. It would be good if it could upload the
1843
# tarball; once that works we'd have to create repositories of
1844
# different formats. -- mbp 20070410
1845
dest_url = self.get_vfs_only_url('repo2')
1846
dest_bzrdir = BzrDir.create(dest_url)
1847
dest_repo = dest_bzrdir.create_repository()
1848
self.assertFalse(isinstance(dest_repo, RemoteRepository))
1849
self.assertTrue(isinstance(src_repo, RemoteRepository))
1850
src_repo.copy_content_into(dest_repo)
1853
class _StubRealPackRepository(object):
1855
def __init__(self, calls):
1856
self._pack_collection = _StubPackCollection(calls)
1859
class _StubPackCollection(object):
1861
def __init__(self, calls):
1865
self.calls.append(('pack collection autopack',))
1867
def reload_pack_names(self):
1868
self.calls.append(('pack collection reload_pack_names',))
1871
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1872
"""Tests for RemoteRepository.autopack implementation."""
1875
"""When the server returns 'ok' and there's no _real_repository, then
1876
nothing else happens: the autopack method is done.
1878
transport_path = 'quack'
1879
repo, client = self.setup_fake_client_and_repository(transport_path)
1880
client.add_expected_call(
1881
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
1883
client.finished_test()
1885
def test_ok_with_real_repo(self):
1886
"""When the server returns 'ok' and there is a _real_repository, then
1887
the _real_repository's reload_pack_name's method will be called.
1889
transport_path = 'quack'
1890
repo, client = self.setup_fake_client_and_repository(transport_path)
1891
client.add_expected_call(
1892
'PackRepository.autopack', ('quack/',),
1894
repo._real_repository = _StubRealPackRepository(client._calls)
1897
[('call', 'PackRepository.autopack', ('quack/',)),
1898
('pack collection reload_pack_names',)],
1901
def test_backwards_compatibility(self):
1902
"""If the server does not recognise the PackRepository.autopack verb,
1903
fallback to the real_repository's implementation.
1905
transport_path = 'quack'
1906
repo, client = self.setup_fake_client_and_repository(transport_path)
1907
client.add_unknown_method_response('PackRepository.autopack')
1908
def stub_ensure_real():
1909
client._calls.append(('_ensure_real',))
1910
repo._real_repository = _StubRealPackRepository(client._calls)
1911
repo._ensure_real = stub_ensure_real
1914
[('call', 'PackRepository.autopack', ('quack/',)),
1916
('pack collection autopack',)],
1920
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
1921
"""Base class for unit tests for bzrlib.remote._translate_error."""
1923
def translateTuple(self, error_tuple, **context):
1924
"""Call _translate_error with an ErrorFromSmartServer built from the
1927
:param error_tuple: A tuple of a smart server response, as would be
1928
passed to an ErrorFromSmartServer.
1929
:kwargs context: context items to call _translate_error with.
1931
:returns: The error raised by _translate_error.
1933
# Raise the ErrorFromSmartServer before passing it as an argument,
1934
# because _translate_error may need to re-raise it with a bare 'raise'
1936
server_error = errors.ErrorFromSmartServer(error_tuple)
1937
translated_error = self.translateErrorFromSmartServer(
1938
server_error, **context)
1939
return translated_error
1941
def translateErrorFromSmartServer(self, error_object, **context):
1942
"""Like translateTuple, but takes an already constructed
1943
ErrorFromSmartServer rather than a tuple.
1947
except errors.ErrorFromSmartServer, server_error:
1948
translated_error = self.assertRaises(
1949
errors.BzrError, remote._translate_error, server_error,
1951
return translated_error
1954
class TestErrorTranslationSuccess(TestErrorTranslationBase):
1955
"""Unit tests for bzrlib.remote._translate_error.
1957
Given an ErrorFromSmartServer (which has an error tuple from a smart
1958
server) and some context, _translate_error raises more specific errors from
1961
This test case covers the cases where _translate_error succeeds in
1962
translating an ErrorFromSmartServer to something better. See
1963
TestErrorTranslationRobustness for other cases.
1966
def test_NoSuchRevision(self):
1967
branch = self.make_branch('')
1969
translated_error = self.translateTuple(
1970
('NoSuchRevision', revid), branch=branch)
1971
expected_error = errors.NoSuchRevision(branch, revid)
1972
self.assertEqual(expected_error, translated_error)
1974
def test_nosuchrevision(self):
1975
repository = self.make_repository('')
1977
translated_error = self.translateTuple(
1978
('nosuchrevision', revid), repository=repository)
1979
expected_error = errors.NoSuchRevision(repository, revid)
1980
self.assertEqual(expected_error, translated_error)
1982
def test_nobranch(self):
1983
bzrdir = self.make_bzrdir('')
1984
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
1985
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
1986
self.assertEqual(expected_error, translated_error)
1988
def test_LockContention(self):
1989
translated_error = self.translateTuple(('LockContention',))
1990
expected_error = errors.LockContention('(remote lock)')
1991
self.assertEqual(expected_error, translated_error)
1993
def test_UnlockableTransport(self):
1994
bzrdir = self.make_bzrdir('')
1995
translated_error = self.translateTuple(
1996
('UnlockableTransport',), bzrdir=bzrdir)
1997
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
1998
self.assertEqual(expected_error, translated_error)
2000
def test_LockFailed(self):
2001
lock = 'str() of a server lock'
2002
why = 'str() of why'
2003
translated_error = self.translateTuple(('LockFailed', lock, why))
2004
expected_error = errors.LockFailed(lock, why)
2005
self.assertEqual(expected_error, translated_error)
2007
def test_TokenMismatch(self):
2008
token = 'a lock token'
2009
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2010
expected_error = errors.TokenMismatch(token, '(remote token)')
2011
self.assertEqual(expected_error, translated_error)
2013
def test_Diverged(self):
2014
branch = self.make_branch('a')
2015
other_branch = self.make_branch('b')
2016
translated_error = self.translateTuple(
2017
('Diverged',), branch=branch, other_branch=other_branch)
2018
expected_error = errors.DivergedBranches(branch, other_branch)
2019
self.assertEqual(expected_error, translated_error)
2021
def test_ReadError_no_args(self):
2023
translated_error = self.translateTuple(('ReadError',), path=path)
2024
expected_error = errors.ReadError(path)
2025
self.assertEqual(expected_error, translated_error)
2027
def test_ReadError(self):
2029
translated_error = self.translateTuple(('ReadError', path))
2030
expected_error = errors.ReadError(path)
2031
self.assertEqual(expected_error, translated_error)
2033
def test_PermissionDenied_no_args(self):
2035
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2036
expected_error = errors.PermissionDenied(path)
2037
self.assertEqual(expected_error, translated_error)
2039
def test_PermissionDenied_one_arg(self):
2041
translated_error = self.translateTuple(('PermissionDenied', path))
2042
expected_error = errors.PermissionDenied(path)
2043
self.assertEqual(expected_error, translated_error)
2045
def test_PermissionDenied_one_arg_and_context(self):
2046
"""Given a choice between a path from the local context and a path on
2047
the wire, _translate_error prefers the path from the local context.
2049
local_path = 'local path'
2050
remote_path = 'remote path'
2051
translated_error = self.translateTuple(
2052
('PermissionDenied', remote_path), path=local_path)
2053
expected_error = errors.PermissionDenied(local_path)
2054
self.assertEqual(expected_error, translated_error)
2056
def test_PermissionDenied_two_args(self):
2058
extra = 'a string with extra info'
2059
translated_error = self.translateTuple(
2060
('PermissionDenied', path, extra))
2061
expected_error = errors.PermissionDenied(path, extra)
2062
self.assertEqual(expected_error, translated_error)
2065
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2066
"""Unit tests for bzrlib.remote._translate_error's robustness.
2068
TestErrorTranslationSuccess is for cases where _translate_error can
2069
translate successfully. This class about how _translate_err behaves when
2070
it fails to translate: it re-raises the original error.
2073
def test_unrecognised_server_error(self):
2074
"""If the error code from the server is not recognised, the original
2075
ErrorFromSmartServer is propagated unmodified.
2077
error_tuple = ('An unknown error tuple',)
2078
server_error = errors.ErrorFromSmartServer(error_tuple)
2079
translated_error = self.translateErrorFromSmartServer(server_error)
2080
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2081
self.assertEqual(expected_error, translated_error)
2083
def test_context_missing_a_key(self):
2084
"""In case of a bug in the client, or perhaps an unexpected response
2085
from a server, _translate_error returns the original error tuple from
2086
the server and mutters a warning.
2088
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2089
# in the context dict. So let's give it an empty context dict instead
2090
# to exercise its error recovery.
2092
error_tuple = ('NoSuchRevision', 'revid')
2093
server_error = errors.ErrorFromSmartServer(error_tuple)
2094
translated_error = self.translateErrorFromSmartServer(server_error)
2095
self.assertEqual(server_error, translated_error)
2096
# In addition to re-raising ErrorFromSmartServer, some debug info has
2097
# been muttered to the log file for developer to look at.
2098
self.assertContainsRe(
2099
self._get_log(keep_log_file=True),
2100
"Missing key 'branch' in context")
2102
def test_path_missing(self):
2103
"""Some translations (PermissionDenied, ReadError) can determine the
2104
'path' variable from either the wire or the local context. If neither
2105
has it, then an error is raised.
2107
error_tuple = ('ReadError',)
2108
server_error = errors.ErrorFromSmartServer(error_tuple)
2109
translated_error = self.translateErrorFromSmartServer(server_error)
2110
self.assertEqual(server_error, translated_error)
2111
# In addition to re-raising ErrorFromSmartServer, some debug info has
2112
# been muttered to the log file for developer to look at.
2113
self.assertContainsRe(
2114
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2117
class TestStacking(tests.TestCaseWithTransport):
2118
"""Tests for operations on stacked remote repositories.
2120
The underlying format type must support stacking.
2123
def test_access_stacked_remote(self):
2124
# based on <http://launchpad.net/bugs/261315>
2125
# make a branch stacked on another repository containing an empty
2126
# revision, then open it over hpss - we should be able to see that
2128
base_transport = self.get_transport()
2129
base_builder = self.make_branch_builder('base', format='1.6')
2130
base_builder.start_series()
2131
base_revid = base_builder.build_snapshot('rev-id', None,
2132
[('add', ('', None, 'directory', None))],
2134
base_builder.finish_series()
2135
stacked_branch = self.make_branch('stacked', format='1.6')
2136
stacked_branch.set_stacked_on_url('../base')
2137
# start a server looking at this
2138
smart_server = server.SmartTCPServer_for_testing()
2139
smart_server.setUp()
2140
self.addCleanup(smart_server.tearDown)
2141
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2142
# can get its branch and repository
2143
remote_branch = remote_bzrdir.open_branch()
2144
remote_repo = remote_branch.repository
2145
remote_repo.lock_read()
2147
# it should have an appropriate fallback repository, which should also
2148
# be a RemoteRepository
2149
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2150
self.assertIsInstance(remote_repo._fallback_repositories[0],
2152
# and it has the revision committed to the underlying repository;
2153
# these have varying implementations so we try several of them
2154
self.assertTrue(remote_repo.has_revisions([base_revid]))
2155
self.assertTrue(remote_repo.has_revision(base_revid))
2156
self.assertEqual(remote_repo.get_revision(base_revid).message,
2159
remote_repo.unlock()
2161
def prepare_stacked_remote_branch(self):
2162
smart_server = server.SmartTCPServer_for_testing()
2163
smart_server.setUp()
2164
self.addCleanup(smart_server.tearDown)
2165
tree1 = self.make_branch_and_tree('tree1')
2166
tree1.commit('rev1', rev_id='rev1')
2167
tree2 = self.make_branch_and_tree('tree2', format='1.6')
2168
tree2.branch.set_stacked_on_url(tree1.branch.base)
2169
branch2 = Branch.open(smart_server.get_url() + '/tree2')
2171
self.addCleanup(branch2.unlock)
2174
def test_stacked_get_parent_map(self):
2175
# the public implementation of get_parent_map obeys stacking
2176
branch = self.prepare_stacked_remote_branch()
2177
repo = branch.repository
2178
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2180
def test_unstacked_get_parent_map(self):
2181
# _unstacked_provider.get_parent_map ignores stacking
2182
branch = self.prepare_stacked_remote_branch()
2183
provider = branch.repository._unstacked_provider
2184
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2187
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2190
super(TestRemoteBranchEffort, self).setUp()
2191
# Create a smart server that publishes whatever the backing VFS server
2193
self.smart_server = server.SmartTCPServer_for_testing()
2194
self.smart_server.setUp(self.get_server())
2195
self.addCleanup(self.smart_server.tearDown)
2196
# Log all HPSS calls into self.hpss_calls.
2197
_SmartClient.hooks.install_named_hook(
2198
'call', self.capture_hpss_call, None)
2199
self.hpss_calls = []
2201
def capture_hpss_call(self, params):
2202
self.hpss_calls.append(params.method)
2204
def test_copy_content_into_avoids_revision_history(self):
2205
local = self.make_branch('local')
2206
remote_backing_tree = self.make_branch_and_tree('remote')
2207
remote_backing_tree.commit("Commit.")
2208
remote_branch_url = self.smart_server.get_url() + 'remote'
2209
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2210
local.repository.fetch(remote_branch.repository)
2211
self.hpss_calls = []
2212
remote_branch.copy_content_into(local)
2213
self.assertFalse('Branch.revision_history' in self.hpss_calls)