1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests for remote bzrdir/branch/repo/etc
19
These are proxy objects which act on remote objects by sending messages
20
through a smart client. The proxies are to be created when attempting to open
21
the object given a transport that supports smartserver rpc operations.
23
These tests correspond to tests.test_smart, which exercises the server side.
27
from cStringIO import StringIO
41
from bzrlib.branch import Branch
42
from bzrlib.bzrdir import BzrDir, BzrDirFormat
43
from bzrlib.remote import (
50
from bzrlib.revision import NULL_REVISION
51
from bzrlib.smart import server, medium
52
from bzrlib.smart.client import _SmartClient
53
from bzrlib.symbol_versioning import one_four
54
from bzrlib.tests import (
56
split_suite_by_condition,
59
from bzrlib.transport import get_transport, http
60
from bzrlib.transport.memory import MemoryTransport
61
from bzrlib.transport.remote import (
67
def load_tests(standard_tests, module, loader):
68
to_adapt, result = split_suite_by_condition(
69
standard_tests, condition_isinstance(BasicRemoteObjectTests))
70
smart_server_version_scenarios = [
72
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
74
{'transport_server': server.SmartTCPServer_for_testing})]
75
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
78
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
81
super(BasicRemoteObjectTests, self).setUp()
82
self.transport = self.get_transport()
83
# make a branch that can be opened over the smart transport
84
self.local_wt = BzrDir.create_standalone_workingtree('.')
87
self.transport.disconnect()
88
tests.TestCaseWithTransport.tearDown(self)
90
def test_create_remote_bzrdir(self):
91
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
92
self.assertIsInstance(b, BzrDir)
94
def test_open_remote_branch(self):
95
# open a standalone branch in the working directory
96
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
97
branch = b.open_branch()
98
self.assertIsInstance(branch, Branch)
100
def test_remote_repository(self):
101
b = BzrDir.open_from_transport(self.transport)
102
repo = b.open_repository()
103
revid = u'\xc823123123'.encode('utf8')
104
self.assertFalse(repo.has_revision(revid))
105
self.local_wt.commit(message='test commit', rev_id=revid)
106
self.assertTrue(repo.has_revision(revid))
108
def test_remote_branch_revision_history(self):
109
b = BzrDir.open_from_transport(self.transport).open_branch()
110
self.assertEqual([], b.revision_history())
111
r1 = self.local_wt.commit('1st commit')
112
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
113
self.assertEqual([r1, r2], b.revision_history())
115
def test_find_correct_format(self):
116
"""Should open a RemoteBzrDir over a RemoteTransport"""
117
fmt = BzrDirFormat.find_format(self.transport)
118
self.assertTrue(RemoteBzrDirFormat
119
in BzrDirFormat._control_server_formats)
120
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
122
def test_open_detected_smart_format(self):
123
fmt = BzrDirFormat.find_format(self.transport)
124
d = fmt.open(self.transport)
125
self.assertIsInstance(d, BzrDir)
127
def test_remote_branch_repr(self):
128
b = BzrDir.open_from_transport(self.transport).open_branch()
129
self.assertStartsWith(str(b), 'RemoteBranch(')
131
def test_remote_branch_format_supports_stacking(self):
133
self.make_branch('unstackable', format='pack-0.92')
134
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
135
self.assertFalse(b._format.supports_stacking())
136
self.make_branch('stackable', format='1.9')
137
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
138
self.assertTrue(b._format.supports_stacking())
140
def test_remote_repo_format_supports_external_references(self):
142
bd = self.make_bzrdir('unstackable', format='pack-0.92')
143
r = bd.create_repository()
144
self.assertFalse(r._format.supports_external_lookups)
145
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
146
self.assertFalse(r._format.supports_external_lookups)
147
bd = self.make_bzrdir('stackable', format='1.9')
148
r = bd.create_repository()
149
self.assertTrue(r._format.supports_external_lookups)
150
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
151
self.assertTrue(r._format.supports_external_lookups)
154
class FakeProtocol(object):
155
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
157
def __init__(self, body, fake_client):
159
self._body_buffer = None
160
self._fake_client = fake_client
162
def read_body_bytes(self, count=-1):
163
if self._body_buffer is None:
164
self._body_buffer = StringIO(self.body)
165
bytes = self._body_buffer.read(count)
166
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
167
self._fake_client.expecting_body = False
170
def cancel_read_body(self):
171
self._fake_client.expecting_body = False
173
def read_streamed_body(self):
177
class FakeClient(_SmartClient):
178
"""Lookalike for _SmartClient allowing testing."""
180
def __init__(self, fake_medium_base='fake base'):
181
"""Create a FakeClient."""
184
self.expecting_body = False
185
# if non-None, this is the list of expected calls, with only the
186
# method name and arguments included. the body might be hard to
187
# compute so is not included. If a call is None, that call can
189
self._expected_calls = None
190
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
192
def add_expected_call(self, call_name, call_args, response_type,
193
response_args, response_body=None):
194
if self._expected_calls is None:
195
self._expected_calls = []
196
self._expected_calls.append((call_name, call_args))
197
self.responses.append((response_type, response_args, response_body))
199
def add_success_response(self, *args):
200
self.responses.append(('success', args, None))
202
def add_success_response_with_body(self, body, *args):
203
self.responses.append(('success', args, body))
204
if self._expected_calls is not None:
205
self._expected_calls.append(None)
207
def add_error_response(self, *args):
208
self.responses.append(('error', args))
210
def add_unknown_method_response(self, verb):
211
self.responses.append(('unknown', verb))
213
def finished_test(self):
214
if self._expected_calls:
215
raise AssertionError("%r finished but was still expecting %r"
216
% (self, self._expected_calls[0]))
218
def _get_next_response(self):
220
response_tuple = self.responses.pop(0)
221
except IndexError, e:
222
raise AssertionError("%r didn't expect any more calls"
224
if response_tuple[0] == 'unknown':
225
raise errors.UnknownSmartMethod(response_tuple[1])
226
elif response_tuple[0] == 'error':
227
raise errors.ErrorFromSmartServer(response_tuple[1])
228
return response_tuple
230
def _check_call(self, method, args):
231
if self._expected_calls is None:
232
# the test should be updated to say what it expects
235
next_call = self._expected_calls.pop(0)
237
raise AssertionError("%r didn't expect any more calls "
239
% (self, method, args,))
240
if next_call is None:
242
if method != next_call[0] or args != next_call[1]:
243
raise AssertionError("%r expected %r%r "
245
% (self, next_call[0], next_call[1], method, args,))
247
def call(self, method, *args):
248
self._check_call(method, args)
249
self._calls.append(('call', method, args))
250
return self._get_next_response()[1]
252
def call_expecting_body(self, method, *args):
253
self._check_call(method, args)
254
self._calls.append(('call_expecting_body', method, args))
255
result = self._get_next_response()
256
self.expecting_body = True
257
return result[1], FakeProtocol(result[2], self)
259
def call_with_body_bytes_expecting_body(self, method, args, body):
260
self._check_call(method, args)
261
self._calls.append(('call_with_body_bytes_expecting_body', method,
263
result = self._get_next_response()
264
self.expecting_body = True
265
return result[1], FakeProtocol(result[2], self)
267
def call_with_body_stream(self, args, stream):
268
# Explicitly consume the stream before checking for an error, because
269
# that's what happens a real medium.
270
stream = list(stream)
271
self._check_call(args[0], args[1:])
272
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
273
result = self._get_next_response()
274
# The second value returned from call_with_body_stream is supposed to
275
# be a response_handler object, but so far no tests depend on that.
276
response_handler = None
277
return result[1], response_handler
280
class FakeMedium(medium.SmartClientMedium):
282
def __init__(self, client_calls, base):
283
medium.SmartClientMedium.__init__(self, base)
284
self._client_calls = client_calls
286
def disconnect(self):
287
self._client_calls.append(('disconnect medium',))
290
class TestVfsHas(tests.TestCase):
292
def test_unicode_path(self):
293
client = FakeClient('/')
294
client.add_success_response('yes',)
295
transport = RemoteTransport('bzr://localhost/', _client=client)
296
filename = u'/hell\u00d8'.encode('utf8')
297
result = transport.has(filename)
299
[('call', 'has', (filename,))],
301
self.assertTrue(result)
304
class TestRemote(tests.TestCaseWithMemoryTransport):
306
def get_branch_format(self):
307
reference_bzrdir_format = bzrdir.format_registry.get('default')()
308
return reference_bzrdir_format.get_branch_format()
310
def get_repo_format(self):
311
reference_bzrdir_format = bzrdir.format_registry.get('default')()
312
return reference_bzrdir_format.repository_format
314
def disable_verb(self, verb):
315
"""Disable a verb for one test."""
316
request_handlers = smart.request.request_handlers
317
orig_method = request_handlers.get(verb)
318
request_handlers.remove(verb)
320
request_handlers.register(verb, orig_method)
321
self.addCleanup(restoreVerb)
324
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
325
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
327
def assertRemotePath(self, expected, client_base, transport_base):
328
"""Assert that the result of
329
SmartClientMedium.remote_path_from_transport is the expected value for
330
a given client_base and transport_base.
332
client_medium = medium.SmartClientMedium(client_base)
333
transport = get_transport(transport_base)
334
result = client_medium.remote_path_from_transport(transport)
335
self.assertEqual(expected, result)
337
def test_remote_path_from_transport(self):
338
"""SmartClientMedium.remote_path_from_transport calculates a URL for
339
the given transport relative to the root of the client base URL.
341
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
342
self.assertRemotePath(
343
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
345
def assertRemotePathHTTP(self, expected, transport_base, relpath):
346
"""Assert that the result of
347
HttpTransportBase.remote_path_from_transport is the expected value for
348
a given transport_base and relpath of that transport. (Note that
349
HttpTransportBase is a subclass of SmartClientMedium)
351
base_transport = get_transport(transport_base)
352
client_medium = base_transport.get_smart_medium()
353
cloned_transport = base_transport.clone(relpath)
354
result = client_medium.remote_path_from_transport(cloned_transport)
355
self.assertEqual(expected, result)
357
def test_remote_path_from_transport_http(self):
358
"""Remote paths for HTTP transports are calculated differently to other
359
transports. They are just relative to the client base, not the root
360
directory of the host.
362
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
363
self.assertRemotePathHTTP(
364
'../xyz/', scheme + '//host/path', '../xyz/')
365
self.assertRemotePathHTTP(
366
'xyz/', scheme + '//host/path', 'xyz/')
369
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
370
"""Tests for the behaviour of client_medium.remote_is_at_least."""
372
def test_initially_unlimited(self):
373
"""A fresh medium assumes that the remote side supports all
376
client_medium = medium.SmartClientMedium('dummy base')
377
self.assertFalse(client_medium._is_remote_before((99, 99)))
379
def test__remember_remote_is_before(self):
380
"""Calling _remember_remote_is_before ratchets down the known remote
383
client_medium = medium.SmartClientMedium('dummy base')
384
# Mark the remote side as being less than 1.6. The remote side may
386
client_medium._remember_remote_is_before((1, 6))
387
self.assertTrue(client_medium._is_remote_before((1, 6)))
388
self.assertFalse(client_medium._is_remote_before((1, 5)))
389
# Calling _remember_remote_is_before again with a lower value works.
390
client_medium._remember_remote_is_before((1, 5))
391
self.assertTrue(client_medium._is_remote_before((1, 5)))
392
# You cannot call _remember_remote_is_before with a larger value.
394
AssertionError, client_medium._remember_remote_is_before, (1, 9))
397
class TestBzrDirCloningMetaDir(TestRemote):
399
def test_backwards_compat(self):
400
self.setup_smart_server_with_call_log()
401
a_dir = self.make_bzrdir('.')
402
self.reset_smart_call_log()
403
verb = 'BzrDir.cloning_metadir'
404
self.disable_verb(verb)
405
format = a_dir.cloning_metadir()
406
call_count = len([call for call in self.hpss_calls if
407
call.call.method == verb])
408
self.assertEqual(1, call_count)
410
def test_current_server(self):
411
transport = self.get_transport('.')
412
transport = transport.clone('quack')
413
self.make_bzrdir('quack')
414
client = FakeClient(transport.base)
415
reference_bzrdir_format = bzrdir.format_registry.get('default')()
416
control_name = reference_bzrdir_format.network_name()
417
client.add_expected_call(
418
'BzrDir.cloning_metadir', ('quack/', 'False'),
419
'success', (control_name, '', ('branch', ''))),
420
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
422
result = a_bzrdir.cloning_metadir()
423
# We should have got a reference control dir with default branch and
424
# repository formats.
425
# This pokes a little, just to be sure.
426
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
427
self.assertEqual(None, result._repository_format)
428
self.assertEqual(None, result._branch_format)
429
client.finished_test()
432
class TestBzrDirOpenBranch(TestRemote):
434
def test_backwards_compat(self):
435
self.setup_smart_server_with_call_log()
436
self.make_branch('.')
437
a_dir = BzrDir.open(self.get_url('.'))
438
self.reset_smart_call_log()
439
verb = 'BzrDir.open_branchV2'
440
self.disable_verb(verb)
441
format = a_dir.open_branch()
442
call_count = len([call for call in self.hpss_calls if
443
call.call.method == verb])
444
self.assertEqual(1, call_count)
446
def test_branch_present(self):
447
reference_format = self.get_repo_format()
448
network_name = reference_format.network_name()
449
branch_network_name = self.get_branch_format().network_name()
450
transport = MemoryTransport()
451
transport.mkdir('quack')
452
transport = transport.clone('quack')
453
client = FakeClient(transport.base)
454
client.add_expected_call(
455
'BzrDir.open_branchV2', ('quack/',),
456
'success', ('branch', branch_network_name))
457
client.add_expected_call(
458
'BzrDir.find_repositoryV3', ('quack/',),
459
'success', ('ok', '', 'no', 'no', 'no', network_name))
460
client.add_expected_call(
461
'Branch.get_stacked_on_url', ('quack/',),
462
'error', ('NotStacked',))
463
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
465
result = bzrdir.open_branch()
466
self.assertIsInstance(result, RemoteBranch)
467
self.assertEqual(bzrdir, result.bzrdir)
468
client.finished_test()
470
def test_branch_missing(self):
471
transport = MemoryTransport()
472
transport.mkdir('quack')
473
transport = transport.clone('quack')
474
client = FakeClient(transport.base)
475
client.add_error_response('nobranch')
476
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
478
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
480
[('call', 'BzrDir.open_branchV2', ('quack/',))],
483
def test__get_tree_branch(self):
484
# _get_tree_branch is a form of open_branch, but it should only ask for
485
# branch opening, not any other network requests.
488
calls.append("Called")
490
transport = MemoryTransport()
491
# no requests on the network - catches other api calls being made.
492
client = FakeClient(transport.base)
493
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
495
# patch the open_branch call to record that it was called.
496
bzrdir.open_branch = open_branch
497
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
498
self.assertEqual(["Called"], calls)
499
self.assertEqual([], client._calls)
501
def test_url_quoting_of_path(self):
502
# Relpaths on the wire should not be URL-escaped. So "~" should be
503
# transmitted as "~", not "%7E".
504
transport = RemoteTCPTransport('bzr://localhost/~hello/')
505
client = FakeClient(transport.base)
506
reference_format = self.get_repo_format()
507
network_name = reference_format.network_name()
508
branch_network_name = self.get_branch_format().network_name()
509
client.add_expected_call(
510
'BzrDir.open_branchV2', ('~hello/',),
511
'success', ('branch', branch_network_name))
512
client.add_expected_call(
513
'BzrDir.find_repositoryV3', ('~hello/',),
514
'success', ('ok', '', 'no', 'no', 'no', network_name))
515
client.add_expected_call(
516
'Branch.get_stacked_on_url', ('~hello/',),
517
'error', ('NotStacked',))
518
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
520
result = bzrdir.open_branch()
521
client.finished_test()
523
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
524
reference_format = self.get_repo_format()
525
network_name = reference_format.network_name()
526
transport = MemoryTransport()
527
transport.mkdir('quack')
528
transport = transport.clone('quack')
530
rich_response = 'yes'
534
subtree_response = 'yes'
536
subtree_response = 'no'
537
client = FakeClient(transport.base)
538
client.add_success_response(
539
'ok', '', rich_response, subtree_response, external_lookup,
541
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
543
result = bzrdir.open_repository()
545
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
547
self.assertIsInstance(result, RemoteRepository)
548
self.assertEqual(bzrdir, result.bzrdir)
549
self.assertEqual(rich_root, result._format.rich_root_data)
550
self.assertEqual(subtrees, result._format.supports_tree_reference)
552
def test_open_repository_sets_format_attributes(self):
553
self.check_open_repository(True, True)
554
self.check_open_repository(False, True)
555
self.check_open_repository(True, False)
556
self.check_open_repository(False, False)
557
self.check_open_repository(False, False, 'yes')
559
def test_old_server(self):
560
"""RemoteBzrDirFormat should fail to probe if the server version is too
563
self.assertRaises(errors.NotBranchError,
564
RemoteBzrDirFormat.probe_transport, OldServerTransport())
567
class TestBzrDirCreateBranch(TestRemote):
569
def test_backwards_compat(self):
570
self.setup_smart_server_with_call_log()
571
repo = self.make_repository('.')
572
self.reset_smart_call_log()
573
self.disable_verb('BzrDir.create_branch')
574
branch = repo.bzrdir.create_branch()
575
create_branch_call_count = len([call for call in self.hpss_calls if
576
call.call.method == 'BzrDir.create_branch'])
577
self.assertEqual(1, create_branch_call_count)
579
def test_current_server(self):
580
transport = self.get_transport('.')
581
transport = transport.clone('quack')
582
self.make_repository('quack')
583
client = FakeClient(transport.base)
584
reference_bzrdir_format = bzrdir.format_registry.get('default')()
585
reference_format = reference_bzrdir_format.get_branch_format()
586
network_name = reference_format.network_name()
587
reference_repo_fmt = reference_bzrdir_format.repository_format
588
reference_repo_name = reference_repo_fmt.network_name()
589
client.add_expected_call(
590
'BzrDir.create_branch', ('quack/', network_name),
591
'success', ('ok', network_name, '', 'no', 'no', 'yes',
592
reference_repo_name))
593
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
595
branch = a_bzrdir.create_branch()
596
# We should have got a remote branch
597
self.assertIsInstance(branch, remote.RemoteBranch)
598
# its format should have the settings from the response
599
format = branch._format
600
self.assertEqual(network_name, format.network_name())
603
class TestBzrDirCreateRepository(TestRemote):
605
def test_backwards_compat(self):
606
self.setup_smart_server_with_call_log()
607
bzrdir = self.make_bzrdir('.')
608
self.reset_smart_call_log()
609
self.disable_verb('BzrDir.create_repository')
610
repo = bzrdir.create_repository()
611
create_repo_call_count = len([call for call in self.hpss_calls if
612
call.call.method == 'BzrDir.create_repository'])
613
self.assertEqual(1, create_repo_call_count)
615
def test_current_server(self):
616
transport = self.get_transport('.')
617
transport = transport.clone('quack')
618
self.make_bzrdir('quack')
619
client = FakeClient(transport.base)
620
reference_bzrdir_format = bzrdir.format_registry.get('default')()
621
reference_format = reference_bzrdir_format.repository_format
622
network_name = reference_format.network_name()
623
client.add_expected_call(
624
'BzrDir.create_repository', ('quack/',
625
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
626
'success', ('ok', 'no', 'no', 'no', network_name))
627
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
629
repo = a_bzrdir.create_repository()
630
# We should have got a remote repository
631
self.assertIsInstance(repo, remote.RemoteRepository)
632
# its format should have the settings from the response
633
format = repo._format
634
self.assertFalse(format.rich_root_data)
635
self.assertFalse(format.supports_tree_reference)
636
self.assertFalse(format.supports_external_lookups)
637
self.assertEqual(network_name, format.network_name())
640
class TestBzrDirOpenRepository(TestRemote):
642
def test_backwards_compat_1_2_3(self):
643
# fallback all the way to the first version.
644
reference_format = self.get_repo_format()
645
network_name = reference_format.network_name()
646
client = FakeClient('bzr://example.com/')
647
client.add_unknown_method_response('BzrDir.find_repositoryV3')
648
client.add_unknown_method_response('BzrDir.find_repositoryV2')
649
client.add_success_response('ok', '', 'no', 'no')
650
# A real repository instance will be created to determine the network
652
client.add_success_response_with_body(
653
"Bazaar-NG meta directory, format 1\n", 'ok')
654
client.add_success_response_with_body(
655
reference_format.get_format_string(), 'ok')
656
# PackRepository wants to do a stat
657
client.add_success_response('stat', '0', '65535')
658
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
660
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
662
repo = bzrdir.open_repository()
664
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
665
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
666
('call', 'BzrDir.find_repository', ('quack/',)),
667
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
668
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
669
('call', 'stat', ('/quack/.bzr/repository',)),
672
self.assertEqual(network_name, repo._format.network_name())
674
def test_backwards_compat_2(self):
675
# fallback to find_repositoryV2
676
reference_format = self.get_repo_format()
677
network_name = reference_format.network_name()
678
client = FakeClient('bzr://example.com/')
679
client.add_unknown_method_response('BzrDir.find_repositoryV3')
680
client.add_success_response('ok', '', 'no', 'no', 'no')
681
# A real repository instance will be created to determine the network
683
client.add_success_response_with_body(
684
"Bazaar-NG meta directory, format 1\n", 'ok')
685
client.add_success_response_with_body(
686
reference_format.get_format_string(), 'ok')
687
# PackRepository wants to do a stat
688
client.add_success_response('stat', '0', '65535')
689
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
691
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
693
repo = bzrdir.open_repository()
695
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
696
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
697
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
698
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
699
('call', 'stat', ('/quack/.bzr/repository',)),
702
self.assertEqual(network_name, repo._format.network_name())
704
def test_current_server(self):
705
reference_format = self.get_repo_format()
706
network_name = reference_format.network_name()
707
transport = MemoryTransport()
708
transport.mkdir('quack')
709
transport = transport.clone('quack')
710
client = FakeClient(transport.base)
711
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
712
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
714
repo = bzrdir.open_repository()
716
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
718
self.assertEqual(network_name, repo._format.network_name())
721
class OldSmartClient(object):
722
"""A fake smart client for test_old_version that just returns a version one
723
response to the 'hello' (query version) command.
726
def get_request(self):
727
input_file = StringIO('ok\x011\n')
728
output_file = StringIO()
729
client_medium = medium.SmartSimplePipesClientMedium(
730
input_file, output_file)
731
return medium.SmartClientStreamMediumRequest(client_medium)
733
def protocol_version(self):
737
class OldServerTransport(object):
738
"""A fake transport for test_old_server that reports it's smart server
739
protocol version as version one.
745
def get_smart_client(self):
746
return OldSmartClient()
749
class RemoteBranchTestCase(TestRemote):
751
def make_remote_branch(self, transport, client):
752
"""Make a RemoteBranch using 'client' as its _SmartClient.
754
A RemoteBzrDir and RemoteRepository will also be created to fill out
755
the RemoteBranch, albeit with stub values for some of their attributes.
757
# we do not want bzrdir to make any remote calls, so use False as its
758
# _client. If it tries to make a remote call, this will fail
760
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
762
repo = RemoteRepository(bzrdir, None, _client=client)
763
branch_format = self.get_branch_format()
764
format = RemoteBranchFormat(network_name=branch_format.network_name())
765
return RemoteBranch(bzrdir, repo, _client=client, format=format)
768
class TestBranchGetParent(RemoteBranchTestCase):
770
def test_no_parent(self):
771
# in an empty branch we decode the response properly
772
transport = MemoryTransport()
773
client = FakeClient(transport.base)
774
client.add_expected_call(
775
'Branch.get_stacked_on_url', ('quack/',),
776
'error', ('NotStacked',))
777
client.add_expected_call(
778
'Branch.get_parent', ('quack/',),
780
transport.mkdir('quack')
781
transport = transport.clone('quack')
782
branch = self.make_remote_branch(transport, client)
783
result = branch.get_parent()
784
client.finished_test()
785
self.assertEqual(None, result)
787
def test_parent_relative(self):
788
transport = MemoryTransport()
789
client = FakeClient(transport.base)
790
client.add_expected_call(
791
'Branch.get_stacked_on_url', ('kwaak/',),
792
'error', ('NotStacked',))
793
client.add_expected_call(
794
'Branch.get_parent', ('kwaak/',),
795
'success', ('../foo/',))
796
transport.mkdir('kwaak')
797
transport = transport.clone('kwaak')
798
branch = self.make_remote_branch(transport, client)
799
result = branch.get_parent()
800
self.assertEqual(transport.clone('../foo').base, result)
802
def test_parent_absolute(self):
803
transport = MemoryTransport()
804
client = FakeClient(transport.base)
805
client.add_expected_call(
806
'Branch.get_stacked_on_url', ('kwaak/',),
807
'error', ('NotStacked',))
808
client.add_expected_call(
809
'Branch.get_parent', ('kwaak/',),
810
'success', ('http://foo/',))
811
transport.mkdir('kwaak')
812
transport = transport.clone('kwaak')
813
branch = self.make_remote_branch(transport, client)
814
result = branch.get_parent()
815
self.assertEqual('http://foo/', result)
818
class TestBranchGetTagsBytes(RemoteBranchTestCase):
820
def test_backwards_compat(self):
821
self.setup_smart_server_with_call_log()
822
branch = self.make_branch('.')
823
self.reset_smart_call_log()
824
verb = 'Branch.get_tags_bytes'
825
self.disable_verb(verb)
826
branch.tags.get_tag_dict()
827
call_count = len([call for call in self.hpss_calls if
828
call.call.method == verb])
829
self.assertEqual(1, call_count)
831
def test_trivial(self):
832
transport = MemoryTransport()
833
client = FakeClient(transport.base)
834
client.add_expected_call(
835
'Branch.get_stacked_on_url', ('quack/',),
836
'error', ('NotStacked',))
837
client.add_expected_call(
838
'Branch.get_tags_bytes', ('quack/',),
840
transport.mkdir('quack')
841
transport = transport.clone('quack')
842
branch = self.make_remote_branch(transport, client)
843
result = branch.tags.get_tag_dict()
844
client.finished_test()
845
self.assertEqual({}, result)
848
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
850
def test_empty_branch(self):
851
# in an empty branch we decode the response properly
852
transport = MemoryTransport()
853
client = FakeClient(transport.base)
854
client.add_expected_call(
855
'Branch.get_stacked_on_url', ('quack/',),
856
'error', ('NotStacked',))
857
client.add_expected_call(
858
'Branch.last_revision_info', ('quack/',),
859
'success', ('ok', '0', 'null:'))
860
transport.mkdir('quack')
861
transport = transport.clone('quack')
862
branch = self.make_remote_branch(transport, client)
863
result = branch.last_revision_info()
864
client.finished_test()
865
self.assertEqual((0, NULL_REVISION), result)
867
def test_non_empty_branch(self):
868
# in a non-empty branch we also decode the response properly
869
revid = u'\xc8'.encode('utf8')
870
transport = MemoryTransport()
871
client = FakeClient(transport.base)
872
client.add_expected_call(
873
'Branch.get_stacked_on_url', ('kwaak/',),
874
'error', ('NotStacked',))
875
client.add_expected_call(
876
'Branch.last_revision_info', ('kwaak/',),
877
'success', ('ok', '2', revid))
878
transport.mkdir('kwaak')
879
transport = transport.clone('kwaak')
880
branch = self.make_remote_branch(transport, client)
881
result = branch.last_revision_info()
882
self.assertEqual((2, revid), result)
885
class TestBranch_get_stacked_on_url(TestRemote):
886
"""Test Branch._get_stacked_on_url rpc"""
888
def test_get_stacked_on_invalid_url(self):
889
# test that asking for a stacked on url the server can't access works.
890
# This isn't perfect, but then as we're in the same process there
891
# really isn't anything we can do to be 100% sure that the server
892
# doesn't just open in - this test probably needs to be rewritten using
893
# a spawn()ed server.
894
stacked_branch = self.make_branch('stacked', format='1.9')
895
memory_branch = self.make_branch('base', format='1.9')
896
vfs_url = self.get_vfs_only_url('base')
897
stacked_branch.set_stacked_on_url(vfs_url)
898
transport = stacked_branch.bzrdir.root_transport
899
client = FakeClient(transport.base)
900
client.add_expected_call(
901
'Branch.get_stacked_on_url', ('stacked/',),
902
'success', ('ok', vfs_url))
903
# XXX: Multiple calls are bad, this second call documents what is
905
client.add_expected_call(
906
'Branch.get_stacked_on_url', ('stacked/',),
907
'success', ('ok', vfs_url))
908
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
910
repo_fmt = remote.RemoteRepositoryFormat()
911
repo_fmt._custom_format = stacked_branch.repository._format
912
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
914
result = branch.get_stacked_on_url()
915
self.assertEqual(vfs_url, result)
917
def test_backwards_compatible(self):
918
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
919
base_branch = self.make_branch('base', format='1.6')
920
stacked_branch = self.make_branch('stacked', format='1.6')
921
stacked_branch.set_stacked_on_url('../base')
922
client = FakeClient(self.get_url())
923
branch_network_name = self.get_branch_format().network_name()
924
client.add_expected_call(
925
'BzrDir.open_branchV2', ('stacked/',),
926
'success', ('branch', branch_network_name))
927
client.add_expected_call(
928
'BzrDir.find_repositoryV3', ('stacked/',),
929
'success', ('ok', '', 'no', 'no', 'yes',
930
stacked_branch.repository._format.network_name()))
931
# called twice, once from constructor and then again by us
932
client.add_expected_call(
933
'Branch.get_stacked_on_url', ('stacked/',),
934
'unknown', ('Branch.get_stacked_on_url',))
935
client.add_expected_call(
936
'Branch.get_stacked_on_url', ('stacked/',),
937
'unknown', ('Branch.get_stacked_on_url',))
938
# this will also do vfs access, but that goes direct to the transport
939
# and isn't seen by the FakeClient.
940
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
941
remote.RemoteBzrDirFormat(), _client=client)
942
branch = bzrdir.open_branch()
943
result = branch.get_stacked_on_url()
944
self.assertEqual('../base', result)
945
client.finished_test()
946
# it's in the fallback list both for the RemoteRepository and its vfs
948
self.assertEqual(1, len(branch.repository._fallback_repositories))
950
len(branch.repository._real_repository._fallback_repositories))
952
def test_get_stacked_on_real_branch(self):
953
base_branch = self.make_branch('base', format='1.6')
954
stacked_branch = self.make_branch('stacked', format='1.6')
955
stacked_branch.set_stacked_on_url('../base')
956
reference_format = self.get_repo_format()
957
network_name = reference_format.network_name()
958
client = FakeClient(self.get_url())
959
branch_network_name = self.get_branch_format().network_name()
960
client.add_expected_call(
961
'BzrDir.open_branchV2', ('stacked/',),
962
'success', ('branch', branch_network_name))
963
client.add_expected_call(
964
'BzrDir.find_repositoryV3', ('stacked/',),
965
'success', ('ok', '', 'no', 'no', 'yes', network_name))
966
# called twice, once from constructor and then again by us
967
client.add_expected_call(
968
'Branch.get_stacked_on_url', ('stacked/',),
969
'success', ('ok', '../base'))
970
client.add_expected_call(
971
'Branch.get_stacked_on_url', ('stacked/',),
972
'success', ('ok', '../base'))
973
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
974
remote.RemoteBzrDirFormat(), _client=client)
975
branch = bzrdir.open_branch()
976
result = branch.get_stacked_on_url()
977
self.assertEqual('../base', result)
978
client.finished_test()
979
# it's in the fallback list both for the RemoteRepository and its vfs
981
self.assertEqual(1, len(branch.repository._fallback_repositories))
983
len(branch.repository._real_repository._fallback_repositories))
986
class TestBranchSetLastRevision(RemoteBranchTestCase):
988
def test_set_empty(self):
989
# set_revision_history([]) is translated to calling
990
# Branch.set_last_revision(path, '') on the wire.
991
transport = MemoryTransport()
992
transport.mkdir('branch')
993
transport = transport.clone('branch')
995
client = FakeClient(transport.base)
996
client.add_expected_call(
997
'Branch.get_stacked_on_url', ('branch/',),
998
'error', ('NotStacked',))
999
client.add_expected_call(
1000
'Branch.lock_write', ('branch/', '', ''),
1001
'success', ('ok', 'branch token', 'repo token'))
1002
client.add_expected_call(
1003
'Branch.last_revision_info',
1005
'success', ('ok', '0', 'null:'))
1006
client.add_expected_call(
1007
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1009
client.add_expected_call(
1010
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1012
branch = self.make_remote_branch(transport, client)
1013
# This is a hack to work around the problem that RemoteBranch currently
1014
# unnecessarily invokes _ensure_real upon a call to lock_write.
1015
branch._ensure_real = lambda: None
1017
result = branch.set_revision_history([])
1019
self.assertEqual(None, result)
1020
client.finished_test()
1022
def test_set_nonempty(self):
1023
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1024
# Branch.set_last_revision(path, rev-idN) on the wire.
1025
transport = MemoryTransport()
1026
transport.mkdir('branch')
1027
transport = transport.clone('branch')
1029
client = FakeClient(transport.base)
1030
client.add_expected_call(
1031
'Branch.get_stacked_on_url', ('branch/',),
1032
'error', ('NotStacked',))
1033
client.add_expected_call(
1034
'Branch.lock_write', ('branch/', '', ''),
1035
'success', ('ok', 'branch token', 'repo token'))
1036
client.add_expected_call(
1037
'Branch.last_revision_info',
1039
'success', ('ok', '0', 'null:'))
1041
encoded_body = bz2.compress('\n'.join(lines))
1042
client.add_success_response_with_body(encoded_body, 'ok')
1043
client.add_expected_call(
1044
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1046
client.add_expected_call(
1047
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1049
branch = self.make_remote_branch(transport, client)
1050
# This is a hack to work around the problem that RemoteBranch currently
1051
# unnecessarily invokes _ensure_real upon a call to lock_write.
1052
branch._ensure_real = lambda: None
1053
# Lock the branch, reset the record of remote calls.
1055
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1057
self.assertEqual(None, result)
1058
client.finished_test()
1060
def test_no_such_revision(self):
1061
transport = MemoryTransport()
1062
transport.mkdir('branch')
1063
transport = transport.clone('branch')
1064
# A response of 'NoSuchRevision' is translated into an exception.
1065
client = FakeClient(transport.base)
1066
client.add_expected_call(
1067
'Branch.get_stacked_on_url', ('branch/',),
1068
'error', ('NotStacked',))
1069
client.add_expected_call(
1070
'Branch.lock_write', ('branch/', '', ''),
1071
'success', ('ok', 'branch token', 'repo token'))
1072
client.add_expected_call(
1073
'Branch.last_revision_info',
1075
'success', ('ok', '0', 'null:'))
1076
# get_graph calls to construct the revision history, for the set_rh
1079
encoded_body = bz2.compress('\n'.join(lines))
1080
client.add_success_response_with_body(encoded_body, 'ok')
1081
client.add_expected_call(
1082
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1083
'error', ('NoSuchRevision', 'rev-id'))
1084
client.add_expected_call(
1085
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1088
branch = self.make_remote_branch(transport, client)
1091
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1093
client.finished_test()
1095
def test_tip_change_rejected(self):
1096
"""TipChangeRejected responses cause a TipChangeRejected exception to
1099
transport = MemoryTransport()
1100
transport.mkdir('branch')
1101
transport = transport.clone('branch')
1102
client = FakeClient(transport.base)
1103
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1104
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1105
client.add_expected_call(
1106
'Branch.get_stacked_on_url', ('branch/',),
1107
'error', ('NotStacked',))
1108
client.add_expected_call(
1109
'Branch.lock_write', ('branch/', '', ''),
1110
'success', ('ok', 'branch token', 'repo token'))
1111
client.add_expected_call(
1112
'Branch.last_revision_info',
1114
'success', ('ok', '0', 'null:'))
1116
encoded_body = bz2.compress('\n'.join(lines))
1117
client.add_success_response_with_body(encoded_body, 'ok')
1118
client.add_expected_call(
1119
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1120
'error', ('TipChangeRejected', rejection_msg_utf8))
1121
client.add_expected_call(
1122
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1124
branch = self.make_remote_branch(transport, client)
1125
branch._ensure_real = lambda: None
1127
# The 'TipChangeRejected' error response triggered by calling
1128
# set_revision_history causes a TipChangeRejected exception.
1129
err = self.assertRaises(
1130
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1131
# The UTF-8 message from the response has been decoded into a unicode
1133
self.assertIsInstance(err.msg, unicode)
1134
self.assertEqual(rejection_msg_unicode, err.msg)
1136
client.finished_test()
1139
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1141
def test_set_last_revision_info(self):
1142
# set_last_revision_info(num, 'rev-id') is translated to calling
1143
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1144
transport = MemoryTransport()
1145
transport.mkdir('branch')
1146
transport = transport.clone('branch')
1147
client = FakeClient(transport.base)
1148
# get_stacked_on_url
1149
client.add_error_response('NotStacked')
1151
client.add_success_response('ok', 'branch token', 'repo token')
1152
# query the current revision
1153
client.add_success_response('ok', '0', 'null:')
1155
client.add_success_response('ok')
1157
client.add_success_response('ok')
1159
branch = self.make_remote_branch(transport, client)
1160
# Lock the branch, reset the record of remote calls.
1163
result = branch.set_last_revision_info(1234, 'a-revision-id')
1165
[('call', 'Branch.last_revision_info', ('branch/',)),
1166
('call', 'Branch.set_last_revision_info',
1167
('branch/', 'branch token', 'repo token',
1168
'1234', 'a-revision-id'))],
1170
self.assertEqual(None, result)
1172
def test_no_such_revision(self):
1173
# A response of 'NoSuchRevision' is translated into an exception.
1174
transport = MemoryTransport()
1175
transport.mkdir('branch')
1176
transport = transport.clone('branch')
1177
client = FakeClient(transport.base)
1178
# get_stacked_on_url
1179
client.add_error_response('NotStacked')
1181
client.add_success_response('ok', 'branch token', 'repo token')
1183
client.add_error_response('NoSuchRevision', 'revid')
1185
client.add_success_response('ok')
1187
branch = self.make_remote_branch(transport, client)
1188
# Lock the branch, reset the record of remote calls.
1193
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1196
def lock_remote_branch(self, branch):
1197
"""Trick a RemoteBranch into thinking it is locked."""
1198
branch._lock_mode = 'w'
1199
branch._lock_count = 2
1200
branch._lock_token = 'branch token'
1201
branch._repo_lock_token = 'repo token'
1202
branch.repository._lock_mode = 'w'
1203
branch.repository._lock_count = 2
1204
branch.repository._lock_token = 'repo token'
1206
def test_backwards_compatibility(self):
1207
"""If the server does not support the Branch.set_last_revision_info
1208
verb (which is new in 1.4), then the client falls back to VFS methods.
1210
# This test is a little messy. Unlike most tests in this file, it
1211
# doesn't purely test what a Remote* object sends over the wire, and
1212
# how it reacts to responses from the wire. It instead relies partly
1213
# on asserting that the RemoteBranch will call
1214
# self._real_branch.set_last_revision_info(...).
1216
# First, set up our RemoteBranch with a FakeClient that raises
1217
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1218
transport = MemoryTransport()
1219
transport.mkdir('branch')
1220
transport = transport.clone('branch')
1221
client = FakeClient(transport.base)
1222
client.add_expected_call(
1223
'Branch.get_stacked_on_url', ('branch/',),
1224
'error', ('NotStacked',))
1225
client.add_expected_call(
1226
'Branch.last_revision_info',
1228
'success', ('ok', '0', 'null:'))
1229
client.add_expected_call(
1230
'Branch.set_last_revision_info',
1231
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1232
'unknown', 'Branch.set_last_revision_info')
1234
branch = self.make_remote_branch(transport, client)
1235
class StubRealBranch(object):
1238
def set_last_revision_info(self, revno, revision_id):
1240
('set_last_revision_info', revno, revision_id))
1241
def _clear_cached_state(self):
1243
real_branch = StubRealBranch()
1244
branch._real_branch = real_branch
1245
self.lock_remote_branch(branch)
1247
# Call set_last_revision_info, and verify it behaved as expected.
1248
result = branch.set_last_revision_info(1234, 'a-revision-id')
1250
[('set_last_revision_info', 1234, 'a-revision-id')],
1252
client.finished_test()
1254
def test_unexpected_error(self):
1255
# If the server sends an error the client doesn't understand, it gets
1256
# turned into an UnknownErrorFromSmartServer, which is presented as a
1257
# non-internal error to the user.
1258
transport = MemoryTransport()
1259
transport.mkdir('branch')
1260
transport = transport.clone('branch')
1261
client = FakeClient(transport.base)
1262
# get_stacked_on_url
1263
client.add_error_response('NotStacked')
1265
client.add_success_response('ok', 'branch token', 'repo token')
1267
client.add_error_response('UnexpectedError')
1269
client.add_success_response('ok')
1271
branch = self.make_remote_branch(transport, client)
1272
# Lock the branch, reset the record of remote calls.
1276
err = self.assertRaises(
1277
errors.UnknownErrorFromSmartServer,
1278
branch.set_last_revision_info, 123, 'revid')
1279
self.assertEqual(('UnexpectedError',), err.error_tuple)
1282
def test_tip_change_rejected(self):
1283
"""TipChangeRejected responses cause a TipChangeRejected exception to
1286
transport = MemoryTransport()
1287
transport.mkdir('branch')
1288
transport = transport.clone('branch')
1289
client = FakeClient(transport.base)
1290
# get_stacked_on_url
1291
client.add_error_response('NotStacked')
1293
client.add_success_response('ok', 'branch token', 'repo token')
1295
client.add_error_response('TipChangeRejected', 'rejection message')
1297
client.add_success_response('ok')
1299
branch = self.make_remote_branch(transport, client)
1300
# Lock the branch, reset the record of remote calls.
1302
self.addCleanup(branch.unlock)
1305
# The 'TipChangeRejected' error response triggered by calling
1306
# set_last_revision_info causes a TipChangeRejected exception.
1307
err = self.assertRaises(
1308
errors.TipChangeRejected,
1309
branch.set_last_revision_info, 123, 'revid')
1310
self.assertEqual('rejection message', err.msg)
1313
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1314
"""Getting the branch configuration should use an abstract method not vfs.
1317
def test_get_branch_conf(self):
1318
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1319
## # We should see that branch.get_config() does a single rpc to get the
1320
## # remote configuration file, abstracting away where that is stored on
1321
## # the server. However at the moment it always falls back to using the
1322
## # vfs, and this would need some changes in config.py.
1324
## # in an empty branch we decode the response properly
1325
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1326
## # we need to make a real branch because the remote_branch.control_files
1327
## # will trigger _ensure_real.
1328
## branch = self.make_branch('quack')
1329
## transport = branch.bzrdir.root_transport
1330
## # we do not want bzrdir to make any remote calls
1331
## bzrdir = RemoteBzrDir(transport, _client=False)
1332
## branch = RemoteBranch(bzrdir, None, _client=client)
1333
## config = branch.get_config()
1334
## self.assertEqual(
1335
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1339
class TestBranchLockWrite(RemoteBranchTestCase):
1341
def test_lock_write_unlockable(self):
1342
transport = MemoryTransport()
1343
client = FakeClient(transport.base)
1344
client.add_expected_call(
1345
'Branch.get_stacked_on_url', ('quack/',),
1346
'error', ('NotStacked',),)
1347
client.add_expected_call(
1348
'Branch.lock_write', ('quack/', '', ''),
1349
'error', ('UnlockableTransport',))
1350
transport.mkdir('quack')
1351
transport = transport.clone('quack')
1352
branch = self.make_remote_branch(transport, client)
1353
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1354
client.finished_test()
1357
class TestTransportIsReadonly(tests.TestCase):
1359
def test_true(self):
1360
client = FakeClient()
1361
client.add_success_response('yes')
1362
transport = RemoteTransport('bzr://example.com/', medium=False,
1364
self.assertEqual(True, transport.is_readonly())
1366
[('call', 'Transport.is_readonly', ())],
1369
def test_false(self):
1370
client = FakeClient()
1371
client.add_success_response('no')
1372
transport = RemoteTransport('bzr://example.com/', medium=False,
1374
self.assertEqual(False, transport.is_readonly())
1376
[('call', 'Transport.is_readonly', ())],
1379
def test_error_from_old_server(self):
1380
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1382
Clients should treat it as a "no" response, because is_readonly is only
1383
advisory anyway (a transport could be read-write, but then the
1384
underlying filesystem could be readonly anyway).
1386
client = FakeClient()
1387
client.add_unknown_method_response('Transport.is_readonly')
1388
transport = RemoteTransport('bzr://example.com/', medium=False,
1390
self.assertEqual(False, transport.is_readonly())
1392
[('call', 'Transport.is_readonly', ())],
1396
class TestTransportMkdir(tests.TestCase):
1398
def test_permissiondenied(self):
1399
client = FakeClient()
1400
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1401
transport = RemoteTransport('bzr://example.com/', medium=False,
1403
exc = self.assertRaises(
1404
errors.PermissionDenied, transport.mkdir, 'client path')
1405
expected_error = errors.PermissionDenied('/client path', 'extra')
1406
self.assertEqual(expected_error, exc)
1409
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1411
def test_defaults_to_none(self):
1412
t = RemoteSSHTransport('bzr+ssh://example.com')
1413
self.assertIs(None, t._get_credentials()[0])
1415
def test_uses_authentication_config(self):
1416
conf = config.AuthenticationConfig()
1417
conf._get_config().update(
1418
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1421
t = RemoteSSHTransport('bzr+ssh://example.com')
1422
self.assertEqual('bar', t._get_credentials()[0])
1425
class TestRemoteRepository(TestRemote):
1426
"""Base for testing RemoteRepository protocol usage.
1428
These tests contain frozen requests and responses. We want any changes to
1429
what is sent or expected to be require a thoughtful update to these tests
1430
because they might break compatibility with different-versioned servers.
1433
def setup_fake_client_and_repository(self, transport_path):
1434
"""Create the fake client and repository for testing with.
1436
There's no real server here; we just have canned responses sent
1439
:param transport_path: Path below the root of the MemoryTransport
1440
where the repository will be created.
1442
transport = MemoryTransport()
1443
transport.mkdir(transport_path)
1444
client = FakeClient(transport.base)
1445
transport = transport.clone(transport_path)
1446
# we do not want bzrdir to make any remote calls
1447
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1449
repo = RemoteRepository(bzrdir, None, _client=client)
1453
class TestRepositoryGatherStats(TestRemoteRepository):
1455
def test_revid_none(self):
1456
# ('ok',), body with revisions and size
1457
transport_path = 'quack'
1458
repo, client = self.setup_fake_client_and_repository(transport_path)
1459
client.add_success_response_with_body(
1460
'revisions: 2\nsize: 18\n', 'ok')
1461
result = repo.gather_stats(None)
1463
[('call_expecting_body', 'Repository.gather_stats',
1464
('quack/','','no'))],
1466
self.assertEqual({'revisions': 2, 'size': 18}, result)
1468
def test_revid_no_committers(self):
1469
# ('ok',), body without committers
1470
body = ('firstrev: 123456.300 3600\n'
1471
'latestrev: 654231.400 0\n'
1474
transport_path = 'quick'
1475
revid = u'\xc8'.encode('utf8')
1476
repo, client = self.setup_fake_client_and_repository(transport_path)
1477
client.add_success_response_with_body(body, 'ok')
1478
result = repo.gather_stats(revid)
1480
[('call_expecting_body', 'Repository.gather_stats',
1481
('quick/', revid, 'no'))],
1483
self.assertEqual({'revisions': 2, 'size': 18,
1484
'firstrev': (123456.300, 3600),
1485
'latestrev': (654231.400, 0),},
1488
def test_revid_with_committers(self):
1489
# ('ok',), body with committers
1490
body = ('committers: 128\n'
1491
'firstrev: 123456.300 3600\n'
1492
'latestrev: 654231.400 0\n'
1495
transport_path = 'buick'
1496
revid = u'\xc8'.encode('utf8')
1497
repo, client = self.setup_fake_client_and_repository(transport_path)
1498
client.add_success_response_with_body(body, 'ok')
1499
result = repo.gather_stats(revid, True)
1501
[('call_expecting_body', 'Repository.gather_stats',
1502
('buick/', revid, 'yes'))],
1504
self.assertEqual({'revisions': 2, 'size': 18,
1506
'firstrev': (123456.300, 3600),
1507
'latestrev': (654231.400, 0),},
1511
class TestRepositoryGetGraph(TestRemoteRepository):
1513
def test_get_graph(self):
1514
# get_graph returns a graph with a custom parents provider.
1515
transport_path = 'quack'
1516
repo, client = self.setup_fake_client_and_repository(transport_path)
1517
graph = repo.get_graph()
1518
self.assertNotEqual(graph._parents_provider, repo)
1521
class TestRepositoryGetParentMap(TestRemoteRepository):
1523
def test_get_parent_map_caching(self):
1524
# get_parent_map returns from cache until unlock()
1525
# setup a reponse with two revisions
1526
r1 = u'\u0e33'.encode('utf8')
1527
r2 = u'\u0dab'.encode('utf8')
1528
lines = [' '.join([r2, r1]), r1]
1529
encoded_body = bz2.compress('\n'.join(lines))
1531
transport_path = 'quack'
1532
repo, client = self.setup_fake_client_and_repository(transport_path)
1533
client.add_success_response_with_body(encoded_body, 'ok')
1534
client.add_success_response_with_body(encoded_body, 'ok')
1536
graph = repo.get_graph()
1537
parents = graph.get_parent_map([r2])
1538
self.assertEqual({r2: (r1,)}, parents)
1539
# locking and unlocking deeper should not reset
1542
parents = graph.get_parent_map([r1])
1543
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1545
[('call_with_body_bytes_expecting_body',
1546
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
1549
# now we call again, and it should use the second response.
1551
graph = repo.get_graph()
1552
parents = graph.get_parent_map([r1])
1553
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1555
[('call_with_body_bytes_expecting_body',
1556
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
1557
('call_with_body_bytes_expecting_body',
1558
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
1563
def test_get_parent_map_reconnects_if_unknown_method(self):
1564
transport_path = 'quack'
1565
repo, client = self.setup_fake_client_and_repository(transport_path)
1566
client.add_unknown_method_response('Repository,get_parent_map')
1567
client.add_success_response_with_body('', 'ok')
1568
self.assertFalse(client._medium._is_remote_before((1, 2)))
1569
rev_id = 'revision-id'
1570
expected_deprecations = [
1571
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1573
parents = self.callDeprecated(
1574
expected_deprecations, repo.get_parent_map, [rev_id])
1576
[('call_with_body_bytes_expecting_body',
1577
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
1578
('disconnect medium',),
1579
('call_expecting_body', 'Repository.get_revision_graph',
1582
# The medium is now marked as being connected to an older server
1583
self.assertTrue(client._medium._is_remote_before((1, 2)))
1585
def test_get_parent_map_fallback_parentless_node(self):
1586
"""get_parent_map falls back to get_revision_graph on old servers. The
1587
results from get_revision_graph are tweaked to match the get_parent_map
1590
Specifically, a {key: ()} result from get_revision_graph means "no
1591
parents" for that key, which in get_parent_map results should be
1592
represented as {key: ('null:',)}.
1594
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1596
rev_id = 'revision-id'
1597
transport_path = 'quack'
1598
repo, client = self.setup_fake_client_and_repository(transport_path)
1599
client.add_success_response_with_body(rev_id, 'ok')
1600
client._medium._remember_remote_is_before((1, 2))
1601
expected_deprecations = [
1602
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1604
parents = self.callDeprecated(
1605
expected_deprecations, repo.get_parent_map, [rev_id])
1607
[('call_expecting_body', 'Repository.get_revision_graph',
1610
self.assertEqual({rev_id: ('null:',)}, parents)
1612
def test_get_parent_map_unexpected_response(self):
1613
repo, client = self.setup_fake_client_and_repository('path')
1614
client.add_success_response('something unexpected!')
1616
errors.UnexpectedSmartServerResponse,
1617
repo.get_parent_map, ['a-revision-id'])
1620
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1622
def test_allows_new_revisions(self):
1623
"""get_parent_map's results can be updated by commit."""
1624
smart_server = server.SmartTCPServer_for_testing()
1625
smart_server.setUp()
1626
self.addCleanup(smart_server.tearDown)
1627
self.make_branch('branch')
1628
branch = Branch.open(smart_server.get_url() + '/branch')
1629
tree = branch.create_checkout('tree', lightweight=True)
1631
self.addCleanup(tree.unlock)
1632
graph = tree.branch.repository.get_graph()
1633
# This provides an opportunity for the missing rev-id to be cached.
1634
self.assertEqual({}, graph.get_parent_map(['rev1']))
1635
tree.commit('message', rev_id='rev1')
1636
graph = tree.branch.repository.get_graph()
1637
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1640
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1642
def test_null_revision(self):
1643
# a null revision has the predictable result {}, we should have no wire
1644
# traffic when calling it with this argument
1645
transport_path = 'empty'
1646
repo, client = self.setup_fake_client_and_repository(transport_path)
1647
client.add_success_response('notused')
1648
result = self.applyDeprecated(one_four, repo.get_revision_graph,
1650
self.assertEqual([], client._calls)
1651
self.assertEqual({}, result)
1653
def test_none_revision(self):
1654
# with none we want the entire graph
1655
r1 = u'\u0e33'.encode('utf8')
1656
r2 = u'\u0dab'.encode('utf8')
1657
lines = [' '.join([r2, r1]), r1]
1658
encoded_body = '\n'.join(lines)
1660
transport_path = 'sinhala'
1661
repo, client = self.setup_fake_client_and_repository(transport_path)
1662
client.add_success_response_with_body(encoded_body, 'ok')
1663
result = self.applyDeprecated(one_four, repo.get_revision_graph)
1665
[('call_expecting_body', 'Repository.get_revision_graph',
1668
self.assertEqual({r1: (), r2: (r1, )}, result)
1670
def test_specific_revision(self):
1671
# with a specific revision we want the graph for that
1672
# with none we want the entire graph
1673
r11 = u'\u0e33'.encode('utf8')
1674
r12 = u'\xc9'.encode('utf8')
1675
r2 = u'\u0dab'.encode('utf8')
1676
lines = [' '.join([r2, r11, r12]), r11, r12]
1677
encoded_body = '\n'.join(lines)
1679
transport_path = 'sinhala'
1680
repo, client = self.setup_fake_client_and_repository(transport_path)
1681
client.add_success_response_with_body(encoded_body, 'ok')
1682
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
1684
[('call_expecting_body', 'Repository.get_revision_graph',
1687
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1689
def test_no_such_revision(self):
1691
transport_path = 'sinhala'
1692
repo, client = self.setup_fake_client_and_repository(transport_path)
1693
client.add_error_response('nosuchrevision', revid)
1694
# also check that the right revision is reported in the error
1695
self.assertRaises(errors.NoSuchRevision,
1696
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1698
[('call_expecting_body', 'Repository.get_revision_graph',
1699
('sinhala/', revid))],
1702
def test_unexpected_error(self):
1704
transport_path = 'sinhala'
1705
repo, client = self.setup_fake_client_and_repository(transport_path)
1706
client.add_error_response('AnUnexpectedError')
1707
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1708
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1709
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1712
class TestRepositoryIsShared(TestRemoteRepository):
1714
def test_is_shared(self):
1715
# ('yes', ) for Repository.is_shared -> 'True'.
1716
transport_path = 'quack'
1717
repo, client = self.setup_fake_client_and_repository(transport_path)
1718
client.add_success_response('yes')
1719
result = repo.is_shared()
1721
[('call', 'Repository.is_shared', ('quack/',))],
1723
self.assertEqual(True, result)
1725
def test_is_not_shared(self):
1726
# ('no', ) for Repository.is_shared -> 'False'.
1727
transport_path = 'qwack'
1728
repo, client = self.setup_fake_client_and_repository(transport_path)
1729
client.add_success_response('no')
1730
result = repo.is_shared()
1732
[('call', 'Repository.is_shared', ('qwack/',))],
1734
self.assertEqual(False, result)
1737
class TestRepositoryLockWrite(TestRemoteRepository):
1739
def test_lock_write(self):
1740
transport_path = 'quack'
1741
repo, client = self.setup_fake_client_and_repository(transport_path)
1742
client.add_success_response('ok', 'a token')
1743
result = repo.lock_write()
1745
[('call', 'Repository.lock_write', ('quack/', ''))],
1747
self.assertEqual('a token', result)
1749
def test_lock_write_already_locked(self):
1750
transport_path = 'quack'
1751
repo, client = self.setup_fake_client_and_repository(transport_path)
1752
client.add_error_response('LockContention')
1753
self.assertRaises(errors.LockContention, repo.lock_write)
1755
[('call', 'Repository.lock_write', ('quack/', ''))],
1758
def test_lock_write_unlockable(self):
1759
transport_path = 'quack'
1760
repo, client = self.setup_fake_client_and_repository(transport_path)
1761
client.add_error_response('UnlockableTransport')
1762
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1764
[('call', 'Repository.lock_write', ('quack/', ''))],
1768
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1770
def test_backwards_compat(self):
1771
self.setup_smart_server_with_call_log()
1772
repo = self.make_repository('.')
1773
self.reset_smart_call_log()
1774
verb = 'Repository.set_make_working_trees'
1775
self.disable_verb(verb)
1776
repo.set_make_working_trees(True)
1777
call_count = len([call for call in self.hpss_calls if
1778
call.call.method == verb])
1779
self.assertEqual(1, call_count)
1781
def test_current(self):
1782
transport_path = 'quack'
1783
repo, client = self.setup_fake_client_and_repository(transport_path)
1784
client.add_expected_call(
1785
'Repository.set_make_working_trees', ('quack/', 'True'),
1787
client.add_expected_call(
1788
'Repository.set_make_working_trees', ('quack/', 'False'),
1790
repo.set_make_working_trees(True)
1791
repo.set_make_working_trees(False)
1794
class TestRepositoryUnlock(TestRemoteRepository):
1796
def test_unlock(self):
1797
transport_path = 'quack'
1798
repo, client = self.setup_fake_client_and_repository(transport_path)
1799
client.add_success_response('ok', 'a token')
1800
client.add_success_response('ok')
1804
[('call', 'Repository.lock_write', ('quack/', '')),
1805
('call', 'Repository.unlock', ('quack/', 'a token'))],
1808
def test_unlock_wrong_token(self):
1809
# If somehow the token is wrong, unlock will raise TokenMismatch.
1810
transport_path = 'quack'
1811
repo, client = self.setup_fake_client_and_repository(transport_path)
1812
client.add_success_response('ok', 'a token')
1813
client.add_error_response('TokenMismatch')
1815
self.assertRaises(errors.TokenMismatch, repo.unlock)
1818
class TestRepositoryHasRevision(TestRemoteRepository):
1820
def test_none(self):
1821
# repo.has_revision(None) should not cause any traffic.
1822
transport_path = 'quack'
1823
repo, client = self.setup_fake_client_and_repository(transport_path)
1825
# The null revision is always there, so has_revision(None) == True.
1826
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1828
# The remote repo shouldn't be accessed.
1829
self.assertEqual([], client._calls)
1832
class TestRepositoryInsertStream(TestRemoteRepository):
1834
def test_unlocked_repo(self):
1835
transport_path = 'quack'
1836
repo, client = self.setup_fake_client_and_repository(transport_path)
1837
client.add_expected_call(
1838
'Repository.insert_stream', ('quack/', ''),
1840
client.add_expected_call(
1841
'Repository.insert_stream', ('quack/', ''),
1843
sink = repo._get_sink()
1844
fmt = repository.RepositoryFormat.get_default_format()
1845
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1846
self.assertEqual([], resume_tokens)
1847
self.assertEqual(set(), missing_keys)
1848
client.finished_test()
1850
def test_locked_repo_with_no_lock_token(self):
1851
transport_path = 'quack'
1852
repo, client = self.setup_fake_client_and_repository(transport_path)
1853
client.add_expected_call(
1854
'Repository.lock_write', ('quack/', ''),
1855
'success', ('ok', ''))
1856
client.add_expected_call(
1857
'Repository.insert_stream', ('quack/', ''),
1859
client.add_expected_call(
1860
'Repository.insert_stream', ('quack/', ''),
1863
sink = repo._get_sink()
1864
fmt = repository.RepositoryFormat.get_default_format()
1865
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1866
self.assertEqual([], resume_tokens)
1867
self.assertEqual(set(), missing_keys)
1868
client.finished_test()
1870
def test_locked_repo_with_lock_token(self):
1871
transport_path = 'quack'
1872
repo, client = self.setup_fake_client_and_repository(transport_path)
1873
client.add_expected_call(
1874
'Repository.lock_write', ('quack/', ''),
1875
'success', ('ok', 'a token'))
1876
client.add_expected_call(
1877
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
1879
client.add_expected_call(
1880
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
1883
sink = repo._get_sink()
1884
fmt = repository.RepositoryFormat.get_default_format()
1885
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1886
self.assertEqual([], resume_tokens)
1887
self.assertEqual(set(), missing_keys)
1888
client.finished_test()
1891
class TestRepositoryTarball(TestRemoteRepository):
1893
# This is a canned tarball reponse we can validate against
1895
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1896
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1897
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1898
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1899
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1900
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1901
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1902
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1903
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1904
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1905
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1906
'nWQ7QH/F3JFOFCQ0aSPfA='
1909
def test_repository_tarball(self):
1910
# Test that Repository.tarball generates the right operations
1911
transport_path = 'repo'
1912
expected_calls = [('call_expecting_body', 'Repository.tarball',
1913
('repo/', 'bz2',),),
1915
repo, client = self.setup_fake_client_and_repository(transport_path)
1916
client.add_success_response_with_body(self.tarball_content, 'ok')
1917
# Now actually ask for the tarball
1918
tarball_file = repo._get_tarball('bz2')
1920
self.assertEqual(expected_calls, client._calls)
1921
self.assertEqual(self.tarball_content, tarball_file.read())
1923
tarball_file.close()
1926
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
1927
"""RemoteRepository.copy_content_into optimizations"""
1929
def test_copy_content_remote_to_local(self):
1930
self.transport_server = server.SmartTCPServer_for_testing
1931
src_repo = self.make_repository('repo1')
1932
src_repo = repository.Repository.open(self.get_url('repo1'))
1933
# At the moment the tarball-based copy_content_into can't write back
1934
# into a smart server. It would be good if it could upload the
1935
# tarball; once that works we'd have to create repositories of
1936
# different formats. -- mbp 20070410
1937
dest_url = self.get_vfs_only_url('repo2')
1938
dest_bzrdir = BzrDir.create(dest_url)
1939
dest_repo = dest_bzrdir.create_repository()
1940
self.assertFalse(isinstance(dest_repo, RemoteRepository))
1941
self.assertTrue(isinstance(src_repo, RemoteRepository))
1942
src_repo.copy_content_into(dest_repo)
1945
class _StubRealPackRepository(object):
1947
def __init__(self, calls):
1949
self._pack_collection = _StubPackCollection(calls)
1951
def is_in_write_group(self):
1954
def refresh_data(self):
1955
self.calls.append(('pack collection reload_pack_names',))
1958
class _StubPackCollection(object):
1960
def __init__(self, calls):
1964
self.calls.append(('pack collection autopack',))
1967
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1968
"""Tests for RemoteRepository.autopack implementation."""
1971
"""When the server returns 'ok' and there's no _real_repository, then
1972
nothing else happens: the autopack method is done.
1974
transport_path = 'quack'
1975
repo, client = self.setup_fake_client_and_repository(transport_path)
1976
client.add_expected_call(
1977
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
1979
client.finished_test()
1981
def test_ok_with_real_repo(self):
1982
"""When the server returns 'ok' and there is a _real_repository, then
1983
the _real_repository's reload_pack_name's method will be called.
1985
transport_path = 'quack'
1986
repo, client = self.setup_fake_client_and_repository(transport_path)
1987
client.add_expected_call(
1988
'PackRepository.autopack', ('quack/',),
1990
repo._real_repository = _StubRealPackRepository(client._calls)
1993
[('call', 'PackRepository.autopack', ('quack/',)),
1994
('pack collection reload_pack_names',)],
1997
def test_backwards_compatibility(self):
1998
"""If the server does not recognise the PackRepository.autopack verb,
1999
fallback to the real_repository's implementation.
2001
transport_path = 'quack'
2002
repo, client = self.setup_fake_client_and_repository(transport_path)
2003
client.add_unknown_method_response('PackRepository.autopack')
2004
def stub_ensure_real():
2005
client._calls.append(('_ensure_real',))
2006
repo._real_repository = _StubRealPackRepository(client._calls)
2007
repo._ensure_real = stub_ensure_real
2010
[('call', 'PackRepository.autopack', ('quack/',)),
2012
('pack collection autopack',)],
2016
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2017
"""Base class for unit tests for bzrlib.remote._translate_error."""
2019
def translateTuple(self, error_tuple, **context):
2020
"""Call _translate_error with an ErrorFromSmartServer built from the
2023
:param error_tuple: A tuple of a smart server response, as would be
2024
passed to an ErrorFromSmartServer.
2025
:kwargs context: context items to call _translate_error with.
2027
:returns: The error raised by _translate_error.
2029
# Raise the ErrorFromSmartServer before passing it as an argument,
2030
# because _translate_error may need to re-raise it with a bare 'raise'
2032
server_error = errors.ErrorFromSmartServer(error_tuple)
2033
translated_error = self.translateErrorFromSmartServer(
2034
server_error, **context)
2035
return translated_error
2037
def translateErrorFromSmartServer(self, error_object, **context):
2038
"""Like translateTuple, but takes an already constructed
2039
ErrorFromSmartServer rather than a tuple.
2043
except errors.ErrorFromSmartServer, server_error:
2044
translated_error = self.assertRaises(
2045
errors.BzrError, remote._translate_error, server_error,
2047
return translated_error
2050
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2051
"""Unit tests for bzrlib.remote._translate_error.
2053
Given an ErrorFromSmartServer (which has an error tuple from a smart
2054
server) and some context, _translate_error raises more specific errors from
2057
This test case covers the cases where _translate_error succeeds in
2058
translating an ErrorFromSmartServer to something better. See
2059
TestErrorTranslationRobustness for other cases.
2062
def test_NoSuchRevision(self):
2063
branch = self.make_branch('')
2065
translated_error = self.translateTuple(
2066
('NoSuchRevision', revid), branch=branch)
2067
expected_error = errors.NoSuchRevision(branch, revid)
2068
self.assertEqual(expected_error, translated_error)
2070
def test_nosuchrevision(self):
2071
repository = self.make_repository('')
2073
translated_error = self.translateTuple(
2074
('nosuchrevision', revid), repository=repository)
2075
expected_error = errors.NoSuchRevision(repository, revid)
2076
self.assertEqual(expected_error, translated_error)
2078
def test_nobranch(self):
2079
bzrdir = self.make_bzrdir('')
2080
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2081
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2082
self.assertEqual(expected_error, translated_error)
2084
def test_LockContention(self):
2085
translated_error = self.translateTuple(('LockContention',))
2086
expected_error = errors.LockContention('(remote lock)')
2087
self.assertEqual(expected_error, translated_error)
2089
def test_UnlockableTransport(self):
2090
bzrdir = self.make_bzrdir('')
2091
translated_error = self.translateTuple(
2092
('UnlockableTransport',), bzrdir=bzrdir)
2093
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2094
self.assertEqual(expected_error, translated_error)
2096
def test_LockFailed(self):
2097
lock = 'str() of a server lock'
2098
why = 'str() of why'
2099
translated_error = self.translateTuple(('LockFailed', lock, why))
2100
expected_error = errors.LockFailed(lock, why)
2101
self.assertEqual(expected_error, translated_error)
2103
def test_TokenMismatch(self):
2104
token = 'a lock token'
2105
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2106
expected_error = errors.TokenMismatch(token, '(remote token)')
2107
self.assertEqual(expected_error, translated_error)
2109
def test_Diverged(self):
2110
branch = self.make_branch('a')
2111
other_branch = self.make_branch('b')
2112
translated_error = self.translateTuple(
2113
('Diverged',), branch=branch, other_branch=other_branch)
2114
expected_error = errors.DivergedBranches(branch, other_branch)
2115
self.assertEqual(expected_error, translated_error)
2117
def test_ReadError_no_args(self):
2119
translated_error = self.translateTuple(('ReadError',), path=path)
2120
expected_error = errors.ReadError(path)
2121
self.assertEqual(expected_error, translated_error)
2123
def test_ReadError(self):
2125
translated_error = self.translateTuple(('ReadError', path))
2126
expected_error = errors.ReadError(path)
2127
self.assertEqual(expected_error, translated_error)
2129
def test_PermissionDenied_no_args(self):
2131
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2132
expected_error = errors.PermissionDenied(path)
2133
self.assertEqual(expected_error, translated_error)
2135
def test_PermissionDenied_one_arg(self):
2137
translated_error = self.translateTuple(('PermissionDenied', path))
2138
expected_error = errors.PermissionDenied(path)
2139
self.assertEqual(expected_error, translated_error)
2141
def test_PermissionDenied_one_arg_and_context(self):
2142
"""Given a choice between a path from the local context and a path on
2143
the wire, _translate_error prefers the path from the local context.
2145
local_path = 'local path'
2146
remote_path = 'remote path'
2147
translated_error = self.translateTuple(
2148
('PermissionDenied', remote_path), path=local_path)
2149
expected_error = errors.PermissionDenied(local_path)
2150
self.assertEqual(expected_error, translated_error)
2152
def test_PermissionDenied_two_args(self):
2154
extra = 'a string with extra info'
2155
translated_error = self.translateTuple(
2156
('PermissionDenied', path, extra))
2157
expected_error = errors.PermissionDenied(path, extra)
2158
self.assertEqual(expected_error, translated_error)
2161
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2162
"""Unit tests for bzrlib.remote._translate_error's robustness.
2164
TestErrorTranslationSuccess is for cases where _translate_error can
2165
translate successfully. This class about how _translate_err behaves when
2166
it fails to translate: it re-raises the original error.
2169
def test_unrecognised_server_error(self):
2170
"""If the error code from the server is not recognised, the original
2171
ErrorFromSmartServer is propagated unmodified.
2173
error_tuple = ('An unknown error tuple',)
2174
server_error = errors.ErrorFromSmartServer(error_tuple)
2175
translated_error = self.translateErrorFromSmartServer(server_error)
2176
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2177
self.assertEqual(expected_error, translated_error)
2179
def test_context_missing_a_key(self):
2180
"""In case of a bug in the client, or perhaps an unexpected response
2181
from a server, _translate_error returns the original error tuple from
2182
the server and mutters a warning.
2184
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2185
# in the context dict. So let's give it an empty context dict instead
2186
# to exercise its error recovery.
2188
error_tuple = ('NoSuchRevision', 'revid')
2189
server_error = errors.ErrorFromSmartServer(error_tuple)
2190
translated_error = self.translateErrorFromSmartServer(server_error)
2191
self.assertEqual(server_error, translated_error)
2192
# In addition to re-raising ErrorFromSmartServer, some debug info has
2193
# been muttered to the log file for developer to look at.
2194
self.assertContainsRe(
2195
self._get_log(keep_log_file=True),
2196
"Missing key 'branch' in context")
2198
def test_path_missing(self):
2199
"""Some translations (PermissionDenied, ReadError) can determine the
2200
'path' variable from either the wire or the local context. If neither
2201
has it, then an error is raised.
2203
error_tuple = ('ReadError',)
2204
server_error = errors.ErrorFromSmartServer(error_tuple)
2205
translated_error = self.translateErrorFromSmartServer(server_error)
2206
self.assertEqual(server_error, translated_error)
2207
# In addition to re-raising ErrorFromSmartServer, some debug info has
2208
# been muttered to the log file for developer to look at.
2209
self.assertContainsRe(
2210
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2213
class TestStacking(tests.TestCaseWithTransport):
2214
"""Tests for operations on stacked remote repositories.
2216
The underlying format type must support stacking.
2219
def test_access_stacked_remote(self):
2220
# based on <http://launchpad.net/bugs/261315>
2221
# make a branch stacked on another repository containing an empty
2222
# revision, then open it over hpss - we should be able to see that
2224
base_transport = self.get_transport()
2225
base_builder = self.make_branch_builder('base', format='1.9')
2226
base_builder.start_series()
2227
base_revid = base_builder.build_snapshot('rev-id', None,
2228
[('add', ('', None, 'directory', None))],
2230
base_builder.finish_series()
2231
stacked_branch = self.make_branch('stacked', format='1.9')
2232
stacked_branch.set_stacked_on_url('../base')
2233
# start a server looking at this
2234
smart_server = server.SmartTCPServer_for_testing()
2235
smart_server.setUp()
2236
self.addCleanup(smart_server.tearDown)
2237
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2238
# can get its branch and repository
2239
remote_branch = remote_bzrdir.open_branch()
2240
remote_repo = remote_branch.repository
2241
remote_repo.lock_read()
2243
# it should have an appropriate fallback repository, which should also
2244
# be a RemoteRepository
2245
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2246
self.assertIsInstance(remote_repo._fallback_repositories[0],
2248
# and it has the revision committed to the underlying repository;
2249
# these have varying implementations so we try several of them
2250
self.assertTrue(remote_repo.has_revisions([base_revid]))
2251
self.assertTrue(remote_repo.has_revision(base_revid))
2252
self.assertEqual(remote_repo.get_revision(base_revid).message,
2255
remote_repo.unlock()
2257
def prepare_stacked_remote_branch(self):
2258
"""Get stacked_upon and stacked branches with content in each."""
2259
self.setup_smart_server_with_call_log()
2260
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2261
tree1.commit('rev1', rev_id='rev1')
2262
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2263
).open_workingtree()
2264
tree2.commit('local changes make me feel good.')
2265
branch2 = Branch.open(self.get_url('tree2'))
2267
self.addCleanup(branch2.unlock)
2268
return tree1.branch, branch2
2270
def test_stacked_get_parent_map(self):
2271
# the public implementation of get_parent_map obeys stacking
2272
_, branch = self.prepare_stacked_remote_branch()
2273
repo = branch.repository
2274
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2276
def test_unstacked_get_parent_map(self):
2277
# _unstacked_provider.get_parent_map ignores stacking
2278
_, branch = self.prepare_stacked_remote_branch()
2279
provider = branch.repository._unstacked_provider
2280
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2282
def fetch_stream_to_rev_order(self, stream):
2284
for kind, substream in stream:
2285
if not kind == 'revisions':
2288
for content in substream:
2289
result.append(content.key[-1])
2292
def get_ordered_revs(self, format, order):
2293
"""Get a list of the revisions in a stream to format format.
2295
:param format: The format of the target.
2296
:param order: the order that target should have requested.
2297
:result: The revision ids in the stream, in the order seen,
2298
the topological order of revisions in the source.
2300
unordered_format = bzrdir.format_registry.get(format)()
2301
target_repository_format = unordered_format.repository_format
2303
self.assertEqual(order, target_repository_format._fetch_order)
2304
trunk, stacked = self.prepare_stacked_remote_branch()
2305
source = stacked.repository._get_source(target_repository_format)
2306
tip = stacked.last_revision()
2307
revs = stacked.repository.get_ancestry(tip)
2308
search = graph.PendingAncestryResult([tip], stacked.repository)
2309
self.reset_smart_call_log()
2310
stream = source.get_stream(search)
2313
# We trust that if a revision is in the stream the rest of the new
2314
# content for it is too, as per our main fetch tests; here we are
2315
# checking that the revisions are actually included at all, and their
2317
return self.fetch_stream_to_rev_order(stream), revs
2319
def test_stacked_get_stream_unordered(self):
2320
# Repository._get_source.get_stream() from a stacked repository with
2321
# unordered yields the full data from both stacked and stacked upon
2323
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2324
self.assertEqual(set(expected_revs), set(rev_ord))
2325
# Getting unordered results should have made a streaming data request
2326
# from the server, then one from the backing branch.
2327
self.assertLength(2, self.hpss_calls)
2329
def test_stacked_get_stream_topological(self):
2330
# Repository._get_source.get_stream() from a stacked repository with
2331
# topological sorting yields the full data from both stacked and
2332
# stacked upon sources in topological order.
2333
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2334
self.assertEqual(expected_revs, rev_ord)
2335
# Getting topological sort requires VFS calls still
2336
self.assertLength(14, self.hpss_calls)
2338
def test_stacked_get_stream_groupcompress(self):
2339
# Repository._get_source.get_stream() from a stacked repository with
2340
# groupcompress sorting yields the full data from both stacked and
2341
# stacked upon sources in groupcompress order.
2342
raise tests.TestSkipped('No groupcompress ordered format available')
2343
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2344
self.assertEqual(expected_revs, reversed(rev_ord))
2345
# Getting unordered results should have made a streaming data request
2346
# from the backing branch, and one from the stacked on branch.
2347
self.assertLength(2, self.hpss_calls)
2350
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2353
super(TestRemoteBranchEffort, self).setUp()
2354
# Create a smart server that publishes whatever the backing VFS server
2356
self.smart_server = server.SmartTCPServer_for_testing()
2357
self.smart_server.setUp(self.get_server())
2358
self.addCleanup(self.smart_server.tearDown)
2359
# Log all HPSS calls into self.hpss_calls.
2360
_SmartClient.hooks.install_named_hook(
2361
'call', self.capture_hpss_call, None)
2362
self.hpss_calls = []
2364
def capture_hpss_call(self, params):
2365
self.hpss_calls.append(params.method)
2367
def test_copy_content_into_avoids_revision_history(self):
2368
local = self.make_branch('local')
2369
remote_backing_tree = self.make_branch_and_tree('remote')
2370
remote_backing_tree.commit("Commit.")
2371
remote_branch_url = self.smart_server.get_url() + 'remote'
2372
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2373
local.repository.fetch(remote_branch.repository)
2374
self.hpss_calls = []
2375
remote_branch.copy_content_into(local)
2376
self.assertFalse('Branch.revision_history' in self.hpss_calls)