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(')
118
class FakeProtocol(object):
119
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
121
def __init__(self, body, fake_client):
123
self._body_buffer = None
124
self._fake_client = fake_client
126
def read_body_bytes(self, count=-1):
127
if self._body_buffer is None:
128
self._body_buffer = StringIO(self.body)
129
bytes = self._body_buffer.read(count)
130
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
131
self._fake_client.expecting_body = False
134
def cancel_read_body(self):
135
self._fake_client.expecting_body = False
137
def read_streamed_body(self):
141
class FakeClient(_SmartClient):
142
"""Lookalike for _SmartClient allowing testing."""
144
def __init__(self, fake_medium_base='fake base'):
145
"""Create a FakeClient."""
148
self.expecting_body = False
149
# if non-None, this is the list of expected calls, with only the
150
# method name and arguments included. the body might be hard to
151
# compute so is not included. If a call is None, that call can
153
self._expected_calls = None
154
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
156
def add_expected_call(self, call_name, call_args, response_type,
157
response_args, response_body=None):
158
if self._expected_calls is None:
159
self._expected_calls = []
160
self._expected_calls.append((call_name, call_args))
161
self.responses.append((response_type, response_args, response_body))
163
def add_success_response(self, *args):
164
self.responses.append(('success', args, None))
166
def add_success_response_with_body(self, body, *args):
167
self.responses.append(('success', args, body))
168
if self._expected_calls is not None:
169
self._expected_calls.append(None)
171
def add_error_response(self, *args):
172
self.responses.append(('error', args))
174
def add_unknown_method_response(self, verb):
175
self.responses.append(('unknown', verb))
177
def finished_test(self):
178
if self._expected_calls:
179
raise AssertionError("%r finished but was still expecting %r"
180
% (self, self._expected_calls[0]))
182
def _get_next_response(self):
184
response_tuple = self.responses.pop(0)
185
except IndexError, e:
186
raise AssertionError("%r didn't expect any more calls"
188
if response_tuple[0] == 'unknown':
189
raise errors.UnknownSmartMethod(response_tuple[1])
190
elif response_tuple[0] == 'error':
191
raise errors.ErrorFromSmartServer(response_tuple[1])
192
return response_tuple
194
def _check_call(self, method, args):
195
if self._expected_calls is None:
196
# the test should be updated to say what it expects
199
next_call = self._expected_calls.pop(0)
201
raise AssertionError("%r didn't expect any more calls "
203
% (self, method, args,))
204
if next_call is None:
206
if method != next_call[0] or args != next_call[1]:
207
raise AssertionError("%r expected %r%r "
209
% (self, next_call[0], next_call[1], method, args,))
211
def call(self, method, *args):
212
self._check_call(method, args)
213
self._calls.append(('call', method, args))
214
return self._get_next_response()[1]
216
def call_expecting_body(self, method, *args):
217
self._check_call(method, args)
218
self._calls.append(('call_expecting_body', method, args))
219
result = self._get_next_response()
220
self.expecting_body = True
221
return result[1], FakeProtocol(result[2], self)
223
def call_with_body_bytes_expecting_body(self, method, args, body):
224
self._check_call(method, args)
225
self._calls.append(('call_with_body_bytes_expecting_body', method,
227
result = self._get_next_response()
228
self.expecting_body = True
229
return result[1], FakeProtocol(result[2], self)
231
def call_with_body_stream(self, args, stream):
232
# Explicitly consume the stream before checking for an error, because
233
# that's what happens a real medium.
234
stream = list(stream)
235
self._check_call(args[0], args[1:])
236
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
237
return self._get_next_response()[1]
240
class FakeMedium(medium.SmartClientMedium):
242
def __init__(self, client_calls, base):
243
medium.SmartClientMedium.__init__(self, base)
244
self._client_calls = client_calls
246
def disconnect(self):
247
self._client_calls.append(('disconnect medium',))
250
class TestVfsHas(tests.TestCase):
252
def test_unicode_path(self):
253
client = FakeClient('/')
254
client.add_success_response('yes',)
255
transport = RemoteTransport('bzr://localhost/', _client=client)
256
filename = u'/hell\u00d8'.encode('utf8')
257
result = transport.has(filename)
259
[('call', 'has', (filename,))],
261
self.assertTrue(result)
264
class TestRemote(tests.TestCaseWithMemoryTransport):
266
def get_branch_format(self):
267
reference_bzrdir_format = bzrdir.format_registry.get('default')()
268
return reference_bzrdir_format.get_branch_format()
270
def get_repo_format(self):
271
reference_bzrdir_format = bzrdir.format_registry.get('default')()
272
return reference_bzrdir_format.repository_format
274
def disable_verb(self, verb):
275
"""Disable a verb for one test."""
276
request_handlers = smart.request.request_handlers
277
orig_method = request_handlers.get(verb)
278
request_handlers.remove(verb)
280
request_handlers.register(verb, orig_method)
281
self.addCleanup(restoreVerb)
284
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
285
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
287
def assertRemotePath(self, expected, client_base, transport_base):
288
"""Assert that the result of
289
SmartClientMedium.remote_path_from_transport is the expected value for
290
a given client_base and transport_base.
292
client_medium = medium.SmartClientMedium(client_base)
293
transport = get_transport(transport_base)
294
result = client_medium.remote_path_from_transport(transport)
295
self.assertEqual(expected, result)
297
def test_remote_path_from_transport(self):
298
"""SmartClientMedium.remote_path_from_transport calculates a URL for
299
the given transport relative to the root of the client base URL.
301
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
302
self.assertRemotePath(
303
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
305
def assertRemotePathHTTP(self, expected, transport_base, relpath):
306
"""Assert that the result of
307
HttpTransportBase.remote_path_from_transport is the expected value for
308
a given transport_base and relpath of that transport. (Note that
309
HttpTransportBase is a subclass of SmartClientMedium)
311
base_transport = get_transport(transport_base)
312
client_medium = base_transport.get_smart_medium()
313
cloned_transport = base_transport.clone(relpath)
314
result = client_medium.remote_path_from_transport(cloned_transport)
315
self.assertEqual(expected, result)
317
def test_remote_path_from_transport_http(self):
318
"""Remote paths for HTTP transports are calculated differently to other
319
transports. They are just relative to the client base, not the root
320
directory of the host.
322
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
323
self.assertRemotePathHTTP(
324
'../xyz/', scheme + '//host/path', '../xyz/')
325
self.assertRemotePathHTTP(
326
'xyz/', scheme + '//host/path', 'xyz/')
329
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
330
"""Tests for the behaviour of client_medium.remote_is_at_least."""
332
def test_initially_unlimited(self):
333
"""A fresh medium assumes that the remote side supports all
336
client_medium = medium.SmartClientMedium('dummy base')
337
self.assertFalse(client_medium._is_remote_before((99, 99)))
339
def test__remember_remote_is_before(self):
340
"""Calling _remember_remote_is_before ratchets down the known remote
343
client_medium = medium.SmartClientMedium('dummy base')
344
# Mark the remote side as being less than 1.6. The remote side may
346
client_medium._remember_remote_is_before((1, 6))
347
self.assertTrue(client_medium._is_remote_before((1, 6)))
348
self.assertFalse(client_medium._is_remote_before((1, 5)))
349
# Calling _remember_remote_is_before again with a lower value works.
350
client_medium._remember_remote_is_before((1, 5))
351
self.assertTrue(client_medium._is_remote_before((1, 5)))
352
# You cannot call _remember_remote_is_before with a larger value.
354
AssertionError, client_medium._remember_remote_is_before, (1, 9))
357
class TestBzrDirCloningMetaDir(TestRemote):
359
def test_backwards_compat(self):
360
self.setup_smart_server_with_call_log()
361
a_dir = self.make_bzrdir('.')
362
self.reset_smart_call_log()
363
verb = 'BzrDir.cloning_metadir'
364
self.disable_verb(verb)
365
format = a_dir.cloning_metadir()
366
call_count = len([call for call in self.hpss_calls if
367
call.call.method == verb])
368
self.assertEqual(1, call_count)
370
def test_current_server(self):
371
transport = self.get_transport('.')
372
transport = transport.clone('quack')
373
self.make_bzrdir('quack')
374
client = FakeClient(transport.base)
375
reference_bzrdir_format = bzrdir.format_registry.get('default')()
376
control_name = reference_bzrdir_format.network_name()
377
client.add_expected_call(
378
'BzrDir.cloning_metadir', ('quack/', 'False'),
379
'success', (control_name, '', ('branch', ''))),
380
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
382
result = a_bzrdir.cloning_metadir()
383
# We should have got a reference control dir with default branch and
384
# repository formats.
385
# This pokes a little, just to be sure.
386
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
387
self.assertEqual(None, result._repository_format)
388
self.assertEqual(None, result._branch_format)
389
client.finished_test()
392
class TestBzrDirOpenBranch(TestRemote):
394
def test_backwards_compat(self):
395
self.setup_smart_server_with_call_log()
396
self.make_branch('.')
397
a_dir = BzrDir.open(self.get_url('.'))
398
self.reset_smart_call_log()
399
verb = 'BzrDir.open_branchV2'
400
self.disable_verb(verb)
401
format = a_dir.open_branch()
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_branch_present(self):
407
reference_format = self.get_repo_format()
408
network_name = reference_format.network_name()
409
branch_network_name = self.get_branch_format().network_name()
410
transport = MemoryTransport()
411
transport.mkdir('quack')
412
transport = transport.clone('quack')
413
client = FakeClient(transport.base)
414
client.add_expected_call(
415
'BzrDir.open_branchV2', ('quack/',),
416
'success', ('branch', branch_network_name))
417
client.add_expected_call(
418
'BzrDir.find_repositoryV3', ('quack/',),
419
'success', ('ok', '', 'no', 'no', 'no', network_name))
420
client.add_expected_call(
421
'Branch.get_stacked_on_url', ('quack/',),
422
'error', ('NotStacked',))
423
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
425
result = bzrdir.open_branch()
426
self.assertIsInstance(result, RemoteBranch)
427
self.assertEqual(bzrdir, result.bzrdir)
428
client.finished_test()
430
def test_branch_missing(self):
431
transport = MemoryTransport()
432
transport.mkdir('quack')
433
transport = transport.clone('quack')
434
client = FakeClient(transport.base)
435
client.add_error_response('nobranch')
436
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
438
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
440
[('call', 'BzrDir.open_branchV2', ('quack/',))],
443
def test__get_tree_branch(self):
444
# _get_tree_branch is a form of open_branch, but it should only ask for
445
# branch opening, not any other network requests.
448
calls.append("Called")
450
transport = MemoryTransport()
451
# no requests on the network - catches other api calls being made.
452
client = FakeClient(transport.base)
453
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
455
# patch the open_branch call to record that it was called.
456
bzrdir.open_branch = open_branch
457
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
458
self.assertEqual(["Called"], calls)
459
self.assertEqual([], client._calls)
461
def test_url_quoting_of_path(self):
462
# Relpaths on the wire should not be URL-escaped. So "~" should be
463
# transmitted as "~", not "%7E".
464
transport = RemoteTCPTransport('bzr://localhost/~hello/')
465
client = FakeClient(transport.base)
466
reference_format = self.get_repo_format()
467
network_name = reference_format.network_name()
468
branch_network_name = self.get_branch_format().network_name()
469
client.add_expected_call(
470
'BzrDir.open_branchV2', ('~hello/',),
471
'success', ('branch', branch_network_name))
472
client.add_expected_call(
473
'BzrDir.find_repositoryV3', ('~hello/',),
474
'success', ('ok', '', 'no', 'no', 'no', network_name))
475
client.add_expected_call(
476
'Branch.get_stacked_on_url', ('~hello/',),
477
'error', ('NotStacked',))
478
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
480
result = bzrdir.open_branch()
481
client.finished_test()
483
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
484
reference_format = self.get_repo_format()
485
network_name = reference_format.network_name()
486
transport = MemoryTransport()
487
transport.mkdir('quack')
488
transport = transport.clone('quack')
490
rich_response = 'yes'
494
subtree_response = 'yes'
496
subtree_response = 'no'
497
client = FakeClient(transport.base)
498
client.add_success_response(
499
'ok', '', rich_response, subtree_response, external_lookup,
501
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
503
result = bzrdir.open_repository()
505
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
507
self.assertIsInstance(result, RemoteRepository)
508
self.assertEqual(bzrdir, result.bzrdir)
509
self.assertEqual(rich_root, result._format.rich_root_data)
510
self.assertEqual(subtrees, result._format.supports_tree_reference)
512
def test_open_repository_sets_format_attributes(self):
513
self.check_open_repository(True, True)
514
self.check_open_repository(False, True)
515
self.check_open_repository(True, False)
516
self.check_open_repository(False, False)
517
self.check_open_repository(False, False, 'yes')
519
def test_old_server(self):
520
"""RemoteBzrDirFormat should fail to probe if the server version is too
523
self.assertRaises(errors.NotBranchError,
524
RemoteBzrDirFormat.probe_transport, OldServerTransport())
527
class TestBzrDirCreateBranch(TestRemote):
529
def test_backwards_compat(self):
530
self.setup_smart_server_with_call_log()
531
repo = self.make_repository('.')
532
self.reset_smart_call_log()
533
self.disable_verb('BzrDir.create_branch')
534
branch = repo.bzrdir.create_branch()
535
create_branch_call_count = len([call for call in self.hpss_calls if
536
call.call.method == 'BzrDir.create_branch'])
537
self.assertEqual(1, create_branch_call_count)
539
def test_current_server(self):
540
transport = self.get_transport('.')
541
transport = transport.clone('quack')
542
self.make_repository('quack')
543
client = FakeClient(transport.base)
544
reference_bzrdir_format = bzrdir.format_registry.get('default')()
545
reference_format = reference_bzrdir_format.get_branch_format()
546
network_name = reference_format.network_name()
547
reference_repo_fmt = reference_bzrdir_format.repository_format
548
reference_repo_name = reference_repo_fmt.network_name()
549
client.add_expected_call(
550
'BzrDir.create_branch', ('quack/', network_name),
551
'success', ('ok', network_name, '', 'no', 'no', 'yes',
552
reference_repo_name))
553
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
555
branch = a_bzrdir.create_branch()
556
# We should have got a remote branch
557
self.assertIsInstance(branch, remote.RemoteBranch)
558
# its format should have the settings from the response
559
format = branch._format
560
self.assertEqual(network_name, format.network_name())
563
class TestBzrDirCreateRepository(TestRemote):
565
def test_backwards_compat(self):
566
self.setup_smart_server_with_call_log()
567
bzrdir = self.make_bzrdir('.')
568
self.reset_smart_call_log()
569
self.disable_verb('BzrDir.create_repository')
570
repo = bzrdir.create_repository()
571
create_repo_call_count = len([call for call in self.hpss_calls if
572
call.call.method == 'BzrDir.create_repository'])
573
self.assertEqual(1, create_repo_call_count)
575
def test_current_server(self):
576
transport = self.get_transport('.')
577
transport = transport.clone('quack')
578
self.make_bzrdir('quack')
579
client = FakeClient(transport.base)
580
reference_bzrdir_format = bzrdir.format_registry.get('default')()
581
reference_format = reference_bzrdir_format.repository_format
582
network_name = reference_format.network_name()
583
client.add_expected_call(
584
'BzrDir.create_repository', ('quack/',
585
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
586
'success', ('ok', 'no', 'no', 'no', network_name))
587
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
589
repo = a_bzrdir.create_repository()
590
# We should have got a remote repository
591
self.assertIsInstance(repo, remote.RemoteRepository)
592
# its format should have the settings from the response
593
format = repo._format
594
self.assertFalse(format.rich_root_data)
595
self.assertFalse(format.supports_tree_reference)
596
self.assertFalse(format.supports_external_lookups)
597
self.assertEqual(network_name, format.network_name())
600
class TestBzrDirOpenRepository(TestRemote):
602
def test_backwards_compat_1_2_3(self):
603
# fallback all the way to the first version.
604
reference_format = self.get_repo_format()
605
network_name = reference_format.network_name()
606
client = FakeClient('bzr://example.com/')
607
client.add_unknown_method_response('BzrDir.find_repositoryV3')
608
client.add_unknown_method_response('BzrDir.find_repositoryV2')
609
client.add_success_response('ok', '', 'no', 'no')
610
# A real repository instance will be created to determine the network
612
client.add_success_response_with_body(
613
"Bazaar-NG meta directory, format 1\n", 'ok')
614
client.add_success_response_with_body(
615
reference_format.get_format_string(), 'ok')
616
# PackRepository wants to do a stat
617
client.add_success_response('stat', '0', '65535')
618
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
620
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
622
repo = bzrdir.open_repository()
624
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
625
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
626
('call', 'BzrDir.find_repository', ('quack/',)),
627
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
628
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
629
('call', 'stat', ('/quack/.bzr/repository',)),
632
self.assertEqual(network_name, repo._format.network_name())
634
def test_backwards_compat_2(self):
635
# fallback to find_repositoryV2
636
reference_format = self.get_repo_format()
637
network_name = reference_format.network_name()
638
client = FakeClient('bzr://example.com/')
639
client.add_unknown_method_response('BzrDir.find_repositoryV3')
640
client.add_success_response('ok', '', 'no', 'no', 'no')
641
# A real repository instance will be created to determine the network
643
client.add_success_response_with_body(
644
"Bazaar-NG meta directory, format 1\n", 'ok')
645
client.add_success_response_with_body(
646
reference_format.get_format_string(), 'ok')
647
# PackRepository wants to do a stat
648
client.add_success_response('stat', '0', '65535')
649
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
651
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
653
repo = bzrdir.open_repository()
655
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
656
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
657
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
658
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
659
('call', 'stat', ('/quack/.bzr/repository',)),
662
self.assertEqual(network_name, repo._format.network_name())
664
def test_current_server(self):
665
reference_format = self.get_repo_format()
666
network_name = reference_format.network_name()
667
transport = MemoryTransport()
668
transport.mkdir('quack')
669
transport = transport.clone('quack')
670
client = FakeClient(transport.base)
671
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
672
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
674
repo = bzrdir.open_repository()
676
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
678
self.assertEqual(network_name, repo._format.network_name())
681
class OldSmartClient(object):
682
"""A fake smart client for test_old_version that just returns a version one
683
response to the 'hello' (query version) command.
686
def get_request(self):
687
input_file = StringIO('ok\x011\n')
688
output_file = StringIO()
689
client_medium = medium.SmartSimplePipesClientMedium(
690
input_file, output_file)
691
return medium.SmartClientStreamMediumRequest(client_medium)
693
def protocol_version(self):
697
class OldServerTransport(object):
698
"""A fake transport for test_old_server that reports it's smart server
699
protocol version as version one.
705
def get_smart_client(self):
706
return OldSmartClient()
709
class RemoteBranchTestCase(TestRemote):
711
def make_remote_branch(self, transport, client):
712
"""Make a RemoteBranch using 'client' as its _SmartClient.
714
A RemoteBzrDir and RemoteRepository will also be created to fill out
715
the RemoteBranch, albeit with stub values for some of their attributes.
717
# we do not want bzrdir to make any remote calls, so use False as its
718
# _client. If it tries to make a remote call, this will fail
720
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
722
repo = RemoteRepository(bzrdir, None, _client=client)
723
branch_format = self.get_branch_format()
724
format = RemoteBranchFormat(network_name=branch_format.network_name())
725
return RemoteBranch(bzrdir, repo, _client=client, format=format)
728
class TestBranchGetParent(RemoteBranchTestCase):
730
def test_no_parent(self):
731
# in an empty branch we decode the response properly
732
transport = MemoryTransport()
733
client = FakeClient(transport.base)
734
client.add_expected_call(
735
'Branch.get_stacked_on_url', ('quack/',),
736
'error', ('NotStacked',))
737
client.add_expected_call(
738
'Branch.get_parent', ('quack/',),
740
transport.mkdir('quack')
741
transport = transport.clone('quack')
742
branch = self.make_remote_branch(transport, client)
743
result = branch.get_parent()
744
client.finished_test()
745
self.assertEqual(None, result)
747
def test_parent_relative(self):
748
transport = MemoryTransport()
749
client = FakeClient(transport.base)
750
client.add_expected_call(
751
'Branch.get_stacked_on_url', ('kwaak/',),
752
'error', ('NotStacked',))
753
client.add_expected_call(
754
'Branch.get_parent', ('kwaak/',),
755
'success', ('../foo/',))
756
transport.mkdir('kwaak')
757
transport = transport.clone('kwaak')
758
branch = self.make_remote_branch(transport, client)
759
result = branch.get_parent()
760
self.assertEqual(transport.clone('../foo').base, result)
762
def test_parent_absolute(self):
763
transport = MemoryTransport()
764
client = FakeClient(transport.base)
765
client.add_expected_call(
766
'Branch.get_stacked_on_url', ('kwaak/',),
767
'error', ('NotStacked',))
768
client.add_expected_call(
769
'Branch.get_parent', ('kwaak/',),
770
'success', ('http://foo/',))
771
transport.mkdir('kwaak')
772
transport = transport.clone('kwaak')
773
branch = self.make_remote_branch(transport, client)
774
result = branch.get_parent()
775
self.assertEqual('http://foo/', result)
778
class TestBranchGetTagsBytes(RemoteBranchTestCase):
780
def test_backwards_compat(self):
781
self.setup_smart_server_with_call_log()
782
branch = self.make_branch('.')
783
self.reset_smart_call_log()
784
verb = 'Branch.get_tags_bytes'
785
self.disable_verb(verb)
786
branch.tags.get_tag_dict()
787
call_count = len([call for call in self.hpss_calls if
788
call.call.method == verb])
789
self.assertEqual(1, call_count)
791
def test_trivial(self):
792
transport = MemoryTransport()
793
client = FakeClient(transport.base)
794
client.add_expected_call(
795
'Branch.get_stacked_on_url', ('quack/',),
796
'error', ('NotStacked',))
797
client.add_expected_call(
798
'Branch.get_tags_bytes', ('quack/',),
800
transport.mkdir('quack')
801
transport = transport.clone('quack')
802
branch = self.make_remote_branch(transport, client)
803
result = branch.tags.get_tag_dict()
804
client.finished_test()
805
self.assertEqual({}, result)
808
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
810
def test_empty_branch(self):
811
# in an empty branch we decode the response properly
812
transport = MemoryTransport()
813
client = FakeClient(transport.base)
814
client.add_expected_call(
815
'Branch.get_stacked_on_url', ('quack/',),
816
'error', ('NotStacked',))
817
client.add_expected_call(
818
'Branch.last_revision_info', ('quack/',),
819
'success', ('ok', '0', 'null:'))
820
transport.mkdir('quack')
821
transport = transport.clone('quack')
822
branch = self.make_remote_branch(transport, client)
823
result = branch.last_revision_info()
824
client.finished_test()
825
self.assertEqual((0, NULL_REVISION), result)
827
def test_non_empty_branch(self):
828
# in a non-empty branch we also decode the response properly
829
revid = u'\xc8'.encode('utf8')
830
transport = MemoryTransport()
831
client = FakeClient(transport.base)
832
client.add_expected_call(
833
'Branch.get_stacked_on_url', ('kwaak/',),
834
'error', ('NotStacked',))
835
client.add_expected_call(
836
'Branch.last_revision_info', ('kwaak/',),
837
'success', ('ok', '2', revid))
838
transport.mkdir('kwaak')
839
transport = transport.clone('kwaak')
840
branch = self.make_remote_branch(transport, client)
841
result = branch.last_revision_info()
842
self.assertEqual((2, revid), result)
845
class TestBranch_get_stacked_on_url(TestRemote):
846
"""Test Branch._get_stacked_on_url rpc"""
848
def test_get_stacked_on_invalid_url(self):
849
# test that asking for a stacked on url the server can't access works.
850
# This isn't perfect, but then as we're in the same process there
851
# really isn't anything we can do to be 100% sure that the server
852
# doesn't just open in - this test probably needs to be rewritten using
853
# a spawn()ed server.
854
stacked_branch = self.make_branch('stacked', format='1.9')
855
memory_branch = self.make_branch('base', format='1.9')
856
vfs_url = self.get_vfs_only_url('base')
857
stacked_branch.set_stacked_on_url(vfs_url)
858
transport = stacked_branch.bzrdir.root_transport
859
client = FakeClient(transport.base)
860
client.add_expected_call(
861
'Branch.get_stacked_on_url', ('stacked/',),
862
'success', ('ok', vfs_url))
863
# XXX: Multiple calls are bad, this second call documents what is
865
client.add_expected_call(
866
'Branch.get_stacked_on_url', ('stacked/',),
867
'success', ('ok', vfs_url))
868
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
870
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, None),
872
result = branch.get_stacked_on_url()
873
self.assertEqual(vfs_url, result)
875
def test_backwards_compatible(self):
876
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
877
base_branch = self.make_branch('base', format='1.6')
878
stacked_branch = self.make_branch('stacked', format='1.6')
879
stacked_branch.set_stacked_on_url('../base')
880
client = FakeClient(self.get_url())
881
branch_network_name = self.get_branch_format().network_name()
882
client.add_expected_call(
883
'BzrDir.open_branchV2', ('stacked/',),
884
'success', ('branch', branch_network_name))
885
client.add_expected_call(
886
'BzrDir.find_repositoryV3', ('stacked/',),
887
'success', ('ok', '', 'no', 'no', 'no',
888
stacked_branch.repository._format.network_name()))
889
# called twice, once from constructor and then again by us
890
client.add_expected_call(
891
'Branch.get_stacked_on_url', ('stacked/',),
892
'unknown', ('Branch.get_stacked_on_url',))
893
client.add_expected_call(
894
'Branch.get_stacked_on_url', ('stacked/',),
895
'unknown', ('Branch.get_stacked_on_url',))
896
# this will also do vfs access, but that goes direct to the transport
897
# and isn't seen by the FakeClient.
898
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
899
remote.RemoteBzrDirFormat(), _client=client)
900
branch = bzrdir.open_branch()
901
result = branch.get_stacked_on_url()
902
self.assertEqual('../base', result)
903
client.finished_test()
904
# it's in the fallback list both for the RemoteRepository and its vfs
906
self.assertEqual(1, len(branch.repository._fallback_repositories))
908
len(branch.repository._real_repository._fallback_repositories))
910
def test_get_stacked_on_real_branch(self):
911
base_branch = self.make_branch('base', format='1.6')
912
stacked_branch = self.make_branch('stacked', format='1.6')
913
stacked_branch.set_stacked_on_url('../base')
914
reference_format = self.get_repo_format()
915
network_name = reference_format.network_name()
916
client = FakeClient(self.get_url())
917
branch_network_name = self.get_branch_format().network_name()
918
client.add_expected_call(
919
'BzrDir.open_branchV2', ('stacked/',),
920
'success', ('branch', branch_network_name))
921
client.add_expected_call(
922
'BzrDir.find_repositoryV3', ('stacked/',),
923
'success', ('ok', '', 'no', 'no', 'no', network_name))
924
# called twice, once from constructor and then again by us
925
client.add_expected_call(
926
'Branch.get_stacked_on_url', ('stacked/',),
927
'success', ('ok', '../base'))
928
client.add_expected_call(
929
'Branch.get_stacked_on_url', ('stacked/',),
930
'success', ('ok', '../base'))
931
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
932
remote.RemoteBzrDirFormat(), _client=client)
933
branch = bzrdir.open_branch()
934
result = branch.get_stacked_on_url()
935
self.assertEqual('../base', result)
936
client.finished_test()
937
# it's in the fallback list both for the RemoteRepository and its vfs
939
self.assertEqual(1, len(branch.repository._fallback_repositories))
941
len(branch.repository._real_repository._fallback_repositories))
944
class TestBranchSetLastRevision(RemoteBranchTestCase):
946
def test_set_empty(self):
947
# set_revision_history([]) is translated to calling
948
# Branch.set_last_revision(path, '') on the wire.
949
transport = MemoryTransport()
950
transport.mkdir('branch')
951
transport = transport.clone('branch')
953
client = FakeClient(transport.base)
954
client.add_expected_call(
955
'Branch.get_stacked_on_url', ('branch/',),
956
'error', ('NotStacked',))
957
client.add_expected_call(
958
'Branch.lock_write', ('branch/', '', ''),
959
'success', ('ok', 'branch token', 'repo token'))
960
client.add_expected_call(
961
'Branch.last_revision_info',
963
'success', ('ok', '0', 'null:'))
964
client.add_expected_call(
965
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
967
client.add_expected_call(
968
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
970
branch = self.make_remote_branch(transport, client)
971
# This is a hack to work around the problem that RemoteBranch currently
972
# unnecessarily invokes _ensure_real upon a call to lock_write.
973
branch._ensure_real = lambda: None
975
result = branch.set_revision_history([])
977
self.assertEqual(None, result)
978
client.finished_test()
980
def test_set_nonempty(self):
981
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
982
# Branch.set_last_revision(path, rev-idN) on the wire.
983
transport = MemoryTransport()
984
transport.mkdir('branch')
985
transport = transport.clone('branch')
987
client = FakeClient(transport.base)
988
client.add_expected_call(
989
'Branch.get_stacked_on_url', ('branch/',),
990
'error', ('NotStacked',))
991
client.add_expected_call(
992
'Branch.lock_write', ('branch/', '', ''),
993
'success', ('ok', 'branch token', 'repo token'))
994
client.add_expected_call(
995
'Branch.last_revision_info',
997
'success', ('ok', '0', 'null:'))
999
encoded_body = bz2.compress('\n'.join(lines))
1000
client.add_success_response_with_body(encoded_body, 'ok')
1001
client.add_expected_call(
1002
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1004
client.add_expected_call(
1005
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1007
branch = self.make_remote_branch(transport, client)
1008
# This is a hack to work around the problem that RemoteBranch currently
1009
# unnecessarily invokes _ensure_real upon a call to lock_write.
1010
branch._ensure_real = lambda: None
1011
# Lock the branch, reset the record of remote calls.
1013
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1015
self.assertEqual(None, result)
1016
client.finished_test()
1018
def test_no_such_revision(self):
1019
transport = MemoryTransport()
1020
transport.mkdir('branch')
1021
transport = transport.clone('branch')
1022
# A response of 'NoSuchRevision' is translated into an exception.
1023
client = FakeClient(transport.base)
1024
client.add_expected_call(
1025
'Branch.get_stacked_on_url', ('branch/',),
1026
'error', ('NotStacked',))
1027
client.add_expected_call(
1028
'Branch.lock_write', ('branch/', '', ''),
1029
'success', ('ok', 'branch token', 'repo token'))
1030
client.add_expected_call(
1031
'Branch.last_revision_info',
1033
'success', ('ok', '0', 'null:'))
1034
# get_graph calls to construct the revision history, for the set_rh
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-id',),
1041
'error', ('NoSuchRevision', 'rev-id'))
1042
client.add_expected_call(
1043
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1046
branch = self.make_remote_branch(transport, client)
1049
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1051
client.finished_test()
1053
def test_tip_change_rejected(self):
1054
"""TipChangeRejected responses cause a TipChangeRejected exception to
1057
transport = MemoryTransport()
1058
transport.mkdir('branch')
1059
transport = transport.clone('branch')
1060
client = FakeClient(transport.base)
1061
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1062
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1063
client.add_expected_call(
1064
'Branch.get_stacked_on_url', ('branch/',),
1065
'error', ('NotStacked',))
1066
client.add_expected_call(
1067
'Branch.lock_write', ('branch/', '', ''),
1068
'success', ('ok', 'branch token', 'repo token'))
1069
client.add_expected_call(
1070
'Branch.last_revision_info',
1072
'success', ('ok', '0', 'null:'))
1074
encoded_body = bz2.compress('\n'.join(lines))
1075
client.add_success_response_with_body(encoded_body, 'ok')
1076
client.add_expected_call(
1077
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1078
'error', ('TipChangeRejected', rejection_msg_utf8))
1079
client.add_expected_call(
1080
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1082
branch = self.make_remote_branch(transport, client)
1083
branch._ensure_real = lambda: None
1085
# The 'TipChangeRejected' error response triggered by calling
1086
# set_revision_history causes a TipChangeRejected exception.
1087
err = self.assertRaises(
1088
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1089
# The UTF-8 message from the response has been decoded into a unicode
1091
self.assertIsInstance(err.msg, unicode)
1092
self.assertEqual(rejection_msg_unicode, err.msg)
1094
client.finished_test()
1097
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1099
def test_set_last_revision_info(self):
1100
# set_last_revision_info(num, 'rev-id') is translated to calling
1101
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1102
transport = MemoryTransport()
1103
transport.mkdir('branch')
1104
transport = transport.clone('branch')
1105
client = FakeClient(transport.base)
1106
# get_stacked_on_url
1107
client.add_error_response('NotStacked')
1109
client.add_success_response('ok', 'branch token', 'repo token')
1110
# query the current revision
1111
client.add_success_response('ok', '0', 'null:')
1113
client.add_success_response('ok')
1115
client.add_success_response('ok')
1117
branch = self.make_remote_branch(transport, client)
1118
# Lock the branch, reset the record of remote calls.
1121
result = branch.set_last_revision_info(1234, 'a-revision-id')
1123
[('call', 'Branch.last_revision_info', ('branch/',)),
1124
('call', 'Branch.set_last_revision_info',
1125
('branch/', 'branch token', 'repo token',
1126
'1234', 'a-revision-id'))],
1128
self.assertEqual(None, result)
1130
def test_no_such_revision(self):
1131
# A response of 'NoSuchRevision' is translated into an exception.
1132
transport = MemoryTransport()
1133
transport.mkdir('branch')
1134
transport = transport.clone('branch')
1135
client = FakeClient(transport.base)
1136
# get_stacked_on_url
1137
client.add_error_response('NotStacked')
1139
client.add_success_response('ok', 'branch token', 'repo token')
1141
client.add_error_response('NoSuchRevision', 'revid')
1143
client.add_success_response('ok')
1145
branch = self.make_remote_branch(transport, client)
1146
# Lock the branch, reset the record of remote calls.
1151
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1154
def lock_remote_branch(self, branch):
1155
"""Trick a RemoteBranch into thinking it is locked."""
1156
branch._lock_mode = 'w'
1157
branch._lock_count = 2
1158
branch._lock_token = 'branch token'
1159
branch._repo_lock_token = 'repo token'
1160
branch.repository._lock_mode = 'w'
1161
branch.repository._lock_count = 2
1162
branch.repository._lock_token = 'repo token'
1164
def test_backwards_compatibility(self):
1165
"""If the server does not support the Branch.set_last_revision_info
1166
verb (which is new in 1.4), then the client falls back to VFS methods.
1168
# This test is a little messy. Unlike most tests in this file, it
1169
# doesn't purely test what a Remote* object sends over the wire, and
1170
# how it reacts to responses from the wire. It instead relies partly
1171
# on asserting that the RemoteBranch will call
1172
# self._real_branch.set_last_revision_info(...).
1174
# First, set up our RemoteBranch with a FakeClient that raises
1175
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1176
transport = MemoryTransport()
1177
transport.mkdir('branch')
1178
transport = transport.clone('branch')
1179
client = FakeClient(transport.base)
1180
client.add_expected_call(
1181
'Branch.get_stacked_on_url', ('branch/',),
1182
'error', ('NotStacked',))
1183
client.add_expected_call(
1184
'Branch.last_revision_info',
1186
'success', ('ok', '0', 'null:'))
1187
client.add_expected_call(
1188
'Branch.set_last_revision_info',
1189
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1190
'unknown', 'Branch.set_last_revision_info')
1192
branch = self.make_remote_branch(transport, client)
1193
class StubRealBranch(object):
1196
def set_last_revision_info(self, revno, revision_id):
1198
('set_last_revision_info', revno, revision_id))
1199
def _clear_cached_state(self):
1201
real_branch = StubRealBranch()
1202
branch._real_branch = real_branch
1203
self.lock_remote_branch(branch)
1205
# Call set_last_revision_info, and verify it behaved as expected.
1206
result = branch.set_last_revision_info(1234, 'a-revision-id')
1208
[('set_last_revision_info', 1234, 'a-revision-id')],
1210
client.finished_test()
1212
def test_unexpected_error(self):
1213
# If the server sends an error the client doesn't understand, it gets
1214
# turned into an UnknownErrorFromSmartServer, which is presented as a
1215
# non-internal error to the user.
1216
transport = MemoryTransport()
1217
transport.mkdir('branch')
1218
transport = transport.clone('branch')
1219
client = FakeClient(transport.base)
1220
# get_stacked_on_url
1221
client.add_error_response('NotStacked')
1223
client.add_success_response('ok', 'branch token', 'repo token')
1225
client.add_error_response('UnexpectedError')
1227
client.add_success_response('ok')
1229
branch = self.make_remote_branch(transport, client)
1230
# Lock the branch, reset the record of remote calls.
1234
err = self.assertRaises(
1235
errors.UnknownErrorFromSmartServer,
1236
branch.set_last_revision_info, 123, 'revid')
1237
self.assertEqual(('UnexpectedError',), err.error_tuple)
1240
def test_tip_change_rejected(self):
1241
"""TipChangeRejected responses cause a TipChangeRejected exception to
1244
transport = MemoryTransport()
1245
transport.mkdir('branch')
1246
transport = transport.clone('branch')
1247
client = FakeClient(transport.base)
1248
# get_stacked_on_url
1249
client.add_error_response('NotStacked')
1251
client.add_success_response('ok', 'branch token', 'repo token')
1253
client.add_error_response('TipChangeRejected', 'rejection message')
1255
client.add_success_response('ok')
1257
branch = self.make_remote_branch(transport, client)
1258
# Lock the branch, reset the record of remote calls.
1260
self.addCleanup(branch.unlock)
1263
# The 'TipChangeRejected' error response triggered by calling
1264
# set_last_revision_info causes a TipChangeRejected exception.
1265
err = self.assertRaises(
1266
errors.TipChangeRejected,
1267
branch.set_last_revision_info, 123, 'revid')
1268
self.assertEqual('rejection message', err.msg)
1271
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1272
"""Getting the branch configuration should use an abstract method not vfs.
1275
def test_get_branch_conf(self):
1276
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1277
## # We should see that branch.get_config() does a single rpc to get the
1278
## # remote configuration file, abstracting away where that is stored on
1279
## # the server. However at the moment it always falls back to using the
1280
## # vfs, and this would need some changes in config.py.
1282
## # in an empty branch we decode the response properly
1283
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1284
## # we need to make a real branch because the remote_branch.control_files
1285
## # will trigger _ensure_real.
1286
## branch = self.make_branch('quack')
1287
## transport = branch.bzrdir.root_transport
1288
## # we do not want bzrdir to make any remote calls
1289
## bzrdir = RemoteBzrDir(transport, _client=False)
1290
## branch = RemoteBranch(bzrdir, None, _client=client)
1291
## config = branch.get_config()
1292
## self.assertEqual(
1293
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1297
class TestBranchLockWrite(RemoteBranchTestCase):
1299
def test_lock_write_unlockable(self):
1300
transport = MemoryTransport()
1301
client = FakeClient(transport.base)
1302
client.add_expected_call(
1303
'Branch.get_stacked_on_url', ('quack/',),
1304
'error', ('NotStacked',),)
1305
client.add_expected_call(
1306
'Branch.lock_write', ('quack/', '', ''),
1307
'error', ('UnlockableTransport',))
1308
transport.mkdir('quack')
1309
transport = transport.clone('quack')
1310
branch = self.make_remote_branch(transport, client)
1311
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1312
client.finished_test()
1315
class TestTransportIsReadonly(tests.TestCase):
1317
def test_true(self):
1318
client = FakeClient()
1319
client.add_success_response('yes')
1320
transport = RemoteTransport('bzr://example.com/', medium=False,
1322
self.assertEqual(True, transport.is_readonly())
1324
[('call', 'Transport.is_readonly', ())],
1327
def test_false(self):
1328
client = FakeClient()
1329
client.add_success_response('no')
1330
transport = RemoteTransport('bzr://example.com/', medium=False,
1332
self.assertEqual(False, transport.is_readonly())
1334
[('call', 'Transport.is_readonly', ())],
1337
def test_error_from_old_server(self):
1338
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1340
Clients should treat it as a "no" response, because is_readonly is only
1341
advisory anyway (a transport could be read-write, but then the
1342
underlying filesystem could be readonly anyway).
1344
client = FakeClient()
1345
client.add_unknown_method_response('Transport.is_readonly')
1346
transport = RemoteTransport('bzr://example.com/', medium=False,
1348
self.assertEqual(False, transport.is_readonly())
1350
[('call', 'Transport.is_readonly', ())],
1354
class TestTransportMkdir(tests.TestCase):
1356
def test_permissiondenied(self):
1357
client = FakeClient()
1358
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1359
transport = RemoteTransport('bzr://example.com/', medium=False,
1361
exc = self.assertRaises(
1362
errors.PermissionDenied, transport.mkdir, 'client path')
1363
expected_error = errors.PermissionDenied('/client path', 'extra')
1364
self.assertEqual(expected_error, exc)
1367
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1369
def test_defaults_to_none(self):
1370
t = RemoteSSHTransport('bzr+ssh://example.com')
1371
self.assertIs(None, t._get_credentials()[0])
1373
def test_uses_authentication_config(self):
1374
conf = config.AuthenticationConfig()
1375
conf._get_config().update(
1376
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1379
t = RemoteSSHTransport('bzr+ssh://example.com')
1380
self.assertEqual('bar', t._get_credentials()[0])
1383
class TestRemoteRepository(TestRemote):
1384
"""Base for testing RemoteRepository protocol usage.
1386
These tests contain frozen requests and responses. We want any changes to
1387
what is sent or expected to be require a thoughtful update to these tests
1388
because they might break compatibility with different-versioned servers.
1391
def setup_fake_client_and_repository(self, transport_path):
1392
"""Create the fake client and repository for testing with.
1394
There's no real server here; we just have canned responses sent
1397
:param transport_path: Path below the root of the MemoryTransport
1398
where the repository will be created.
1400
transport = MemoryTransport()
1401
transport.mkdir(transport_path)
1402
client = FakeClient(transport.base)
1403
transport = transport.clone(transport_path)
1404
# we do not want bzrdir to make any remote calls
1405
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1407
repo = RemoteRepository(bzrdir, None, _client=client)
1411
class TestRepositoryGatherStats(TestRemoteRepository):
1413
def test_revid_none(self):
1414
# ('ok',), body with revisions and size
1415
transport_path = 'quack'
1416
repo, client = self.setup_fake_client_and_repository(transport_path)
1417
client.add_success_response_with_body(
1418
'revisions: 2\nsize: 18\n', 'ok')
1419
result = repo.gather_stats(None)
1421
[('call_expecting_body', 'Repository.gather_stats',
1422
('quack/','','no'))],
1424
self.assertEqual({'revisions': 2, 'size': 18}, result)
1426
def test_revid_no_committers(self):
1427
# ('ok',), body without committers
1428
body = ('firstrev: 123456.300 3600\n'
1429
'latestrev: 654231.400 0\n'
1432
transport_path = 'quick'
1433
revid = u'\xc8'.encode('utf8')
1434
repo, client = self.setup_fake_client_and_repository(transport_path)
1435
client.add_success_response_with_body(body, 'ok')
1436
result = repo.gather_stats(revid)
1438
[('call_expecting_body', 'Repository.gather_stats',
1439
('quick/', revid, 'no'))],
1441
self.assertEqual({'revisions': 2, 'size': 18,
1442
'firstrev': (123456.300, 3600),
1443
'latestrev': (654231.400, 0),},
1446
def test_revid_with_committers(self):
1447
# ('ok',), body with committers
1448
body = ('committers: 128\n'
1449
'firstrev: 123456.300 3600\n'
1450
'latestrev: 654231.400 0\n'
1453
transport_path = 'buick'
1454
revid = u'\xc8'.encode('utf8')
1455
repo, client = self.setup_fake_client_and_repository(transport_path)
1456
client.add_success_response_with_body(body, 'ok')
1457
result = repo.gather_stats(revid, True)
1459
[('call_expecting_body', 'Repository.gather_stats',
1460
('buick/', revid, 'yes'))],
1462
self.assertEqual({'revisions': 2, 'size': 18,
1464
'firstrev': (123456.300, 3600),
1465
'latestrev': (654231.400, 0),},
1469
class TestRepositoryGetGraph(TestRemoteRepository):
1471
def test_get_graph(self):
1472
# get_graph returns a graph with a custom parents provider.
1473
transport_path = 'quack'
1474
repo, client = self.setup_fake_client_and_repository(transport_path)
1475
graph = repo.get_graph()
1476
self.assertNotEqual(graph._parents_provider, repo)
1479
class TestRepositoryGetParentMap(TestRemoteRepository):
1481
def test_get_parent_map_caching(self):
1482
# get_parent_map returns from cache until unlock()
1483
# setup a reponse with two revisions
1484
r1 = u'\u0e33'.encode('utf8')
1485
r2 = u'\u0dab'.encode('utf8')
1486
lines = [' '.join([r2, r1]), r1]
1487
encoded_body = bz2.compress('\n'.join(lines))
1489
transport_path = 'quack'
1490
repo, client = self.setup_fake_client_and_repository(transport_path)
1491
client.add_success_response_with_body(encoded_body, 'ok')
1492
client.add_success_response_with_body(encoded_body, 'ok')
1494
graph = repo.get_graph()
1495
parents = graph.get_parent_map([r2])
1496
self.assertEqual({r2: (r1,)}, parents)
1497
# locking and unlocking deeper should not reset
1500
parents = graph.get_parent_map([r1])
1501
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1503
[('call_with_body_bytes_expecting_body',
1504
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
1507
# now we call again, and it should use the second response.
1509
graph = repo.get_graph()
1510
parents = graph.get_parent_map([r1])
1511
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1513
[('call_with_body_bytes_expecting_body',
1514
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
1515
('call_with_body_bytes_expecting_body',
1516
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
1521
def test_get_parent_map_reconnects_if_unknown_method(self):
1522
transport_path = 'quack'
1523
repo, client = self.setup_fake_client_and_repository(transport_path)
1524
client.add_unknown_method_response('Repository,get_parent_map')
1525
client.add_success_response_with_body('', 'ok')
1526
self.assertFalse(client._medium._is_remote_before((1, 2)))
1527
rev_id = 'revision-id'
1528
expected_deprecations = [
1529
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1531
parents = self.callDeprecated(
1532
expected_deprecations, repo.get_parent_map, [rev_id])
1534
[('call_with_body_bytes_expecting_body',
1535
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
1536
('disconnect medium',),
1537
('call_expecting_body', 'Repository.get_revision_graph',
1540
# The medium is now marked as being connected to an older server
1541
self.assertTrue(client._medium._is_remote_before((1, 2)))
1543
def test_get_parent_map_fallback_parentless_node(self):
1544
"""get_parent_map falls back to get_revision_graph on old servers. The
1545
results from get_revision_graph are tweaked to match the get_parent_map
1548
Specifically, a {key: ()} result from get_revision_graph means "no
1549
parents" for that key, which in get_parent_map results should be
1550
represented as {key: ('null:',)}.
1552
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1554
rev_id = 'revision-id'
1555
transport_path = 'quack'
1556
repo, client = self.setup_fake_client_and_repository(transport_path)
1557
client.add_success_response_with_body(rev_id, 'ok')
1558
client._medium._remember_remote_is_before((1, 2))
1559
expected_deprecations = [
1560
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1562
parents = self.callDeprecated(
1563
expected_deprecations, repo.get_parent_map, [rev_id])
1565
[('call_expecting_body', 'Repository.get_revision_graph',
1568
self.assertEqual({rev_id: ('null:',)}, parents)
1570
def test_get_parent_map_unexpected_response(self):
1571
repo, client = self.setup_fake_client_and_repository('path')
1572
client.add_success_response('something unexpected!')
1574
errors.UnexpectedSmartServerResponse,
1575
repo.get_parent_map, ['a-revision-id'])
1578
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1580
def test_allows_new_revisions(self):
1581
"""get_parent_map's results can be updated by commit."""
1582
smart_server = server.SmartTCPServer_for_testing()
1583
smart_server.setUp()
1584
self.addCleanup(smart_server.tearDown)
1585
self.make_branch('branch')
1586
branch = Branch.open(smart_server.get_url() + '/branch')
1587
tree = branch.create_checkout('tree', lightweight=True)
1589
self.addCleanup(tree.unlock)
1590
graph = tree.branch.repository.get_graph()
1591
# This provides an opportunity for the missing rev-id to be cached.
1592
self.assertEqual({}, graph.get_parent_map(['rev1']))
1593
tree.commit('message', rev_id='rev1')
1594
graph = tree.branch.repository.get_graph()
1595
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1598
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1600
def test_null_revision(self):
1601
# a null revision has the predictable result {}, we should have no wire
1602
# traffic when calling it with this argument
1603
transport_path = 'empty'
1604
repo, client = self.setup_fake_client_and_repository(transport_path)
1605
client.add_success_response('notused')
1606
result = self.applyDeprecated(one_four, repo.get_revision_graph,
1608
self.assertEqual([], client._calls)
1609
self.assertEqual({}, result)
1611
def test_none_revision(self):
1612
# with none we want the entire graph
1613
r1 = u'\u0e33'.encode('utf8')
1614
r2 = u'\u0dab'.encode('utf8')
1615
lines = [' '.join([r2, r1]), r1]
1616
encoded_body = '\n'.join(lines)
1618
transport_path = 'sinhala'
1619
repo, client = self.setup_fake_client_and_repository(transport_path)
1620
client.add_success_response_with_body(encoded_body, 'ok')
1621
result = self.applyDeprecated(one_four, repo.get_revision_graph)
1623
[('call_expecting_body', 'Repository.get_revision_graph',
1626
self.assertEqual({r1: (), r2: (r1, )}, result)
1628
def test_specific_revision(self):
1629
# with a specific revision we want the graph for that
1630
# with none we want the entire graph
1631
r11 = u'\u0e33'.encode('utf8')
1632
r12 = u'\xc9'.encode('utf8')
1633
r2 = u'\u0dab'.encode('utf8')
1634
lines = [' '.join([r2, r11, r12]), r11, r12]
1635
encoded_body = '\n'.join(lines)
1637
transport_path = 'sinhala'
1638
repo, client = self.setup_fake_client_and_repository(transport_path)
1639
client.add_success_response_with_body(encoded_body, 'ok')
1640
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
1642
[('call_expecting_body', 'Repository.get_revision_graph',
1645
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1647
def test_no_such_revision(self):
1649
transport_path = 'sinhala'
1650
repo, client = self.setup_fake_client_and_repository(transport_path)
1651
client.add_error_response('nosuchrevision', revid)
1652
# also check that the right revision is reported in the error
1653
self.assertRaises(errors.NoSuchRevision,
1654
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1656
[('call_expecting_body', 'Repository.get_revision_graph',
1657
('sinhala/', revid))],
1660
def test_unexpected_error(self):
1662
transport_path = 'sinhala'
1663
repo, client = self.setup_fake_client_and_repository(transport_path)
1664
client.add_error_response('AnUnexpectedError')
1665
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1666
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1667
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1670
class TestRepositoryIsShared(TestRemoteRepository):
1672
def test_is_shared(self):
1673
# ('yes', ) for Repository.is_shared -> 'True'.
1674
transport_path = 'quack'
1675
repo, client = self.setup_fake_client_and_repository(transport_path)
1676
client.add_success_response('yes')
1677
result = repo.is_shared()
1679
[('call', 'Repository.is_shared', ('quack/',))],
1681
self.assertEqual(True, result)
1683
def test_is_not_shared(self):
1684
# ('no', ) for Repository.is_shared -> 'False'.
1685
transport_path = 'qwack'
1686
repo, client = self.setup_fake_client_and_repository(transport_path)
1687
client.add_success_response('no')
1688
result = repo.is_shared()
1690
[('call', 'Repository.is_shared', ('qwack/',))],
1692
self.assertEqual(False, result)
1695
class TestRepositoryLockWrite(TestRemoteRepository):
1697
def test_lock_write(self):
1698
transport_path = 'quack'
1699
repo, client = self.setup_fake_client_and_repository(transport_path)
1700
client.add_success_response('ok', 'a token')
1701
result = repo.lock_write()
1703
[('call', 'Repository.lock_write', ('quack/', ''))],
1705
self.assertEqual('a token', result)
1707
def test_lock_write_already_locked(self):
1708
transport_path = 'quack'
1709
repo, client = self.setup_fake_client_and_repository(transport_path)
1710
client.add_error_response('LockContention')
1711
self.assertRaises(errors.LockContention, repo.lock_write)
1713
[('call', 'Repository.lock_write', ('quack/', ''))],
1716
def test_lock_write_unlockable(self):
1717
transport_path = 'quack'
1718
repo, client = self.setup_fake_client_and_repository(transport_path)
1719
client.add_error_response('UnlockableTransport')
1720
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1722
[('call', 'Repository.lock_write', ('quack/', ''))],
1726
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1728
def test_backwards_compat(self):
1729
self.setup_smart_server_with_call_log()
1730
repo = self.make_repository('.')
1731
self.reset_smart_call_log()
1732
verb = 'Repository.set_make_working_trees'
1733
self.disable_verb(verb)
1734
repo.set_make_working_trees(True)
1735
call_count = len([call for call in self.hpss_calls if
1736
call.call.method == verb])
1737
self.assertEqual(1, call_count)
1739
def test_current(self):
1740
transport_path = 'quack'
1741
repo, client = self.setup_fake_client_and_repository(transport_path)
1742
client.add_expected_call(
1743
'Repository.set_make_working_trees', ('quack/', 'True'),
1745
client.add_expected_call(
1746
'Repository.set_make_working_trees', ('quack/', 'False'),
1748
repo.set_make_working_trees(True)
1749
repo.set_make_working_trees(False)
1752
class TestRepositoryUnlock(TestRemoteRepository):
1754
def test_unlock(self):
1755
transport_path = 'quack'
1756
repo, client = self.setup_fake_client_and_repository(transport_path)
1757
client.add_success_response('ok', 'a token')
1758
client.add_success_response('ok')
1762
[('call', 'Repository.lock_write', ('quack/', '')),
1763
('call', 'Repository.unlock', ('quack/', 'a token'))],
1766
def test_unlock_wrong_token(self):
1767
# If somehow the token is wrong, unlock will raise TokenMismatch.
1768
transport_path = 'quack'
1769
repo, client = self.setup_fake_client_and_repository(transport_path)
1770
client.add_success_response('ok', 'a token')
1771
client.add_error_response('TokenMismatch')
1773
self.assertRaises(errors.TokenMismatch, repo.unlock)
1776
class TestRepositoryHasRevision(TestRemoteRepository):
1778
def test_none(self):
1779
# repo.has_revision(None) should not cause any traffic.
1780
transport_path = 'quack'
1781
repo, client = self.setup_fake_client_and_repository(transport_path)
1783
# The null revision is always there, so has_revision(None) == True.
1784
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1786
# The remote repo shouldn't be accessed.
1787
self.assertEqual([], client._calls)
1790
class TestRepositoryTarball(TestRemoteRepository):
1792
# This is a canned tarball reponse we can validate against
1794
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1795
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1796
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1797
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1798
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1799
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1800
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1801
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1802
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1803
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1804
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1805
'nWQ7QH/F3JFOFCQ0aSPfA='
1808
def test_repository_tarball(self):
1809
# Test that Repository.tarball generates the right operations
1810
transport_path = 'repo'
1811
expected_calls = [('call_expecting_body', 'Repository.tarball',
1812
('repo/', 'bz2',),),
1814
repo, client = self.setup_fake_client_and_repository(transport_path)
1815
client.add_success_response_with_body(self.tarball_content, 'ok')
1816
# Now actually ask for the tarball
1817
tarball_file = repo._get_tarball('bz2')
1819
self.assertEqual(expected_calls, client._calls)
1820
self.assertEqual(self.tarball_content, tarball_file.read())
1822
tarball_file.close()
1825
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
1826
"""RemoteRepository.copy_content_into optimizations"""
1828
def test_copy_content_remote_to_local(self):
1829
self.transport_server = server.SmartTCPServer_for_testing
1830
src_repo = self.make_repository('repo1')
1831
src_repo = repository.Repository.open(self.get_url('repo1'))
1832
# At the moment the tarball-based copy_content_into can't write back
1833
# into a smart server. It would be good if it could upload the
1834
# tarball; once that works we'd have to create repositories of
1835
# different formats. -- mbp 20070410
1836
dest_url = self.get_vfs_only_url('repo2')
1837
dest_bzrdir = BzrDir.create(dest_url)
1838
dest_repo = dest_bzrdir.create_repository()
1839
self.assertFalse(isinstance(dest_repo, RemoteRepository))
1840
self.assertTrue(isinstance(src_repo, RemoteRepository))
1841
src_repo.copy_content_into(dest_repo)
1844
class _StubRealPackRepository(object):
1846
def __init__(self, calls):
1847
self._pack_collection = _StubPackCollection(calls)
1850
class _StubPackCollection(object):
1852
def __init__(self, calls):
1856
self.calls.append(('pack collection autopack',))
1858
def reload_pack_names(self):
1859
self.calls.append(('pack collection reload_pack_names',))
1862
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1863
"""Tests for RemoteRepository.autopack implementation."""
1866
"""When the server returns 'ok' and there's no _real_repository, then
1867
nothing else happens: the autopack method is done.
1869
transport_path = 'quack'
1870
repo, client = self.setup_fake_client_and_repository(transport_path)
1871
client.add_expected_call(
1872
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
1874
client.finished_test()
1876
def test_ok_with_real_repo(self):
1877
"""When the server returns 'ok' and there is a _real_repository, then
1878
the _real_repository's reload_pack_name's method will be called.
1880
transport_path = 'quack'
1881
repo, client = self.setup_fake_client_and_repository(transport_path)
1882
client.add_expected_call(
1883
'PackRepository.autopack', ('quack/',),
1885
repo._real_repository = _StubRealPackRepository(client._calls)
1888
[('call', 'PackRepository.autopack', ('quack/',)),
1889
('pack collection reload_pack_names',)],
1892
def test_backwards_compatibility(self):
1893
"""If the server does not recognise the PackRepository.autopack verb,
1894
fallback to the real_repository's implementation.
1896
transport_path = 'quack'
1897
repo, client = self.setup_fake_client_and_repository(transport_path)
1898
client.add_unknown_method_response('PackRepository.autopack')
1899
def stub_ensure_real():
1900
client._calls.append(('_ensure_real',))
1901
repo._real_repository = _StubRealPackRepository(client._calls)
1902
repo._ensure_real = stub_ensure_real
1905
[('call', 'PackRepository.autopack', ('quack/',)),
1907
('pack collection autopack',)],
1911
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
1912
"""Base class for unit tests for bzrlib.remote._translate_error."""
1914
def translateTuple(self, error_tuple, **context):
1915
"""Call _translate_error with an ErrorFromSmartServer built from the
1918
:param error_tuple: A tuple of a smart server response, as would be
1919
passed to an ErrorFromSmartServer.
1920
:kwargs context: context items to call _translate_error with.
1922
:returns: The error raised by _translate_error.
1924
# Raise the ErrorFromSmartServer before passing it as an argument,
1925
# because _translate_error may need to re-raise it with a bare 'raise'
1927
server_error = errors.ErrorFromSmartServer(error_tuple)
1928
translated_error = self.translateErrorFromSmartServer(
1929
server_error, **context)
1930
return translated_error
1932
def translateErrorFromSmartServer(self, error_object, **context):
1933
"""Like translateTuple, but takes an already constructed
1934
ErrorFromSmartServer rather than a tuple.
1938
except errors.ErrorFromSmartServer, server_error:
1939
translated_error = self.assertRaises(
1940
errors.BzrError, remote._translate_error, server_error,
1942
return translated_error
1945
class TestErrorTranslationSuccess(TestErrorTranslationBase):
1946
"""Unit tests for bzrlib.remote._translate_error.
1948
Given an ErrorFromSmartServer (which has an error tuple from a smart
1949
server) and some context, _translate_error raises more specific errors from
1952
This test case covers the cases where _translate_error succeeds in
1953
translating an ErrorFromSmartServer to something better. See
1954
TestErrorTranslationRobustness for other cases.
1957
def test_NoSuchRevision(self):
1958
branch = self.make_branch('')
1960
translated_error = self.translateTuple(
1961
('NoSuchRevision', revid), branch=branch)
1962
expected_error = errors.NoSuchRevision(branch, revid)
1963
self.assertEqual(expected_error, translated_error)
1965
def test_nosuchrevision(self):
1966
repository = self.make_repository('')
1968
translated_error = self.translateTuple(
1969
('nosuchrevision', revid), repository=repository)
1970
expected_error = errors.NoSuchRevision(repository, revid)
1971
self.assertEqual(expected_error, translated_error)
1973
def test_nobranch(self):
1974
bzrdir = self.make_bzrdir('')
1975
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
1976
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
1977
self.assertEqual(expected_error, translated_error)
1979
def test_LockContention(self):
1980
translated_error = self.translateTuple(('LockContention',))
1981
expected_error = errors.LockContention('(remote lock)')
1982
self.assertEqual(expected_error, translated_error)
1984
def test_UnlockableTransport(self):
1985
bzrdir = self.make_bzrdir('')
1986
translated_error = self.translateTuple(
1987
('UnlockableTransport',), bzrdir=bzrdir)
1988
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
1989
self.assertEqual(expected_error, translated_error)
1991
def test_LockFailed(self):
1992
lock = 'str() of a server lock'
1993
why = 'str() of why'
1994
translated_error = self.translateTuple(('LockFailed', lock, why))
1995
expected_error = errors.LockFailed(lock, why)
1996
self.assertEqual(expected_error, translated_error)
1998
def test_TokenMismatch(self):
1999
token = 'a lock token'
2000
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2001
expected_error = errors.TokenMismatch(token, '(remote token)')
2002
self.assertEqual(expected_error, translated_error)
2004
def test_Diverged(self):
2005
branch = self.make_branch('a')
2006
other_branch = self.make_branch('b')
2007
translated_error = self.translateTuple(
2008
('Diverged',), branch=branch, other_branch=other_branch)
2009
expected_error = errors.DivergedBranches(branch, other_branch)
2010
self.assertEqual(expected_error, translated_error)
2012
def test_ReadError_no_args(self):
2014
translated_error = self.translateTuple(('ReadError',), path=path)
2015
expected_error = errors.ReadError(path)
2016
self.assertEqual(expected_error, translated_error)
2018
def test_ReadError(self):
2020
translated_error = self.translateTuple(('ReadError', path))
2021
expected_error = errors.ReadError(path)
2022
self.assertEqual(expected_error, translated_error)
2024
def test_PermissionDenied_no_args(self):
2026
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2027
expected_error = errors.PermissionDenied(path)
2028
self.assertEqual(expected_error, translated_error)
2030
def test_PermissionDenied_one_arg(self):
2032
translated_error = self.translateTuple(('PermissionDenied', path))
2033
expected_error = errors.PermissionDenied(path)
2034
self.assertEqual(expected_error, translated_error)
2036
def test_PermissionDenied_one_arg_and_context(self):
2037
"""Given a choice between a path from the local context and a path on
2038
the wire, _translate_error prefers the path from the local context.
2040
local_path = 'local path'
2041
remote_path = 'remote path'
2042
translated_error = self.translateTuple(
2043
('PermissionDenied', remote_path), path=local_path)
2044
expected_error = errors.PermissionDenied(local_path)
2045
self.assertEqual(expected_error, translated_error)
2047
def test_PermissionDenied_two_args(self):
2049
extra = 'a string with extra info'
2050
translated_error = self.translateTuple(
2051
('PermissionDenied', path, extra))
2052
expected_error = errors.PermissionDenied(path, extra)
2053
self.assertEqual(expected_error, translated_error)
2056
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2057
"""Unit tests for bzrlib.remote._translate_error's robustness.
2059
TestErrorTranslationSuccess is for cases where _translate_error can
2060
translate successfully. This class about how _translate_err behaves when
2061
it fails to translate: it re-raises the original error.
2064
def test_unrecognised_server_error(self):
2065
"""If the error code from the server is not recognised, the original
2066
ErrorFromSmartServer is propagated unmodified.
2068
error_tuple = ('An unknown error tuple',)
2069
server_error = errors.ErrorFromSmartServer(error_tuple)
2070
translated_error = self.translateErrorFromSmartServer(server_error)
2071
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2072
self.assertEqual(expected_error, translated_error)
2074
def test_context_missing_a_key(self):
2075
"""In case of a bug in the client, or perhaps an unexpected response
2076
from a server, _translate_error returns the original error tuple from
2077
the server and mutters a warning.
2079
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2080
# in the context dict. So let's give it an empty context dict instead
2081
# to exercise its error recovery.
2083
error_tuple = ('NoSuchRevision', 'revid')
2084
server_error = errors.ErrorFromSmartServer(error_tuple)
2085
translated_error = self.translateErrorFromSmartServer(server_error)
2086
self.assertEqual(server_error, translated_error)
2087
# In addition to re-raising ErrorFromSmartServer, some debug info has
2088
# been muttered to the log file for developer to look at.
2089
self.assertContainsRe(
2090
self._get_log(keep_log_file=True),
2091
"Missing key 'branch' in context")
2093
def test_path_missing(self):
2094
"""Some translations (PermissionDenied, ReadError) can determine the
2095
'path' variable from either the wire or the local context. If neither
2096
has it, then an error is raised.
2098
error_tuple = ('ReadError',)
2099
server_error = errors.ErrorFromSmartServer(error_tuple)
2100
translated_error = self.translateErrorFromSmartServer(server_error)
2101
self.assertEqual(server_error, translated_error)
2102
# In addition to re-raising ErrorFromSmartServer, some debug info has
2103
# been muttered to the log file for developer to look at.
2104
self.assertContainsRe(
2105
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2108
class TestStacking(tests.TestCaseWithTransport):
2109
"""Tests for operations on stacked remote repositories.
2111
The underlying format type must support stacking.
2114
def test_access_stacked_remote(self):
2115
# based on <http://launchpad.net/bugs/261315>
2116
# make a branch stacked on another repository containing an empty
2117
# revision, then open it over hpss - we should be able to see that
2119
base_transport = self.get_transport()
2120
base_builder = self.make_branch_builder('base', format='1.6')
2121
base_builder.start_series()
2122
base_revid = base_builder.build_snapshot('rev-id', None,
2123
[('add', ('', None, 'directory', None))],
2125
base_builder.finish_series()
2126
stacked_branch = self.make_branch('stacked', format='1.6')
2127
stacked_branch.set_stacked_on_url('../base')
2128
# start a server looking at this
2129
smart_server = server.SmartTCPServer_for_testing()
2130
smart_server.setUp()
2131
self.addCleanup(smart_server.tearDown)
2132
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2133
# can get its branch and repository
2134
remote_branch = remote_bzrdir.open_branch()
2135
remote_repo = remote_branch.repository
2136
remote_repo.lock_read()
2138
# it should have an appropriate fallback repository, which should also
2139
# be a RemoteRepository
2140
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2141
self.assertIsInstance(remote_repo._fallback_repositories[0],
2143
# and it has the revision committed to the underlying repository;
2144
# these have varying implementations so we try several of them
2145
self.assertTrue(remote_repo.has_revisions([base_revid]))
2146
self.assertTrue(remote_repo.has_revision(base_revid))
2147
self.assertEqual(remote_repo.get_revision(base_revid).message,
2150
remote_repo.unlock()
2152
def prepare_stacked_remote_branch(self):
2153
smart_server = server.SmartTCPServer_for_testing()
2154
smart_server.setUp()
2155
self.addCleanup(smart_server.tearDown)
2156
tree1 = self.make_branch_and_tree('tree1')
2157
tree1.commit('rev1', rev_id='rev1')
2158
tree2 = self.make_branch_and_tree('tree2', format='1.6')
2159
tree2.branch.set_stacked_on_url(tree1.branch.base)
2160
branch2 = Branch.open(smart_server.get_url() + '/tree2')
2162
self.addCleanup(branch2.unlock)
2165
def test_stacked_get_parent_map(self):
2166
# the public implementation of get_parent_map obeys stacking
2167
branch = self.prepare_stacked_remote_branch()
2168
repo = branch.repository
2169
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2171
def test_unstacked_get_parent_map(self):
2172
# _unstacked_provider.get_parent_map ignores stacking
2173
branch = self.prepare_stacked_remote_branch()
2174
provider = branch.repository._unstacked_provider
2175
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2178
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2181
super(TestRemoteBranchEffort, self).setUp()
2182
# Create a smart server that publishes whatever the backing VFS server
2184
self.smart_server = server.SmartTCPServer_for_testing()
2185
self.smart_server.setUp(self.get_server())
2186
self.addCleanup(self.smart_server.tearDown)
2187
# Log all HPSS calls into self.hpss_calls.
2188
_SmartClient.hooks.install_named_hook(
2189
'call', self.capture_hpss_call, None)
2190
self.hpss_calls = []
2192
def capture_hpss_call(self, params):
2193
self.hpss_calls.append(params.method)
2195
def test_copy_content_into_avoids_revision_history(self):
2196
local = self.make_branch('local')
2197
remote_backing_tree = self.make_branch_and_tree('remote')
2198
remote_backing_tree.commit("Commit.")
2199
remote_branch_url = self.smart_server.get_url() + 'remote'
2200
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2201
local.repository.fetch(remote_branch.repository)
2202
self.hpss_calls = []
2203
remote_branch.copy_content_into(local)
2204
self.assertFalse('Branch.revision_history' in self.hpss_calls)