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
42
from bzrlib.branch import Branch
43
from bzrlib.bzrdir import BzrDir, BzrDirFormat
44
from bzrlib.remote import (
50
RemoteRepositoryFormat,
52
from bzrlib.repofmt import pack_repo
53
from bzrlib.revision import NULL_REVISION
54
from bzrlib.smart import server, medium
55
from bzrlib.smart.client import _SmartClient
56
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
57
from bzrlib.tests import (
59
split_suite_by_condition,
62
from bzrlib.transport import get_transport, http
63
from bzrlib.transport.memory import MemoryTransport
64
from bzrlib.transport.remote import (
70
def load_tests(standard_tests, module, loader):
71
to_adapt, result = split_suite_by_condition(
72
standard_tests, condition_isinstance(BasicRemoteObjectTests))
73
smart_server_version_scenarios = [
75
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
77
{'transport_server': server.SmartTCPServer_for_testing})]
78
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
81
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
84
super(BasicRemoteObjectTests, self).setUp()
85
self.transport = self.get_transport()
86
# make a branch that can be opened over the smart transport
87
self.local_wt = BzrDir.create_standalone_workingtree('.')
90
self.transport.disconnect()
91
tests.TestCaseWithTransport.tearDown(self)
93
def test_create_remote_bzrdir(self):
94
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
95
self.assertIsInstance(b, BzrDir)
97
def test_open_remote_branch(self):
98
# open a standalone branch in the working directory
99
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
100
branch = b.open_branch()
101
self.assertIsInstance(branch, Branch)
103
def test_remote_repository(self):
104
b = BzrDir.open_from_transport(self.transport)
105
repo = b.open_repository()
106
revid = u'\xc823123123'.encode('utf8')
107
self.assertFalse(repo.has_revision(revid))
108
self.local_wt.commit(message='test commit', rev_id=revid)
109
self.assertTrue(repo.has_revision(revid))
111
def test_remote_branch_revision_history(self):
112
b = BzrDir.open_from_transport(self.transport).open_branch()
113
self.assertEqual([], b.revision_history())
114
r1 = self.local_wt.commit('1st commit')
115
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
116
self.assertEqual([r1, r2], b.revision_history())
118
def test_find_correct_format(self):
119
"""Should open a RemoteBzrDir over a RemoteTransport"""
120
fmt = BzrDirFormat.find_format(self.transport)
121
self.assertTrue(RemoteBzrDirFormat
122
in BzrDirFormat._control_server_formats)
123
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
125
def test_open_detected_smart_format(self):
126
fmt = BzrDirFormat.find_format(self.transport)
127
d = fmt.open(self.transport)
128
self.assertIsInstance(d, BzrDir)
130
def test_remote_branch_repr(self):
131
b = BzrDir.open_from_transport(self.transport).open_branch()
132
self.assertStartsWith(str(b), 'RemoteBranch(')
134
def test_remote_branch_format_supports_stacking(self):
136
self.make_branch('unstackable', format='pack-0.92')
137
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
138
self.assertFalse(b._format.supports_stacking())
139
self.make_branch('stackable', format='1.9')
140
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
141
self.assertTrue(b._format.supports_stacking())
143
def test_remote_repo_format_supports_external_references(self):
145
bd = self.make_bzrdir('unstackable', format='pack-0.92')
146
r = bd.create_repository()
147
self.assertFalse(r._format.supports_external_lookups)
148
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
149
self.assertFalse(r._format.supports_external_lookups)
150
bd = self.make_bzrdir('stackable', format='1.9')
151
r = bd.create_repository()
152
self.assertTrue(r._format.supports_external_lookups)
153
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
154
self.assertTrue(r._format.supports_external_lookups)
157
class FakeProtocol(object):
158
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
160
def __init__(self, body, fake_client):
162
self._body_buffer = None
163
self._fake_client = fake_client
165
def read_body_bytes(self, count=-1):
166
if self._body_buffer is None:
167
self._body_buffer = StringIO(self.body)
168
bytes = self._body_buffer.read(count)
169
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
170
self._fake_client.expecting_body = False
173
def cancel_read_body(self):
174
self._fake_client.expecting_body = False
176
def read_streamed_body(self):
180
class FakeClient(_SmartClient):
181
"""Lookalike for _SmartClient allowing testing."""
183
def __init__(self, fake_medium_base='fake base'):
184
"""Create a FakeClient."""
187
self.expecting_body = False
188
# if non-None, this is the list of expected calls, with only the
189
# method name and arguments included. the body might be hard to
190
# compute so is not included. If a call is None, that call can
192
self._expected_calls = None
193
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
195
def add_expected_call(self, call_name, call_args, response_type,
196
response_args, response_body=None):
197
if self._expected_calls is None:
198
self._expected_calls = []
199
self._expected_calls.append((call_name, call_args))
200
self.responses.append((response_type, response_args, response_body))
202
def add_success_response(self, *args):
203
self.responses.append(('success', args, None))
205
def add_success_response_with_body(self, body, *args):
206
self.responses.append(('success', args, body))
207
if self._expected_calls is not None:
208
self._expected_calls.append(None)
210
def add_error_response(self, *args):
211
self.responses.append(('error', args))
213
def add_unknown_method_response(self, verb):
214
self.responses.append(('unknown', verb))
216
def finished_test(self):
217
if self._expected_calls:
218
raise AssertionError("%r finished but was still expecting %r"
219
% (self, self._expected_calls[0]))
221
def _get_next_response(self):
223
response_tuple = self.responses.pop(0)
224
except IndexError, e:
225
raise AssertionError("%r didn't expect any more calls"
227
if response_tuple[0] == 'unknown':
228
raise errors.UnknownSmartMethod(response_tuple[1])
229
elif response_tuple[0] == 'error':
230
raise errors.ErrorFromSmartServer(response_tuple[1])
231
return response_tuple
233
def _check_call(self, method, args):
234
if self._expected_calls is None:
235
# the test should be updated to say what it expects
238
next_call = self._expected_calls.pop(0)
240
raise AssertionError("%r didn't expect any more calls "
242
% (self, method, args,))
243
if next_call is None:
245
if method != next_call[0] or args != next_call[1]:
246
raise AssertionError("%r expected %r%r "
248
% (self, next_call[0], next_call[1], method, args,))
250
def call(self, method, *args):
251
self._check_call(method, args)
252
self._calls.append(('call', method, args))
253
return self._get_next_response()[1]
255
def call_expecting_body(self, method, *args):
256
self._check_call(method, args)
257
self._calls.append(('call_expecting_body', method, args))
258
result = self._get_next_response()
259
self.expecting_body = True
260
return result[1], FakeProtocol(result[2], self)
262
def call_with_body_bytes_expecting_body(self, method, args, body):
263
self._check_call(method, args)
264
self._calls.append(('call_with_body_bytes_expecting_body', method,
266
result = self._get_next_response()
267
self.expecting_body = True
268
return result[1], FakeProtocol(result[2], self)
270
def call_with_body_stream(self, args, stream):
271
# Explicitly consume the stream before checking for an error, because
272
# that's what happens a real medium.
273
stream = list(stream)
274
self._check_call(args[0], args[1:])
275
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
276
result = self._get_next_response()
277
# The second value returned from call_with_body_stream is supposed to
278
# be a response_handler object, but so far no tests depend on that.
279
response_handler = None
280
return result[1], response_handler
283
class FakeMedium(medium.SmartClientMedium):
285
def __init__(self, client_calls, base):
286
medium.SmartClientMedium.__init__(self, base)
287
self._client_calls = client_calls
289
def disconnect(self):
290
self._client_calls.append(('disconnect medium',))
293
class TestVfsHas(tests.TestCase):
295
def test_unicode_path(self):
296
client = FakeClient('/')
297
client.add_success_response('yes',)
298
transport = RemoteTransport('bzr://localhost/', _client=client)
299
filename = u'/hell\u00d8'.encode('utf8')
300
result = transport.has(filename)
302
[('call', 'has', (filename,))],
304
self.assertTrue(result)
307
class TestRemote(tests.TestCaseWithMemoryTransport):
309
def get_branch_format(self):
310
reference_bzrdir_format = bzrdir.format_registry.get('default')()
311
return reference_bzrdir_format.get_branch_format()
313
def get_repo_format(self):
314
reference_bzrdir_format = bzrdir.format_registry.get('default')()
315
return reference_bzrdir_format.repository_format
317
def disable_verb(self, verb):
318
"""Disable a verb for one test."""
319
request_handlers = smart.request.request_handlers
320
orig_method = request_handlers.get(verb)
321
request_handlers.remove(verb)
323
request_handlers.register(verb, orig_method)
324
self.addCleanup(restoreVerb)
327
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
328
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
330
def assertRemotePath(self, expected, client_base, transport_base):
331
"""Assert that the result of
332
SmartClientMedium.remote_path_from_transport is the expected value for
333
a given client_base and transport_base.
335
client_medium = medium.SmartClientMedium(client_base)
336
transport = get_transport(transport_base)
337
result = client_medium.remote_path_from_transport(transport)
338
self.assertEqual(expected, result)
340
def test_remote_path_from_transport(self):
341
"""SmartClientMedium.remote_path_from_transport calculates a URL for
342
the given transport relative to the root of the client base URL.
344
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
345
self.assertRemotePath(
346
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
348
def assertRemotePathHTTP(self, expected, transport_base, relpath):
349
"""Assert that the result of
350
HttpTransportBase.remote_path_from_transport is the expected value for
351
a given transport_base and relpath of that transport. (Note that
352
HttpTransportBase is a subclass of SmartClientMedium)
354
base_transport = get_transport(transport_base)
355
client_medium = base_transport.get_smart_medium()
356
cloned_transport = base_transport.clone(relpath)
357
result = client_medium.remote_path_from_transport(cloned_transport)
358
self.assertEqual(expected, result)
360
def test_remote_path_from_transport_http(self):
361
"""Remote paths for HTTP transports are calculated differently to other
362
transports. They are just relative to the client base, not the root
363
directory of the host.
365
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
366
self.assertRemotePathHTTP(
367
'../xyz/', scheme + '//host/path', '../xyz/')
368
self.assertRemotePathHTTP(
369
'xyz/', scheme + '//host/path', 'xyz/')
372
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
373
"""Tests for the behaviour of client_medium.remote_is_at_least."""
375
def test_initially_unlimited(self):
376
"""A fresh medium assumes that the remote side supports all
379
client_medium = medium.SmartClientMedium('dummy base')
380
self.assertFalse(client_medium._is_remote_before((99, 99)))
382
def test__remember_remote_is_before(self):
383
"""Calling _remember_remote_is_before ratchets down the known remote
386
client_medium = medium.SmartClientMedium('dummy base')
387
# Mark the remote side as being less than 1.6. The remote side may
389
client_medium._remember_remote_is_before((1, 6))
390
self.assertTrue(client_medium._is_remote_before((1, 6)))
391
self.assertFalse(client_medium._is_remote_before((1, 5)))
392
# Calling _remember_remote_is_before again with a lower value works.
393
client_medium._remember_remote_is_before((1, 5))
394
self.assertTrue(client_medium._is_remote_before((1, 5)))
395
# You cannot call _remember_remote_is_before with a larger value.
397
AssertionError, client_medium._remember_remote_is_before, (1, 9))
400
class TestBzrDirCloningMetaDir(TestRemote):
402
def test_backwards_compat(self):
403
self.setup_smart_server_with_call_log()
404
a_dir = self.make_bzrdir('.')
405
self.reset_smart_call_log()
406
verb = 'BzrDir.cloning_metadir'
407
self.disable_verb(verb)
408
format = a_dir.cloning_metadir()
409
call_count = len([call for call in self.hpss_calls if
410
call.call.method == verb])
411
self.assertEqual(1, call_count)
413
def test_branch_reference(self):
414
transport = self.get_transport('quack')
415
referenced = self.make_branch('referenced')
416
expected = referenced.bzrdir.cloning_metadir()
417
client = FakeClient(transport.base)
418
client.add_expected_call(
419
'BzrDir.cloning_metadir', ('quack/', 'False'),
420
'error', ('BranchReference',)),
421
client.add_expected_call(
422
'BzrDir.open_branchV2', ('quack/',),
423
'success', ('ref', self.get_url('referenced'))),
424
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
426
result = a_bzrdir.cloning_metadir()
427
# We should have got a control dir matching the referenced branch.
428
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
429
self.assertEqual(expected._repository_format, result._repository_format)
430
self.assertEqual(expected._branch_format, result._branch_format)
431
client.finished_test()
433
def test_current_server(self):
434
transport = self.get_transport('.')
435
transport = transport.clone('quack')
436
self.make_bzrdir('quack')
437
client = FakeClient(transport.base)
438
reference_bzrdir_format = bzrdir.format_registry.get('default')()
439
control_name = reference_bzrdir_format.network_name()
440
client.add_expected_call(
441
'BzrDir.cloning_metadir', ('quack/', 'False'),
442
'success', (control_name, '', ('branch', ''))),
443
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
445
result = a_bzrdir.cloning_metadir()
446
# We should have got a reference control dir with default branch and
447
# repository formats.
448
# This pokes a little, just to be sure.
449
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
450
self.assertEqual(None, result._repository_format)
451
self.assertEqual(None, result._branch_format)
452
client.finished_test()
455
class TestBzrDirOpenBranch(TestRemote):
457
def test_backwards_compat(self):
458
self.setup_smart_server_with_call_log()
459
self.make_branch('.')
460
a_dir = BzrDir.open(self.get_url('.'))
461
self.reset_smart_call_log()
462
verb = 'BzrDir.open_branchV2'
463
self.disable_verb(verb)
464
format = a_dir.open_branch()
465
call_count = len([call for call in self.hpss_calls if
466
call.call.method == verb])
467
self.assertEqual(1, call_count)
469
def test_branch_present(self):
470
reference_format = self.get_repo_format()
471
network_name = reference_format.network_name()
472
branch_network_name = self.get_branch_format().network_name()
473
transport = MemoryTransport()
474
transport.mkdir('quack')
475
transport = transport.clone('quack')
476
client = FakeClient(transport.base)
477
client.add_expected_call(
478
'BzrDir.open_branchV2', ('quack/',),
479
'success', ('branch', branch_network_name))
480
client.add_expected_call(
481
'BzrDir.find_repositoryV3', ('quack/',),
482
'success', ('ok', '', 'no', 'no', 'no', network_name))
483
client.add_expected_call(
484
'Branch.get_stacked_on_url', ('quack/',),
485
'error', ('NotStacked',))
486
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
488
result = bzrdir.open_branch()
489
self.assertIsInstance(result, RemoteBranch)
490
self.assertEqual(bzrdir, result.bzrdir)
491
client.finished_test()
493
def test_branch_missing(self):
494
transport = MemoryTransport()
495
transport.mkdir('quack')
496
transport = transport.clone('quack')
497
client = FakeClient(transport.base)
498
client.add_error_response('nobranch')
499
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
501
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
503
[('call', 'BzrDir.open_branchV2', ('quack/',))],
506
def test__get_tree_branch(self):
507
# _get_tree_branch is a form of open_branch, but it should only ask for
508
# branch opening, not any other network requests.
511
calls.append("Called")
513
transport = MemoryTransport()
514
# no requests on the network - catches other api calls being made.
515
client = FakeClient(transport.base)
516
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
518
# patch the open_branch call to record that it was called.
519
bzrdir.open_branch = open_branch
520
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
521
self.assertEqual(["Called"], calls)
522
self.assertEqual([], client._calls)
524
def test_url_quoting_of_path(self):
525
# Relpaths on the wire should not be URL-escaped. So "~" should be
526
# transmitted as "~", not "%7E".
527
transport = RemoteTCPTransport('bzr://localhost/~hello/')
528
client = FakeClient(transport.base)
529
reference_format = self.get_repo_format()
530
network_name = reference_format.network_name()
531
branch_network_name = self.get_branch_format().network_name()
532
client.add_expected_call(
533
'BzrDir.open_branchV2', ('~hello/',),
534
'success', ('branch', branch_network_name))
535
client.add_expected_call(
536
'BzrDir.find_repositoryV3', ('~hello/',),
537
'success', ('ok', '', 'no', 'no', 'no', network_name))
538
client.add_expected_call(
539
'Branch.get_stacked_on_url', ('~hello/',),
540
'error', ('NotStacked',))
541
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
543
result = bzrdir.open_branch()
544
client.finished_test()
546
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
547
reference_format = self.get_repo_format()
548
network_name = reference_format.network_name()
549
transport = MemoryTransport()
550
transport.mkdir('quack')
551
transport = transport.clone('quack')
553
rich_response = 'yes'
557
subtree_response = 'yes'
559
subtree_response = 'no'
560
client = FakeClient(transport.base)
561
client.add_success_response(
562
'ok', '', rich_response, subtree_response, external_lookup,
564
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
566
result = bzrdir.open_repository()
568
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
570
self.assertIsInstance(result, RemoteRepository)
571
self.assertEqual(bzrdir, result.bzrdir)
572
self.assertEqual(rich_root, result._format.rich_root_data)
573
self.assertEqual(subtrees, result._format.supports_tree_reference)
575
def test_open_repository_sets_format_attributes(self):
576
self.check_open_repository(True, True)
577
self.check_open_repository(False, True)
578
self.check_open_repository(True, False)
579
self.check_open_repository(False, False)
580
self.check_open_repository(False, False, 'yes')
582
def test_old_server(self):
583
"""RemoteBzrDirFormat should fail to probe if the server version is too
586
self.assertRaises(errors.NotBranchError,
587
RemoteBzrDirFormat.probe_transport, OldServerTransport())
590
class TestBzrDirCreateBranch(TestRemote):
592
def test_backwards_compat(self):
593
self.setup_smart_server_with_call_log()
594
repo = self.make_repository('.')
595
self.reset_smart_call_log()
596
self.disable_verb('BzrDir.create_branch')
597
branch = repo.bzrdir.create_branch()
598
create_branch_call_count = len([call for call in self.hpss_calls if
599
call.call.method == 'BzrDir.create_branch'])
600
self.assertEqual(1, create_branch_call_count)
602
def test_current_server(self):
603
transport = self.get_transport('.')
604
transport = transport.clone('quack')
605
self.make_repository('quack')
606
client = FakeClient(transport.base)
607
reference_bzrdir_format = bzrdir.format_registry.get('default')()
608
reference_format = reference_bzrdir_format.get_branch_format()
609
network_name = reference_format.network_name()
610
reference_repo_fmt = reference_bzrdir_format.repository_format
611
reference_repo_name = reference_repo_fmt.network_name()
612
client.add_expected_call(
613
'BzrDir.create_branch', ('quack/', network_name),
614
'success', ('ok', network_name, '', 'no', 'no', 'yes',
615
reference_repo_name))
616
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
618
branch = a_bzrdir.create_branch()
619
# We should have got a remote branch
620
self.assertIsInstance(branch, remote.RemoteBranch)
621
# its format should have the settings from the response
622
format = branch._format
623
self.assertEqual(network_name, format.network_name())
626
class TestBzrDirCreateRepository(TestRemote):
628
def test_backwards_compat(self):
629
self.setup_smart_server_with_call_log()
630
bzrdir = self.make_bzrdir('.')
631
self.reset_smart_call_log()
632
self.disable_verb('BzrDir.create_repository')
633
repo = bzrdir.create_repository()
634
create_repo_call_count = len([call for call in self.hpss_calls if
635
call.call.method == 'BzrDir.create_repository'])
636
self.assertEqual(1, create_repo_call_count)
638
def test_current_server(self):
639
transport = self.get_transport('.')
640
transport = transport.clone('quack')
641
self.make_bzrdir('quack')
642
client = FakeClient(transport.base)
643
reference_bzrdir_format = bzrdir.format_registry.get('default')()
644
reference_format = reference_bzrdir_format.repository_format
645
network_name = reference_format.network_name()
646
client.add_expected_call(
647
'BzrDir.create_repository', ('quack/',
648
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
649
'success', ('ok', 'no', 'no', 'no', network_name))
650
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
652
repo = a_bzrdir.create_repository()
653
# We should have got a remote repository
654
self.assertIsInstance(repo, remote.RemoteRepository)
655
# its format should have the settings from the response
656
format = repo._format
657
self.assertFalse(format.rich_root_data)
658
self.assertFalse(format.supports_tree_reference)
659
self.assertFalse(format.supports_external_lookups)
660
self.assertEqual(network_name, format.network_name())
663
class TestBzrDirOpenRepository(TestRemote):
665
def test_backwards_compat_1_2_3(self):
666
# fallback all the way to the first version.
667
reference_format = self.get_repo_format()
668
network_name = reference_format.network_name()
669
client = FakeClient('bzr://example.com/')
670
client.add_unknown_method_response('BzrDir.find_repositoryV3')
671
client.add_unknown_method_response('BzrDir.find_repositoryV2')
672
client.add_success_response('ok', '', 'no', 'no')
673
# A real repository instance will be created to determine the network
675
client.add_success_response_with_body(
676
"Bazaar-NG meta directory, format 1\n", 'ok')
677
client.add_success_response_with_body(
678
reference_format.get_format_string(), 'ok')
679
# PackRepository wants to do a stat
680
client.add_success_response('stat', '0', '65535')
681
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
683
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
685
repo = bzrdir.open_repository()
687
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
688
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
689
('call', 'BzrDir.find_repository', ('quack/',)),
690
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
691
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
692
('call', 'stat', ('/quack/.bzr/repository',)),
695
self.assertEqual(network_name, repo._format.network_name())
697
def test_backwards_compat_2(self):
698
# fallback to find_repositoryV2
699
reference_format = self.get_repo_format()
700
network_name = reference_format.network_name()
701
client = FakeClient('bzr://example.com/')
702
client.add_unknown_method_response('BzrDir.find_repositoryV3')
703
client.add_success_response('ok', '', 'no', 'no', 'no')
704
# A real repository instance will be created to determine the network
706
client.add_success_response_with_body(
707
"Bazaar-NG meta directory, format 1\n", 'ok')
708
client.add_success_response_with_body(
709
reference_format.get_format_string(), 'ok')
710
# PackRepository wants to do a stat
711
client.add_success_response('stat', '0', '65535')
712
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
714
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
716
repo = bzrdir.open_repository()
718
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
719
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
720
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
721
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
722
('call', 'stat', ('/quack/.bzr/repository',)),
725
self.assertEqual(network_name, repo._format.network_name())
727
def test_current_server(self):
728
reference_format = self.get_repo_format()
729
network_name = reference_format.network_name()
730
transport = MemoryTransport()
731
transport.mkdir('quack')
732
transport = transport.clone('quack')
733
client = FakeClient(transport.base)
734
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
735
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
737
repo = bzrdir.open_repository()
739
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
741
self.assertEqual(network_name, repo._format.network_name())
744
class OldSmartClient(object):
745
"""A fake smart client for test_old_version that just returns a version one
746
response to the 'hello' (query version) command.
749
def get_request(self):
750
input_file = StringIO('ok\x011\n')
751
output_file = StringIO()
752
client_medium = medium.SmartSimplePipesClientMedium(
753
input_file, output_file)
754
return medium.SmartClientStreamMediumRequest(client_medium)
756
def protocol_version(self):
760
class OldServerTransport(object):
761
"""A fake transport for test_old_server that reports it's smart server
762
protocol version as version one.
768
def get_smart_client(self):
769
return OldSmartClient()
772
class RemoteBranchTestCase(TestRemote):
774
def make_remote_branch(self, transport, client):
775
"""Make a RemoteBranch using 'client' as its _SmartClient.
777
A RemoteBzrDir and RemoteRepository will also be created to fill out
778
the RemoteBranch, albeit with stub values for some of their attributes.
780
# we do not want bzrdir to make any remote calls, so use False as its
781
# _client. If it tries to make a remote call, this will fail
783
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
785
repo = RemoteRepository(bzrdir, None, _client=client)
786
branch_format = self.get_branch_format()
787
format = RemoteBranchFormat(network_name=branch_format.network_name())
788
return RemoteBranch(bzrdir, repo, _client=client, format=format)
791
class TestBranchGetParent(RemoteBranchTestCase):
793
def test_no_parent(self):
794
# in an empty branch we decode the response properly
795
transport = MemoryTransport()
796
client = FakeClient(transport.base)
797
client.add_expected_call(
798
'Branch.get_stacked_on_url', ('quack/',),
799
'error', ('NotStacked',))
800
client.add_expected_call(
801
'Branch.get_parent', ('quack/',),
803
transport.mkdir('quack')
804
transport = transport.clone('quack')
805
branch = self.make_remote_branch(transport, client)
806
result = branch.get_parent()
807
client.finished_test()
808
self.assertEqual(None, result)
810
def test_parent_relative(self):
811
transport = MemoryTransport()
812
client = FakeClient(transport.base)
813
client.add_expected_call(
814
'Branch.get_stacked_on_url', ('kwaak/',),
815
'error', ('NotStacked',))
816
client.add_expected_call(
817
'Branch.get_parent', ('kwaak/',),
818
'success', ('../foo/',))
819
transport.mkdir('kwaak')
820
transport = transport.clone('kwaak')
821
branch = self.make_remote_branch(transport, client)
822
result = branch.get_parent()
823
self.assertEqual(transport.clone('../foo').base, result)
825
def test_parent_absolute(self):
826
transport = MemoryTransport()
827
client = FakeClient(transport.base)
828
client.add_expected_call(
829
'Branch.get_stacked_on_url', ('kwaak/',),
830
'error', ('NotStacked',))
831
client.add_expected_call(
832
'Branch.get_parent', ('kwaak/',),
833
'success', ('http://foo/',))
834
transport.mkdir('kwaak')
835
transport = transport.clone('kwaak')
836
branch = self.make_remote_branch(transport, client)
837
result = branch.get_parent()
838
self.assertEqual('http://foo/', result)
841
class TestBranchGetTagsBytes(RemoteBranchTestCase):
843
def test_backwards_compat(self):
844
self.setup_smart_server_with_call_log()
845
branch = self.make_branch('.')
846
self.reset_smart_call_log()
847
verb = 'Branch.get_tags_bytes'
848
self.disable_verb(verb)
849
branch.tags.get_tag_dict()
850
call_count = len([call for call in self.hpss_calls if
851
call.call.method == verb])
852
self.assertEqual(1, call_count)
854
def test_trivial(self):
855
transport = MemoryTransport()
856
client = FakeClient(transport.base)
857
client.add_expected_call(
858
'Branch.get_stacked_on_url', ('quack/',),
859
'error', ('NotStacked',))
860
client.add_expected_call(
861
'Branch.get_tags_bytes', ('quack/',),
863
transport.mkdir('quack')
864
transport = transport.clone('quack')
865
branch = self.make_remote_branch(transport, client)
866
result = branch.tags.get_tag_dict()
867
client.finished_test()
868
self.assertEqual({}, result)
871
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
873
def test_empty_branch(self):
874
# in an empty branch we decode the response properly
875
transport = MemoryTransport()
876
client = FakeClient(transport.base)
877
client.add_expected_call(
878
'Branch.get_stacked_on_url', ('quack/',),
879
'error', ('NotStacked',))
880
client.add_expected_call(
881
'Branch.last_revision_info', ('quack/',),
882
'success', ('ok', '0', 'null:'))
883
transport.mkdir('quack')
884
transport = transport.clone('quack')
885
branch = self.make_remote_branch(transport, client)
886
result = branch.last_revision_info()
887
client.finished_test()
888
self.assertEqual((0, NULL_REVISION), result)
890
def test_non_empty_branch(self):
891
# in a non-empty branch we also decode the response properly
892
revid = u'\xc8'.encode('utf8')
893
transport = MemoryTransport()
894
client = FakeClient(transport.base)
895
client.add_expected_call(
896
'Branch.get_stacked_on_url', ('kwaak/',),
897
'error', ('NotStacked',))
898
client.add_expected_call(
899
'Branch.last_revision_info', ('kwaak/',),
900
'success', ('ok', '2', revid))
901
transport.mkdir('kwaak')
902
transport = transport.clone('kwaak')
903
branch = self.make_remote_branch(transport, client)
904
result = branch.last_revision_info()
905
self.assertEqual((2, revid), result)
908
class TestBranch_get_stacked_on_url(TestRemote):
909
"""Test Branch._get_stacked_on_url rpc"""
911
def test_get_stacked_on_invalid_url(self):
912
# test that asking for a stacked on url the server can't access works.
913
# This isn't perfect, but then as we're in the same process there
914
# really isn't anything we can do to be 100% sure that the server
915
# doesn't just open in - this test probably needs to be rewritten using
916
# a spawn()ed server.
917
stacked_branch = self.make_branch('stacked', format='1.9')
918
memory_branch = self.make_branch('base', format='1.9')
919
vfs_url = self.get_vfs_only_url('base')
920
stacked_branch.set_stacked_on_url(vfs_url)
921
transport = stacked_branch.bzrdir.root_transport
922
client = FakeClient(transport.base)
923
client.add_expected_call(
924
'Branch.get_stacked_on_url', ('stacked/',),
925
'success', ('ok', vfs_url))
926
# XXX: Multiple calls are bad, this second call documents what is
928
client.add_expected_call(
929
'Branch.get_stacked_on_url', ('stacked/',),
930
'success', ('ok', vfs_url))
931
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
933
repo_fmt = remote.RemoteRepositoryFormat()
934
repo_fmt._custom_format = stacked_branch.repository._format
935
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
937
result = branch.get_stacked_on_url()
938
self.assertEqual(vfs_url, result)
940
def test_backwards_compatible(self):
941
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
942
base_branch = self.make_branch('base', format='1.6')
943
stacked_branch = self.make_branch('stacked', format='1.6')
944
stacked_branch.set_stacked_on_url('../base')
945
client = FakeClient(self.get_url())
946
branch_network_name = self.get_branch_format().network_name()
947
client.add_expected_call(
948
'BzrDir.open_branchV2', ('stacked/',),
949
'success', ('branch', branch_network_name))
950
client.add_expected_call(
951
'BzrDir.find_repositoryV3', ('stacked/',),
952
'success', ('ok', '', 'no', 'no', 'yes',
953
stacked_branch.repository._format.network_name()))
954
# called twice, once from constructor and then again by us
955
client.add_expected_call(
956
'Branch.get_stacked_on_url', ('stacked/',),
957
'unknown', ('Branch.get_stacked_on_url',))
958
client.add_expected_call(
959
'Branch.get_stacked_on_url', ('stacked/',),
960
'unknown', ('Branch.get_stacked_on_url',))
961
# this will also do vfs access, but that goes direct to the transport
962
# and isn't seen by the FakeClient.
963
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
964
remote.RemoteBzrDirFormat(), _client=client)
965
branch = bzrdir.open_branch()
966
result = branch.get_stacked_on_url()
967
self.assertEqual('../base', result)
968
client.finished_test()
969
# it's in the fallback list both for the RemoteRepository and its vfs
971
self.assertEqual(1, len(branch.repository._fallback_repositories))
973
len(branch.repository._real_repository._fallback_repositories))
975
def test_get_stacked_on_real_branch(self):
976
base_branch = self.make_branch('base', format='1.6')
977
stacked_branch = self.make_branch('stacked', format='1.6')
978
stacked_branch.set_stacked_on_url('../base')
979
reference_format = self.get_repo_format()
980
network_name = reference_format.network_name()
981
client = FakeClient(self.get_url())
982
branch_network_name = self.get_branch_format().network_name()
983
client.add_expected_call(
984
'BzrDir.open_branchV2', ('stacked/',),
985
'success', ('branch', branch_network_name))
986
client.add_expected_call(
987
'BzrDir.find_repositoryV3', ('stacked/',),
988
'success', ('ok', '', 'no', 'no', 'yes', network_name))
989
# called twice, once from constructor and then again by us
990
client.add_expected_call(
991
'Branch.get_stacked_on_url', ('stacked/',),
992
'success', ('ok', '../base'))
993
client.add_expected_call(
994
'Branch.get_stacked_on_url', ('stacked/',),
995
'success', ('ok', '../base'))
996
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
997
remote.RemoteBzrDirFormat(), _client=client)
998
branch = bzrdir.open_branch()
999
result = branch.get_stacked_on_url()
1000
self.assertEqual('../base', result)
1001
client.finished_test()
1002
# it's in the fallback list both for the RemoteRepository and its vfs
1004
self.assertEqual(1, len(branch.repository._fallback_repositories))
1006
len(branch.repository._real_repository._fallback_repositories))
1009
class TestBranchSetLastRevision(RemoteBranchTestCase):
1011
def test_set_empty(self):
1012
# set_revision_history([]) is translated to calling
1013
# Branch.set_last_revision(path, '') on the wire.
1014
transport = MemoryTransport()
1015
transport.mkdir('branch')
1016
transport = transport.clone('branch')
1018
client = FakeClient(transport.base)
1019
client.add_expected_call(
1020
'Branch.get_stacked_on_url', ('branch/',),
1021
'error', ('NotStacked',))
1022
client.add_expected_call(
1023
'Branch.lock_write', ('branch/', '', ''),
1024
'success', ('ok', 'branch token', 'repo token'))
1025
client.add_expected_call(
1026
'Branch.last_revision_info',
1028
'success', ('ok', '0', 'null:'))
1029
client.add_expected_call(
1030
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1032
client.add_expected_call(
1033
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1035
branch = self.make_remote_branch(transport, client)
1036
# This is a hack to work around the problem that RemoteBranch currently
1037
# unnecessarily invokes _ensure_real upon a call to lock_write.
1038
branch._ensure_real = lambda: None
1040
result = branch.set_revision_history([])
1042
self.assertEqual(None, result)
1043
client.finished_test()
1045
def test_set_nonempty(self):
1046
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1047
# Branch.set_last_revision(path, rev-idN) on the wire.
1048
transport = MemoryTransport()
1049
transport.mkdir('branch')
1050
transport = transport.clone('branch')
1052
client = FakeClient(transport.base)
1053
client.add_expected_call(
1054
'Branch.get_stacked_on_url', ('branch/',),
1055
'error', ('NotStacked',))
1056
client.add_expected_call(
1057
'Branch.lock_write', ('branch/', '', ''),
1058
'success', ('ok', 'branch token', 'repo token'))
1059
client.add_expected_call(
1060
'Branch.last_revision_info',
1062
'success', ('ok', '0', 'null:'))
1064
encoded_body = bz2.compress('\n'.join(lines))
1065
client.add_success_response_with_body(encoded_body, 'ok')
1066
client.add_expected_call(
1067
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1069
client.add_expected_call(
1070
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1072
branch = self.make_remote_branch(transport, client)
1073
# This is a hack to work around the problem that RemoteBranch currently
1074
# unnecessarily invokes _ensure_real upon a call to lock_write.
1075
branch._ensure_real = lambda: None
1076
# Lock the branch, reset the record of remote calls.
1078
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1080
self.assertEqual(None, result)
1081
client.finished_test()
1083
def test_no_such_revision(self):
1084
transport = MemoryTransport()
1085
transport.mkdir('branch')
1086
transport = transport.clone('branch')
1087
# A response of 'NoSuchRevision' is translated into an exception.
1088
client = FakeClient(transport.base)
1089
client.add_expected_call(
1090
'Branch.get_stacked_on_url', ('branch/',),
1091
'error', ('NotStacked',))
1092
client.add_expected_call(
1093
'Branch.lock_write', ('branch/', '', ''),
1094
'success', ('ok', 'branch token', 'repo token'))
1095
client.add_expected_call(
1096
'Branch.last_revision_info',
1098
'success', ('ok', '0', 'null:'))
1099
# get_graph calls to construct the revision history, for the set_rh
1102
encoded_body = bz2.compress('\n'.join(lines))
1103
client.add_success_response_with_body(encoded_body, 'ok')
1104
client.add_expected_call(
1105
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1106
'error', ('NoSuchRevision', 'rev-id'))
1107
client.add_expected_call(
1108
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1111
branch = self.make_remote_branch(transport, client)
1114
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1116
client.finished_test()
1118
def test_tip_change_rejected(self):
1119
"""TipChangeRejected responses cause a TipChangeRejected exception to
1122
transport = MemoryTransport()
1123
transport.mkdir('branch')
1124
transport = transport.clone('branch')
1125
client = FakeClient(transport.base)
1126
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1127
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1128
client.add_expected_call(
1129
'Branch.get_stacked_on_url', ('branch/',),
1130
'error', ('NotStacked',))
1131
client.add_expected_call(
1132
'Branch.lock_write', ('branch/', '', ''),
1133
'success', ('ok', 'branch token', 'repo token'))
1134
client.add_expected_call(
1135
'Branch.last_revision_info',
1137
'success', ('ok', '0', 'null:'))
1139
encoded_body = bz2.compress('\n'.join(lines))
1140
client.add_success_response_with_body(encoded_body, 'ok')
1141
client.add_expected_call(
1142
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1143
'error', ('TipChangeRejected', rejection_msg_utf8))
1144
client.add_expected_call(
1145
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1147
branch = self.make_remote_branch(transport, client)
1148
branch._ensure_real = lambda: None
1150
# The 'TipChangeRejected' error response triggered by calling
1151
# set_revision_history causes a TipChangeRejected exception.
1152
err = self.assertRaises(
1153
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1154
# The UTF-8 message from the response has been decoded into a unicode
1156
self.assertIsInstance(err.msg, unicode)
1157
self.assertEqual(rejection_msg_unicode, err.msg)
1159
client.finished_test()
1162
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1164
def test_set_last_revision_info(self):
1165
# set_last_revision_info(num, 'rev-id') is translated to calling
1166
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1167
transport = MemoryTransport()
1168
transport.mkdir('branch')
1169
transport = transport.clone('branch')
1170
client = FakeClient(transport.base)
1171
# get_stacked_on_url
1172
client.add_error_response('NotStacked')
1174
client.add_success_response('ok', 'branch token', 'repo token')
1175
# query the current revision
1176
client.add_success_response('ok', '0', 'null:')
1178
client.add_success_response('ok')
1180
client.add_success_response('ok')
1182
branch = self.make_remote_branch(transport, client)
1183
# Lock the branch, reset the record of remote calls.
1186
result = branch.set_last_revision_info(1234, 'a-revision-id')
1188
[('call', 'Branch.last_revision_info', ('branch/',)),
1189
('call', 'Branch.set_last_revision_info',
1190
('branch/', 'branch token', 'repo token',
1191
'1234', 'a-revision-id'))],
1193
self.assertEqual(None, result)
1195
def test_no_such_revision(self):
1196
# A response of 'NoSuchRevision' is translated into an exception.
1197
transport = MemoryTransport()
1198
transport.mkdir('branch')
1199
transport = transport.clone('branch')
1200
client = FakeClient(transport.base)
1201
# get_stacked_on_url
1202
client.add_error_response('NotStacked')
1204
client.add_success_response('ok', 'branch token', 'repo token')
1206
client.add_error_response('NoSuchRevision', 'revid')
1208
client.add_success_response('ok')
1210
branch = self.make_remote_branch(transport, client)
1211
# Lock the branch, reset the record of remote calls.
1216
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1219
def lock_remote_branch(self, branch):
1220
"""Trick a RemoteBranch into thinking it is locked."""
1221
branch._lock_mode = 'w'
1222
branch._lock_count = 2
1223
branch._lock_token = 'branch token'
1224
branch._repo_lock_token = 'repo token'
1225
branch.repository._lock_mode = 'w'
1226
branch.repository._lock_count = 2
1227
branch.repository._lock_token = 'repo token'
1229
def test_backwards_compatibility(self):
1230
"""If the server does not support the Branch.set_last_revision_info
1231
verb (which is new in 1.4), then the client falls back to VFS methods.
1233
# This test is a little messy. Unlike most tests in this file, it
1234
# doesn't purely test what a Remote* object sends over the wire, and
1235
# how it reacts to responses from the wire. It instead relies partly
1236
# on asserting that the RemoteBranch will call
1237
# self._real_branch.set_last_revision_info(...).
1239
# First, set up our RemoteBranch with a FakeClient that raises
1240
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1241
transport = MemoryTransport()
1242
transport.mkdir('branch')
1243
transport = transport.clone('branch')
1244
client = FakeClient(transport.base)
1245
client.add_expected_call(
1246
'Branch.get_stacked_on_url', ('branch/',),
1247
'error', ('NotStacked',))
1248
client.add_expected_call(
1249
'Branch.last_revision_info',
1251
'success', ('ok', '0', 'null:'))
1252
client.add_expected_call(
1253
'Branch.set_last_revision_info',
1254
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1255
'unknown', 'Branch.set_last_revision_info')
1257
branch = self.make_remote_branch(transport, client)
1258
class StubRealBranch(object):
1261
def set_last_revision_info(self, revno, revision_id):
1263
('set_last_revision_info', revno, revision_id))
1264
def _clear_cached_state(self):
1266
real_branch = StubRealBranch()
1267
branch._real_branch = real_branch
1268
self.lock_remote_branch(branch)
1270
# Call set_last_revision_info, and verify it behaved as expected.
1271
result = branch.set_last_revision_info(1234, 'a-revision-id')
1273
[('set_last_revision_info', 1234, 'a-revision-id')],
1275
client.finished_test()
1277
def test_unexpected_error(self):
1278
# If the server sends an error the client doesn't understand, it gets
1279
# turned into an UnknownErrorFromSmartServer, which is presented as a
1280
# non-internal error to the user.
1281
transport = MemoryTransport()
1282
transport.mkdir('branch')
1283
transport = transport.clone('branch')
1284
client = FakeClient(transport.base)
1285
# get_stacked_on_url
1286
client.add_error_response('NotStacked')
1288
client.add_success_response('ok', 'branch token', 'repo token')
1290
client.add_error_response('UnexpectedError')
1292
client.add_success_response('ok')
1294
branch = self.make_remote_branch(transport, client)
1295
# Lock the branch, reset the record of remote calls.
1299
err = self.assertRaises(
1300
errors.UnknownErrorFromSmartServer,
1301
branch.set_last_revision_info, 123, 'revid')
1302
self.assertEqual(('UnexpectedError',), err.error_tuple)
1305
def test_tip_change_rejected(self):
1306
"""TipChangeRejected responses cause a TipChangeRejected exception to
1309
transport = MemoryTransport()
1310
transport.mkdir('branch')
1311
transport = transport.clone('branch')
1312
client = FakeClient(transport.base)
1313
# get_stacked_on_url
1314
client.add_error_response('NotStacked')
1316
client.add_success_response('ok', 'branch token', 'repo token')
1318
client.add_error_response('TipChangeRejected', 'rejection message')
1320
client.add_success_response('ok')
1322
branch = self.make_remote_branch(transport, client)
1323
# Lock the branch, reset the record of remote calls.
1325
self.addCleanup(branch.unlock)
1328
# The 'TipChangeRejected' error response triggered by calling
1329
# set_last_revision_info causes a TipChangeRejected exception.
1330
err = self.assertRaises(
1331
errors.TipChangeRejected,
1332
branch.set_last_revision_info, 123, 'revid')
1333
self.assertEqual('rejection message', err.msg)
1336
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1337
"""Getting the branch configuration should use an abstract method not vfs.
1340
def test_get_branch_conf(self):
1341
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1342
## # We should see that branch.get_config() does a single rpc to get the
1343
## # remote configuration file, abstracting away where that is stored on
1344
## # the server. However at the moment it always falls back to using the
1345
## # vfs, and this would need some changes in config.py.
1347
## # in an empty branch we decode the response properly
1348
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1349
## # we need to make a real branch because the remote_branch.control_files
1350
## # will trigger _ensure_real.
1351
## branch = self.make_branch('quack')
1352
## transport = branch.bzrdir.root_transport
1353
## # we do not want bzrdir to make any remote calls
1354
## bzrdir = RemoteBzrDir(transport, _client=False)
1355
## branch = RemoteBranch(bzrdir, None, _client=client)
1356
## config = branch.get_config()
1357
## self.assertEqual(
1358
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1362
class TestBranchLockWrite(RemoteBranchTestCase):
1364
def test_lock_write_unlockable(self):
1365
transport = MemoryTransport()
1366
client = FakeClient(transport.base)
1367
client.add_expected_call(
1368
'Branch.get_stacked_on_url', ('quack/',),
1369
'error', ('NotStacked',),)
1370
client.add_expected_call(
1371
'Branch.lock_write', ('quack/', '', ''),
1372
'error', ('UnlockableTransport',))
1373
transport.mkdir('quack')
1374
transport = transport.clone('quack')
1375
branch = self.make_remote_branch(transport, client)
1376
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1377
client.finished_test()
1380
class TestTransportIsReadonly(tests.TestCase):
1382
def test_true(self):
1383
client = FakeClient()
1384
client.add_success_response('yes')
1385
transport = RemoteTransport('bzr://example.com/', medium=False,
1387
self.assertEqual(True, transport.is_readonly())
1389
[('call', 'Transport.is_readonly', ())],
1392
def test_false(self):
1393
client = FakeClient()
1394
client.add_success_response('no')
1395
transport = RemoteTransport('bzr://example.com/', medium=False,
1397
self.assertEqual(False, transport.is_readonly())
1399
[('call', 'Transport.is_readonly', ())],
1402
def test_error_from_old_server(self):
1403
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1405
Clients should treat it as a "no" response, because is_readonly is only
1406
advisory anyway (a transport could be read-write, but then the
1407
underlying filesystem could be readonly anyway).
1409
client = FakeClient()
1410
client.add_unknown_method_response('Transport.is_readonly')
1411
transport = RemoteTransport('bzr://example.com/', medium=False,
1413
self.assertEqual(False, transport.is_readonly())
1415
[('call', 'Transport.is_readonly', ())],
1419
class TestTransportMkdir(tests.TestCase):
1421
def test_permissiondenied(self):
1422
client = FakeClient()
1423
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1424
transport = RemoteTransport('bzr://example.com/', medium=False,
1426
exc = self.assertRaises(
1427
errors.PermissionDenied, transport.mkdir, 'client path')
1428
expected_error = errors.PermissionDenied('/client path', 'extra')
1429
self.assertEqual(expected_error, exc)
1432
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1434
def test_defaults_to_none(self):
1435
t = RemoteSSHTransport('bzr+ssh://example.com')
1436
self.assertIs(None, t._get_credentials()[0])
1438
def test_uses_authentication_config(self):
1439
conf = config.AuthenticationConfig()
1440
conf._get_config().update(
1441
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1444
t = RemoteSSHTransport('bzr+ssh://example.com')
1445
self.assertEqual('bar', t._get_credentials()[0])
1448
class TestRemoteRepository(TestRemote):
1449
"""Base for testing RemoteRepository protocol usage.
1451
These tests contain frozen requests and responses. We want any changes to
1452
what is sent or expected to be require a thoughtful update to these tests
1453
because they might break compatibility with different-versioned servers.
1456
def setup_fake_client_and_repository(self, transport_path):
1457
"""Create the fake client and repository for testing with.
1459
There's no real server here; we just have canned responses sent
1462
:param transport_path: Path below the root of the MemoryTransport
1463
where the repository will be created.
1465
transport = MemoryTransport()
1466
transport.mkdir(transport_path)
1467
client = FakeClient(transport.base)
1468
transport = transport.clone(transport_path)
1469
# we do not want bzrdir to make any remote calls
1470
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1472
repo = RemoteRepository(bzrdir, None, _client=client)
1476
class TestRepositoryFormat(TestRemoteRepository):
1478
def test_fast_delta(self):
1479
true_name = pack_repo.RepositoryFormatPackDevelopment2().network_name()
1480
true_format = RemoteRepositoryFormat()
1481
true_format._network_name = true_name
1482
self.assertEqual(True, true_format.fast_deltas)
1483
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1484
false_format = RemoteRepositoryFormat()
1485
false_format._network_name = false_name
1486
self.assertEqual(False, false_format.fast_deltas)
1489
class TestRepositoryGatherStats(TestRemoteRepository):
1491
def test_revid_none(self):
1492
# ('ok',), body with revisions and size
1493
transport_path = 'quack'
1494
repo, client = self.setup_fake_client_and_repository(transport_path)
1495
client.add_success_response_with_body(
1496
'revisions: 2\nsize: 18\n', 'ok')
1497
result = repo.gather_stats(None)
1499
[('call_expecting_body', 'Repository.gather_stats',
1500
('quack/','','no'))],
1502
self.assertEqual({'revisions': 2, 'size': 18}, result)
1504
def test_revid_no_committers(self):
1505
# ('ok',), body without committers
1506
body = ('firstrev: 123456.300 3600\n'
1507
'latestrev: 654231.400 0\n'
1510
transport_path = 'quick'
1511
revid = u'\xc8'.encode('utf8')
1512
repo, client = self.setup_fake_client_and_repository(transport_path)
1513
client.add_success_response_with_body(body, 'ok')
1514
result = repo.gather_stats(revid)
1516
[('call_expecting_body', 'Repository.gather_stats',
1517
('quick/', revid, 'no'))],
1519
self.assertEqual({'revisions': 2, 'size': 18,
1520
'firstrev': (123456.300, 3600),
1521
'latestrev': (654231.400, 0),},
1524
def test_revid_with_committers(self):
1525
# ('ok',), body with committers
1526
body = ('committers: 128\n'
1527
'firstrev: 123456.300 3600\n'
1528
'latestrev: 654231.400 0\n'
1531
transport_path = 'buick'
1532
revid = u'\xc8'.encode('utf8')
1533
repo, client = self.setup_fake_client_and_repository(transport_path)
1534
client.add_success_response_with_body(body, 'ok')
1535
result = repo.gather_stats(revid, True)
1537
[('call_expecting_body', 'Repository.gather_stats',
1538
('buick/', revid, 'yes'))],
1540
self.assertEqual({'revisions': 2, 'size': 18,
1542
'firstrev': (123456.300, 3600),
1543
'latestrev': (654231.400, 0),},
1547
class TestRepositoryGetGraph(TestRemoteRepository):
1549
def test_get_graph(self):
1550
# get_graph returns a graph with a custom parents provider.
1551
transport_path = 'quack'
1552
repo, client = self.setup_fake_client_and_repository(transport_path)
1553
graph = repo.get_graph()
1554
self.assertNotEqual(graph._parents_provider, repo)
1557
class TestRepositoryGetParentMap(TestRemoteRepository):
1559
def test_get_parent_map_caching(self):
1560
# get_parent_map returns from cache until unlock()
1561
# setup a reponse with two revisions
1562
r1 = u'\u0e33'.encode('utf8')
1563
r2 = u'\u0dab'.encode('utf8')
1564
lines = [' '.join([r2, r1]), r1]
1565
encoded_body = bz2.compress('\n'.join(lines))
1567
transport_path = 'quack'
1568
repo, client = self.setup_fake_client_and_repository(transport_path)
1569
client.add_success_response_with_body(encoded_body, 'ok')
1570
client.add_success_response_with_body(encoded_body, 'ok')
1572
graph = repo.get_graph()
1573
parents = graph.get_parent_map([r2])
1574
self.assertEqual({r2: (r1,)}, parents)
1575
# locking and unlocking deeper should not reset
1578
parents = graph.get_parent_map([r1])
1579
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1581
[('call_with_body_bytes_expecting_body',
1582
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1586
# now we call again, and it should use the second response.
1588
graph = repo.get_graph()
1589
parents = graph.get_parent_map([r1])
1590
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1592
[('call_with_body_bytes_expecting_body',
1593
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1595
('call_with_body_bytes_expecting_body',
1596
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1602
def test_get_parent_map_reconnects_if_unknown_method(self):
1603
transport_path = 'quack'
1604
rev_id = 'revision-id'
1605
repo, client = self.setup_fake_client_and_repository(transport_path)
1606
client.add_unknown_method_response('Repository.get_parent_map')
1607
client.add_success_response_with_body(rev_id, 'ok')
1608
self.assertFalse(client._medium._is_remote_before((1, 2)))
1609
parents = repo.get_parent_map([rev_id])
1611
[('call_with_body_bytes_expecting_body',
1612
'Repository.get_parent_map', ('quack/', 'include-missing:',
1614
('disconnect medium',),
1615
('call_expecting_body', 'Repository.get_revision_graph',
1618
# The medium is now marked as being connected to an older server
1619
self.assertTrue(client._medium._is_remote_before((1, 2)))
1620
self.assertEqual({rev_id: ('null:',)}, parents)
1622
def test_get_parent_map_fallback_parentless_node(self):
1623
"""get_parent_map falls back to get_revision_graph on old servers. The
1624
results from get_revision_graph are tweaked to match the get_parent_map
1627
Specifically, a {key: ()} result from get_revision_graph means "no
1628
parents" for that key, which in get_parent_map results should be
1629
represented as {key: ('null:',)}.
1631
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1633
rev_id = 'revision-id'
1634
transport_path = 'quack'
1635
repo, client = self.setup_fake_client_and_repository(transport_path)
1636
client.add_success_response_with_body(rev_id, 'ok')
1637
client._medium._remember_remote_is_before((1, 2))
1638
parents = repo.get_parent_map([rev_id])
1640
[('call_expecting_body', 'Repository.get_revision_graph',
1643
self.assertEqual({rev_id: ('null:',)}, parents)
1645
def test_get_parent_map_unexpected_response(self):
1646
repo, client = self.setup_fake_client_and_repository('path')
1647
client.add_success_response('something unexpected!')
1649
errors.UnexpectedSmartServerResponse,
1650
repo.get_parent_map, ['a-revision-id'])
1652
def test_get_parent_map_negative_caches_missing_keys(self):
1653
self.setup_smart_server_with_call_log()
1654
repo = self.make_repository('foo')
1655
self.assertIsInstance(repo, RemoteRepository)
1657
self.addCleanup(repo.unlock)
1658
self.reset_smart_call_log()
1659
graph = repo.get_graph()
1660
self.assertEqual({},
1661
graph.get_parent_map(['some-missing', 'other-missing']))
1662
self.assertLength(1, self.hpss_calls)
1663
# No call if we repeat this
1664
self.reset_smart_call_log()
1665
graph = repo.get_graph()
1666
self.assertEqual({},
1667
graph.get_parent_map(['some-missing', 'other-missing']))
1668
self.assertLength(0, self.hpss_calls)
1669
# Asking for more unknown keys makes a request.
1670
self.reset_smart_call_log()
1671
graph = repo.get_graph()
1672
self.assertEqual({},
1673
graph.get_parent_map(['some-missing', 'other-missing',
1675
self.assertLength(1, self.hpss_calls)
1677
def disableExtraResults(self):
1678
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1679
SmartServerRepositoryGetParentMap.no_extra_results = True
1681
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1682
self.addCleanup(reset_values)
1684
def test_null_cached_missing_and_stop_key(self):
1685
self.setup_smart_server_with_call_log()
1686
# Make a branch with a single revision.
1687
builder = self.make_branch_builder('foo')
1688
builder.start_series()
1689
builder.build_snapshot('first', None, [
1690
('add', ('', 'root-id', 'directory', ''))])
1691
builder.finish_series()
1692
branch = builder.get_branch()
1693
repo = branch.repository
1694
self.assertIsInstance(repo, RemoteRepository)
1695
# Stop the server from sending extra results.
1696
self.disableExtraResults()
1698
self.addCleanup(repo.unlock)
1699
self.reset_smart_call_log()
1700
graph = repo.get_graph()
1701
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1702
# 'first' it will be a candidate for the stop_keys of subsequent
1703
# requests, and because 'null:' was queried but not returned it will be
1704
# cached as missing.
1705
self.assertEqual({'first': ('null:',)},
1706
graph.get_parent_map(['first', 'null:']))
1707
# Now query for another key. This request will pass along a recipe of
1708
# start and stop keys describing the already cached results, and this
1709
# recipe's revision count must be correct (or else it will trigger an
1710
# error from the server).
1711
self.assertEqual({}, graph.get_parent_map(['another-key']))
1712
# This assertion guards against disableExtraResults silently failing to
1713
# work, thus invalidating the test.
1714
self.assertLength(2, self.hpss_calls)
1716
def test_get_parent_map_gets_ghosts_from_result(self):
1717
# asking for a revision should negatively cache close ghosts in its
1719
self.setup_smart_server_with_call_log()
1720
tree = self.make_branch_and_memory_tree('foo')
1723
builder = treebuilder.TreeBuilder()
1724
builder.start_tree(tree)
1726
builder.finish_tree()
1727
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1728
rev_id = tree.commit('')
1732
self.addCleanup(tree.unlock)
1733
repo = tree.branch.repository
1734
self.assertIsInstance(repo, RemoteRepository)
1736
repo.get_parent_map([rev_id])
1737
self.reset_smart_call_log()
1738
# Now asking for rev_id's ghost parent should not make calls
1739
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1740
self.assertLength(0, self.hpss_calls)
1743
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1745
def test_allows_new_revisions(self):
1746
"""get_parent_map's results can be updated by commit."""
1747
smart_server = server.SmartTCPServer_for_testing()
1748
smart_server.setUp()
1749
self.addCleanup(smart_server.tearDown)
1750
self.make_branch('branch')
1751
branch = Branch.open(smart_server.get_url() + '/branch')
1752
tree = branch.create_checkout('tree', lightweight=True)
1754
self.addCleanup(tree.unlock)
1755
graph = tree.branch.repository.get_graph()
1756
# This provides an opportunity for the missing rev-id to be cached.
1757
self.assertEqual({}, graph.get_parent_map(['rev1']))
1758
tree.commit('message', rev_id='rev1')
1759
graph = tree.branch.repository.get_graph()
1760
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1763
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1765
def test_null_revision(self):
1766
# a null revision has the predictable result {}, we should have no wire
1767
# traffic when calling it with this argument
1768
transport_path = 'empty'
1769
repo, client = self.setup_fake_client_and_repository(transport_path)
1770
client.add_success_response('notused')
1771
# actual RemoteRepository.get_revision_graph is gone, but there's an
1772
# equivalent private method for testing
1773
result = repo._get_revision_graph(NULL_REVISION)
1774
self.assertEqual([], client._calls)
1775
self.assertEqual({}, result)
1777
def test_none_revision(self):
1778
# with none we want the entire graph
1779
r1 = u'\u0e33'.encode('utf8')
1780
r2 = u'\u0dab'.encode('utf8')
1781
lines = [' '.join([r2, r1]), r1]
1782
encoded_body = '\n'.join(lines)
1784
transport_path = 'sinhala'
1785
repo, client = self.setup_fake_client_and_repository(transport_path)
1786
client.add_success_response_with_body(encoded_body, 'ok')
1787
# actual RemoteRepository.get_revision_graph is gone, but there's an
1788
# equivalent private method for testing
1789
result = repo._get_revision_graph(None)
1791
[('call_expecting_body', 'Repository.get_revision_graph',
1794
self.assertEqual({r1: (), r2: (r1, )}, result)
1796
def test_specific_revision(self):
1797
# with a specific revision we want the graph for that
1798
# with none we want the entire graph
1799
r11 = u'\u0e33'.encode('utf8')
1800
r12 = u'\xc9'.encode('utf8')
1801
r2 = u'\u0dab'.encode('utf8')
1802
lines = [' '.join([r2, r11, r12]), r11, r12]
1803
encoded_body = '\n'.join(lines)
1805
transport_path = 'sinhala'
1806
repo, client = self.setup_fake_client_and_repository(transport_path)
1807
client.add_success_response_with_body(encoded_body, 'ok')
1808
result = repo._get_revision_graph(r2)
1810
[('call_expecting_body', 'Repository.get_revision_graph',
1813
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1815
def test_no_such_revision(self):
1817
transport_path = 'sinhala'
1818
repo, client = self.setup_fake_client_and_repository(transport_path)
1819
client.add_error_response('nosuchrevision', revid)
1820
# also check that the right revision is reported in the error
1821
self.assertRaises(errors.NoSuchRevision,
1822
repo._get_revision_graph, revid)
1824
[('call_expecting_body', 'Repository.get_revision_graph',
1825
('sinhala/', revid))],
1828
def test_unexpected_error(self):
1830
transport_path = 'sinhala'
1831
repo, client = self.setup_fake_client_and_repository(transport_path)
1832
client.add_error_response('AnUnexpectedError')
1833
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1834
repo._get_revision_graph, revid)
1835
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1838
class TestRepositoryIsShared(TestRemoteRepository):
1840
def test_is_shared(self):
1841
# ('yes', ) for Repository.is_shared -> 'True'.
1842
transport_path = 'quack'
1843
repo, client = self.setup_fake_client_and_repository(transport_path)
1844
client.add_success_response('yes')
1845
result = repo.is_shared()
1847
[('call', 'Repository.is_shared', ('quack/',))],
1849
self.assertEqual(True, result)
1851
def test_is_not_shared(self):
1852
# ('no', ) for Repository.is_shared -> 'False'.
1853
transport_path = 'qwack'
1854
repo, client = self.setup_fake_client_and_repository(transport_path)
1855
client.add_success_response('no')
1856
result = repo.is_shared()
1858
[('call', 'Repository.is_shared', ('qwack/',))],
1860
self.assertEqual(False, result)
1863
class TestRepositoryLockWrite(TestRemoteRepository):
1865
def test_lock_write(self):
1866
transport_path = 'quack'
1867
repo, client = self.setup_fake_client_and_repository(transport_path)
1868
client.add_success_response('ok', 'a token')
1869
result = repo.lock_write()
1871
[('call', 'Repository.lock_write', ('quack/', ''))],
1873
self.assertEqual('a token', result)
1875
def test_lock_write_already_locked(self):
1876
transport_path = 'quack'
1877
repo, client = self.setup_fake_client_and_repository(transport_path)
1878
client.add_error_response('LockContention')
1879
self.assertRaises(errors.LockContention, repo.lock_write)
1881
[('call', 'Repository.lock_write', ('quack/', ''))],
1884
def test_lock_write_unlockable(self):
1885
transport_path = 'quack'
1886
repo, client = self.setup_fake_client_and_repository(transport_path)
1887
client.add_error_response('UnlockableTransport')
1888
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1890
[('call', 'Repository.lock_write', ('quack/', ''))],
1894
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1896
def test_backwards_compat(self):
1897
self.setup_smart_server_with_call_log()
1898
repo = self.make_repository('.')
1899
self.reset_smart_call_log()
1900
verb = 'Repository.set_make_working_trees'
1901
self.disable_verb(verb)
1902
repo.set_make_working_trees(True)
1903
call_count = len([call for call in self.hpss_calls if
1904
call.call.method == verb])
1905
self.assertEqual(1, call_count)
1907
def test_current(self):
1908
transport_path = 'quack'
1909
repo, client = self.setup_fake_client_and_repository(transport_path)
1910
client.add_expected_call(
1911
'Repository.set_make_working_trees', ('quack/', 'True'),
1913
client.add_expected_call(
1914
'Repository.set_make_working_trees', ('quack/', 'False'),
1916
repo.set_make_working_trees(True)
1917
repo.set_make_working_trees(False)
1920
class TestRepositoryUnlock(TestRemoteRepository):
1922
def test_unlock(self):
1923
transport_path = 'quack'
1924
repo, client = self.setup_fake_client_and_repository(transport_path)
1925
client.add_success_response('ok', 'a token')
1926
client.add_success_response('ok')
1930
[('call', 'Repository.lock_write', ('quack/', '')),
1931
('call', 'Repository.unlock', ('quack/', 'a token'))],
1934
def test_unlock_wrong_token(self):
1935
# If somehow the token is wrong, unlock will raise TokenMismatch.
1936
transport_path = 'quack'
1937
repo, client = self.setup_fake_client_and_repository(transport_path)
1938
client.add_success_response('ok', 'a token')
1939
client.add_error_response('TokenMismatch')
1941
self.assertRaises(errors.TokenMismatch, repo.unlock)
1944
class TestRepositoryHasRevision(TestRemoteRepository):
1946
def test_none(self):
1947
# repo.has_revision(None) should not cause any traffic.
1948
transport_path = 'quack'
1949
repo, client = self.setup_fake_client_and_repository(transport_path)
1951
# The null revision is always there, so has_revision(None) == True.
1952
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1954
# The remote repo shouldn't be accessed.
1955
self.assertEqual([], client._calls)
1958
class TestRepositoryInsertStream(TestRemoteRepository):
1960
def test_unlocked_repo(self):
1961
transport_path = 'quack'
1962
repo, client = self.setup_fake_client_and_repository(transport_path)
1963
client.add_expected_call(
1964
'Repository.insert_stream', ('quack/', ''),
1966
client.add_expected_call(
1967
'Repository.insert_stream', ('quack/', ''),
1969
sink = repo._get_sink()
1970
fmt = repository.RepositoryFormat.get_default_format()
1971
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1972
self.assertEqual([], resume_tokens)
1973
self.assertEqual(set(), missing_keys)
1974
client.finished_test()
1976
def test_locked_repo_with_no_lock_token(self):
1977
transport_path = 'quack'
1978
repo, client = self.setup_fake_client_and_repository(transport_path)
1979
client.add_expected_call(
1980
'Repository.lock_write', ('quack/', ''),
1981
'success', ('ok', ''))
1982
client.add_expected_call(
1983
'Repository.insert_stream', ('quack/', ''),
1985
client.add_expected_call(
1986
'Repository.insert_stream', ('quack/', ''),
1989
sink = repo._get_sink()
1990
fmt = repository.RepositoryFormat.get_default_format()
1991
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1992
self.assertEqual([], resume_tokens)
1993
self.assertEqual(set(), missing_keys)
1994
client.finished_test()
1996
def test_locked_repo_with_lock_token(self):
1997
transport_path = 'quack'
1998
repo, client = self.setup_fake_client_and_repository(transport_path)
1999
client.add_expected_call(
2000
'Repository.lock_write', ('quack/', ''),
2001
'success', ('ok', 'a token'))
2002
client.add_expected_call(
2003
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2005
client.add_expected_call(
2006
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2009
sink = repo._get_sink()
2010
fmt = repository.RepositoryFormat.get_default_format()
2011
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2012
self.assertEqual([], resume_tokens)
2013
self.assertEqual(set(), missing_keys)
2014
client.finished_test()
2017
class TestRepositoryTarball(TestRemoteRepository):
2019
# This is a canned tarball reponse we can validate against
2021
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2022
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2023
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2024
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2025
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2026
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2027
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2028
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2029
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2030
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2031
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2032
'nWQ7QH/F3JFOFCQ0aSPfA='
2035
def test_repository_tarball(self):
2036
# Test that Repository.tarball generates the right operations
2037
transport_path = 'repo'
2038
expected_calls = [('call_expecting_body', 'Repository.tarball',
2039
('repo/', 'bz2',),),
2041
repo, client = self.setup_fake_client_and_repository(transport_path)
2042
client.add_success_response_with_body(self.tarball_content, 'ok')
2043
# Now actually ask for the tarball
2044
tarball_file = repo._get_tarball('bz2')
2046
self.assertEqual(expected_calls, client._calls)
2047
self.assertEqual(self.tarball_content, tarball_file.read())
2049
tarball_file.close()
2052
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2053
"""RemoteRepository.copy_content_into optimizations"""
2055
def test_copy_content_remote_to_local(self):
2056
self.transport_server = server.SmartTCPServer_for_testing
2057
src_repo = self.make_repository('repo1')
2058
src_repo = repository.Repository.open(self.get_url('repo1'))
2059
# At the moment the tarball-based copy_content_into can't write back
2060
# into a smart server. It would be good if it could upload the
2061
# tarball; once that works we'd have to create repositories of
2062
# different formats. -- mbp 20070410
2063
dest_url = self.get_vfs_only_url('repo2')
2064
dest_bzrdir = BzrDir.create(dest_url)
2065
dest_repo = dest_bzrdir.create_repository()
2066
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2067
self.assertTrue(isinstance(src_repo, RemoteRepository))
2068
src_repo.copy_content_into(dest_repo)
2071
class _StubRealPackRepository(object):
2073
def __init__(self, calls):
2075
self._pack_collection = _StubPackCollection(calls)
2077
def is_in_write_group(self):
2080
def refresh_data(self):
2081
self.calls.append(('pack collection reload_pack_names',))
2084
class _StubPackCollection(object):
2086
def __init__(self, calls):
2090
self.calls.append(('pack collection autopack',))
2093
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2094
"""Tests for RemoteRepository.autopack implementation."""
2097
"""When the server returns 'ok' and there's no _real_repository, then
2098
nothing else happens: the autopack method is done.
2100
transport_path = 'quack'
2101
repo, client = self.setup_fake_client_and_repository(transport_path)
2102
client.add_expected_call(
2103
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2105
client.finished_test()
2107
def test_ok_with_real_repo(self):
2108
"""When the server returns 'ok' and there is a _real_repository, then
2109
the _real_repository's reload_pack_name's method will be called.
2111
transport_path = 'quack'
2112
repo, client = self.setup_fake_client_and_repository(transport_path)
2113
client.add_expected_call(
2114
'PackRepository.autopack', ('quack/',),
2116
repo._real_repository = _StubRealPackRepository(client._calls)
2119
[('call', 'PackRepository.autopack', ('quack/',)),
2120
('pack collection reload_pack_names',)],
2123
def test_backwards_compatibility(self):
2124
"""If the server does not recognise the PackRepository.autopack verb,
2125
fallback to the real_repository's implementation.
2127
transport_path = 'quack'
2128
repo, client = self.setup_fake_client_and_repository(transport_path)
2129
client.add_unknown_method_response('PackRepository.autopack')
2130
def stub_ensure_real():
2131
client._calls.append(('_ensure_real',))
2132
repo._real_repository = _StubRealPackRepository(client._calls)
2133
repo._ensure_real = stub_ensure_real
2136
[('call', 'PackRepository.autopack', ('quack/',)),
2138
('pack collection autopack',)],
2142
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2143
"""Base class for unit tests for bzrlib.remote._translate_error."""
2145
def translateTuple(self, error_tuple, **context):
2146
"""Call _translate_error with an ErrorFromSmartServer built from the
2149
:param error_tuple: A tuple of a smart server response, as would be
2150
passed to an ErrorFromSmartServer.
2151
:kwargs context: context items to call _translate_error with.
2153
:returns: The error raised by _translate_error.
2155
# Raise the ErrorFromSmartServer before passing it as an argument,
2156
# because _translate_error may need to re-raise it with a bare 'raise'
2158
server_error = errors.ErrorFromSmartServer(error_tuple)
2159
translated_error = self.translateErrorFromSmartServer(
2160
server_error, **context)
2161
return translated_error
2163
def translateErrorFromSmartServer(self, error_object, **context):
2164
"""Like translateTuple, but takes an already constructed
2165
ErrorFromSmartServer rather than a tuple.
2169
except errors.ErrorFromSmartServer, server_error:
2170
translated_error = self.assertRaises(
2171
errors.BzrError, remote._translate_error, server_error,
2173
return translated_error
2176
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2177
"""Unit tests for bzrlib.remote._translate_error.
2179
Given an ErrorFromSmartServer (which has an error tuple from a smart
2180
server) and some context, _translate_error raises more specific errors from
2183
This test case covers the cases where _translate_error succeeds in
2184
translating an ErrorFromSmartServer to something better. See
2185
TestErrorTranslationRobustness for other cases.
2188
def test_NoSuchRevision(self):
2189
branch = self.make_branch('')
2191
translated_error = self.translateTuple(
2192
('NoSuchRevision', revid), branch=branch)
2193
expected_error = errors.NoSuchRevision(branch, revid)
2194
self.assertEqual(expected_error, translated_error)
2196
def test_nosuchrevision(self):
2197
repository = self.make_repository('')
2199
translated_error = self.translateTuple(
2200
('nosuchrevision', revid), repository=repository)
2201
expected_error = errors.NoSuchRevision(repository, revid)
2202
self.assertEqual(expected_error, translated_error)
2204
def test_nobranch(self):
2205
bzrdir = self.make_bzrdir('')
2206
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2207
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2208
self.assertEqual(expected_error, translated_error)
2210
def test_LockContention(self):
2211
translated_error = self.translateTuple(('LockContention',))
2212
expected_error = errors.LockContention('(remote lock)')
2213
self.assertEqual(expected_error, translated_error)
2215
def test_UnlockableTransport(self):
2216
bzrdir = self.make_bzrdir('')
2217
translated_error = self.translateTuple(
2218
('UnlockableTransport',), bzrdir=bzrdir)
2219
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2220
self.assertEqual(expected_error, translated_error)
2222
def test_LockFailed(self):
2223
lock = 'str() of a server lock'
2224
why = 'str() of why'
2225
translated_error = self.translateTuple(('LockFailed', lock, why))
2226
expected_error = errors.LockFailed(lock, why)
2227
self.assertEqual(expected_error, translated_error)
2229
def test_TokenMismatch(self):
2230
token = 'a lock token'
2231
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2232
expected_error = errors.TokenMismatch(token, '(remote token)')
2233
self.assertEqual(expected_error, translated_error)
2235
def test_Diverged(self):
2236
branch = self.make_branch('a')
2237
other_branch = self.make_branch('b')
2238
translated_error = self.translateTuple(
2239
('Diverged',), branch=branch, other_branch=other_branch)
2240
expected_error = errors.DivergedBranches(branch, other_branch)
2241
self.assertEqual(expected_error, translated_error)
2243
def test_ReadError_no_args(self):
2245
translated_error = self.translateTuple(('ReadError',), path=path)
2246
expected_error = errors.ReadError(path)
2247
self.assertEqual(expected_error, translated_error)
2249
def test_ReadError(self):
2251
translated_error = self.translateTuple(('ReadError', path))
2252
expected_error = errors.ReadError(path)
2253
self.assertEqual(expected_error, translated_error)
2255
def test_PermissionDenied_no_args(self):
2257
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2258
expected_error = errors.PermissionDenied(path)
2259
self.assertEqual(expected_error, translated_error)
2261
def test_PermissionDenied_one_arg(self):
2263
translated_error = self.translateTuple(('PermissionDenied', path))
2264
expected_error = errors.PermissionDenied(path)
2265
self.assertEqual(expected_error, translated_error)
2267
def test_PermissionDenied_one_arg_and_context(self):
2268
"""Given a choice between a path from the local context and a path on
2269
the wire, _translate_error prefers the path from the local context.
2271
local_path = 'local path'
2272
remote_path = 'remote path'
2273
translated_error = self.translateTuple(
2274
('PermissionDenied', remote_path), path=local_path)
2275
expected_error = errors.PermissionDenied(local_path)
2276
self.assertEqual(expected_error, translated_error)
2278
def test_PermissionDenied_two_args(self):
2280
extra = 'a string with extra info'
2281
translated_error = self.translateTuple(
2282
('PermissionDenied', path, extra))
2283
expected_error = errors.PermissionDenied(path, extra)
2284
self.assertEqual(expected_error, translated_error)
2287
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2288
"""Unit tests for bzrlib.remote._translate_error's robustness.
2290
TestErrorTranslationSuccess is for cases where _translate_error can
2291
translate successfully. This class about how _translate_err behaves when
2292
it fails to translate: it re-raises the original error.
2295
def test_unrecognised_server_error(self):
2296
"""If the error code from the server is not recognised, the original
2297
ErrorFromSmartServer is propagated unmodified.
2299
error_tuple = ('An unknown error tuple',)
2300
server_error = errors.ErrorFromSmartServer(error_tuple)
2301
translated_error = self.translateErrorFromSmartServer(server_error)
2302
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2303
self.assertEqual(expected_error, translated_error)
2305
def test_context_missing_a_key(self):
2306
"""In case of a bug in the client, or perhaps an unexpected response
2307
from a server, _translate_error returns the original error tuple from
2308
the server and mutters a warning.
2310
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2311
# in the context dict. So let's give it an empty context dict instead
2312
# to exercise its error recovery.
2314
error_tuple = ('NoSuchRevision', 'revid')
2315
server_error = errors.ErrorFromSmartServer(error_tuple)
2316
translated_error = self.translateErrorFromSmartServer(server_error)
2317
self.assertEqual(server_error, translated_error)
2318
# In addition to re-raising ErrorFromSmartServer, some debug info has
2319
# been muttered to the log file for developer to look at.
2320
self.assertContainsRe(
2321
self._get_log(keep_log_file=True),
2322
"Missing key 'branch' in context")
2324
def test_path_missing(self):
2325
"""Some translations (PermissionDenied, ReadError) can determine the
2326
'path' variable from either the wire or the local context. If neither
2327
has it, then an error is raised.
2329
error_tuple = ('ReadError',)
2330
server_error = errors.ErrorFromSmartServer(error_tuple)
2331
translated_error = self.translateErrorFromSmartServer(server_error)
2332
self.assertEqual(server_error, translated_error)
2333
# In addition to re-raising ErrorFromSmartServer, some debug info has
2334
# been muttered to the log file for developer to look at.
2335
self.assertContainsRe(
2336
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2339
class TestStacking(tests.TestCaseWithTransport):
2340
"""Tests for operations on stacked remote repositories.
2342
The underlying format type must support stacking.
2345
def test_access_stacked_remote(self):
2346
# based on <http://launchpad.net/bugs/261315>
2347
# make a branch stacked on another repository containing an empty
2348
# revision, then open it over hpss - we should be able to see that
2350
base_transport = self.get_transport()
2351
base_builder = self.make_branch_builder('base', format='1.9')
2352
base_builder.start_series()
2353
base_revid = base_builder.build_snapshot('rev-id', None,
2354
[('add', ('', None, 'directory', None))],
2356
base_builder.finish_series()
2357
stacked_branch = self.make_branch('stacked', format='1.9')
2358
stacked_branch.set_stacked_on_url('../base')
2359
# start a server looking at this
2360
smart_server = server.SmartTCPServer_for_testing()
2361
smart_server.setUp()
2362
self.addCleanup(smart_server.tearDown)
2363
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2364
# can get its branch and repository
2365
remote_branch = remote_bzrdir.open_branch()
2366
remote_repo = remote_branch.repository
2367
remote_repo.lock_read()
2369
# it should have an appropriate fallback repository, which should also
2370
# be a RemoteRepository
2371
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2372
self.assertIsInstance(remote_repo._fallback_repositories[0],
2374
# and it has the revision committed to the underlying repository;
2375
# these have varying implementations so we try several of them
2376
self.assertTrue(remote_repo.has_revisions([base_revid]))
2377
self.assertTrue(remote_repo.has_revision(base_revid))
2378
self.assertEqual(remote_repo.get_revision(base_revid).message,
2381
remote_repo.unlock()
2383
def prepare_stacked_remote_branch(self):
2384
"""Get stacked_upon and stacked branches with content in each."""
2385
self.setup_smart_server_with_call_log()
2386
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2387
tree1.commit('rev1', rev_id='rev1')
2388
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2389
).open_workingtree()
2390
tree2.commit('local changes make me feel good.')
2391
branch2 = Branch.open(self.get_url('tree2'))
2393
self.addCleanup(branch2.unlock)
2394
return tree1.branch, branch2
2396
def test_stacked_get_parent_map(self):
2397
# the public implementation of get_parent_map obeys stacking
2398
_, branch = self.prepare_stacked_remote_branch()
2399
repo = branch.repository
2400
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2402
def test_unstacked_get_parent_map(self):
2403
# _unstacked_provider.get_parent_map ignores stacking
2404
_, branch = self.prepare_stacked_remote_branch()
2405
provider = branch.repository._unstacked_provider
2406
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2408
def fetch_stream_to_rev_order(self, stream):
2410
for kind, substream in stream:
2411
if not kind == 'revisions':
2414
for content in substream:
2415
result.append(content.key[-1])
2418
def get_ordered_revs(self, format, order):
2419
"""Get a list of the revisions in a stream to format format.
2421
:param format: The format of the target.
2422
:param order: the order that target should have requested.
2423
:result: The revision ids in the stream, in the order seen,
2424
the topological order of revisions in the source.
2426
unordered_format = bzrdir.format_registry.get(format)()
2427
target_repository_format = unordered_format.repository_format
2429
self.assertEqual(order, target_repository_format._fetch_order)
2430
trunk, stacked = self.prepare_stacked_remote_branch()
2431
source = stacked.repository._get_source(target_repository_format)
2432
tip = stacked.last_revision()
2433
revs = stacked.repository.get_ancestry(tip)
2434
search = graph.PendingAncestryResult([tip], stacked.repository)
2435
self.reset_smart_call_log()
2436
stream = source.get_stream(search)
2439
# We trust that if a revision is in the stream the rest of the new
2440
# content for it is too, as per our main fetch tests; here we are
2441
# checking that the revisions are actually included at all, and their
2443
return self.fetch_stream_to_rev_order(stream), revs
2445
def test_stacked_get_stream_unordered(self):
2446
# Repository._get_source.get_stream() from a stacked repository with
2447
# unordered yields the full data from both stacked and stacked upon
2449
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2450
self.assertEqual(set(expected_revs), set(rev_ord))
2451
# Getting unordered results should have made a streaming data request
2452
# from the server, then one from the backing branch.
2453
self.assertLength(2, self.hpss_calls)
2455
def test_stacked_get_stream_topological(self):
2456
# Repository._get_source.get_stream() from a stacked repository with
2457
# topological sorting yields the full data from both stacked and
2458
# stacked upon sources in topological order.
2459
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2460
self.assertEqual(expected_revs, rev_ord)
2461
# Getting topological sort requires VFS calls still
2462
self.assertLength(12, self.hpss_calls)
2464
def test_stacked_get_stream_groupcompress(self):
2465
# Repository._get_source.get_stream() from a stacked repository with
2466
# groupcompress sorting yields the full data from both stacked and
2467
# stacked upon sources in groupcompress order.
2468
raise tests.TestSkipped('No groupcompress ordered format available')
2469
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2470
self.assertEqual(expected_revs, reversed(rev_ord))
2471
# Getting unordered results should have made a streaming data request
2472
# from the backing branch, and one from the stacked on branch.
2473
self.assertLength(2, self.hpss_calls)
2476
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2479
super(TestRemoteBranchEffort, self).setUp()
2480
# Create a smart server that publishes whatever the backing VFS server
2482
self.smart_server = server.SmartTCPServer_for_testing()
2483
self.smart_server.setUp(self.get_server())
2484
self.addCleanup(self.smart_server.tearDown)
2485
# Log all HPSS calls into self.hpss_calls.
2486
_SmartClient.hooks.install_named_hook(
2487
'call', self.capture_hpss_call, None)
2488
self.hpss_calls = []
2490
def capture_hpss_call(self, params):
2491
self.hpss_calls.append(params.method)
2493
def test_copy_content_into_avoids_revision_history(self):
2494
local = self.make_branch('local')
2495
remote_backing_tree = self.make_branch_and_tree('remote')
2496
remote_backing_tree.commit("Commit.")
2497
remote_branch_url = self.smart_server.get_url() + 'remote'
2498
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2499
local.repository.fetch(remote_branch.repository)
2500
self.hpss_calls = []
2501
remote_branch.copy_content_into(local)
2502
self.assertFalse('Branch.revision_history' in self.hpss_calls)