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 (
49
from bzrlib.revision import NULL_REVISION
50
from bzrlib.smart import server, medium
51
from bzrlib.smart.client import _SmartClient
52
from bzrlib.symbol_versioning import one_four
53
from bzrlib.transport import get_transport, http
54
from bzrlib.transport.memory import MemoryTransport
55
from bzrlib.transport.remote import (
62
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
65
self.transport_server = server.SmartTCPServer_for_testing
66
super(BasicRemoteObjectTests, self).setUp()
67
self.transport = self.get_transport()
68
# make a branch that can be opened over the smart transport
69
self.local_wt = BzrDir.create_standalone_workingtree('.')
72
self.transport.disconnect()
73
tests.TestCaseWithTransport.tearDown(self)
75
def test_create_remote_bzrdir(self):
76
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
77
self.assertIsInstance(b, BzrDir)
79
def test_open_remote_branch(self):
80
# open a standalone branch in the working directory
81
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
82
branch = b.open_branch()
83
self.assertIsInstance(branch, Branch)
85
def test_remote_repository(self):
86
b = BzrDir.open_from_transport(self.transport)
87
repo = b.open_repository()
88
revid = u'\xc823123123'.encode('utf8')
89
self.assertFalse(repo.has_revision(revid))
90
self.local_wt.commit(message='test commit', rev_id=revid)
91
self.assertTrue(repo.has_revision(revid))
93
def test_remote_branch_revision_history(self):
94
b = BzrDir.open_from_transport(self.transport).open_branch()
95
self.assertEqual([], b.revision_history())
96
r1 = self.local_wt.commit('1st commit')
97
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
98
self.assertEqual([r1, r2], b.revision_history())
100
def test_find_correct_format(self):
101
"""Should open a RemoteBzrDir over a RemoteTransport"""
102
fmt = BzrDirFormat.find_format(self.transport)
103
self.assertTrue(RemoteBzrDirFormat
104
in BzrDirFormat._control_server_formats)
105
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
107
def test_open_detected_smart_format(self):
108
fmt = BzrDirFormat.find_format(self.transport)
109
d = fmt.open(self.transport)
110
self.assertIsInstance(d, BzrDir)
112
def test_remote_branch_repr(self):
113
b = BzrDir.open_from_transport(self.transport).open_branch()
114
self.assertStartsWith(str(b), 'RemoteBranch(')
117
class FakeProtocol(object):
118
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
120
def __init__(self, body, fake_client):
122
self._body_buffer = None
123
self._fake_client = fake_client
125
def read_body_bytes(self, count=-1):
126
if self._body_buffer is None:
127
self._body_buffer = StringIO(self.body)
128
bytes = self._body_buffer.read(count)
129
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
130
self._fake_client.expecting_body = False
133
def cancel_read_body(self):
134
self._fake_client.expecting_body = False
136
def read_streamed_body(self):
140
class FakeClient(_SmartClient):
141
"""Lookalike for _SmartClient allowing testing."""
143
def __init__(self, fake_medium_base='fake base'):
144
"""Create a FakeClient."""
147
self.expecting_body = False
148
# if non-None, this is the list of expected calls, with only the
149
# method name and arguments included. the body might be hard to
150
# compute so is not included. If a call is None, that call can
152
self._expected_calls = None
153
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
155
def add_expected_call(self, call_name, call_args, response_type,
156
response_args, response_body=None):
157
if self._expected_calls is None:
158
self._expected_calls = []
159
self._expected_calls.append((call_name, call_args))
160
self.responses.append((response_type, response_args, response_body))
162
def add_success_response(self, *args):
163
self.responses.append(('success', args, None))
165
def add_success_response_with_body(self, body, *args):
166
self.responses.append(('success', args, body))
167
if self._expected_calls is not None:
168
self._expected_calls.append(None)
170
def add_error_response(self, *args):
171
self.responses.append(('error', args))
173
def add_unknown_method_response(self, verb):
174
self.responses.append(('unknown', verb))
176
def finished_test(self):
177
if self._expected_calls:
178
raise AssertionError("%r finished but was still expecting %r"
179
% (self, self._expected_calls[0]))
181
def _get_next_response(self):
183
response_tuple = self.responses.pop(0)
184
except IndexError, e:
185
raise AssertionError("%r didn't expect any more calls"
187
if response_tuple[0] == 'unknown':
188
raise errors.UnknownSmartMethod(response_tuple[1])
189
elif response_tuple[0] == 'error':
190
raise errors.ErrorFromSmartServer(response_tuple[1])
191
return response_tuple
193
def _check_call(self, method, args):
194
if self._expected_calls is None:
195
# the test should be updated to say what it expects
198
next_call = self._expected_calls.pop(0)
200
raise AssertionError("%r didn't expect any more calls "
202
% (self, method, args,))
203
if next_call is None:
205
if method != next_call[0] or args != next_call[1]:
206
raise AssertionError("%r expected %r%r "
208
% (self, next_call[0], next_call[1], method, args,))
210
def call(self, method, *args):
211
self._check_call(method, args)
212
self._calls.append(('call', method, args))
213
return self._get_next_response()[1]
215
def call_expecting_body(self, method, *args):
216
self._check_call(method, args)
217
self._calls.append(('call_expecting_body', method, args))
218
result = self._get_next_response()
219
self.expecting_body = True
220
return result[1], FakeProtocol(result[2], self)
222
def call_with_body_bytes_expecting_body(self, method, args, body):
223
self._check_call(method, args)
224
self._calls.append(('call_with_body_bytes_expecting_body', method,
226
result = self._get_next_response()
227
self.expecting_body = True
228
return result[1], FakeProtocol(result[2], self)
230
def call_with_body_stream(self, args, stream):
231
# Explicitly consume the stream before checking for an error, because
232
# that's what happens a real medium.
233
stream = list(stream)
234
self._check_call(args[0], args[1:])
235
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
236
return self._get_next_response()[1]
239
class FakeMedium(medium.SmartClientMedium):
241
def __init__(self, client_calls, base):
242
medium.SmartClientMedium.__init__(self, base)
243
self._client_calls = client_calls
245
def disconnect(self):
246
self._client_calls.append(('disconnect medium',))
249
class TestVfsHas(tests.TestCase):
251
def test_unicode_path(self):
252
client = FakeClient('/')
253
client.add_success_response('yes',)
254
transport = RemoteTransport('bzr://localhost/', _client=client)
255
filename = u'/hell\u00d8'.encode('utf8')
256
result = transport.has(filename)
258
[('call', 'has', (filename,))],
260
self.assertTrue(result)
263
class TestRemote(tests.TestCaseWithMemoryTransport):
265
def get_repo_format(self):
266
reference_bzrdir_format = bzrdir.format_registry.get('default')()
267
return reference_bzrdir_format.repository_format
269
def disable_verb(self, verb):
270
"""Disable a verb for one test."""
271
request_handlers = smart.request.request_handlers
272
orig_method = request_handlers.get(verb)
273
request_handlers.remove(verb)
275
request_handlers.register(verb, orig_method)
276
self.addCleanup(restoreVerb)
279
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
280
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
282
def assertRemotePath(self, expected, client_base, transport_base):
283
"""Assert that the result of
284
SmartClientMedium.remote_path_from_transport is the expected value for
285
a given client_base and transport_base.
287
client_medium = medium.SmartClientMedium(client_base)
288
transport = get_transport(transport_base)
289
result = client_medium.remote_path_from_transport(transport)
290
self.assertEqual(expected, result)
292
def test_remote_path_from_transport(self):
293
"""SmartClientMedium.remote_path_from_transport calculates a URL for
294
the given transport relative to the root of the client base URL.
296
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
297
self.assertRemotePath(
298
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
300
def assertRemotePathHTTP(self, expected, transport_base, relpath):
301
"""Assert that the result of
302
HttpTransportBase.remote_path_from_transport is the expected value for
303
a given transport_base and relpath of that transport. (Note that
304
HttpTransportBase is a subclass of SmartClientMedium)
306
base_transport = get_transport(transport_base)
307
client_medium = base_transport.get_smart_medium()
308
cloned_transport = base_transport.clone(relpath)
309
result = client_medium.remote_path_from_transport(cloned_transport)
310
self.assertEqual(expected, result)
312
def test_remote_path_from_transport_http(self):
313
"""Remote paths for HTTP transports are calculated differently to other
314
transports. They are just relative to the client base, not the root
315
directory of the host.
317
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
318
self.assertRemotePathHTTP(
319
'../xyz/', scheme + '//host/path', '../xyz/')
320
self.assertRemotePathHTTP(
321
'xyz/', scheme + '//host/path', 'xyz/')
324
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
325
"""Tests for the behaviour of client_medium.remote_is_at_least."""
327
def test_initially_unlimited(self):
328
"""A fresh medium assumes that the remote side supports all
331
client_medium = medium.SmartClientMedium('dummy base')
332
self.assertFalse(client_medium._is_remote_before((99, 99)))
334
def test__remember_remote_is_before(self):
335
"""Calling _remember_remote_is_before ratchets down the known remote
338
client_medium = medium.SmartClientMedium('dummy base')
339
# Mark the remote side as being less than 1.6. The remote side may
341
client_medium._remember_remote_is_before((1, 6))
342
self.assertTrue(client_medium._is_remote_before((1, 6)))
343
self.assertFalse(client_medium._is_remote_before((1, 5)))
344
# Calling _remember_remote_is_before again with a lower value works.
345
client_medium._remember_remote_is_before((1, 5))
346
self.assertTrue(client_medium._is_remote_before((1, 5)))
347
# You cannot call _remember_remote_is_before with a larger value.
349
AssertionError, client_medium._remember_remote_is_before, (1, 9))
352
class TestBzrDirCloningMetaDir(TestRemote):
354
def test_backwards_compat(self):
355
self.setup_smart_server_with_call_log()
356
a_dir = self.make_bzrdir('.')
357
self.reset_smart_call_log()
358
verb = 'BzrDir.cloning_metadir'
359
self.disable_verb(verb)
360
format = a_dir.cloning_metadir()
361
call_count = len([call for call in self.hpss_calls if
362
call.call.method == verb])
363
self.assertEqual(1, call_count)
365
def test_current_server(self):
366
transport = self.get_transport('.')
367
transport = transport.clone('quack')
368
self.make_bzrdir('quack')
369
client = FakeClient(transport.base)
370
reference_bzrdir_format = bzrdir.format_registry.get('default')()
371
control_name = reference_bzrdir_format.network_name()
372
client.add_expected_call(
373
'BzrDir.cloning_metadir', ('quack/', 'False'),
374
'success', (control_name, '', ('direct', ''))),
375
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
377
result = a_bzrdir.cloning_metadir()
378
# We should have got a reference control dir with default branch and
379
# repository formats.
380
# This pokes a little, just to be sure.
381
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
382
self.assertEqual(None, result._repository_format)
383
self.assertEqual(None, result._branch_format)
384
client.finished_test()
387
class TestBzrDirOpenBranch(TestRemote):
389
def test_branch_present(self):
390
reference_format = self.get_repo_format()
391
network_name = reference_format.network_name()
392
transport = MemoryTransport()
393
transport.mkdir('quack')
394
transport = transport.clone('quack')
395
client = FakeClient(transport.base)
396
client.add_expected_call(
397
'BzrDir.open_branch', ('quack/',),
398
'success', ('ok', ''))
399
client.add_expected_call(
400
'BzrDir.find_repositoryV3', ('quack/',),
401
'success', ('ok', '', 'no', 'no', 'no', network_name))
402
client.add_expected_call(
403
'Branch.get_stacked_on_url', ('quack/',),
404
'error', ('NotStacked',))
405
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
407
result = bzrdir.open_branch()
408
self.assertIsInstance(result, RemoteBranch)
409
self.assertEqual(bzrdir, result.bzrdir)
410
client.finished_test()
412
def test_branch_missing(self):
413
transport = MemoryTransport()
414
transport.mkdir('quack')
415
transport = transport.clone('quack')
416
client = FakeClient(transport.base)
417
client.add_error_response('nobranch')
418
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
420
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
422
[('call', 'BzrDir.open_branch', ('quack/',))],
425
def test__get_tree_branch(self):
426
# _get_tree_branch is a form of open_branch, but it should only ask for
427
# branch opening, not any other network requests.
430
calls.append("Called")
432
transport = MemoryTransport()
433
# no requests on the network - catches other api calls being made.
434
client = FakeClient(transport.base)
435
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
437
# patch the open_branch call to record that it was called.
438
bzrdir.open_branch = open_branch
439
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
440
self.assertEqual(["Called"], calls)
441
self.assertEqual([], client._calls)
443
def test_url_quoting_of_path(self):
444
# Relpaths on the wire should not be URL-escaped. So "~" should be
445
# transmitted as "~", not "%7E".
446
transport = RemoteTCPTransport('bzr://localhost/~hello/')
447
client = FakeClient(transport.base)
448
reference_format = self.get_repo_format()
449
network_name = reference_format.network_name()
450
client.add_expected_call(
451
'BzrDir.open_branch', ('~hello/',),
452
'success', ('ok', ''))
453
client.add_expected_call(
454
'BzrDir.find_repositoryV3', ('~hello/',),
455
'success', ('ok', '', 'no', 'no', 'no', network_name))
456
client.add_expected_call(
457
'Branch.get_stacked_on_url', ('~hello/',),
458
'error', ('NotStacked',))
459
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
461
result = bzrdir.open_branch()
462
client.finished_test()
464
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
465
reference_format = self.get_repo_format()
466
network_name = reference_format.network_name()
467
transport = MemoryTransport()
468
transport.mkdir('quack')
469
transport = transport.clone('quack')
471
rich_response = 'yes'
475
subtree_response = 'yes'
477
subtree_response = 'no'
478
client = FakeClient(transport.base)
479
client.add_success_response(
480
'ok', '', rich_response, subtree_response, external_lookup,
482
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
484
result = bzrdir.open_repository()
486
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
488
self.assertIsInstance(result, RemoteRepository)
489
self.assertEqual(bzrdir, result.bzrdir)
490
self.assertEqual(rich_root, result._format.rich_root_data)
491
self.assertEqual(subtrees, result._format.supports_tree_reference)
493
def test_open_repository_sets_format_attributes(self):
494
self.check_open_repository(True, True)
495
self.check_open_repository(False, True)
496
self.check_open_repository(True, False)
497
self.check_open_repository(False, False)
498
self.check_open_repository(False, False, 'yes')
500
def test_old_server(self):
501
"""RemoteBzrDirFormat should fail to probe if the server version is too
504
self.assertRaises(errors.NotBranchError,
505
RemoteBzrDirFormat.probe_transport, OldServerTransport())
508
class TestBzrDirCreateBranch(TestRemote):
510
def test_backwards_compat(self):
511
self.setup_smart_server_with_call_log()
512
repo = self.make_repository('.')
513
self.reset_smart_call_log()
514
self.disable_verb('BzrDir.create_branch')
515
branch = repo.bzrdir.create_branch()
516
create_branch_call_count = len([call for call in self.hpss_calls if
517
call.call.method == 'BzrDir.create_branch'])
518
self.assertEqual(1, create_branch_call_count)
520
def test_current_server(self):
521
transport = self.get_transport('.')
522
transport = transport.clone('quack')
523
self.make_repository('quack')
524
client = FakeClient(transport.base)
525
reference_bzrdir_format = bzrdir.format_registry.get('default')()
526
reference_format = reference_bzrdir_format.get_branch_format()
527
network_name = reference_format.network_name()
528
reference_repo_fmt = reference_bzrdir_format.repository_format
529
reference_repo_name = reference_repo_fmt.network_name()
530
client.add_expected_call(
531
'BzrDir.create_branch', ('quack/', network_name),
532
'success', ('ok', network_name, '', 'no', 'no', 'yes',
533
reference_repo_name))
534
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
536
branch = a_bzrdir.create_branch()
537
# We should have got a remote branch
538
self.assertIsInstance(branch, remote.RemoteBranch)
539
# its format should have the settings from the response
540
format = branch._format
541
self.assertEqual(network_name, format.network_name())
544
class TestBzrDirCreateRepository(TestRemote):
546
def test_backwards_compat(self):
547
self.setup_smart_server_with_call_log()
548
bzrdir = self.make_bzrdir('.')
549
self.reset_smart_call_log()
550
self.disable_verb('BzrDir.create_repository')
551
repo = bzrdir.create_repository()
552
create_repo_call_count = len([call for call in self.hpss_calls if
553
call.call.method == 'BzrDir.create_repository'])
554
self.assertEqual(1, create_repo_call_count)
556
def test_current_server(self):
557
transport = self.get_transport('.')
558
transport = transport.clone('quack')
559
self.make_bzrdir('quack')
560
client = FakeClient(transport.base)
561
reference_bzrdir_format = bzrdir.format_registry.get('default')()
562
reference_format = reference_bzrdir_format.repository_format
563
network_name = reference_format.network_name()
564
client.add_expected_call(
565
'BzrDir.create_repository', ('quack/',
566
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
567
'success', ('ok', 'no', 'no', 'no', network_name))
568
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
570
repo = a_bzrdir.create_repository()
571
# We should have got a remote repository
572
self.assertIsInstance(repo, remote.RemoteRepository)
573
# its format should have the settings from the response
574
format = repo._format
575
self.assertFalse(format.rich_root_data)
576
self.assertFalse(format.supports_tree_reference)
577
self.assertFalse(format.supports_external_lookups)
578
self.assertEqual(network_name, format.network_name())
581
class TestBzrDirOpenRepository(TestRemote):
583
def test_backwards_compat_1_2_3(self):
584
# fallback all the way to the first version.
585
reference_format = self.get_repo_format()
586
network_name = reference_format.network_name()
587
client = FakeClient('bzr://example.com/')
588
client.add_unknown_method_response('BzrDir.find_repositoryV3')
589
client.add_unknown_method_response('BzrDir.find_repositoryV2')
590
client.add_success_response('ok', '', 'no', 'no')
591
# A real repository instance will be created to determine the network
593
client.add_success_response_with_body(
594
"Bazaar-NG meta directory, format 1\n", 'ok')
595
client.add_success_response_with_body(
596
reference_format.get_format_string(), 'ok')
597
# PackRepository wants to do a stat
598
client.add_success_response('stat', '0', '65535')
599
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
601
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
603
repo = bzrdir.open_repository()
605
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
606
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
607
('call', 'BzrDir.find_repository', ('quack/',)),
608
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
609
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
610
('call', 'stat', ('/quack/.bzr/repository',)),
613
self.assertEqual(network_name, repo._format.network_name())
615
def test_backwards_compat_2(self):
616
# fallback to find_repositoryV2
617
reference_format = self.get_repo_format()
618
network_name = reference_format.network_name()
619
client = FakeClient('bzr://example.com/')
620
client.add_unknown_method_response('BzrDir.find_repositoryV3')
621
client.add_success_response('ok', '', 'no', 'no', 'no')
622
# A real repository instance will be created to determine the network
624
client.add_success_response_with_body(
625
"Bazaar-NG meta directory, format 1\n", 'ok')
626
client.add_success_response_with_body(
627
reference_format.get_format_string(), 'ok')
628
# PackRepository wants to do a stat
629
client.add_success_response('stat', '0', '65535')
630
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
632
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
634
repo = bzrdir.open_repository()
636
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
637
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
638
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
639
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
640
('call', 'stat', ('/quack/.bzr/repository',)),
643
self.assertEqual(network_name, repo._format.network_name())
645
def test_current_server(self):
646
reference_format = self.get_repo_format()
647
network_name = reference_format.network_name()
648
transport = MemoryTransport()
649
transport.mkdir('quack')
650
transport = transport.clone('quack')
651
client = FakeClient(transport.base)
652
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
653
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
655
repo = bzrdir.open_repository()
657
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
659
self.assertEqual(network_name, repo._format.network_name())
662
class OldSmartClient(object):
663
"""A fake smart client for test_old_version that just returns a version one
664
response to the 'hello' (query version) command.
667
def get_request(self):
668
input_file = StringIO('ok\x011\n')
669
output_file = StringIO()
670
client_medium = medium.SmartSimplePipesClientMedium(
671
input_file, output_file)
672
return medium.SmartClientStreamMediumRequest(client_medium)
674
def protocol_version(self):
678
class OldServerTransport(object):
679
"""A fake transport for test_old_server that reports it's smart server
680
protocol version as version one.
686
def get_smart_client(self):
687
return OldSmartClient()
690
class RemoteBranchTestCase(tests.TestCase):
692
def make_remote_branch(self, transport, client):
693
"""Make a RemoteBranch using 'client' as its _SmartClient.
695
A RemoteBzrDir and RemoteRepository will also be created to fill out
696
the RemoteBranch, albeit with stub values for some of their attributes.
698
# we do not want bzrdir to make any remote calls, so use False as its
699
# _client. If it tries to make a remote call, this will fail
701
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
703
repo = RemoteRepository(bzrdir, None, _client=client)
704
return RemoteBranch(bzrdir, repo, _client=client)
707
class TestBranchGetParent(RemoteBranchTestCase):
709
def test_no_parent(self):
710
# in an empty branch we decode the response properly
711
transport = MemoryTransport()
712
client = FakeClient(transport.base)
713
client.add_expected_call(
714
'Branch.get_stacked_on_url', ('quack/',),
715
'error', ('NotStacked',))
716
client.add_expected_call(
717
'Branch.get_parent', ('quack/',),
719
transport.mkdir('quack')
720
transport = transport.clone('quack')
721
branch = self.make_remote_branch(transport, client)
722
result = branch.get_parent()
723
client.finished_test()
724
self.assertEqual(None, result)
726
def test_parent_relative(self):
727
transport = MemoryTransport()
728
client = FakeClient(transport.base)
729
client.add_expected_call(
730
'Branch.get_stacked_on_url', ('kwaak/',),
731
'error', ('NotStacked',))
732
client.add_expected_call(
733
'Branch.get_parent', ('kwaak/',),
734
'success', ('../foo/'))
735
transport.mkdir('kwaak')
736
transport = transport.clone('kwaak')
737
branch = self.make_remote_branch(transport, client)
738
result = branch.get_parent()
739
self.assertEqual(transport.clone('../foo').base, result)
741
def test_parent_absolute(self):
742
transport = MemoryTransport()
743
client = FakeClient(transport.base)
744
client.add_expected_call(
745
'Branch.get_stacked_on_url', ('kwaak/',),
746
'error', ('NotStacked',))
747
client.add_expected_call(
748
'Branch.get_parent', ('kwaak/',),
749
'success', ('http://foo/'))
750
transport.mkdir('kwaak')
751
transport = transport.clone('kwaak')
752
branch = self.make_remote_branch(transport, client)
753
result = branch.get_parent()
754
self.assertEqual('http://foo/', result)
757
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
759
def test_empty_branch(self):
760
# in an empty branch we decode the response properly
761
transport = MemoryTransport()
762
client = FakeClient(transport.base)
763
client.add_expected_call(
764
'Branch.get_stacked_on_url', ('quack/',),
765
'error', ('NotStacked',))
766
client.add_expected_call(
767
'Branch.last_revision_info', ('quack/',),
768
'success', ('ok', '0', 'null:'))
769
transport.mkdir('quack')
770
transport = transport.clone('quack')
771
branch = self.make_remote_branch(transport, client)
772
result = branch.last_revision_info()
773
client.finished_test()
774
self.assertEqual((0, NULL_REVISION), result)
776
def test_non_empty_branch(self):
777
# in a non-empty branch we also decode the response properly
778
revid = u'\xc8'.encode('utf8')
779
transport = MemoryTransport()
780
client = FakeClient(transport.base)
781
client.add_expected_call(
782
'Branch.get_stacked_on_url', ('kwaak/',),
783
'error', ('NotStacked',))
784
client.add_expected_call(
785
'Branch.last_revision_info', ('kwaak/',),
786
'success', ('ok', '2', revid))
787
transport.mkdir('kwaak')
788
transport = transport.clone('kwaak')
789
branch = self.make_remote_branch(transport, client)
790
result = branch.last_revision_info()
791
self.assertEqual((2, revid), result)
794
class TestBranch_get_stacked_on_url(TestRemote):
795
"""Test Branch._get_stacked_on_url rpc"""
797
def test_get_stacked_on_invalid_url(self):
798
# test that asking for a stacked on url the server can't access works.
799
# This isn't perfect, but then as we're in the same process there
800
# really isn't anything we can do to be 100% sure that the server
801
# doesn't just open in - this test probably needs to be rewritten using
802
# a spawn()ed server.
803
stacked_branch = self.make_branch('stacked', format='1.9')
804
memory_branch = self.make_branch('base', format='1.9')
805
vfs_url = self.get_vfs_only_url('base')
806
stacked_branch.set_stacked_on_url(vfs_url)
807
transport = stacked_branch.bzrdir.root_transport
808
client = FakeClient(transport.base)
809
client.add_expected_call(
810
'Branch.get_stacked_on_url', ('stacked/',),
811
'success', ('ok', vfs_url))
812
# XXX: Multiple calls are bad, this second call documents what is
814
client.add_expected_call(
815
'Branch.get_stacked_on_url', ('stacked/',),
816
'success', ('ok', vfs_url))
817
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
819
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, None),
821
result = branch.get_stacked_on_url()
822
self.assertEqual(vfs_url, result)
824
def test_backwards_compatible(self):
825
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
826
base_branch = self.make_branch('base', format='1.6')
827
stacked_branch = self.make_branch('stacked', format='1.6')
828
stacked_branch.set_stacked_on_url('../base')
829
client = FakeClient(self.get_url())
830
client.add_expected_call(
831
'BzrDir.open_branch', ('stacked/',),
832
'success', ('ok', ''))
833
client.add_expected_call(
834
'BzrDir.find_repositoryV3', ('stacked/',),
835
'success', ('ok', '', 'no', 'no', 'no',
836
stacked_branch.repository._format.network_name()))
837
# called twice, once from constructor and then again by us
838
client.add_expected_call(
839
'Branch.get_stacked_on_url', ('stacked/',),
840
'unknown', ('Branch.get_stacked_on_url',))
841
client.add_expected_call(
842
'Branch.get_stacked_on_url', ('stacked/',),
843
'unknown', ('Branch.get_stacked_on_url',))
844
# this will also do vfs access, but that goes direct to the transport
845
# and isn't seen by the FakeClient.
846
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
847
remote.RemoteBzrDirFormat(), _client=client)
848
branch = bzrdir.open_branch()
849
result = branch.get_stacked_on_url()
850
self.assertEqual('../base', result)
851
client.finished_test()
852
# it's in the fallback list both for the RemoteRepository and its vfs
854
self.assertEqual(1, len(branch.repository._fallback_repositories))
856
len(branch.repository._real_repository._fallback_repositories))
858
def test_get_stacked_on_real_branch(self):
859
base_branch = self.make_branch('base', format='1.6')
860
stacked_branch = self.make_branch('stacked', format='1.6')
861
stacked_branch.set_stacked_on_url('../base')
862
reference_format = self.get_repo_format()
863
network_name = reference_format.network_name()
864
client = FakeClient(self.get_url())
865
client.add_expected_call(
866
'BzrDir.open_branch', ('stacked/',),
867
'success', ('ok', ''))
868
client.add_expected_call(
869
'BzrDir.find_repositoryV3', ('stacked/',),
870
'success', ('ok', '', 'no', 'no', 'no', network_name))
871
# called twice, once from constructor and then again by us
872
client.add_expected_call(
873
'Branch.get_stacked_on_url', ('stacked/',),
874
'success', ('ok', '../base'))
875
client.add_expected_call(
876
'Branch.get_stacked_on_url', ('stacked/',),
877
'success', ('ok', '../base'))
878
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
879
remote.RemoteBzrDirFormat(), _client=client)
880
branch = bzrdir.open_branch()
881
result = branch.get_stacked_on_url()
882
self.assertEqual('../base', result)
883
client.finished_test()
884
# it's in the fallback list both for the RemoteRepository and its vfs
886
self.assertEqual(1, len(branch.repository._fallback_repositories))
888
len(branch.repository._real_repository._fallback_repositories))
891
class TestBranchSetLastRevision(RemoteBranchTestCase):
893
def test_set_empty(self):
894
# set_revision_history([]) is translated to calling
895
# Branch.set_last_revision(path, '') on the wire.
896
transport = MemoryTransport()
897
transport.mkdir('branch')
898
transport = transport.clone('branch')
900
client = FakeClient(transport.base)
901
client.add_expected_call(
902
'Branch.get_stacked_on_url', ('branch/',),
903
'error', ('NotStacked',))
904
client.add_expected_call(
905
'Branch.lock_write', ('branch/', '', ''),
906
'success', ('ok', 'branch token', 'repo token'))
907
client.add_expected_call(
908
'Branch.last_revision_info',
910
'success', ('ok', '0', 'null:'))
911
client.add_expected_call(
912
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
914
client.add_expected_call(
915
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
917
branch = self.make_remote_branch(transport, client)
918
# This is a hack to work around the problem that RemoteBranch currently
919
# unnecessarily invokes _ensure_real upon a call to lock_write.
920
branch._ensure_real = lambda: None
922
result = branch.set_revision_history([])
924
self.assertEqual(None, result)
925
client.finished_test()
927
def test_set_nonempty(self):
928
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
929
# Branch.set_last_revision(path, rev-idN) on the wire.
930
transport = MemoryTransport()
931
transport.mkdir('branch')
932
transport = transport.clone('branch')
934
client = FakeClient(transport.base)
935
client.add_expected_call(
936
'Branch.get_stacked_on_url', ('branch/',),
937
'error', ('NotStacked',))
938
client.add_expected_call(
939
'Branch.lock_write', ('branch/', '', ''),
940
'success', ('ok', 'branch token', 'repo token'))
941
client.add_expected_call(
942
'Branch.last_revision_info',
944
'success', ('ok', '0', 'null:'))
946
encoded_body = bz2.compress('\n'.join(lines))
947
client.add_success_response_with_body(encoded_body, 'ok')
948
client.add_expected_call(
949
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
951
client.add_expected_call(
952
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
954
branch = self.make_remote_branch(transport, client)
955
# This is a hack to work around the problem that RemoteBranch currently
956
# unnecessarily invokes _ensure_real upon a call to lock_write.
957
branch._ensure_real = lambda: None
958
# Lock the branch, reset the record of remote calls.
960
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
962
self.assertEqual(None, result)
963
client.finished_test()
965
def test_no_such_revision(self):
966
transport = MemoryTransport()
967
transport.mkdir('branch')
968
transport = transport.clone('branch')
969
# A response of 'NoSuchRevision' is translated into an exception.
970
client = FakeClient(transport.base)
971
client.add_expected_call(
972
'Branch.get_stacked_on_url', ('branch/',),
973
'error', ('NotStacked',))
974
client.add_expected_call(
975
'Branch.lock_write', ('branch/', '', ''),
976
'success', ('ok', 'branch token', 'repo token'))
977
client.add_expected_call(
978
'Branch.last_revision_info',
980
'success', ('ok', '0', 'null:'))
981
# get_graph calls to construct the revision history, for the set_rh
984
encoded_body = bz2.compress('\n'.join(lines))
985
client.add_success_response_with_body(encoded_body, 'ok')
986
client.add_expected_call(
987
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
988
'error', ('NoSuchRevision', 'rev-id'))
989
client.add_expected_call(
990
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
993
branch = self.make_remote_branch(transport, client)
996
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
998
client.finished_test()
1000
def test_tip_change_rejected(self):
1001
"""TipChangeRejected responses cause a TipChangeRejected exception to
1004
transport = MemoryTransport()
1005
transport.mkdir('branch')
1006
transport = transport.clone('branch')
1007
client = FakeClient(transport.base)
1008
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1009
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1010
client.add_expected_call(
1011
'Branch.get_stacked_on_url', ('branch/',),
1012
'error', ('NotStacked',))
1013
client.add_expected_call(
1014
'Branch.lock_write', ('branch/', '', ''),
1015
'success', ('ok', 'branch token', 'repo token'))
1016
client.add_expected_call(
1017
'Branch.last_revision_info',
1019
'success', ('ok', '0', 'null:'))
1021
encoded_body = bz2.compress('\n'.join(lines))
1022
client.add_success_response_with_body(encoded_body, 'ok')
1023
client.add_expected_call(
1024
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1025
'error', ('TipChangeRejected', rejection_msg_utf8))
1026
client.add_expected_call(
1027
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1029
branch = self.make_remote_branch(transport, client)
1030
branch._ensure_real = lambda: None
1032
# The 'TipChangeRejected' error response triggered by calling
1033
# set_revision_history causes a TipChangeRejected exception.
1034
err = self.assertRaises(
1035
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1036
# The UTF-8 message from the response has been decoded into a unicode
1038
self.assertIsInstance(err.msg, unicode)
1039
self.assertEqual(rejection_msg_unicode, err.msg)
1041
client.finished_test()
1044
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1046
def test_set_last_revision_info(self):
1047
# set_last_revision_info(num, 'rev-id') is translated to calling
1048
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1049
transport = MemoryTransport()
1050
transport.mkdir('branch')
1051
transport = transport.clone('branch')
1052
client = FakeClient(transport.base)
1053
# get_stacked_on_url
1054
client.add_error_response('NotStacked')
1056
client.add_success_response('ok', 'branch token', 'repo token')
1057
# query the current revision
1058
client.add_success_response('ok', '0', 'null:')
1060
client.add_success_response('ok')
1062
client.add_success_response('ok')
1064
branch = self.make_remote_branch(transport, client)
1065
# Lock the branch, reset the record of remote calls.
1068
result = branch.set_last_revision_info(1234, 'a-revision-id')
1070
[('call', 'Branch.last_revision_info', ('branch/',)),
1071
('call', 'Branch.set_last_revision_info',
1072
('branch/', 'branch token', 'repo token',
1073
'1234', 'a-revision-id'))],
1075
self.assertEqual(None, result)
1077
def test_no_such_revision(self):
1078
# A response of 'NoSuchRevision' is translated into an exception.
1079
transport = MemoryTransport()
1080
transport.mkdir('branch')
1081
transport = transport.clone('branch')
1082
client = FakeClient(transport.base)
1083
# get_stacked_on_url
1084
client.add_error_response('NotStacked')
1086
client.add_success_response('ok', 'branch token', 'repo token')
1088
client.add_error_response('NoSuchRevision', 'revid')
1090
client.add_success_response('ok')
1092
branch = self.make_remote_branch(transport, client)
1093
# Lock the branch, reset the record of remote calls.
1098
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1101
def lock_remote_branch(self, branch):
1102
"""Trick a RemoteBranch into thinking it is locked."""
1103
branch._lock_mode = 'w'
1104
branch._lock_count = 2
1105
branch._lock_token = 'branch token'
1106
branch._repo_lock_token = 'repo token'
1107
branch.repository._lock_mode = 'w'
1108
branch.repository._lock_count = 2
1109
branch.repository._lock_token = 'repo token'
1111
def test_backwards_compatibility(self):
1112
"""If the server does not support the Branch.set_last_revision_info
1113
verb (which is new in 1.4), then the client falls back to VFS methods.
1115
# This test is a little messy. Unlike most tests in this file, it
1116
# doesn't purely test what a Remote* object sends over the wire, and
1117
# how it reacts to responses from the wire. It instead relies partly
1118
# on asserting that the RemoteBranch will call
1119
# self._real_branch.set_last_revision_info(...).
1121
# First, set up our RemoteBranch with a FakeClient that raises
1122
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1123
transport = MemoryTransport()
1124
transport.mkdir('branch')
1125
transport = transport.clone('branch')
1126
client = FakeClient(transport.base)
1127
client.add_expected_call(
1128
'Branch.get_stacked_on_url', ('branch/',),
1129
'error', ('NotStacked',))
1130
client.add_expected_call(
1131
'Branch.last_revision_info',
1133
'success', ('ok', '0', 'null:'))
1134
client.add_expected_call(
1135
'Branch.set_last_revision_info',
1136
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1137
'unknown', 'Branch.set_last_revision_info')
1139
branch = self.make_remote_branch(transport, client)
1140
class StubRealBranch(object):
1143
def set_last_revision_info(self, revno, revision_id):
1145
('set_last_revision_info', revno, revision_id))
1146
def _clear_cached_state(self):
1148
real_branch = StubRealBranch()
1149
branch._real_branch = real_branch
1150
self.lock_remote_branch(branch)
1152
# Call set_last_revision_info, and verify it behaved as expected.
1153
result = branch.set_last_revision_info(1234, 'a-revision-id')
1155
[('set_last_revision_info', 1234, 'a-revision-id')],
1157
client.finished_test()
1159
def test_unexpected_error(self):
1160
# If the server sends an error the client doesn't understand, it gets
1161
# turned into an UnknownErrorFromSmartServer, which is presented as a
1162
# non-internal error to the user.
1163
transport = MemoryTransport()
1164
transport.mkdir('branch')
1165
transport = transport.clone('branch')
1166
client = FakeClient(transport.base)
1167
# get_stacked_on_url
1168
client.add_error_response('NotStacked')
1170
client.add_success_response('ok', 'branch token', 'repo token')
1172
client.add_error_response('UnexpectedError')
1174
client.add_success_response('ok')
1176
branch = self.make_remote_branch(transport, client)
1177
# Lock the branch, reset the record of remote calls.
1181
err = self.assertRaises(
1182
errors.UnknownErrorFromSmartServer,
1183
branch.set_last_revision_info, 123, 'revid')
1184
self.assertEqual(('UnexpectedError',), err.error_tuple)
1187
def test_tip_change_rejected(self):
1188
"""TipChangeRejected responses cause a TipChangeRejected exception to
1191
transport = MemoryTransport()
1192
transport.mkdir('branch')
1193
transport = transport.clone('branch')
1194
client = FakeClient(transport.base)
1195
# get_stacked_on_url
1196
client.add_error_response('NotStacked')
1198
client.add_success_response('ok', 'branch token', 'repo token')
1200
client.add_error_response('TipChangeRejected', 'rejection message')
1202
client.add_success_response('ok')
1204
branch = self.make_remote_branch(transport, client)
1205
# Lock the branch, reset the record of remote calls.
1207
self.addCleanup(branch.unlock)
1210
# The 'TipChangeRejected' error response triggered by calling
1211
# set_last_revision_info causes a TipChangeRejected exception.
1212
err = self.assertRaises(
1213
errors.TipChangeRejected,
1214
branch.set_last_revision_info, 123, 'revid')
1215
self.assertEqual('rejection message', err.msg)
1218
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1219
"""Getting the branch configuration should use an abstract method not vfs.
1222
def test_get_branch_conf(self):
1223
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1224
## # We should see that branch.get_config() does a single rpc to get the
1225
## # remote configuration file, abstracting away where that is stored on
1226
## # the server. However at the moment it always falls back to using the
1227
## # vfs, and this would need some changes in config.py.
1229
## # in an empty branch we decode the response properly
1230
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1231
## # we need to make a real branch because the remote_branch.control_files
1232
## # will trigger _ensure_real.
1233
## branch = self.make_branch('quack')
1234
## transport = branch.bzrdir.root_transport
1235
## # we do not want bzrdir to make any remote calls
1236
## bzrdir = RemoteBzrDir(transport, _client=False)
1237
## branch = RemoteBranch(bzrdir, None, _client=client)
1238
## config = branch.get_config()
1239
## self.assertEqual(
1240
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1244
class TestBranchLockWrite(RemoteBranchTestCase):
1246
def test_lock_write_unlockable(self):
1247
transport = MemoryTransport()
1248
client = FakeClient(transport.base)
1249
client.add_expected_call(
1250
'Branch.get_stacked_on_url', ('quack/',),
1251
'error', ('NotStacked',),)
1252
client.add_expected_call(
1253
'Branch.lock_write', ('quack/', '', ''),
1254
'error', ('UnlockableTransport',))
1255
transport.mkdir('quack')
1256
transport = transport.clone('quack')
1257
branch = self.make_remote_branch(transport, client)
1258
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1259
client.finished_test()
1262
class TestTransportIsReadonly(tests.TestCase):
1264
def test_true(self):
1265
client = FakeClient()
1266
client.add_success_response('yes')
1267
transport = RemoteTransport('bzr://example.com/', medium=False,
1269
self.assertEqual(True, transport.is_readonly())
1271
[('call', 'Transport.is_readonly', ())],
1274
def test_false(self):
1275
client = FakeClient()
1276
client.add_success_response('no')
1277
transport = RemoteTransport('bzr://example.com/', medium=False,
1279
self.assertEqual(False, transport.is_readonly())
1281
[('call', 'Transport.is_readonly', ())],
1284
def test_error_from_old_server(self):
1285
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1287
Clients should treat it as a "no" response, because is_readonly is only
1288
advisory anyway (a transport could be read-write, but then the
1289
underlying filesystem could be readonly anyway).
1291
client = FakeClient()
1292
client.add_unknown_method_response('Transport.is_readonly')
1293
transport = RemoteTransport('bzr://example.com/', medium=False,
1295
self.assertEqual(False, transport.is_readonly())
1297
[('call', 'Transport.is_readonly', ())],
1301
class TestTransportMkdir(tests.TestCase):
1303
def test_permissiondenied(self):
1304
client = FakeClient()
1305
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1306
transport = RemoteTransport('bzr://example.com/', medium=False,
1308
exc = self.assertRaises(
1309
errors.PermissionDenied, transport.mkdir, 'client path')
1310
expected_error = errors.PermissionDenied('/client path', 'extra')
1311
self.assertEqual(expected_error, exc)
1314
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1316
def test_defaults_to_none(self):
1317
t = RemoteSSHTransport('bzr+ssh://example.com')
1318
self.assertIs(None, t._get_credentials()[0])
1320
def test_uses_authentication_config(self):
1321
conf = config.AuthenticationConfig()
1322
conf._get_config().update(
1323
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1326
t = RemoteSSHTransport('bzr+ssh://example.com')
1327
self.assertEqual('bar', t._get_credentials()[0])
1330
class TestRemoteRepository(TestRemote):
1331
"""Base for testing RemoteRepository protocol usage.
1333
These tests contain frozen requests and responses. We want any changes to
1334
what is sent or expected to be require a thoughtful update to these tests
1335
because they might break compatibility with different-versioned servers.
1338
def setup_fake_client_and_repository(self, transport_path):
1339
"""Create the fake client and repository for testing with.
1341
There's no real server here; we just have canned responses sent
1344
:param transport_path: Path below the root of the MemoryTransport
1345
where the repository will be created.
1347
transport = MemoryTransport()
1348
transport.mkdir(transport_path)
1349
client = FakeClient(transport.base)
1350
transport = transport.clone(transport_path)
1351
# we do not want bzrdir to make any remote calls
1352
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1354
repo = RemoteRepository(bzrdir, None, _client=client)
1358
class TestRepositoryGatherStats(TestRemoteRepository):
1360
def test_revid_none(self):
1361
# ('ok',), body with revisions and size
1362
transport_path = 'quack'
1363
repo, client = self.setup_fake_client_and_repository(transport_path)
1364
client.add_success_response_with_body(
1365
'revisions: 2\nsize: 18\n', 'ok')
1366
result = repo.gather_stats(None)
1368
[('call_expecting_body', 'Repository.gather_stats',
1369
('quack/','','no'))],
1371
self.assertEqual({'revisions': 2, 'size': 18}, result)
1373
def test_revid_no_committers(self):
1374
# ('ok',), body without committers
1375
body = ('firstrev: 123456.300 3600\n'
1376
'latestrev: 654231.400 0\n'
1379
transport_path = 'quick'
1380
revid = u'\xc8'.encode('utf8')
1381
repo, client = self.setup_fake_client_and_repository(transport_path)
1382
client.add_success_response_with_body(body, 'ok')
1383
result = repo.gather_stats(revid)
1385
[('call_expecting_body', 'Repository.gather_stats',
1386
('quick/', revid, 'no'))],
1388
self.assertEqual({'revisions': 2, 'size': 18,
1389
'firstrev': (123456.300, 3600),
1390
'latestrev': (654231.400, 0),},
1393
def test_revid_with_committers(self):
1394
# ('ok',), body with committers
1395
body = ('committers: 128\n'
1396
'firstrev: 123456.300 3600\n'
1397
'latestrev: 654231.400 0\n'
1400
transport_path = 'buick'
1401
revid = u'\xc8'.encode('utf8')
1402
repo, client = self.setup_fake_client_and_repository(transport_path)
1403
client.add_success_response_with_body(body, 'ok')
1404
result = repo.gather_stats(revid, True)
1406
[('call_expecting_body', 'Repository.gather_stats',
1407
('buick/', revid, 'yes'))],
1409
self.assertEqual({'revisions': 2, 'size': 18,
1411
'firstrev': (123456.300, 3600),
1412
'latestrev': (654231.400, 0),},
1416
class TestRepositoryGetGraph(TestRemoteRepository):
1418
def test_get_graph(self):
1419
# get_graph returns a graph with a custom parents provider.
1420
transport_path = 'quack'
1421
repo, client = self.setup_fake_client_and_repository(transport_path)
1422
graph = repo.get_graph()
1423
self.assertNotEqual(graph._parents_provider, repo)
1426
class TestRepositoryGetParentMap(TestRemoteRepository):
1428
def test_get_parent_map_caching(self):
1429
# get_parent_map returns from cache until unlock()
1430
# setup a reponse with two revisions
1431
r1 = u'\u0e33'.encode('utf8')
1432
r2 = u'\u0dab'.encode('utf8')
1433
lines = [' '.join([r2, r1]), r1]
1434
encoded_body = bz2.compress('\n'.join(lines))
1436
transport_path = 'quack'
1437
repo, client = self.setup_fake_client_and_repository(transport_path)
1438
client.add_success_response_with_body(encoded_body, 'ok')
1439
client.add_success_response_with_body(encoded_body, 'ok')
1441
graph = repo.get_graph()
1442
parents = graph.get_parent_map([r2])
1443
self.assertEqual({r2: (r1,)}, parents)
1444
# locking and unlocking deeper should not reset
1447
parents = graph.get_parent_map([r1])
1448
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1450
[('call_with_body_bytes_expecting_body',
1451
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
1454
# now we call again, and it should use the second response.
1456
graph = repo.get_graph()
1457
parents = graph.get_parent_map([r1])
1458
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1460
[('call_with_body_bytes_expecting_body',
1461
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
1462
('call_with_body_bytes_expecting_body',
1463
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
1468
def test_get_parent_map_reconnects_if_unknown_method(self):
1469
transport_path = 'quack'
1470
repo, client = self.setup_fake_client_and_repository(transport_path)
1471
client.add_unknown_method_response('Repository,get_parent_map')
1472
client.add_success_response_with_body('', 'ok')
1473
self.assertFalse(client._medium._is_remote_before((1, 2)))
1474
rev_id = 'revision-id'
1475
expected_deprecations = [
1476
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1478
parents = self.callDeprecated(
1479
expected_deprecations, repo.get_parent_map, [rev_id])
1481
[('call_with_body_bytes_expecting_body',
1482
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
1483
('disconnect medium',),
1484
('call_expecting_body', 'Repository.get_revision_graph',
1487
# The medium is now marked as being connected to an older server
1488
self.assertTrue(client._medium._is_remote_before((1, 2)))
1490
def test_get_parent_map_fallback_parentless_node(self):
1491
"""get_parent_map falls back to get_revision_graph on old servers. The
1492
results from get_revision_graph are tweaked to match the get_parent_map
1495
Specifically, a {key: ()} result from get_revision_graph means "no
1496
parents" for that key, which in get_parent_map results should be
1497
represented as {key: ('null:',)}.
1499
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1501
rev_id = 'revision-id'
1502
transport_path = 'quack'
1503
repo, client = self.setup_fake_client_and_repository(transport_path)
1504
client.add_success_response_with_body(rev_id, 'ok')
1505
client._medium._remember_remote_is_before((1, 2))
1506
expected_deprecations = [
1507
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1509
parents = self.callDeprecated(
1510
expected_deprecations, repo.get_parent_map, [rev_id])
1512
[('call_expecting_body', 'Repository.get_revision_graph',
1515
self.assertEqual({rev_id: ('null:',)}, parents)
1517
def test_get_parent_map_unexpected_response(self):
1518
repo, client = self.setup_fake_client_and_repository('path')
1519
client.add_success_response('something unexpected!')
1521
errors.UnexpectedSmartServerResponse,
1522
repo.get_parent_map, ['a-revision-id'])
1525
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1527
def test_allows_new_revisions(self):
1528
"""get_parent_map's results can be updated by commit."""
1529
smart_server = server.SmartTCPServer_for_testing()
1530
smart_server.setUp()
1531
self.addCleanup(smart_server.tearDown)
1532
self.make_branch('branch')
1533
branch = Branch.open(smart_server.get_url() + '/branch')
1534
tree = branch.create_checkout('tree', lightweight=True)
1536
self.addCleanup(tree.unlock)
1537
graph = tree.branch.repository.get_graph()
1538
# This provides an opportunity for the missing rev-id to be cached.
1539
self.assertEqual({}, graph.get_parent_map(['rev1']))
1540
tree.commit('message', rev_id='rev1')
1541
graph = tree.branch.repository.get_graph()
1542
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1545
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1547
def test_null_revision(self):
1548
# a null revision has the predictable result {}, we should have no wire
1549
# traffic when calling it with this argument
1550
transport_path = 'empty'
1551
repo, client = self.setup_fake_client_and_repository(transport_path)
1552
client.add_success_response('notused')
1553
result = self.applyDeprecated(one_four, repo.get_revision_graph,
1555
self.assertEqual([], client._calls)
1556
self.assertEqual({}, result)
1558
def test_none_revision(self):
1559
# with none we want the entire graph
1560
r1 = u'\u0e33'.encode('utf8')
1561
r2 = u'\u0dab'.encode('utf8')
1562
lines = [' '.join([r2, r1]), r1]
1563
encoded_body = '\n'.join(lines)
1565
transport_path = 'sinhala'
1566
repo, client = self.setup_fake_client_and_repository(transport_path)
1567
client.add_success_response_with_body(encoded_body, 'ok')
1568
result = self.applyDeprecated(one_four, repo.get_revision_graph)
1570
[('call_expecting_body', 'Repository.get_revision_graph',
1573
self.assertEqual({r1: (), r2: (r1, )}, result)
1575
def test_specific_revision(self):
1576
# with a specific revision we want the graph for that
1577
# with none we want the entire graph
1578
r11 = u'\u0e33'.encode('utf8')
1579
r12 = u'\xc9'.encode('utf8')
1580
r2 = u'\u0dab'.encode('utf8')
1581
lines = [' '.join([r2, r11, r12]), r11, r12]
1582
encoded_body = '\n'.join(lines)
1584
transport_path = 'sinhala'
1585
repo, client = self.setup_fake_client_and_repository(transport_path)
1586
client.add_success_response_with_body(encoded_body, 'ok')
1587
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
1589
[('call_expecting_body', 'Repository.get_revision_graph',
1592
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1594
def test_no_such_revision(self):
1596
transport_path = 'sinhala'
1597
repo, client = self.setup_fake_client_and_repository(transport_path)
1598
client.add_error_response('nosuchrevision', revid)
1599
# also check that the right revision is reported in the error
1600
self.assertRaises(errors.NoSuchRevision,
1601
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1603
[('call_expecting_body', 'Repository.get_revision_graph',
1604
('sinhala/', revid))],
1607
def test_unexpected_error(self):
1609
transport_path = 'sinhala'
1610
repo, client = self.setup_fake_client_and_repository(transport_path)
1611
client.add_error_response('AnUnexpectedError')
1612
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1613
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1614
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1617
class TestRepositoryIsShared(TestRemoteRepository):
1619
def test_is_shared(self):
1620
# ('yes', ) for Repository.is_shared -> 'True'.
1621
transport_path = 'quack'
1622
repo, client = self.setup_fake_client_and_repository(transport_path)
1623
client.add_success_response('yes')
1624
result = repo.is_shared()
1626
[('call', 'Repository.is_shared', ('quack/',))],
1628
self.assertEqual(True, result)
1630
def test_is_not_shared(self):
1631
# ('no', ) for Repository.is_shared -> 'False'.
1632
transport_path = 'qwack'
1633
repo, client = self.setup_fake_client_and_repository(transport_path)
1634
client.add_success_response('no')
1635
result = repo.is_shared()
1637
[('call', 'Repository.is_shared', ('qwack/',))],
1639
self.assertEqual(False, result)
1642
class TestRepositoryLockWrite(TestRemoteRepository):
1644
def test_lock_write(self):
1645
transport_path = 'quack'
1646
repo, client = self.setup_fake_client_and_repository(transport_path)
1647
client.add_success_response('ok', 'a token')
1648
result = repo.lock_write()
1650
[('call', 'Repository.lock_write', ('quack/', ''))],
1652
self.assertEqual('a token', result)
1654
def test_lock_write_already_locked(self):
1655
transport_path = 'quack'
1656
repo, client = self.setup_fake_client_and_repository(transport_path)
1657
client.add_error_response('LockContention')
1658
self.assertRaises(errors.LockContention, repo.lock_write)
1660
[('call', 'Repository.lock_write', ('quack/', ''))],
1663
def test_lock_write_unlockable(self):
1664
transport_path = 'quack'
1665
repo, client = self.setup_fake_client_and_repository(transport_path)
1666
client.add_error_response('UnlockableTransport')
1667
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1669
[('call', 'Repository.lock_write', ('quack/', ''))],
1673
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1675
def test_backwards_compat(self):
1676
self.setup_smart_server_with_call_log()
1677
repo = self.make_repository('.')
1678
self.reset_smart_call_log()
1679
verb = 'Repository.set_make_working_trees'
1680
self.disable_verb(verb)
1681
repo.set_make_working_trees(True)
1682
call_count = len([call for call in self.hpss_calls if
1683
call.call.method == verb])
1684
self.assertEqual(1, call_count)
1686
def test_current(self):
1687
transport_path = 'quack'
1688
repo, client = self.setup_fake_client_and_repository(transport_path)
1689
client.add_expected_call(
1690
'Repository.set_make_working_trees', ('quack/', 'True'),
1692
client.add_expected_call(
1693
'Repository.set_make_working_trees', ('quack/', 'False'),
1695
repo.set_make_working_trees(True)
1696
repo.set_make_working_trees(False)
1699
class TestRepositoryUnlock(TestRemoteRepository):
1701
def test_unlock(self):
1702
transport_path = 'quack'
1703
repo, client = self.setup_fake_client_and_repository(transport_path)
1704
client.add_success_response('ok', 'a token')
1705
client.add_success_response('ok')
1709
[('call', 'Repository.lock_write', ('quack/', '')),
1710
('call', 'Repository.unlock', ('quack/', 'a token'))],
1713
def test_unlock_wrong_token(self):
1714
# If somehow the token is wrong, unlock will raise TokenMismatch.
1715
transport_path = 'quack'
1716
repo, client = self.setup_fake_client_and_repository(transport_path)
1717
client.add_success_response('ok', 'a token')
1718
client.add_error_response('TokenMismatch')
1720
self.assertRaises(errors.TokenMismatch, repo.unlock)
1723
class TestRepositoryHasRevision(TestRemoteRepository):
1725
def test_none(self):
1726
# repo.has_revision(None) should not cause any traffic.
1727
transport_path = 'quack'
1728
repo, client = self.setup_fake_client_and_repository(transport_path)
1730
# The null revision is always there, so has_revision(None) == True.
1731
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1733
# The remote repo shouldn't be accessed.
1734
self.assertEqual([], client._calls)
1737
class TestRepositoryTarball(TestRemoteRepository):
1739
# This is a canned tarball reponse we can validate against
1741
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1742
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1743
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1744
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1745
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1746
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1747
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1748
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1749
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1750
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1751
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1752
'nWQ7QH/F3JFOFCQ0aSPfA='
1755
def test_repository_tarball(self):
1756
# Test that Repository.tarball generates the right operations
1757
transport_path = 'repo'
1758
expected_calls = [('call_expecting_body', 'Repository.tarball',
1759
('repo/', 'bz2',),),
1761
repo, client = self.setup_fake_client_and_repository(transport_path)
1762
client.add_success_response_with_body(self.tarball_content, 'ok')
1763
# Now actually ask for the tarball
1764
tarball_file = repo._get_tarball('bz2')
1766
self.assertEqual(expected_calls, client._calls)
1767
self.assertEqual(self.tarball_content, tarball_file.read())
1769
tarball_file.close()
1772
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
1773
"""RemoteRepository.copy_content_into optimizations"""
1775
def test_copy_content_remote_to_local(self):
1776
self.transport_server = server.SmartTCPServer_for_testing
1777
src_repo = self.make_repository('repo1')
1778
src_repo = repository.Repository.open(self.get_url('repo1'))
1779
# At the moment the tarball-based copy_content_into can't write back
1780
# into a smart server. It would be good if it could upload the
1781
# tarball; once that works we'd have to create repositories of
1782
# different formats. -- mbp 20070410
1783
dest_url = self.get_vfs_only_url('repo2')
1784
dest_bzrdir = BzrDir.create(dest_url)
1785
dest_repo = dest_bzrdir.create_repository()
1786
self.assertFalse(isinstance(dest_repo, RemoteRepository))
1787
self.assertTrue(isinstance(src_repo, RemoteRepository))
1788
src_repo.copy_content_into(dest_repo)
1791
class _StubRealPackRepository(object):
1793
def __init__(self, calls):
1794
self._pack_collection = _StubPackCollection(calls)
1797
class _StubPackCollection(object):
1799
def __init__(self, calls):
1803
self.calls.append(('pack collection autopack',))
1805
def reload_pack_names(self):
1806
self.calls.append(('pack collection reload_pack_names',))
1809
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1810
"""Tests for RemoteRepository.autopack implementation."""
1813
"""When the server returns 'ok' and there's no _real_repository, then
1814
nothing else happens: the autopack method is done.
1816
transport_path = 'quack'
1817
repo, client = self.setup_fake_client_and_repository(transport_path)
1818
client.add_expected_call(
1819
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
1821
client.finished_test()
1823
def test_ok_with_real_repo(self):
1824
"""When the server returns 'ok' and there is a _real_repository, then
1825
the _real_repository's reload_pack_name's method will be called.
1827
transport_path = 'quack'
1828
repo, client = self.setup_fake_client_and_repository(transport_path)
1829
client.add_expected_call(
1830
'PackRepository.autopack', ('quack/',),
1832
repo._real_repository = _StubRealPackRepository(client._calls)
1835
[('call', 'PackRepository.autopack', ('quack/',)),
1836
('pack collection reload_pack_names',)],
1839
def test_backwards_compatibility(self):
1840
"""If the server does not recognise the PackRepository.autopack verb,
1841
fallback to the real_repository's implementation.
1843
transport_path = 'quack'
1844
repo, client = self.setup_fake_client_and_repository(transport_path)
1845
client.add_unknown_method_response('PackRepository.autopack')
1846
def stub_ensure_real():
1847
client._calls.append(('_ensure_real',))
1848
repo._real_repository = _StubRealPackRepository(client._calls)
1849
repo._ensure_real = stub_ensure_real
1852
[('call', 'PackRepository.autopack', ('quack/',)),
1854
('pack collection autopack',)],
1858
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
1859
"""Base class for unit tests for bzrlib.remote._translate_error."""
1861
def translateTuple(self, error_tuple, **context):
1862
"""Call _translate_error with an ErrorFromSmartServer built from the
1865
:param error_tuple: A tuple of a smart server response, as would be
1866
passed to an ErrorFromSmartServer.
1867
:kwargs context: context items to call _translate_error with.
1869
:returns: The error raised by _translate_error.
1871
# Raise the ErrorFromSmartServer before passing it as an argument,
1872
# because _translate_error may need to re-raise it with a bare 'raise'
1874
server_error = errors.ErrorFromSmartServer(error_tuple)
1875
translated_error = self.translateErrorFromSmartServer(
1876
server_error, **context)
1877
return translated_error
1879
def translateErrorFromSmartServer(self, error_object, **context):
1880
"""Like translateTuple, but takes an already constructed
1881
ErrorFromSmartServer rather than a tuple.
1885
except errors.ErrorFromSmartServer, server_error:
1886
translated_error = self.assertRaises(
1887
errors.BzrError, remote._translate_error, server_error,
1889
return translated_error
1892
class TestErrorTranslationSuccess(TestErrorTranslationBase):
1893
"""Unit tests for bzrlib.remote._translate_error.
1895
Given an ErrorFromSmartServer (which has an error tuple from a smart
1896
server) and some context, _translate_error raises more specific errors from
1899
This test case covers the cases where _translate_error succeeds in
1900
translating an ErrorFromSmartServer to something better. See
1901
TestErrorTranslationRobustness for other cases.
1904
def test_NoSuchRevision(self):
1905
branch = self.make_branch('')
1907
translated_error = self.translateTuple(
1908
('NoSuchRevision', revid), branch=branch)
1909
expected_error = errors.NoSuchRevision(branch, revid)
1910
self.assertEqual(expected_error, translated_error)
1912
def test_nosuchrevision(self):
1913
repository = self.make_repository('')
1915
translated_error = self.translateTuple(
1916
('nosuchrevision', revid), repository=repository)
1917
expected_error = errors.NoSuchRevision(repository, revid)
1918
self.assertEqual(expected_error, translated_error)
1920
def test_nobranch(self):
1921
bzrdir = self.make_bzrdir('')
1922
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
1923
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
1924
self.assertEqual(expected_error, translated_error)
1926
def test_LockContention(self):
1927
translated_error = self.translateTuple(('LockContention',))
1928
expected_error = errors.LockContention('(remote lock)')
1929
self.assertEqual(expected_error, translated_error)
1931
def test_UnlockableTransport(self):
1932
bzrdir = self.make_bzrdir('')
1933
translated_error = self.translateTuple(
1934
('UnlockableTransport',), bzrdir=bzrdir)
1935
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
1936
self.assertEqual(expected_error, translated_error)
1938
def test_LockFailed(self):
1939
lock = 'str() of a server lock'
1940
why = 'str() of why'
1941
translated_error = self.translateTuple(('LockFailed', lock, why))
1942
expected_error = errors.LockFailed(lock, why)
1943
self.assertEqual(expected_error, translated_error)
1945
def test_TokenMismatch(self):
1946
token = 'a lock token'
1947
translated_error = self.translateTuple(('TokenMismatch',), token=token)
1948
expected_error = errors.TokenMismatch(token, '(remote token)')
1949
self.assertEqual(expected_error, translated_error)
1951
def test_Diverged(self):
1952
branch = self.make_branch('a')
1953
other_branch = self.make_branch('b')
1954
translated_error = self.translateTuple(
1955
('Diverged',), branch=branch, other_branch=other_branch)
1956
expected_error = errors.DivergedBranches(branch, other_branch)
1957
self.assertEqual(expected_error, translated_error)
1959
def test_ReadError_no_args(self):
1961
translated_error = self.translateTuple(('ReadError',), path=path)
1962
expected_error = errors.ReadError(path)
1963
self.assertEqual(expected_error, translated_error)
1965
def test_ReadError(self):
1967
translated_error = self.translateTuple(('ReadError', path))
1968
expected_error = errors.ReadError(path)
1969
self.assertEqual(expected_error, translated_error)
1971
def test_PermissionDenied_no_args(self):
1973
translated_error = self.translateTuple(('PermissionDenied',), path=path)
1974
expected_error = errors.PermissionDenied(path)
1975
self.assertEqual(expected_error, translated_error)
1977
def test_PermissionDenied_one_arg(self):
1979
translated_error = self.translateTuple(('PermissionDenied', path))
1980
expected_error = errors.PermissionDenied(path)
1981
self.assertEqual(expected_error, translated_error)
1983
def test_PermissionDenied_one_arg_and_context(self):
1984
"""Given a choice between a path from the local context and a path on
1985
the wire, _translate_error prefers the path from the local context.
1987
local_path = 'local path'
1988
remote_path = 'remote path'
1989
translated_error = self.translateTuple(
1990
('PermissionDenied', remote_path), path=local_path)
1991
expected_error = errors.PermissionDenied(local_path)
1992
self.assertEqual(expected_error, translated_error)
1994
def test_PermissionDenied_two_args(self):
1996
extra = 'a string with extra info'
1997
translated_error = self.translateTuple(
1998
('PermissionDenied', path, extra))
1999
expected_error = errors.PermissionDenied(path, extra)
2000
self.assertEqual(expected_error, translated_error)
2003
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2004
"""Unit tests for bzrlib.remote._translate_error's robustness.
2006
TestErrorTranslationSuccess is for cases where _translate_error can
2007
translate successfully. This class about how _translate_err behaves when
2008
it fails to translate: it re-raises the original error.
2011
def test_unrecognised_server_error(self):
2012
"""If the error code from the server is not recognised, the original
2013
ErrorFromSmartServer is propagated unmodified.
2015
error_tuple = ('An unknown error tuple',)
2016
server_error = errors.ErrorFromSmartServer(error_tuple)
2017
translated_error = self.translateErrorFromSmartServer(server_error)
2018
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2019
self.assertEqual(expected_error, translated_error)
2021
def test_context_missing_a_key(self):
2022
"""In case of a bug in the client, or perhaps an unexpected response
2023
from a server, _translate_error returns the original error tuple from
2024
the server and mutters a warning.
2026
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2027
# in the context dict. So let's give it an empty context dict instead
2028
# to exercise its error recovery.
2030
error_tuple = ('NoSuchRevision', 'revid')
2031
server_error = errors.ErrorFromSmartServer(error_tuple)
2032
translated_error = self.translateErrorFromSmartServer(server_error)
2033
self.assertEqual(server_error, translated_error)
2034
# In addition to re-raising ErrorFromSmartServer, some debug info has
2035
# been muttered to the log file for developer to look at.
2036
self.assertContainsRe(
2037
self._get_log(keep_log_file=True),
2038
"Missing key 'branch' in context")
2040
def test_path_missing(self):
2041
"""Some translations (PermissionDenied, ReadError) can determine the
2042
'path' variable from either the wire or the local context. If neither
2043
has it, then an error is raised.
2045
error_tuple = ('ReadError',)
2046
server_error = errors.ErrorFromSmartServer(error_tuple)
2047
translated_error = self.translateErrorFromSmartServer(server_error)
2048
self.assertEqual(server_error, translated_error)
2049
# In addition to re-raising ErrorFromSmartServer, some debug info has
2050
# been muttered to the log file for developer to look at.
2051
self.assertContainsRe(
2052
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2055
class TestStacking(tests.TestCaseWithTransport):
2056
"""Tests for operations on stacked remote repositories.
2058
The underlying format type must support stacking.
2061
def test_access_stacked_remote(self):
2062
# based on <http://launchpad.net/bugs/261315>
2063
# make a branch stacked on another repository containing an empty
2064
# revision, then open it over hpss - we should be able to see that
2066
base_transport = self.get_transport()
2067
base_builder = self.make_branch_builder('base', format='1.6')
2068
base_builder.start_series()
2069
base_revid = base_builder.build_snapshot('rev-id', None,
2070
[('add', ('', None, 'directory', None))],
2072
base_builder.finish_series()
2073
stacked_branch = self.make_branch('stacked', format='1.6')
2074
stacked_branch.set_stacked_on_url('../base')
2075
# start a server looking at this
2076
smart_server = server.SmartTCPServer_for_testing()
2077
smart_server.setUp()
2078
self.addCleanup(smart_server.tearDown)
2079
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2080
# can get its branch and repository
2081
remote_branch = remote_bzrdir.open_branch()
2082
remote_repo = remote_branch.repository
2083
remote_repo.lock_read()
2085
# it should have an appropriate fallback repository, which should also
2086
# be a RemoteRepository
2087
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2088
self.assertIsInstance(remote_repo._fallback_repositories[0],
2090
# and it has the revision committed to the underlying repository;
2091
# these have varying implementations so we try several of them
2092
self.assertTrue(remote_repo.has_revisions([base_revid]))
2093
self.assertTrue(remote_repo.has_revision(base_revid))
2094
self.assertEqual(remote_repo.get_revision(base_revid).message,
2097
remote_repo.unlock()
2099
def prepare_stacked_remote_branch(self):
2100
smart_server = server.SmartTCPServer_for_testing()
2101
smart_server.setUp()
2102
self.addCleanup(smart_server.tearDown)
2103
tree1 = self.make_branch_and_tree('tree1')
2104
tree1.commit('rev1', rev_id='rev1')
2105
tree2 = self.make_branch_and_tree('tree2', format='1.6')
2106
tree2.branch.set_stacked_on_url(tree1.branch.base)
2107
branch2 = Branch.open(smart_server.get_url() + '/tree2')
2109
self.addCleanup(branch2.unlock)
2112
def test_stacked_get_parent_map(self):
2113
# the public implementation of get_parent_map obeys stacking
2114
branch = self.prepare_stacked_remote_branch()
2115
repo = branch.repository
2116
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2118
def test_unstacked_get_parent_map(self):
2119
# _unstacked_provider.get_parent_map ignores stacking
2120
branch = self.prepare_stacked_remote_branch()
2121
provider = branch.repository._unstacked_provider
2122
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2125
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2128
super(TestRemoteBranchEffort, self).setUp()
2129
# Create a smart server that publishes whatever the backing VFS server
2131
self.smart_server = server.SmartTCPServer_for_testing()
2132
self.smart_server.setUp(self.get_server())
2133
self.addCleanup(self.smart_server.tearDown)
2134
# Log all HPSS calls into self.hpss_calls.
2135
_SmartClient.hooks.install_named_hook(
2136
'call', self.capture_hpss_call, None)
2137
self.hpss_calls = []
2139
def capture_hpss_call(self, params):
2140
self.hpss_calls.append(params.method)
2142
def test_copy_content_into_avoids_revision_history(self):
2143
local = self.make_branch('local')
2144
remote_backing_tree = self.make_branch_and_tree('remote')
2145
remote_backing_tree.commit("Commit.")
2146
remote_branch_url = self.smart_server.get_url() + 'remote'
2147
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2148
local.repository.fetch(remote_branch.repository)
2149
self.hpss_calls = []
2150
remote_branch.copy_content_into(local)
2151
self.assertFalse('Branch.revision_history' in self.hpss_calls)