1
# Copyright (C) 2006, 2007, 2008, 2009 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
RemoteRepositoryFormat,
51
from bzrlib.repofmt import pack_repo
52
from bzrlib.revision import NULL_REVISION
53
from bzrlib.smart import server, medium
54
from bzrlib.smart.client import _SmartClient
55
from bzrlib.tests import (
57
split_suite_by_condition,
60
from bzrlib.transport import get_transport, http
61
from bzrlib.transport.memory import MemoryTransport
62
from bzrlib.transport.remote import (
68
def load_tests(standard_tests, module, loader):
69
to_adapt, result = split_suite_by_condition(
70
standard_tests, condition_isinstance(BasicRemoteObjectTests))
71
smart_server_version_scenarios = [
73
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
75
{'transport_server': server.SmartTCPServer_for_testing})]
76
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
79
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
82
super(BasicRemoteObjectTests, self).setUp()
83
self.transport = self.get_transport()
84
# make a branch that can be opened over the smart transport
85
self.local_wt = BzrDir.create_standalone_workingtree('.')
88
self.transport.disconnect()
89
tests.TestCaseWithTransport.tearDown(self)
91
def test_create_remote_bzrdir(self):
92
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
93
self.assertIsInstance(b, BzrDir)
95
def test_open_remote_branch(self):
96
# open a standalone branch in the working directory
97
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
98
branch = b.open_branch()
99
self.assertIsInstance(branch, Branch)
101
def test_remote_repository(self):
102
b = BzrDir.open_from_transport(self.transport)
103
repo = b.open_repository()
104
revid = u'\xc823123123'.encode('utf8')
105
self.assertFalse(repo.has_revision(revid))
106
self.local_wt.commit(message='test commit', rev_id=revid)
107
self.assertTrue(repo.has_revision(revid))
109
def test_remote_branch_revision_history(self):
110
b = BzrDir.open_from_transport(self.transport).open_branch()
111
self.assertEqual([], b.revision_history())
112
r1 = self.local_wt.commit('1st commit')
113
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
114
self.assertEqual([r1, r2], b.revision_history())
116
def test_find_correct_format(self):
117
"""Should open a RemoteBzrDir over a RemoteTransport"""
118
fmt = BzrDirFormat.find_format(self.transport)
119
self.assertTrue(RemoteBzrDirFormat
120
in BzrDirFormat._control_server_formats)
121
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
123
def test_open_detected_smart_format(self):
124
fmt = BzrDirFormat.find_format(self.transport)
125
d = fmt.open(self.transport)
126
self.assertIsInstance(d, BzrDir)
128
def test_remote_branch_repr(self):
129
b = BzrDir.open_from_transport(self.transport).open_branch()
130
self.assertStartsWith(str(b), 'RemoteBranch(')
132
def test_remote_branch_format_supports_stacking(self):
134
self.make_branch('unstackable', format='pack-0.92')
135
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
136
self.assertFalse(b._format.supports_stacking())
137
self.make_branch('stackable', format='1.9')
138
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
139
self.assertTrue(b._format.supports_stacking())
141
def test_remote_repo_format_supports_external_references(self):
143
bd = self.make_bzrdir('unstackable', format='pack-0.92')
144
r = bd.create_repository()
145
self.assertFalse(r._format.supports_external_lookups)
146
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
147
self.assertFalse(r._format.supports_external_lookups)
148
bd = self.make_bzrdir('stackable', format='1.9')
149
r = bd.create_repository()
150
self.assertTrue(r._format.supports_external_lookups)
151
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
152
self.assertTrue(r._format.supports_external_lookups)
155
class FakeProtocol(object):
156
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
158
def __init__(self, body, fake_client):
160
self._body_buffer = None
161
self._fake_client = fake_client
163
def read_body_bytes(self, count=-1):
164
if self._body_buffer is None:
165
self._body_buffer = StringIO(self.body)
166
bytes = self._body_buffer.read(count)
167
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
168
self._fake_client.expecting_body = False
171
def cancel_read_body(self):
172
self._fake_client.expecting_body = False
174
def read_streamed_body(self):
178
class FakeClient(_SmartClient):
179
"""Lookalike for _SmartClient allowing testing."""
181
def __init__(self, fake_medium_base='fake base'):
182
"""Create a FakeClient."""
185
self.expecting_body = False
186
# if non-None, this is the list of expected calls, with only the
187
# method name and arguments included. the body might be hard to
188
# compute so is not included. If a call is None, that call can
190
self._expected_calls = None
191
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
193
def add_expected_call(self, call_name, call_args, response_type,
194
response_args, response_body=None):
195
if self._expected_calls is None:
196
self._expected_calls = []
197
self._expected_calls.append((call_name, call_args))
198
self.responses.append((response_type, response_args, response_body))
200
def add_success_response(self, *args):
201
self.responses.append(('success', args, None))
203
def add_success_response_with_body(self, body, *args):
204
self.responses.append(('success', args, body))
205
if self._expected_calls is not None:
206
self._expected_calls.append(None)
208
def add_error_response(self, *args):
209
self.responses.append(('error', args))
211
def add_unknown_method_response(self, verb):
212
self.responses.append(('unknown', verb))
214
def finished_test(self):
215
if self._expected_calls:
216
raise AssertionError("%r finished but was still expecting %r"
217
% (self, self._expected_calls[0]))
219
def _get_next_response(self):
221
response_tuple = self.responses.pop(0)
222
except IndexError, e:
223
raise AssertionError("%r didn't expect any more calls"
225
if response_tuple[0] == 'unknown':
226
raise errors.UnknownSmartMethod(response_tuple[1])
227
elif response_tuple[0] == 'error':
228
raise errors.ErrorFromSmartServer(response_tuple[1])
229
return response_tuple
231
def _check_call(self, method, args):
232
if self._expected_calls is None:
233
# the test should be updated to say what it expects
236
next_call = self._expected_calls.pop(0)
238
raise AssertionError("%r didn't expect any more calls "
240
% (self, method, args,))
241
if next_call is None:
243
if method != next_call[0] or args != next_call[1]:
244
raise AssertionError("%r expected %r%r "
246
% (self, next_call[0], next_call[1], method, args,))
248
def call(self, method, *args):
249
self._check_call(method, args)
250
self._calls.append(('call', method, args))
251
return self._get_next_response()[1]
253
def call_expecting_body(self, method, *args):
254
self._check_call(method, args)
255
self._calls.append(('call_expecting_body', method, args))
256
result = self._get_next_response()
257
self.expecting_body = True
258
return result[1], FakeProtocol(result[2], self)
260
def call_with_body_bytes_expecting_body(self, method, args, body):
261
self._check_call(method, args)
262
self._calls.append(('call_with_body_bytes_expecting_body', method,
264
result = self._get_next_response()
265
self.expecting_body = True
266
return result[1], FakeProtocol(result[2], self)
268
def call_with_body_stream(self, args, stream):
269
# Explicitly consume the stream before checking for an error, because
270
# that's what happens a real medium.
271
stream = list(stream)
272
self._check_call(args[0], args[1:])
273
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
274
result = self._get_next_response()
275
# The second value returned from call_with_body_stream is supposed to
276
# be a response_handler object, but so far no tests depend on that.
277
response_handler = None
278
return result[1], response_handler
281
class FakeMedium(medium.SmartClientMedium):
283
def __init__(self, client_calls, base):
284
medium.SmartClientMedium.__init__(self, base)
285
self._client_calls = client_calls
287
def disconnect(self):
288
self._client_calls.append(('disconnect medium',))
291
class TestVfsHas(tests.TestCase):
293
def test_unicode_path(self):
294
client = FakeClient('/')
295
client.add_success_response('yes',)
296
transport = RemoteTransport('bzr://localhost/', _client=client)
297
filename = u'/hell\u00d8'.encode('utf8')
298
result = transport.has(filename)
300
[('call', 'has', (filename,))],
302
self.assertTrue(result)
305
class TestRemote(tests.TestCaseWithMemoryTransport):
307
def get_branch_format(self):
308
reference_bzrdir_format = bzrdir.format_registry.get('default')()
309
return reference_bzrdir_format.get_branch_format()
311
def get_repo_format(self):
312
reference_bzrdir_format = bzrdir.format_registry.get('default')()
313
return reference_bzrdir_format.repository_format
315
def disable_verb(self, verb):
316
"""Disable a verb for one test."""
317
request_handlers = smart.request.request_handlers
318
orig_method = request_handlers.get(verb)
319
request_handlers.remove(verb)
321
request_handlers.register(verb, orig_method)
322
self.addCleanup(restoreVerb)
325
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
326
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
328
def assertRemotePath(self, expected, client_base, transport_base):
329
"""Assert that the result of
330
SmartClientMedium.remote_path_from_transport is the expected value for
331
a given client_base and transport_base.
333
client_medium = medium.SmartClientMedium(client_base)
334
transport = get_transport(transport_base)
335
result = client_medium.remote_path_from_transport(transport)
336
self.assertEqual(expected, result)
338
def test_remote_path_from_transport(self):
339
"""SmartClientMedium.remote_path_from_transport calculates a URL for
340
the given transport relative to the root of the client base URL.
342
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
343
self.assertRemotePath(
344
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
346
def assertRemotePathHTTP(self, expected, transport_base, relpath):
347
"""Assert that the result of
348
HttpTransportBase.remote_path_from_transport is the expected value for
349
a given transport_base and relpath of that transport. (Note that
350
HttpTransportBase is a subclass of SmartClientMedium)
352
base_transport = get_transport(transport_base)
353
client_medium = base_transport.get_smart_medium()
354
cloned_transport = base_transport.clone(relpath)
355
result = client_medium.remote_path_from_transport(cloned_transport)
356
self.assertEqual(expected, result)
358
def test_remote_path_from_transport_http(self):
359
"""Remote paths for HTTP transports are calculated differently to other
360
transports. They are just relative to the client base, not the root
361
directory of the host.
363
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
364
self.assertRemotePathHTTP(
365
'../xyz/', scheme + '//host/path', '../xyz/')
366
self.assertRemotePathHTTP(
367
'xyz/', scheme + '//host/path', 'xyz/')
370
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
371
"""Tests for the behaviour of client_medium.remote_is_at_least."""
373
def test_initially_unlimited(self):
374
"""A fresh medium assumes that the remote side supports all
377
client_medium = medium.SmartClientMedium('dummy base')
378
self.assertFalse(client_medium._is_remote_before((99, 99)))
380
def test__remember_remote_is_before(self):
381
"""Calling _remember_remote_is_before ratchets down the known remote
384
client_medium = medium.SmartClientMedium('dummy base')
385
# Mark the remote side as being less than 1.6. The remote side may
387
client_medium._remember_remote_is_before((1, 6))
388
self.assertTrue(client_medium._is_remote_before((1, 6)))
389
self.assertFalse(client_medium._is_remote_before((1, 5)))
390
# Calling _remember_remote_is_before again with a lower value works.
391
client_medium._remember_remote_is_before((1, 5))
392
self.assertTrue(client_medium._is_remote_before((1, 5)))
393
# You cannot call _remember_remote_is_before with a larger value.
395
AssertionError, client_medium._remember_remote_is_before, (1, 9))
398
class TestBzrDirCloningMetaDir(TestRemote):
400
def test_backwards_compat(self):
401
self.setup_smart_server_with_call_log()
402
a_dir = self.make_bzrdir('.')
403
self.reset_smart_call_log()
404
verb = 'BzrDir.cloning_metadir'
405
self.disable_verb(verb)
406
format = a_dir.cloning_metadir()
407
call_count = len([call for call in self.hpss_calls if
408
call.call.method == verb])
409
self.assertEqual(1, call_count)
411
def test_current_server(self):
412
transport = self.get_transport('.')
413
transport = transport.clone('quack')
414
self.make_bzrdir('quack')
415
client = FakeClient(transport.base)
416
reference_bzrdir_format = bzrdir.format_registry.get('default')()
417
control_name = reference_bzrdir_format.network_name()
418
client.add_expected_call(
419
'BzrDir.cloning_metadir', ('quack/', 'False'),
420
'success', (control_name, '', ('branch', ''))),
421
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
423
result = a_bzrdir.cloning_metadir()
424
# We should have got a reference control dir with default branch and
425
# repository formats.
426
# This pokes a little, just to be sure.
427
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
428
self.assertEqual(None, result._repository_format)
429
self.assertEqual(None, result._branch_format)
430
client.finished_test()
433
class TestBzrDirOpenBranch(TestRemote):
435
def test_backwards_compat(self):
436
self.setup_smart_server_with_call_log()
437
self.make_branch('.')
438
a_dir = BzrDir.open(self.get_url('.'))
439
self.reset_smart_call_log()
440
verb = 'BzrDir.open_branchV2'
441
self.disable_verb(verb)
442
format = a_dir.open_branch()
443
call_count = len([call for call in self.hpss_calls if
444
call.call.method == verb])
445
self.assertEqual(1, call_count)
447
def test_branch_present(self):
448
reference_format = self.get_repo_format()
449
network_name = reference_format.network_name()
450
branch_network_name = self.get_branch_format().network_name()
451
transport = MemoryTransport()
452
transport.mkdir('quack')
453
transport = transport.clone('quack')
454
client = FakeClient(transport.base)
455
client.add_expected_call(
456
'BzrDir.open_branchV2', ('quack/',),
457
'success', ('branch', branch_network_name))
458
client.add_expected_call(
459
'BzrDir.find_repositoryV3', ('quack/',),
460
'success', ('ok', '', 'no', 'no', 'no', network_name))
461
client.add_expected_call(
462
'Branch.get_stacked_on_url', ('quack/',),
463
'error', ('NotStacked',))
464
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
466
result = bzrdir.open_branch()
467
self.assertIsInstance(result, RemoteBranch)
468
self.assertEqual(bzrdir, result.bzrdir)
469
client.finished_test()
471
def test_branch_missing(self):
472
transport = MemoryTransport()
473
transport.mkdir('quack')
474
transport = transport.clone('quack')
475
client = FakeClient(transport.base)
476
client.add_error_response('nobranch')
477
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
479
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
481
[('call', 'BzrDir.open_branchV2', ('quack/',))],
484
def test__get_tree_branch(self):
485
# _get_tree_branch is a form of open_branch, but it should only ask for
486
# branch opening, not any other network requests.
489
calls.append("Called")
491
transport = MemoryTransport()
492
# no requests on the network - catches other api calls being made.
493
client = FakeClient(transport.base)
494
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
496
# patch the open_branch call to record that it was called.
497
bzrdir.open_branch = open_branch
498
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
499
self.assertEqual(["Called"], calls)
500
self.assertEqual([], client._calls)
502
def test_url_quoting_of_path(self):
503
# Relpaths on the wire should not be URL-escaped. So "~" should be
504
# transmitted as "~", not "%7E".
505
transport = RemoteTCPTransport('bzr://localhost/~hello/')
506
client = FakeClient(transport.base)
507
reference_format = self.get_repo_format()
508
network_name = reference_format.network_name()
509
branch_network_name = self.get_branch_format().network_name()
510
client.add_expected_call(
511
'BzrDir.open_branchV2', ('~hello/',),
512
'success', ('branch', branch_network_name))
513
client.add_expected_call(
514
'BzrDir.find_repositoryV3', ('~hello/',),
515
'success', ('ok', '', 'no', 'no', 'no', network_name))
516
client.add_expected_call(
517
'Branch.get_stacked_on_url', ('~hello/',),
518
'error', ('NotStacked',))
519
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
521
result = bzrdir.open_branch()
522
client.finished_test()
524
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
525
reference_format = self.get_repo_format()
526
network_name = reference_format.network_name()
527
transport = MemoryTransport()
528
transport.mkdir('quack')
529
transport = transport.clone('quack')
531
rich_response = 'yes'
535
subtree_response = 'yes'
537
subtree_response = 'no'
538
client = FakeClient(transport.base)
539
client.add_success_response(
540
'ok', '', rich_response, subtree_response, external_lookup,
542
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
544
result = bzrdir.open_repository()
546
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
548
self.assertIsInstance(result, RemoteRepository)
549
self.assertEqual(bzrdir, result.bzrdir)
550
self.assertEqual(rich_root, result._format.rich_root_data)
551
self.assertEqual(subtrees, result._format.supports_tree_reference)
553
def test_open_repository_sets_format_attributes(self):
554
self.check_open_repository(True, True)
555
self.check_open_repository(False, True)
556
self.check_open_repository(True, False)
557
self.check_open_repository(False, False)
558
self.check_open_repository(False, False, 'yes')
560
def test_old_server(self):
561
"""RemoteBzrDirFormat should fail to probe if the server version is too
564
self.assertRaises(errors.NotBranchError,
565
RemoteBzrDirFormat.probe_transport, OldServerTransport())
568
class TestBzrDirCreateBranch(TestRemote):
570
def test_backwards_compat(self):
571
self.setup_smart_server_with_call_log()
572
repo = self.make_repository('.')
573
self.reset_smart_call_log()
574
self.disable_verb('BzrDir.create_branch')
575
branch = repo.bzrdir.create_branch()
576
create_branch_call_count = len([call for call in self.hpss_calls if
577
call.call.method == 'BzrDir.create_branch'])
578
self.assertEqual(1, create_branch_call_count)
580
def test_current_server(self):
581
transport = self.get_transport('.')
582
transport = transport.clone('quack')
583
self.make_repository('quack')
584
client = FakeClient(transport.base)
585
reference_bzrdir_format = bzrdir.format_registry.get('default')()
586
reference_format = reference_bzrdir_format.get_branch_format()
587
network_name = reference_format.network_name()
588
reference_repo_fmt = reference_bzrdir_format.repository_format
589
reference_repo_name = reference_repo_fmt.network_name()
590
client.add_expected_call(
591
'BzrDir.create_branch', ('quack/', network_name),
592
'success', ('ok', network_name, '', 'no', 'no', 'yes',
593
reference_repo_name))
594
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
596
branch = a_bzrdir.create_branch()
597
# We should have got a remote branch
598
self.assertIsInstance(branch, remote.RemoteBranch)
599
# its format should have the settings from the response
600
format = branch._format
601
self.assertEqual(network_name, format.network_name())
604
class TestBzrDirCreateRepository(TestRemote):
606
def test_backwards_compat(self):
607
self.setup_smart_server_with_call_log()
608
bzrdir = self.make_bzrdir('.')
609
self.reset_smart_call_log()
610
self.disable_verb('BzrDir.create_repository')
611
repo = bzrdir.create_repository()
612
create_repo_call_count = len([call for call in self.hpss_calls if
613
call.call.method == 'BzrDir.create_repository'])
614
self.assertEqual(1, create_repo_call_count)
616
def test_current_server(self):
617
transport = self.get_transport('.')
618
transport = transport.clone('quack')
619
self.make_bzrdir('quack')
620
client = FakeClient(transport.base)
621
reference_bzrdir_format = bzrdir.format_registry.get('default')()
622
reference_format = reference_bzrdir_format.repository_format
623
network_name = reference_format.network_name()
624
client.add_expected_call(
625
'BzrDir.create_repository', ('quack/',
626
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
627
'success', ('ok', 'no', 'no', 'no', network_name))
628
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
630
repo = a_bzrdir.create_repository()
631
# We should have got a remote repository
632
self.assertIsInstance(repo, remote.RemoteRepository)
633
# its format should have the settings from the response
634
format = repo._format
635
self.assertFalse(format.rich_root_data)
636
self.assertFalse(format.supports_tree_reference)
637
self.assertFalse(format.supports_external_lookups)
638
self.assertEqual(network_name, format.network_name())
641
class TestBzrDirOpenRepository(TestRemote):
643
def test_backwards_compat_1_2_3(self):
644
# fallback all the way to the first version.
645
reference_format = self.get_repo_format()
646
network_name = reference_format.network_name()
647
client = FakeClient('bzr://example.com/')
648
client.add_unknown_method_response('BzrDir.find_repositoryV3')
649
client.add_unknown_method_response('BzrDir.find_repositoryV2')
650
client.add_success_response('ok', '', 'no', 'no')
651
# A real repository instance will be created to determine the network
653
client.add_success_response_with_body(
654
"Bazaar-NG meta directory, format 1\n", 'ok')
655
client.add_success_response_with_body(
656
reference_format.get_format_string(), 'ok')
657
# PackRepository wants to do a stat
658
client.add_success_response('stat', '0', '65535')
659
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
661
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
663
repo = bzrdir.open_repository()
665
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
666
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
667
('call', 'BzrDir.find_repository', ('quack/',)),
668
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
669
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
670
('call', 'stat', ('/quack/.bzr/repository',)),
673
self.assertEqual(network_name, repo._format.network_name())
675
def test_backwards_compat_2(self):
676
# fallback to find_repositoryV2
677
reference_format = self.get_repo_format()
678
network_name = reference_format.network_name()
679
client = FakeClient('bzr://example.com/')
680
client.add_unknown_method_response('BzrDir.find_repositoryV3')
681
client.add_success_response('ok', '', 'no', 'no', 'no')
682
# A real repository instance will be created to determine the network
684
client.add_success_response_with_body(
685
"Bazaar-NG meta directory, format 1\n", 'ok')
686
client.add_success_response_with_body(
687
reference_format.get_format_string(), 'ok')
688
# PackRepository wants to do a stat
689
client.add_success_response('stat', '0', '65535')
690
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
692
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
694
repo = bzrdir.open_repository()
696
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
697
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
698
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
699
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
700
('call', 'stat', ('/quack/.bzr/repository',)),
703
self.assertEqual(network_name, repo._format.network_name())
705
def test_current_server(self):
706
reference_format = self.get_repo_format()
707
network_name = reference_format.network_name()
708
transport = MemoryTransport()
709
transport.mkdir('quack')
710
transport = transport.clone('quack')
711
client = FakeClient(transport.base)
712
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
713
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
715
repo = bzrdir.open_repository()
717
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
719
self.assertEqual(network_name, repo._format.network_name())
722
class OldSmartClient(object):
723
"""A fake smart client for test_old_version that just returns a version one
724
response to the 'hello' (query version) command.
727
def get_request(self):
728
input_file = StringIO('ok\x011\n')
729
output_file = StringIO()
730
client_medium = medium.SmartSimplePipesClientMedium(
731
input_file, output_file)
732
return medium.SmartClientStreamMediumRequest(client_medium)
734
def protocol_version(self):
738
class OldServerTransport(object):
739
"""A fake transport for test_old_server that reports it's smart server
740
protocol version as version one.
746
def get_smart_client(self):
747
return OldSmartClient()
750
class RemoteBranchTestCase(TestRemote):
752
def make_remote_branch(self, transport, client):
753
"""Make a RemoteBranch using 'client' as its _SmartClient.
755
A RemoteBzrDir and RemoteRepository will also be created to fill out
756
the RemoteBranch, albeit with stub values for some of their attributes.
758
# we do not want bzrdir to make any remote calls, so use False as its
759
# _client. If it tries to make a remote call, this will fail
761
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
763
repo = RemoteRepository(bzrdir, None, _client=client)
764
branch_format = self.get_branch_format()
765
format = RemoteBranchFormat(network_name=branch_format.network_name())
766
return RemoteBranch(bzrdir, repo, _client=client, format=format)
769
class TestBranchGetParent(RemoteBranchTestCase):
771
def test_no_parent(self):
772
# in an empty branch we decode the response properly
773
transport = MemoryTransport()
774
client = FakeClient(transport.base)
775
client.add_expected_call(
776
'Branch.get_stacked_on_url', ('quack/',),
777
'error', ('NotStacked',))
778
client.add_expected_call(
779
'Branch.get_parent', ('quack/',),
781
transport.mkdir('quack')
782
transport = transport.clone('quack')
783
branch = self.make_remote_branch(transport, client)
784
result = branch.get_parent()
785
client.finished_test()
786
self.assertEqual(None, result)
788
def test_parent_relative(self):
789
transport = MemoryTransport()
790
client = FakeClient(transport.base)
791
client.add_expected_call(
792
'Branch.get_stacked_on_url', ('kwaak/',),
793
'error', ('NotStacked',))
794
client.add_expected_call(
795
'Branch.get_parent', ('kwaak/',),
796
'success', ('../foo/',))
797
transport.mkdir('kwaak')
798
transport = transport.clone('kwaak')
799
branch = self.make_remote_branch(transport, client)
800
result = branch.get_parent()
801
self.assertEqual(transport.clone('../foo').base, result)
803
def test_parent_absolute(self):
804
transport = MemoryTransport()
805
client = FakeClient(transport.base)
806
client.add_expected_call(
807
'Branch.get_stacked_on_url', ('kwaak/',),
808
'error', ('NotStacked',))
809
client.add_expected_call(
810
'Branch.get_parent', ('kwaak/',),
811
'success', ('http://foo/',))
812
transport.mkdir('kwaak')
813
transport = transport.clone('kwaak')
814
branch = self.make_remote_branch(transport, client)
815
result = branch.get_parent()
816
self.assertEqual('http://foo/', result)
819
class TestBranchGetTagsBytes(RemoteBranchTestCase):
821
def test_backwards_compat(self):
822
self.setup_smart_server_with_call_log()
823
branch = self.make_branch('.')
824
self.reset_smart_call_log()
825
verb = 'Branch.get_tags_bytes'
826
self.disable_verb(verb)
827
branch.tags.get_tag_dict()
828
call_count = len([call for call in self.hpss_calls if
829
call.call.method == verb])
830
self.assertEqual(1, call_count)
832
def test_trivial(self):
833
transport = MemoryTransport()
834
client = FakeClient(transport.base)
835
client.add_expected_call(
836
'Branch.get_stacked_on_url', ('quack/',),
837
'error', ('NotStacked',))
838
client.add_expected_call(
839
'Branch.get_tags_bytes', ('quack/',),
841
transport.mkdir('quack')
842
transport = transport.clone('quack')
843
branch = self.make_remote_branch(transport, client)
844
result = branch.tags.get_tag_dict()
845
client.finished_test()
846
self.assertEqual({}, result)
849
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
851
def test_empty_branch(self):
852
# in an empty branch we decode the response properly
853
transport = MemoryTransport()
854
client = FakeClient(transport.base)
855
client.add_expected_call(
856
'Branch.get_stacked_on_url', ('quack/',),
857
'error', ('NotStacked',))
858
client.add_expected_call(
859
'Branch.last_revision_info', ('quack/',),
860
'success', ('ok', '0', 'null:'))
861
transport.mkdir('quack')
862
transport = transport.clone('quack')
863
branch = self.make_remote_branch(transport, client)
864
result = branch.last_revision_info()
865
client.finished_test()
866
self.assertEqual((0, NULL_REVISION), result)
868
def test_non_empty_branch(self):
869
# in a non-empty branch we also decode the response properly
870
revid = u'\xc8'.encode('utf8')
871
transport = MemoryTransport()
872
client = FakeClient(transport.base)
873
client.add_expected_call(
874
'Branch.get_stacked_on_url', ('kwaak/',),
875
'error', ('NotStacked',))
876
client.add_expected_call(
877
'Branch.last_revision_info', ('kwaak/',),
878
'success', ('ok', '2', revid))
879
transport.mkdir('kwaak')
880
transport = transport.clone('kwaak')
881
branch = self.make_remote_branch(transport, client)
882
result = branch.last_revision_info()
883
self.assertEqual((2, revid), result)
886
class TestBranch_get_stacked_on_url(TestRemote):
887
"""Test Branch._get_stacked_on_url rpc"""
889
def test_get_stacked_on_invalid_url(self):
890
# test that asking for a stacked on url the server can't access works.
891
# This isn't perfect, but then as we're in the same process there
892
# really isn't anything we can do to be 100% sure that the server
893
# doesn't just open in - this test probably needs to be rewritten using
894
# a spawn()ed server.
895
stacked_branch = self.make_branch('stacked', format='1.9')
896
memory_branch = self.make_branch('base', format='1.9')
897
vfs_url = self.get_vfs_only_url('base')
898
stacked_branch.set_stacked_on_url(vfs_url)
899
transport = stacked_branch.bzrdir.root_transport
900
client = FakeClient(transport.base)
901
client.add_expected_call(
902
'Branch.get_stacked_on_url', ('stacked/',),
903
'success', ('ok', vfs_url))
904
# XXX: Multiple calls are bad, this second call documents what is
906
client.add_expected_call(
907
'Branch.get_stacked_on_url', ('stacked/',),
908
'success', ('ok', vfs_url))
909
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
911
repo_fmt = remote.RemoteRepositoryFormat()
912
repo_fmt._custom_format = stacked_branch.repository._format
913
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
915
result = branch.get_stacked_on_url()
916
self.assertEqual(vfs_url, result)
918
def test_backwards_compatible(self):
919
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
920
base_branch = self.make_branch('base', format='1.6')
921
stacked_branch = self.make_branch('stacked', format='1.6')
922
stacked_branch.set_stacked_on_url('../base')
923
client = FakeClient(self.get_url())
924
branch_network_name = self.get_branch_format().network_name()
925
client.add_expected_call(
926
'BzrDir.open_branchV2', ('stacked/',),
927
'success', ('branch', branch_network_name))
928
client.add_expected_call(
929
'BzrDir.find_repositoryV3', ('stacked/',),
930
'success', ('ok', '', 'no', 'no', 'yes',
931
stacked_branch.repository._format.network_name()))
932
# called twice, once from constructor and then again by us
933
client.add_expected_call(
934
'Branch.get_stacked_on_url', ('stacked/',),
935
'unknown', ('Branch.get_stacked_on_url',))
936
client.add_expected_call(
937
'Branch.get_stacked_on_url', ('stacked/',),
938
'unknown', ('Branch.get_stacked_on_url',))
939
# this will also do vfs access, but that goes direct to the transport
940
# and isn't seen by the FakeClient.
941
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
942
remote.RemoteBzrDirFormat(), _client=client)
943
branch = bzrdir.open_branch()
944
result = branch.get_stacked_on_url()
945
self.assertEqual('../base', result)
946
client.finished_test()
947
# it's in the fallback list both for the RemoteRepository and its vfs
949
self.assertEqual(1, len(branch.repository._fallback_repositories))
951
len(branch.repository._real_repository._fallback_repositories))
953
def test_get_stacked_on_real_branch(self):
954
base_branch = self.make_branch('base', format='1.6')
955
stacked_branch = self.make_branch('stacked', format='1.6')
956
stacked_branch.set_stacked_on_url('../base')
957
reference_format = self.get_repo_format()
958
network_name = reference_format.network_name()
959
client = FakeClient(self.get_url())
960
branch_network_name = self.get_branch_format().network_name()
961
client.add_expected_call(
962
'BzrDir.open_branchV2', ('stacked/',),
963
'success', ('branch', branch_network_name))
964
client.add_expected_call(
965
'BzrDir.find_repositoryV3', ('stacked/',),
966
'success', ('ok', '', 'no', 'no', 'yes', network_name))
967
# called twice, once from constructor and then again by us
968
client.add_expected_call(
969
'Branch.get_stacked_on_url', ('stacked/',),
970
'success', ('ok', '../base'))
971
client.add_expected_call(
972
'Branch.get_stacked_on_url', ('stacked/',),
973
'success', ('ok', '../base'))
974
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
975
remote.RemoteBzrDirFormat(), _client=client)
976
branch = bzrdir.open_branch()
977
result = branch.get_stacked_on_url()
978
self.assertEqual('../base', result)
979
client.finished_test()
980
# it's in the fallback list both for the RemoteRepository and its vfs
982
self.assertEqual(1, len(branch.repository._fallback_repositories))
984
len(branch.repository._real_repository._fallback_repositories))
987
class TestBranchSetLastRevision(RemoteBranchTestCase):
989
def test_set_empty(self):
990
# set_revision_history([]) is translated to calling
991
# Branch.set_last_revision(path, '') on the wire.
992
transport = MemoryTransport()
993
transport.mkdir('branch')
994
transport = transport.clone('branch')
996
client = FakeClient(transport.base)
997
client.add_expected_call(
998
'Branch.get_stacked_on_url', ('branch/',),
999
'error', ('NotStacked',))
1000
client.add_expected_call(
1001
'Branch.lock_write', ('branch/', '', ''),
1002
'success', ('ok', 'branch token', 'repo token'))
1003
client.add_expected_call(
1004
'Branch.last_revision_info',
1006
'success', ('ok', '0', 'null:'))
1007
client.add_expected_call(
1008
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1010
client.add_expected_call(
1011
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1013
branch = self.make_remote_branch(transport, client)
1014
# This is a hack to work around the problem that RemoteBranch currently
1015
# unnecessarily invokes _ensure_real upon a call to lock_write.
1016
branch._ensure_real = lambda: None
1018
result = branch.set_revision_history([])
1020
self.assertEqual(None, result)
1021
client.finished_test()
1023
def test_set_nonempty(self):
1024
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1025
# Branch.set_last_revision(path, rev-idN) on the wire.
1026
transport = MemoryTransport()
1027
transport.mkdir('branch')
1028
transport = transport.clone('branch')
1030
client = FakeClient(transport.base)
1031
client.add_expected_call(
1032
'Branch.get_stacked_on_url', ('branch/',),
1033
'error', ('NotStacked',))
1034
client.add_expected_call(
1035
'Branch.lock_write', ('branch/', '', ''),
1036
'success', ('ok', 'branch token', 'repo token'))
1037
client.add_expected_call(
1038
'Branch.last_revision_info',
1040
'success', ('ok', '0', 'null:'))
1042
encoded_body = bz2.compress('\n'.join(lines))
1043
client.add_success_response_with_body(encoded_body, 'ok')
1044
client.add_expected_call(
1045
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1047
client.add_expected_call(
1048
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1050
branch = self.make_remote_branch(transport, client)
1051
# This is a hack to work around the problem that RemoteBranch currently
1052
# unnecessarily invokes _ensure_real upon a call to lock_write.
1053
branch._ensure_real = lambda: None
1054
# Lock the branch, reset the record of remote calls.
1056
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1058
self.assertEqual(None, result)
1059
client.finished_test()
1061
def test_no_such_revision(self):
1062
transport = MemoryTransport()
1063
transport.mkdir('branch')
1064
transport = transport.clone('branch')
1065
# A response of 'NoSuchRevision' is translated into an exception.
1066
client = FakeClient(transport.base)
1067
client.add_expected_call(
1068
'Branch.get_stacked_on_url', ('branch/',),
1069
'error', ('NotStacked',))
1070
client.add_expected_call(
1071
'Branch.lock_write', ('branch/', '', ''),
1072
'success', ('ok', 'branch token', 'repo token'))
1073
client.add_expected_call(
1074
'Branch.last_revision_info',
1076
'success', ('ok', '0', 'null:'))
1077
# get_graph calls to construct the revision history, for the set_rh
1080
encoded_body = bz2.compress('\n'.join(lines))
1081
client.add_success_response_with_body(encoded_body, 'ok')
1082
client.add_expected_call(
1083
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1084
'error', ('NoSuchRevision', 'rev-id'))
1085
client.add_expected_call(
1086
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1089
branch = self.make_remote_branch(transport, client)
1092
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1094
client.finished_test()
1096
def test_tip_change_rejected(self):
1097
"""TipChangeRejected responses cause a TipChangeRejected exception to
1100
transport = MemoryTransport()
1101
transport.mkdir('branch')
1102
transport = transport.clone('branch')
1103
client = FakeClient(transport.base)
1104
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1105
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1106
client.add_expected_call(
1107
'Branch.get_stacked_on_url', ('branch/',),
1108
'error', ('NotStacked',))
1109
client.add_expected_call(
1110
'Branch.lock_write', ('branch/', '', ''),
1111
'success', ('ok', 'branch token', 'repo token'))
1112
client.add_expected_call(
1113
'Branch.last_revision_info',
1115
'success', ('ok', '0', 'null:'))
1117
encoded_body = bz2.compress('\n'.join(lines))
1118
client.add_success_response_with_body(encoded_body, 'ok')
1119
client.add_expected_call(
1120
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1121
'error', ('TipChangeRejected', rejection_msg_utf8))
1122
client.add_expected_call(
1123
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1125
branch = self.make_remote_branch(transport, client)
1126
branch._ensure_real = lambda: None
1128
# The 'TipChangeRejected' error response triggered by calling
1129
# set_revision_history causes a TipChangeRejected exception.
1130
err = self.assertRaises(
1131
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1132
# The UTF-8 message from the response has been decoded into a unicode
1134
self.assertIsInstance(err.msg, unicode)
1135
self.assertEqual(rejection_msg_unicode, err.msg)
1137
client.finished_test()
1140
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1142
def test_set_last_revision_info(self):
1143
# set_last_revision_info(num, 'rev-id') is translated to calling
1144
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1145
transport = MemoryTransport()
1146
transport.mkdir('branch')
1147
transport = transport.clone('branch')
1148
client = FakeClient(transport.base)
1149
# get_stacked_on_url
1150
client.add_error_response('NotStacked')
1152
client.add_success_response('ok', 'branch token', 'repo token')
1153
# query the current revision
1154
client.add_success_response('ok', '0', 'null:')
1156
client.add_success_response('ok')
1158
client.add_success_response('ok')
1160
branch = self.make_remote_branch(transport, client)
1161
# Lock the branch, reset the record of remote calls.
1164
result = branch.set_last_revision_info(1234, 'a-revision-id')
1166
[('call', 'Branch.last_revision_info', ('branch/',)),
1167
('call', 'Branch.set_last_revision_info',
1168
('branch/', 'branch token', 'repo token',
1169
'1234', 'a-revision-id'))],
1171
self.assertEqual(None, result)
1173
def test_no_such_revision(self):
1174
# A response of 'NoSuchRevision' is translated into an exception.
1175
transport = MemoryTransport()
1176
transport.mkdir('branch')
1177
transport = transport.clone('branch')
1178
client = FakeClient(transport.base)
1179
# get_stacked_on_url
1180
client.add_error_response('NotStacked')
1182
client.add_success_response('ok', 'branch token', 'repo token')
1184
client.add_error_response('NoSuchRevision', 'revid')
1186
client.add_success_response('ok')
1188
branch = self.make_remote_branch(transport, client)
1189
# Lock the branch, reset the record of remote calls.
1194
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1197
def lock_remote_branch(self, branch):
1198
"""Trick a RemoteBranch into thinking it is locked."""
1199
branch._lock_mode = 'w'
1200
branch._lock_count = 2
1201
branch._lock_token = 'branch token'
1202
branch._repo_lock_token = 'repo token'
1203
branch.repository._lock_mode = 'w'
1204
branch.repository._lock_count = 2
1205
branch.repository._lock_token = 'repo token'
1207
def test_backwards_compatibility(self):
1208
"""If the server does not support the Branch.set_last_revision_info
1209
verb (which is new in 1.4), then the client falls back to VFS methods.
1211
# This test is a little messy. Unlike most tests in this file, it
1212
# doesn't purely test what a Remote* object sends over the wire, and
1213
# how it reacts to responses from the wire. It instead relies partly
1214
# on asserting that the RemoteBranch will call
1215
# self._real_branch.set_last_revision_info(...).
1217
# First, set up our RemoteBranch with a FakeClient that raises
1218
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1219
transport = MemoryTransport()
1220
transport.mkdir('branch')
1221
transport = transport.clone('branch')
1222
client = FakeClient(transport.base)
1223
client.add_expected_call(
1224
'Branch.get_stacked_on_url', ('branch/',),
1225
'error', ('NotStacked',))
1226
client.add_expected_call(
1227
'Branch.last_revision_info',
1229
'success', ('ok', '0', 'null:'))
1230
client.add_expected_call(
1231
'Branch.set_last_revision_info',
1232
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1233
'unknown', 'Branch.set_last_revision_info')
1235
branch = self.make_remote_branch(transport, client)
1236
class StubRealBranch(object):
1239
def set_last_revision_info(self, revno, revision_id):
1241
('set_last_revision_info', revno, revision_id))
1242
def _clear_cached_state(self):
1244
real_branch = StubRealBranch()
1245
branch._real_branch = real_branch
1246
self.lock_remote_branch(branch)
1248
# Call set_last_revision_info, and verify it behaved as expected.
1249
result = branch.set_last_revision_info(1234, 'a-revision-id')
1251
[('set_last_revision_info', 1234, 'a-revision-id')],
1253
client.finished_test()
1255
def test_unexpected_error(self):
1256
# If the server sends an error the client doesn't understand, it gets
1257
# turned into an UnknownErrorFromSmartServer, which is presented as a
1258
# non-internal error to the user.
1259
transport = MemoryTransport()
1260
transport.mkdir('branch')
1261
transport = transport.clone('branch')
1262
client = FakeClient(transport.base)
1263
# get_stacked_on_url
1264
client.add_error_response('NotStacked')
1266
client.add_success_response('ok', 'branch token', 'repo token')
1268
client.add_error_response('UnexpectedError')
1270
client.add_success_response('ok')
1272
branch = self.make_remote_branch(transport, client)
1273
# Lock the branch, reset the record of remote calls.
1277
err = self.assertRaises(
1278
errors.UnknownErrorFromSmartServer,
1279
branch.set_last_revision_info, 123, 'revid')
1280
self.assertEqual(('UnexpectedError',), err.error_tuple)
1283
def test_tip_change_rejected(self):
1284
"""TipChangeRejected responses cause a TipChangeRejected exception to
1287
transport = MemoryTransport()
1288
transport.mkdir('branch')
1289
transport = transport.clone('branch')
1290
client = FakeClient(transport.base)
1291
# get_stacked_on_url
1292
client.add_error_response('NotStacked')
1294
client.add_success_response('ok', 'branch token', 'repo token')
1296
client.add_error_response('TipChangeRejected', 'rejection message')
1298
client.add_success_response('ok')
1300
branch = self.make_remote_branch(transport, client)
1301
# Lock the branch, reset the record of remote calls.
1303
self.addCleanup(branch.unlock)
1306
# The 'TipChangeRejected' error response triggered by calling
1307
# set_last_revision_info causes a TipChangeRejected exception.
1308
err = self.assertRaises(
1309
errors.TipChangeRejected,
1310
branch.set_last_revision_info, 123, 'revid')
1311
self.assertEqual('rejection message', err.msg)
1314
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1315
"""Getting the branch configuration should use an abstract method not vfs.
1318
def test_get_branch_conf(self):
1319
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1320
## # We should see that branch.get_config() does a single rpc to get the
1321
## # remote configuration file, abstracting away where that is stored on
1322
## # the server. However at the moment it always falls back to using the
1323
## # vfs, and this would need some changes in config.py.
1325
## # in an empty branch we decode the response properly
1326
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1327
## # we need to make a real branch because the remote_branch.control_files
1328
## # will trigger _ensure_real.
1329
## branch = self.make_branch('quack')
1330
## transport = branch.bzrdir.root_transport
1331
## # we do not want bzrdir to make any remote calls
1332
## bzrdir = RemoteBzrDir(transport, _client=False)
1333
## branch = RemoteBranch(bzrdir, None, _client=client)
1334
## config = branch.get_config()
1335
## self.assertEqual(
1336
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1340
class TestBranchLockWrite(RemoteBranchTestCase):
1342
def test_lock_write_unlockable(self):
1343
transport = MemoryTransport()
1344
client = FakeClient(transport.base)
1345
client.add_expected_call(
1346
'Branch.get_stacked_on_url', ('quack/',),
1347
'error', ('NotStacked',),)
1348
client.add_expected_call(
1349
'Branch.lock_write', ('quack/', '', ''),
1350
'error', ('UnlockableTransport',))
1351
transport.mkdir('quack')
1352
transport = transport.clone('quack')
1353
branch = self.make_remote_branch(transport, client)
1354
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1355
client.finished_test()
1358
class TestTransportIsReadonly(tests.TestCase):
1360
def test_true(self):
1361
client = FakeClient()
1362
client.add_success_response('yes')
1363
transport = RemoteTransport('bzr://example.com/', medium=False,
1365
self.assertEqual(True, transport.is_readonly())
1367
[('call', 'Transport.is_readonly', ())],
1370
def test_false(self):
1371
client = FakeClient()
1372
client.add_success_response('no')
1373
transport = RemoteTransport('bzr://example.com/', medium=False,
1375
self.assertEqual(False, transport.is_readonly())
1377
[('call', 'Transport.is_readonly', ())],
1380
def test_error_from_old_server(self):
1381
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1383
Clients should treat it as a "no" response, because is_readonly is only
1384
advisory anyway (a transport could be read-write, but then the
1385
underlying filesystem could be readonly anyway).
1387
client = FakeClient()
1388
client.add_unknown_method_response('Transport.is_readonly')
1389
transport = RemoteTransport('bzr://example.com/', medium=False,
1391
self.assertEqual(False, transport.is_readonly())
1393
[('call', 'Transport.is_readonly', ())],
1397
class TestTransportMkdir(tests.TestCase):
1399
def test_permissiondenied(self):
1400
client = FakeClient()
1401
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1402
transport = RemoteTransport('bzr://example.com/', medium=False,
1404
exc = self.assertRaises(
1405
errors.PermissionDenied, transport.mkdir, 'client path')
1406
expected_error = errors.PermissionDenied('/client path', 'extra')
1407
self.assertEqual(expected_error, exc)
1410
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1412
def test_defaults_to_none(self):
1413
t = RemoteSSHTransport('bzr+ssh://example.com')
1414
self.assertIs(None, t._get_credentials()[0])
1416
def test_uses_authentication_config(self):
1417
conf = config.AuthenticationConfig()
1418
conf._get_config().update(
1419
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1422
t = RemoteSSHTransport('bzr+ssh://example.com')
1423
self.assertEqual('bar', t._get_credentials()[0])
1426
class TestRemoteRepository(TestRemote):
1427
"""Base for testing RemoteRepository protocol usage.
1429
These tests contain frozen requests and responses. We want any changes to
1430
what is sent or expected to be require a thoughtful update to these tests
1431
because they might break compatibility with different-versioned servers.
1434
def setup_fake_client_and_repository(self, transport_path):
1435
"""Create the fake client and repository for testing with.
1437
There's no real server here; we just have canned responses sent
1440
:param transport_path: Path below the root of the MemoryTransport
1441
where the repository will be created.
1443
transport = MemoryTransport()
1444
transport.mkdir(transport_path)
1445
client = FakeClient(transport.base)
1446
transport = transport.clone(transport_path)
1447
# we do not want bzrdir to make any remote calls
1448
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1450
repo = RemoteRepository(bzrdir, None, _client=client)
1454
class TestRepositoryFormat(TestRemoteRepository):
1456
def test_fast_delta(self):
1457
true_name = pack_repo.RepositoryFormatPackDevelopment2().network_name()
1458
true_format = RemoteRepositoryFormat()
1459
true_format._network_name = true_name
1460
self.assertEqual(True, true_format.fast_deltas)
1461
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1462
false_format = RemoteRepositoryFormat()
1463
false_format._network_name = false_name
1464
self.assertEqual(False, false_format.fast_deltas)
1467
class TestRepositoryGatherStats(TestRemoteRepository):
1469
def test_revid_none(self):
1470
# ('ok',), body with revisions and size
1471
transport_path = 'quack'
1472
repo, client = self.setup_fake_client_and_repository(transport_path)
1473
client.add_success_response_with_body(
1474
'revisions: 2\nsize: 18\n', 'ok')
1475
result = repo.gather_stats(None)
1477
[('call_expecting_body', 'Repository.gather_stats',
1478
('quack/','','no'))],
1480
self.assertEqual({'revisions': 2, 'size': 18}, result)
1482
def test_revid_no_committers(self):
1483
# ('ok',), body without committers
1484
body = ('firstrev: 123456.300 3600\n'
1485
'latestrev: 654231.400 0\n'
1488
transport_path = 'quick'
1489
revid = u'\xc8'.encode('utf8')
1490
repo, client = self.setup_fake_client_and_repository(transport_path)
1491
client.add_success_response_with_body(body, 'ok')
1492
result = repo.gather_stats(revid)
1494
[('call_expecting_body', 'Repository.gather_stats',
1495
('quick/', revid, 'no'))],
1497
self.assertEqual({'revisions': 2, 'size': 18,
1498
'firstrev': (123456.300, 3600),
1499
'latestrev': (654231.400, 0),},
1502
def test_revid_with_committers(self):
1503
# ('ok',), body with committers
1504
body = ('committers: 128\n'
1505
'firstrev: 123456.300 3600\n'
1506
'latestrev: 654231.400 0\n'
1509
transport_path = 'buick'
1510
revid = u'\xc8'.encode('utf8')
1511
repo, client = self.setup_fake_client_and_repository(transport_path)
1512
client.add_success_response_with_body(body, 'ok')
1513
result = repo.gather_stats(revid, True)
1515
[('call_expecting_body', 'Repository.gather_stats',
1516
('buick/', revid, 'yes'))],
1518
self.assertEqual({'revisions': 2, 'size': 18,
1520
'firstrev': (123456.300, 3600),
1521
'latestrev': (654231.400, 0),},
1525
class TestRepositoryGetGraph(TestRemoteRepository):
1527
def test_get_graph(self):
1528
# get_graph returns a graph with a custom parents provider.
1529
transport_path = 'quack'
1530
repo, client = self.setup_fake_client_and_repository(transport_path)
1531
graph = repo.get_graph()
1532
self.assertNotEqual(graph._parents_provider, repo)
1535
class TestRepositoryGetParentMap(TestRemoteRepository):
1537
def test_get_parent_map_caching(self):
1538
# get_parent_map returns from cache until unlock()
1539
# setup a reponse with two revisions
1540
r1 = u'\u0e33'.encode('utf8')
1541
r2 = u'\u0dab'.encode('utf8')
1542
lines = [' '.join([r2, r1]), r1]
1543
encoded_body = bz2.compress('\n'.join(lines))
1545
transport_path = 'quack'
1546
repo, client = self.setup_fake_client_and_repository(transport_path)
1547
client.add_success_response_with_body(encoded_body, 'ok')
1548
client.add_success_response_with_body(encoded_body, 'ok')
1550
graph = repo.get_graph()
1551
parents = graph.get_parent_map([r2])
1552
self.assertEqual({r2: (r1,)}, parents)
1553
# locking and unlocking deeper should not reset
1556
parents = graph.get_parent_map([r1])
1557
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1559
[('call_with_body_bytes_expecting_body',
1560
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
1563
# now we call again, and it should use the second response.
1565
graph = repo.get_graph()
1566
parents = graph.get_parent_map([r1])
1567
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1569
[('call_with_body_bytes_expecting_body',
1570
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
1571
('call_with_body_bytes_expecting_body',
1572
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
1577
def test_get_parent_map_reconnects_if_unknown_method(self):
1578
transport_path = 'quack'
1579
rev_id = 'revision-id'
1580
repo, client = self.setup_fake_client_and_repository(transport_path)
1581
client.add_unknown_method_response('Repository.get_parent_map')
1582
client.add_success_response_with_body(rev_id, 'ok')
1583
self.assertFalse(client._medium._is_remote_before((1, 2)))
1584
parents = repo.get_parent_map([rev_id])
1586
[('call_with_body_bytes_expecting_body',
1587
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
1588
('disconnect medium',),
1589
('call_expecting_body', 'Repository.get_revision_graph',
1592
# The medium is now marked as being connected to an older server
1593
self.assertTrue(client._medium._is_remote_before((1, 2)))
1594
self.assertEqual({rev_id: ('null:',)}, parents)
1596
def test_get_parent_map_fallback_parentless_node(self):
1597
"""get_parent_map falls back to get_revision_graph on old servers. The
1598
results from get_revision_graph are tweaked to match the get_parent_map
1601
Specifically, a {key: ()} result from get_revision_graph means "no
1602
parents" for that key, which in get_parent_map results should be
1603
represented as {key: ('null:',)}.
1605
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1607
rev_id = 'revision-id'
1608
transport_path = 'quack'
1609
repo, client = self.setup_fake_client_and_repository(transport_path)
1610
client.add_success_response_with_body(rev_id, 'ok')
1611
client._medium._remember_remote_is_before((1, 2))
1612
parents = repo.get_parent_map([rev_id])
1614
[('call_expecting_body', 'Repository.get_revision_graph',
1617
self.assertEqual({rev_id: ('null:',)}, parents)
1619
def test_get_parent_map_unexpected_response(self):
1620
repo, client = self.setup_fake_client_and_repository('path')
1621
client.add_success_response('something unexpected!')
1623
errors.UnexpectedSmartServerResponse,
1624
repo.get_parent_map, ['a-revision-id'])
1627
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1629
def test_allows_new_revisions(self):
1630
"""get_parent_map's results can be updated by commit."""
1631
smart_server = server.SmartTCPServer_for_testing()
1632
smart_server.setUp()
1633
self.addCleanup(smart_server.tearDown)
1634
self.make_branch('branch')
1635
branch = Branch.open(smart_server.get_url() + '/branch')
1636
tree = branch.create_checkout('tree', lightweight=True)
1638
self.addCleanup(tree.unlock)
1639
graph = tree.branch.repository.get_graph()
1640
# This provides an opportunity for the missing rev-id to be cached.
1641
self.assertEqual({}, graph.get_parent_map(['rev1']))
1642
tree.commit('message', rev_id='rev1')
1643
graph = tree.branch.repository.get_graph()
1644
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1647
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1649
def test_null_revision(self):
1650
# a null revision has the predictable result {}, we should have no wire
1651
# traffic when calling it with this argument
1652
transport_path = 'empty'
1653
repo, client = self.setup_fake_client_and_repository(transport_path)
1654
client.add_success_response('notused')
1655
# actual RemoteRepository.get_revision_graph is gone, but there's an
1656
# equivalent private method for testing
1657
result = repo._get_revision_graph(NULL_REVISION)
1658
self.assertEqual([], client._calls)
1659
self.assertEqual({}, result)
1661
def test_none_revision(self):
1662
# with none we want the entire graph
1663
r1 = u'\u0e33'.encode('utf8')
1664
r2 = u'\u0dab'.encode('utf8')
1665
lines = [' '.join([r2, r1]), r1]
1666
encoded_body = '\n'.join(lines)
1668
transport_path = 'sinhala'
1669
repo, client = self.setup_fake_client_and_repository(transport_path)
1670
client.add_success_response_with_body(encoded_body, 'ok')
1671
# actual RemoteRepository.get_revision_graph is gone, but there's an
1672
# equivalent private method for testing
1673
result = repo._get_revision_graph(None)
1675
[('call_expecting_body', 'Repository.get_revision_graph',
1678
self.assertEqual({r1: (), r2: (r1, )}, result)
1680
def test_specific_revision(self):
1681
# with a specific revision we want the graph for that
1682
# with none we want the entire graph
1683
r11 = u'\u0e33'.encode('utf8')
1684
r12 = u'\xc9'.encode('utf8')
1685
r2 = u'\u0dab'.encode('utf8')
1686
lines = [' '.join([r2, r11, r12]), r11, r12]
1687
encoded_body = '\n'.join(lines)
1689
transport_path = 'sinhala'
1690
repo, client = self.setup_fake_client_and_repository(transport_path)
1691
client.add_success_response_with_body(encoded_body, 'ok')
1692
result = repo._get_revision_graph(r2)
1694
[('call_expecting_body', 'Repository.get_revision_graph',
1697
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1699
def test_no_such_revision(self):
1701
transport_path = 'sinhala'
1702
repo, client = self.setup_fake_client_and_repository(transport_path)
1703
client.add_error_response('nosuchrevision', revid)
1704
# also check that the right revision is reported in the error
1705
self.assertRaises(errors.NoSuchRevision,
1706
repo._get_revision_graph, revid)
1708
[('call_expecting_body', 'Repository.get_revision_graph',
1709
('sinhala/', revid))],
1712
def test_unexpected_error(self):
1714
transport_path = 'sinhala'
1715
repo, client = self.setup_fake_client_and_repository(transport_path)
1716
client.add_error_response('AnUnexpectedError')
1717
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1718
repo._get_revision_graph, revid)
1719
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1722
class TestRepositoryIsShared(TestRemoteRepository):
1724
def test_is_shared(self):
1725
# ('yes', ) for Repository.is_shared -> 'True'.
1726
transport_path = 'quack'
1727
repo, client = self.setup_fake_client_and_repository(transport_path)
1728
client.add_success_response('yes')
1729
result = repo.is_shared()
1731
[('call', 'Repository.is_shared', ('quack/',))],
1733
self.assertEqual(True, result)
1735
def test_is_not_shared(self):
1736
# ('no', ) for Repository.is_shared -> 'False'.
1737
transport_path = 'qwack'
1738
repo, client = self.setup_fake_client_and_repository(transport_path)
1739
client.add_success_response('no')
1740
result = repo.is_shared()
1742
[('call', 'Repository.is_shared', ('qwack/',))],
1744
self.assertEqual(False, result)
1747
class TestRepositoryLockWrite(TestRemoteRepository):
1749
def test_lock_write(self):
1750
transport_path = 'quack'
1751
repo, client = self.setup_fake_client_and_repository(transport_path)
1752
client.add_success_response('ok', 'a token')
1753
result = repo.lock_write()
1755
[('call', 'Repository.lock_write', ('quack/', ''))],
1757
self.assertEqual('a token', result)
1759
def test_lock_write_already_locked(self):
1760
transport_path = 'quack'
1761
repo, client = self.setup_fake_client_and_repository(transport_path)
1762
client.add_error_response('LockContention')
1763
self.assertRaises(errors.LockContention, repo.lock_write)
1765
[('call', 'Repository.lock_write', ('quack/', ''))],
1768
def test_lock_write_unlockable(self):
1769
transport_path = 'quack'
1770
repo, client = self.setup_fake_client_and_repository(transport_path)
1771
client.add_error_response('UnlockableTransport')
1772
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1774
[('call', 'Repository.lock_write', ('quack/', ''))],
1778
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1780
def test_backwards_compat(self):
1781
self.setup_smart_server_with_call_log()
1782
repo = self.make_repository('.')
1783
self.reset_smart_call_log()
1784
verb = 'Repository.set_make_working_trees'
1785
self.disable_verb(verb)
1786
repo.set_make_working_trees(True)
1787
call_count = len([call for call in self.hpss_calls if
1788
call.call.method == verb])
1789
self.assertEqual(1, call_count)
1791
def test_current(self):
1792
transport_path = 'quack'
1793
repo, client = self.setup_fake_client_and_repository(transport_path)
1794
client.add_expected_call(
1795
'Repository.set_make_working_trees', ('quack/', 'True'),
1797
client.add_expected_call(
1798
'Repository.set_make_working_trees', ('quack/', 'False'),
1800
repo.set_make_working_trees(True)
1801
repo.set_make_working_trees(False)
1804
class TestRepositoryUnlock(TestRemoteRepository):
1806
def test_unlock(self):
1807
transport_path = 'quack'
1808
repo, client = self.setup_fake_client_and_repository(transport_path)
1809
client.add_success_response('ok', 'a token')
1810
client.add_success_response('ok')
1814
[('call', 'Repository.lock_write', ('quack/', '')),
1815
('call', 'Repository.unlock', ('quack/', 'a token'))],
1818
def test_unlock_wrong_token(self):
1819
# If somehow the token is wrong, unlock will raise TokenMismatch.
1820
transport_path = 'quack'
1821
repo, client = self.setup_fake_client_and_repository(transport_path)
1822
client.add_success_response('ok', 'a token')
1823
client.add_error_response('TokenMismatch')
1825
self.assertRaises(errors.TokenMismatch, repo.unlock)
1828
class TestRepositoryHasRevision(TestRemoteRepository):
1830
def test_none(self):
1831
# repo.has_revision(None) should not cause any traffic.
1832
transport_path = 'quack'
1833
repo, client = self.setup_fake_client_and_repository(transport_path)
1835
# The null revision is always there, so has_revision(None) == True.
1836
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1838
# The remote repo shouldn't be accessed.
1839
self.assertEqual([], client._calls)
1842
class TestRepositoryInsertStream(TestRemoteRepository):
1844
def test_unlocked_repo(self):
1845
transport_path = 'quack'
1846
repo, client = self.setup_fake_client_and_repository(transport_path)
1847
client.add_expected_call(
1848
'Repository.insert_stream', ('quack/', ''),
1850
client.add_expected_call(
1851
'Repository.insert_stream', ('quack/', ''),
1853
sink = repo._get_sink()
1854
fmt = repository.RepositoryFormat.get_default_format()
1855
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1856
self.assertEqual([], resume_tokens)
1857
self.assertEqual(set(), missing_keys)
1858
client.finished_test()
1860
def test_locked_repo_with_no_lock_token(self):
1861
transport_path = 'quack'
1862
repo, client = self.setup_fake_client_and_repository(transport_path)
1863
client.add_expected_call(
1864
'Repository.lock_write', ('quack/', ''),
1865
'success', ('ok', ''))
1866
client.add_expected_call(
1867
'Repository.insert_stream', ('quack/', ''),
1869
client.add_expected_call(
1870
'Repository.insert_stream', ('quack/', ''),
1873
sink = repo._get_sink()
1874
fmt = repository.RepositoryFormat.get_default_format()
1875
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1876
self.assertEqual([], resume_tokens)
1877
self.assertEqual(set(), missing_keys)
1878
client.finished_test()
1880
def test_locked_repo_with_lock_token(self):
1881
transport_path = 'quack'
1882
repo, client = self.setup_fake_client_and_repository(transport_path)
1883
client.add_expected_call(
1884
'Repository.lock_write', ('quack/', ''),
1885
'success', ('ok', 'a token'))
1886
client.add_expected_call(
1887
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
1889
client.add_expected_call(
1890
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
1893
sink = repo._get_sink()
1894
fmt = repository.RepositoryFormat.get_default_format()
1895
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1896
self.assertEqual([], resume_tokens)
1897
self.assertEqual(set(), missing_keys)
1898
client.finished_test()
1901
class TestRepositoryTarball(TestRemoteRepository):
1903
# This is a canned tarball reponse we can validate against
1905
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1906
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1907
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1908
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1909
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1910
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1911
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1912
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1913
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1914
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1915
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1916
'nWQ7QH/F3JFOFCQ0aSPfA='
1919
def test_repository_tarball(self):
1920
# Test that Repository.tarball generates the right operations
1921
transport_path = 'repo'
1922
expected_calls = [('call_expecting_body', 'Repository.tarball',
1923
('repo/', 'bz2',),),
1925
repo, client = self.setup_fake_client_and_repository(transport_path)
1926
client.add_success_response_with_body(self.tarball_content, 'ok')
1927
# Now actually ask for the tarball
1928
tarball_file = repo._get_tarball('bz2')
1930
self.assertEqual(expected_calls, client._calls)
1931
self.assertEqual(self.tarball_content, tarball_file.read())
1933
tarball_file.close()
1936
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
1937
"""RemoteRepository.copy_content_into optimizations"""
1939
def test_copy_content_remote_to_local(self):
1940
self.transport_server = server.SmartTCPServer_for_testing
1941
src_repo = self.make_repository('repo1')
1942
src_repo = repository.Repository.open(self.get_url('repo1'))
1943
# At the moment the tarball-based copy_content_into can't write back
1944
# into a smart server. It would be good if it could upload the
1945
# tarball; once that works we'd have to create repositories of
1946
# different formats. -- mbp 20070410
1947
dest_url = self.get_vfs_only_url('repo2')
1948
dest_bzrdir = BzrDir.create(dest_url)
1949
dest_repo = dest_bzrdir.create_repository()
1950
self.assertFalse(isinstance(dest_repo, RemoteRepository))
1951
self.assertTrue(isinstance(src_repo, RemoteRepository))
1952
src_repo.copy_content_into(dest_repo)
1955
class _StubRealPackRepository(object):
1957
def __init__(self, calls):
1959
self._pack_collection = _StubPackCollection(calls)
1961
def is_in_write_group(self):
1964
def refresh_data(self):
1965
self.calls.append(('pack collection reload_pack_names',))
1968
class _StubPackCollection(object):
1970
def __init__(self, calls):
1974
self.calls.append(('pack collection autopack',))
1977
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1978
"""Tests for RemoteRepository.autopack implementation."""
1981
"""When the server returns 'ok' and there's no _real_repository, then
1982
nothing else happens: the autopack method is done.
1984
transport_path = 'quack'
1985
repo, client = self.setup_fake_client_and_repository(transport_path)
1986
client.add_expected_call(
1987
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
1989
client.finished_test()
1991
def test_ok_with_real_repo(self):
1992
"""When the server returns 'ok' and there is a _real_repository, then
1993
the _real_repository's reload_pack_name's method will be called.
1995
transport_path = 'quack'
1996
repo, client = self.setup_fake_client_and_repository(transport_path)
1997
client.add_expected_call(
1998
'PackRepository.autopack', ('quack/',),
2000
repo._real_repository = _StubRealPackRepository(client._calls)
2003
[('call', 'PackRepository.autopack', ('quack/',)),
2004
('pack collection reload_pack_names',)],
2007
def test_backwards_compatibility(self):
2008
"""If the server does not recognise the PackRepository.autopack verb,
2009
fallback to the real_repository's implementation.
2011
transport_path = 'quack'
2012
repo, client = self.setup_fake_client_and_repository(transport_path)
2013
client.add_unknown_method_response('PackRepository.autopack')
2014
def stub_ensure_real():
2015
client._calls.append(('_ensure_real',))
2016
repo._real_repository = _StubRealPackRepository(client._calls)
2017
repo._ensure_real = stub_ensure_real
2020
[('call', 'PackRepository.autopack', ('quack/',)),
2022
('pack collection autopack',)],
2026
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2027
"""Base class for unit tests for bzrlib.remote._translate_error."""
2029
def translateTuple(self, error_tuple, **context):
2030
"""Call _translate_error with an ErrorFromSmartServer built from the
2033
:param error_tuple: A tuple of a smart server response, as would be
2034
passed to an ErrorFromSmartServer.
2035
:kwargs context: context items to call _translate_error with.
2037
:returns: The error raised by _translate_error.
2039
# Raise the ErrorFromSmartServer before passing it as an argument,
2040
# because _translate_error may need to re-raise it with a bare 'raise'
2042
server_error = errors.ErrorFromSmartServer(error_tuple)
2043
translated_error = self.translateErrorFromSmartServer(
2044
server_error, **context)
2045
return translated_error
2047
def translateErrorFromSmartServer(self, error_object, **context):
2048
"""Like translateTuple, but takes an already constructed
2049
ErrorFromSmartServer rather than a tuple.
2053
except errors.ErrorFromSmartServer, server_error:
2054
translated_error = self.assertRaises(
2055
errors.BzrError, remote._translate_error, server_error,
2057
return translated_error
2060
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2061
"""Unit tests for bzrlib.remote._translate_error.
2063
Given an ErrorFromSmartServer (which has an error tuple from a smart
2064
server) and some context, _translate_error raises more specific errors from
2067
This test case covers the cases where _translate_error succeeds in
2068
translating an ErrorFromSmartServer to something better. See
2069
TestErrorTranslationRobustness for other cases.
2072
def test_NoSuchRevision(self):
2073
branch = self.make_branch('')
2075
translated_error = self.translateTuple(
2076
('NoSuchRevision', revid), branch=branch)
2077
expected_error = errors.NoSuchRevision(branch, revid)
2078
self.assertEqual(expected_error, translated_error)
2080
def test_nosuchrevision(self):
2081
repository = self.make_repository('')
2083
translated_error = self.translateTuple(
2084
('nosuchrevision', revid), repository=repository)
2085
expected_error = errors.NoSuchRevision(repository, revid)
2086
self.assertEqual(expected_error, translated_error)
2088
def test_nobranch(self):
2089
bzrdir = self.make_bzrdir('')
2090
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2091
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2092
self.assertEqual(expected_error, translated_error)
2094
def test_LockContention(self):
2095
translated_error = self.translateTuple(('LockContention',))
2096
expected_error = errors.LockContention('(remote lock)')
2097
self.assertEqual(expected_error, translated_error)
2099
def test_UnlockableTransport(self):
2100
bzrdir = self.make_bzrdir('')
2101
translated_error = self.translateTuple(
2102
('UnlockableTransport',), bzrdir=bzrdir)
2103
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2104
self.assertEqual(expected_error, translated_error)
2106
def test_LockFailed(self):
2107
lock = 'str() of a server lock'
2108
why = 'str() of why'
2109
translated_error = self.translateTuple(('LockFailed', lock, why))
2110
expected_error = errors.LockFailed(lock, why)
2111
self.assertEqual(expected_error, translated_error)
2113
def test_TokenMismatch(self):
2114
token = 'a lock token'
2115
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2116
expected_error = errors.TokenMismatch(token, '(remote token)')
2117
self.assertEqual(expected_error, translated_error)
2119
def test_Diverged(self):
2120
branch = self.make_branch('a')
2121
other_branch = self.make_branch('b')
2122
translated_error = self.translateTuple(
2123
('Diverged',), branch=branch, other_branch=other_branch)
2124
expected_error = errors.DivergedBranches(branch, other_branch)
2125
self.assertEqual(expected_error, translated_error)
2127
def test_ReadError_no_args(self):
2129
translated_error = self.translateTuple(('ReadError',), path=path)
2130
expected_error = errors.ReadError(path)
2131
self.assertEqual(expected_error, translated_error)
2133
def test_ReadError(self):
2135
translated_error = self.translateTuple(('ReadError', path))
2136
expected_error = errors.ReadError(path)
2137
self.assertEqual(expected_error, translated_error)
2139
def test_PermissionDenied_no_args(self):
2141
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2142
expected_error = errors.PermissionDenied(path)
2143
self.assertEqual(expected_error, translated_error)
2145
def test_PermissionDenied_one_arg(self):
2147
translated_error = self.translateTuple(('PermissionDenied', path))
2148
expected_error = errors.PermissionDenied(path)
2149
self.assertEqual(expected_error, translated_error)
2151
def test_PermissionDenied_one_arg_and_context(self):
2152
"""Given a choice between a path from the local context and a path on
2153
the wire, _translate_error prefers the path from the local context.
2155
local_path = 'local path'
2156
remote_path = 'remote path'
2157
translated_error = self.translateTuple(
2158
('PermissionDenied', remote_path), path=local_path)
2159
expected_error = errors.PermissionDenied(local_path)
2160
self.assertEqual(expected_error, translated_error)
2162
def test_PermissionDenied_two_args(self):
2164
extra = 'a string with extra info'
2165
translated_error = self.translateTuple(
2166
('PermissionDenied', path, extra))
2167
expected_error = errors.PermissionDenied(path, extra)
2168
self.assertEqual(expected_error, translated_error)
2171
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2172
"""Unit tests for bzrlib.remote._translate_error's robustness.
2174
TestErrorTranslationSuccess is for cases where _translate_error can
2175
translate successfully. This class about how _translate_err behaves when
2176
it fails to translate: it re-raises the original error.
2179
def test_unrecognised_server_error(self):
2180
"""If the error code from the server is not recognised, the original
2181
ErrorFromSmartServer is propagated unmodified.
2183
error_tuple = ('An unknown error tuple',)
2184
server_error = errors.ErrorFromSmartServer(error_tuple)
2185
translated_error = self.translateErrorFromSmartServer(server_error)
2186
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2187
self.assertEqual(expected_error, translated_error)
2189
def test_context_missing_a_key(self):
2190
"""In case of a bug in the client, or perhaps an unexpected response
2191
from a server, _translate_error returns the original error tuple from
2192
the server and mutters a warning.
2194
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2195
# in the context dict. So let's give it an empty context dict instead
2196
# to exercise its error recovery.
2198
error_tuple = ('NoSuchRevision', 'revid')
2199
server_error = errors.ErrorFromSmartServer(error_tuple)
2200
translated_error = self.translateErrorFromSmartServer(server_error)
2201
self.assertEqual(server_error, translated_error)
2202
# In addition to re-raising ErrorFromSmartServer, some debug info has
2203
# been muttered to the log file for developer to look at.
2204
self.assertContainsRe(
2205
self._get_log(keep_log_file=True),
2206
"Missing key 'branch' in context")
2208
def test_path_missing(self):
2209
"""Some translations (PermissionDenied, ReadError) can determine the
2210
'path' variable from either the wire or the local context. If neither
2211
has it, then an error is raised.
2213
error_tuple = ('ReadError',)
2214
server_error = errors.ErrorFromSmartServer(error_tuple)
2215
translated_error = self.translateErrorFromSmartServer(server_error)
2216
self.assertEqual(server_error, translated_error)
2217
# In addition to re-raising ErrorFromSmartServer, some debug info has
2218
# been muttered to the log file for developer to look at.
2219
self.assertContainsRe(
2220
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2223
class TestStacking(tests.TestCaseWithTransport):
2224
"""Tests for operations on stacked remote repositories.
2226
The underlying format type must support stacking.
2229
def test_access_stacked_remote(self):
2230
# based on <http://launchpad.net/bugs/261315>
2231
# make a branch stacked on another repository containing an empty
2232
# revision, then open it over hpss - we should be able to see that
2234
base_transport = self.get_transport()
2235
base_builder = self.make_branch_builder('base', format='1.9')
2236
base_builder.start_series()
2237
base_revid = base_builder.build_snapshot('rev-id', None,
2238
[('add', ('', None, 'directory', None))],
2240
base_builder.finish_series()
2241
stacked_branch = self.make_branch('stacked', format='1.9')
2242
stacked_branch.set_stacked_on_url('../base')
2243
# start a server looking at this
2244
smart_server = server.SmartTCPServer_for_testing()
2245
smart_server.setUp()
2246
self.addCleanup(smart_server.tearDown)
2247
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2248
# can get its branch and repository
2249
remote_branch = remote_bzrdir.open_branch()
2250
remote_repo = remote_branch.repository
2251
remote_repo.lock_read()
2253
# it should have an appropriate fallback repository, which should also
2254
# be a RemoteRepository
2255
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2256
self.assertIsInstance(remote_repo._fallback_repositories[0],
2258
# and it has the revision committed to the underlying repository;
2259
# these have varying implementations so we try several of them
2260
self.assertTrue(remote_repo.has_revisions([base_revid]))
2261
self.assertTrue(remote_repo.has_revision(base_revid))
2262
self.assertEqual(remote_repo.get_revision(base_revid).message,
2265
remote_repo.unlock()
2267
def prepare_stacked_remote_branch(self):
2268
"""Get stacked_upon and stacked branches with content in each."""
2269
self.setup_smart_server_with_call_log()
2270
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2271
tree1.commit('rev1', rev_id='rev1')
2272
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2273
).open_workingtree()
2274
tree2.commit('local changes make me feel good.')
2275
branch2 = Branch.open(self.get_url('tree2'))
2277
self.addCleanup(branch2.unlock)
2278
return tree1.branch, branch2
2280
def test_stacked_get_parent_map(self):
2281
# the public implementation of get_parent_map obeys stacking
2282
_, branch = self.prepare_stacked_remote_branch()
2283
repo = branch.repository
2284
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2286
def test_unstacked_get_parent_map(self):
2287
# _unstacked_provider.get_parent_map ignores stacking
2288
_, branch = self.prepare_stacked_remote_branch()
2289
provider = branch.repository._unstacked_provider
2290
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2292
def fetch_stream_to_rev_order(self, stream):
2294
for kind, substream in stream:
2295
if not kind == 'revisions':
2298
for content in substream:
2299
result.append(content.key[-1])
2302
def get_ordered_revs(self, format, order):
2303
"""Get a list of the revisions in a stream to format format.
2305
:param format: The format of the target.
2306
:param order: the order that target should have requested.
2307
:result: The revision ids in the stream, in the order seen,
2308
the topological order of revisions in the source.
2310
unordered_format = bzrdir.format_registry.get(format)()
2311
target_repository_format = unordered_format.repository_format
2313
self.assertEqual(order, target_repository_format._fetch_order)
2314
trunk, stacked = self.prepare_stacked_remote_branch()
2315
source = stacked.repository._get_source(target_repository_format)
2316
tip = stacked.last_revision()
2317
revs = stacked.repository.get_ancestry(tip)
2318
search = graph.PendingAncestryResult([tip], stacked.repository)
2319
self.reset_smart_call_log()
2320
stream = source.get_stream(search)
2323
# We trust that if a revision is in the stream the rest of the new
2324
# content for it is too, as per our main fetch tests; here we are
2325
# checking that the revisions are actually included at all, and their
2327
return self.fetch_stream_to_rev_order(stream), revs
2329
def test_stacked_get_stream_unordered(self):
2330
# Repository._get_source.get_stream() from a stacked repository with
2331
# unordered yields the full data from both stacked and stacked upon
2333
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2334
self.assertEqual(set(expected_revs), set(rev_ord))
2335
# Getting unordered results should have made a streaming data request
2336
# from the server, then one from the backing branch.
2337
self.assertLength(2, self.hpss_calls)
2339
def test_stacked_get_stream_topological(self):
2340
# Repository._get_source.get_stream() from a stacked repository with
2341
# topological sorting yields the full data from both stacked and
2342
# stacked upon sources in topological order.
2343
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2344
self.assertEqual(expected_revs, rev_ord)
2345
# Getting topological sort requires VFS calls still
2346
self.assertLength(14, self.hpss_calls)
2348
def test_stacked_get_stream_groupcompress(self):
2349
# Repository._get_source.get_stream() from a stacked repository with
2350
# groupcompress sorting yields the full data from both stacked and
2351
# stacked upon sources in groupcompress order.
2352
raise tests.TestSkipped('No groupcompress ordered format available')
2353
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2354
self.assertEqual(expected_revs, reversed(rev_ord))
2355
# Getting unordered results should have made a streaming data request
2356
# from the backing branch, and one from the stacked on branch.
2357
self.assertLength(2, self.hpss_calls)
2360
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2363
super(TestRemoteBranchEffort, self).setUp()
2364
# Create a smart server that publishes whatever the backing VFS server
2366
self.smart_server = server.SmartTCPServer_for_testing()
2367
self.smart_server.setUp(self.get_server())
2368
self.addCleanup(self.smart_server.tearDown)
2369
# Log all HPSS calls into self.hpss_calls.
2370
_SmartClient.hooks.install_named_hook(
2371
'call', self.capture_hpss_call, None)
2372
self.hpss_calls = []
2374
def capture_hpss_call(self, params):
2375
self.hpss_calls.append(params.method)
2377
def test_copy_content_into_avoids_revision_history(self):
2378
local = self.make_branch('local')
2379
remote_backing_tree = self.make_branch_and_tree('remote')
2380
remote_backing_tree.commit("Commit.")
2381
remote_branch_url = self.smart_server.get_url() + 'remote'
2382
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2383
local.repository.fetch(remote_branch.repository)
2384
self.hpss_calls = []
2385
remote_branch.copy_content_into(local)
2386
self.assertFalse('Branch.revision_history' in self.hpss_calls)