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.
1003
self.assertEqual(1, len(branch.repository._fallback_repositories))
1004
# And we haven't had to construct a real repository.
1005
self.assertEqual(None, branch.repository._real_repository)
1008
class TestBranchSetLastRevision(RemoteBranchTestCase):
1010
def test_set_empty(self):
1011
# set_revision_history([]) is translated to calling
1012
# Branch.set_last_revision(path, '') on the wire.
1013
transport = MemoryTransport()
1014
transport.mkdir('branch')
1015
transport = transport.clone('branch')
1017
client = FakeClient(transport.base)
1018
client.add_expected_call(
1019
'Branch.get_stacked_on_url', ('branch/',),
1020
'error', ('NotStacked',))
1021
client.add_expected_call(
1022
'Branch.lock_write', ('branch/', '', ''),
1023
'success', ('ok', 'branch token', 'repo token'))
1024
client.add_expected_call(
1025
'Branch.last_revision_info',
1027
'success', ('ok', '0', 'null:'))
1028
client.add_expected_call(
1029
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1031
client.add_expected_call(
1032
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1034
branch = self.make_remote_branch(transport, client)
1035
# This is a hack to work around the problem that RemoteBranch currently
1036
# unnecessarily invokes _ensure_real upon a call to lock_write.
1037
branch._ensure_real = lambda: None
1039
result = branch.set_revision_history([])
1041
self.assertEqual(None, result)
1042
client.finished_test()
1044
def test_set_nonempty(self):
1045
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1046
# Branch.set_last_revision(path, rev-idN) on the wire.
1047
transport = MemoryTransport()
1048
transport.mkdir('branch')
1049
transport = transport.clone('branch')
1051
client = FakeClient(transport.base)
1052
client.add_expected_call(
1053
'Branch.get_stacked_on_url', ('branch/',),
1054
'error', ('NotStacked',))
1055
client.add_expected_call(
1056
'Branch.lock_write', ('branch/', '', ''),
1057
'success', ('ok', 'branch token', 'repo token'))
1058
client.add_expected_call(
1059
'Branch.last_revision_info',
1061
'success', ('ok', '0', 'null:'))
1063
encoded_body = bz2.compress('\n'.join(lines))
1064
client.add_success_response_with_body(encoded_body, 'ok')
1065
client.add_expected_call(
1066
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1068
client.add_expected_call(
1069
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1071
branch = self.make_remote_branch(transport, client)
1072
# This is a hack to work around the problem that RemoteBranch currently
1073
# unnecessarily invokes _ensure_real upon a call to lock_write.
1074
branch._ensure_real = lambda: None
1075
# Lock the branch, reset the record of remote calls.
1077
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1079
self.assertEqual(None, result)
1080
client.finished_test()
1082
def test_no_such_revision(self):
1083
transport = MemoryTransport()
1084
transport.mkdir('branch')
1085
transport = transport.clone('branch')
1086
# A response of 'NoSuchRevision' is translated into an exception.
1087
client = FakeClient(transport.base)
1088
client.add_expected_call(
1089
'Branch.get_stacked_on_url', ('branch/',),
1090
'error', ('NotStacked',))
1091
client.add_expected_call(
1092
'Branch.lock_write', ('branch/', '', ''),
1093
'success', ('ok', 'branch token', 'repo token'))
1094
client.add_expected_call(
1095
'Branch.last_revision_info',
1097
'success', ('ok', '0', 'null:'))
1098
# get_graph calls to construct the revision history, for the set_rh
1101
encoded_body = bz2.compress('\n'.join(lines))
1102
client.add_success_response_with_body(encoded_body, 'ok')
1103
client.add_expected_call(
1104
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1105
'error', ('NoSuchRevision', 'rev-id'))
1106
client.add_expected_call(
1107
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1110
branch = self.make_remote_branch(transport, client)
1113
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1115
client.finished_test()
1117
def test_tip_change_rejected(self):
1118
"""TipChangeRejected responses cause a TipChangeRejected exception to
1121
transport = MemoryTransport()
1122
transport.mkdir('branch')
1123
transport = transport.clone('branch')
1124
client = FakeClient(transport.base)
1125
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1126
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1127
client.add_expected_call(
1128
'Branch.get_stacked_on_url', ('branch/',),
1129
'error', ('NotStacked',))
1130
client.add_expected_call(
1131
'Branch.lock_write', ('branch/', '', ''),
1132
'success', ('ok', 'branch token', 'repo token'))
1133
client.add_expected_call(
1134
'Branch.last_revision_info',
1136
'success', ('ok', '0', 'null:'))
1138
encoded_body = bz2.compress('\n'.join(lines))
1139
client.add_success_response_with_body(encoded_body, 'ok')
1140
client.add_expected_call(
1141
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1142
'error', ('TipChangeRejected', rejection_msg_utf8))
1143
client.add_expected_call(
1144
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1146
branch = self.make_remote_branch(transport, client)
1147
branch._ensure_real = lambda: None
1149
# The 'TipChangeRejected' error response triggered by calling
1150
# set_revision_history causes a TipChangeRejected exception.
1151
err = self.assertRaises(
1152
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1153
# The UTF-8 message from the response has been decoded into a unicode
1155
self.assertIsInstance(err.msg, unicode)
1156
self.assertEqual(rejection_msg_unicode, err.msg)
1158
client.finished_test()
1161
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1163
def test_set_last_revision_info(self):
1164
# set_last_revision_info(num, 'rev-id') is translated to calling
1165
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1166
transport = MemoryTransport()
1167
transport.mkdir('branch')
1168
transport = transport.clone('branch')
1169
client = FakeClient(transport.base)
1170
# get_stacked_on_url
1171
client.add_error_response('NotStacked')
1173
client.add_success_response('ok', 'branch token', 'repo token')
1174
# query the current revision
1175
client.add_success_response('ok', '0', 'null:')
1177
client.add_success_response('ok')
1179
client.add_success_response('ok')
1181
branch = self.make_remote_branch(transport, client)
1182
# Lock the branch, reset the record of remote calls.
1185
result = branch.set_last_revision_info(1234, 'a-revision-id')
1187
[('call', 'Branch.last_revision_info', ('branch/',)),
1188
('call', 'Branch.set_last_revision_info',
1189
('branch/', 'branch token', 'repo token',
1190
'1234', 'a-revision-id'))],
1192
self.assertEqual(None, result)
1194
def test_no_such_revision(self):
1195
# A response of 'NoSuchRevision' is translated into an exception.
1196
transport = MemoryTransport()
1197
transport.mkdir('branch')
1198
transport = transport.clone('branch')
1199
client = FakeClient(transport.base)
1200
# get_stacked_on_url
1201
client.add_error_response('NotStacked')
1203
client.add_success_response('ok', 'branch token', 'repo token')
1205
client.add_error_response('NoSuchRevision', 'revid')
1207
client.add_success_response('ok')
1209
branch = self.make_remote_branch(transport, client)
1210
# Lock the branch, reset the record of remote calls.
1215
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1218
def lock_remote_branch(self, branch):
1219
"""Trick a RemoteBranch into thinking it is locked."""
1220
branch._lock_mode = 'w'
1221
branch._lock_count = 2
1222
branch._lock_token = 'branch token'
1223
branch._repo_lock_token = 'repo token'
1224
branch.repository._lock_mode = 'w'
1225
branch.repository._lock_count = 2
1226
branch.repository._lock_token = 'repo token'
1228
def test_backwards_compatibility(self):
1229
"""If the server does not support the Branch.set_last_revision_info
1230
verb (which is new in 1.4), then the client falls back to VFS methods.
1232
# This test is a little messy. Unlike most tests in this file, it
1233
# doesn't purely test what a Remote* object sends over the wire, and
1234
# how it reacts to responses from the wire. It instead relies partly
1235
# on asserting that the RemoteBranch will call
1236
# self._real_branch.set_last_revision_info(...).
1238
# First, set up our RemoteBranch with a FakeClient that raises
1239
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1240
transport = MemoryTransport()
1241
transport.mkdir('branch')
1242
transport = transport.clone('branch')
1243
client = FakeClient(transport.base)
1244
client.add_expected_call(
1245
'Branch.get_stacked_on_url', ('branch/',),
1246
'error', ('NotStacked',))
1247
client.add_expected_call(
1248
'Branch.last_revision_info',
1250
'success', ('ok', '0', 'null:'))
1251
client.add_expected_call(
1252
'Branch.set_last_revision_info',
1253
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1254
'unknown', 'Branch.set_last_revision_info')
1256
branch = self.make_remote_branch(transport, client)
1257
class StubRealBranch(object):
1260
def set_last_revision_info(self, revno, revision_id):
1262
('set_last_revision_info', revno, revision_id))
1263
def _clear_cached_state(self):
1265
real_branch = StubRealBranch()
1266
branch._real_branch = real_branch
1267
self.lock_remote_branch(branch)
1269
# Call set_last_revision_info, and verify it behaved as expected.
1270
result = branch.set_last_revision_info(1234, 'a-revision-id')
1272
[('set_last_revision_info', 1234, 'a-revision-id')],
1274
client.finished_test()
1276
def test_unexpected_error(self):
1277
# If the server sends an error the client doesn't understand, it gets
1278
# turned into an UnknownErrorFromSmartServer, which is presented as a
1279
# non-internal error to the user.
1280
transport = MemoryTransport()
1281
transport.mkdir('branch')
1282
transport = transport.clone('branch')
1283
client = FakeClient(transport.base)
1284
# get_stacked_on_url
1285
client.add_error_response('NotStacked')
1287
client.add_success_response('ok', 'branch token', 'repo token')
1289
client.add_error_response('UnexpectedError')
1291
client.add_success_response('ok')
1293
branch = self.make_remote_branch(transport, client)
1294
# Lock the branch, reset the record of remote calls.
1298
err = self.assertRaises(
1299
errors.UnknownErrorFromSmartServer,
1300
branch.set_last_revision_info, 123, 'revid')
1301
self.assertEqual(('UnexpectedError',), err.error_tuple)
1304
def test_tip_change_rejected(self):
1305
"""TipChangeRejected responses cause a TipChangeRejected exception to
1308
transport = MemoryTransport()
1309
transport.mkdir('branch')
1310
transport = transport.clone('branch')
1311
client = FakeClient(transport.base)
1312
# get_stacked_on_url
1313
client.add_error_response('NotStacked')
1315
client.add_success_response('ok', 'branch token', 'repo token')
1317
client.add_error_response('TipChangeRejected', 'rejection message')
1319
client.add_success_response('ok')
1321
branch = self.make_remote_branch(transport, client)
1322
# Lock the branch, reset the record of remote calls.
1324
self.addCleanup(branch.unlock)
1327
# The 'TipChangeRejected' error response triggered by calling
1328
# set_last_revision_info causes a TipChangeRejected exception.
1329
err = self.assertRaises(
1330
errors.TipChangeRejected,
1331
branch.set_last_revision_info, 123, 'revid')
1332
self.assertEqual('rejection message', err.msg)
1335
class TestBranchGetSetConfig(RemoteBranchTestCase):
1337
def test_get_branch_conf(self):
1338
# in an empty branch we decode the response properly
1339
client = FakeClient()
1340
client.add_expected_call(
1341
'Branch.get_stacked_on_url', ('memory:///',),
1342
'error', ('NotStacked',),)
1343
client.add_success_response_with_body('# config file body', 'ok')
1344
transport = MemoryTransport()
1345
branch = self.make_remote_branch(transport, client)
1346
config = branch.get_config()
1347
config.has_explicit_nickname()
1349
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1350
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1353
def test_get_multi_line_branch_conf(self):
1354
# Make sure that multiple-line branch.conf files are supported
1356
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1357
client = FakeClient()
1358
client.add_expected_call(
1359
'Branch.get_stacked_on_url', ('memory:///',),
1360
'error', ('NotStacked',),)
1361
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1362
transport = MemoryTransport()
1363
branch = self.make_remote_branch(transport, client)
1364
config = branch.get_config()
1365
self.assertEqual(u'2', config.get_user_option('b'))
1367
def test_set_option(self):
1368
client = FakeClient()
1369
client.add_expected_call(
1370
'Branch.get_stacked_on_url', ('memory:///',),
1371
'error', ('NotStacked',),)
1372
client.add_expected_call(
1373
'Branch.lock_write', ('memory:///', '', ''),
1374
'success', ('ok', 'branch token', 'repo token'))
1375
client.add_expected_call(
1376
'Branch.set_config_option', ('memory:///', 'branch token',
1377
'repo token', 'foo', 'bar', ''),
1379
client.add_expected_call(
1380
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1382
transport = MemoryTransport()
1383
branch = self.make_remote_branch(transport, client)
1385
config = branch._get_config()
1386
config.set_option('foo', 'bar')
1388
client.finished_test()
1390
def test_backwards_compat_set_option(self):
1391
self.setup_smart_server_with_call_log()
1392
branch = self.make_branch('.')
1393
verb = 'Branch.set_config_option'
1394
self.disable_verb(verb)
1396
self.addCleanup(branch.unlock)
1397
self.reset_smart_call_log()
1398
branch._get_config().set_option('value', 'name')
1399
self.assertLength(10, self.hpss_calls)
1400
self.assertEqual('value', branch._get_config().get_option('name'))
1403
class TestBranchLockWrite(RemoteBranchTestCase):
1405
def test_lock_write_unlockable(self):
1406
transport = MemoryTransport()
1407
client = FakeClient(transport.base)
1408
client.add_expected_call(
1409
'Branch.get_stacked_on_url', ('quack/',),
1410
'error', ('NotStacked',),)
1411
client.add_expected_call(
1412
'Branch.lock_write', ('quack/', '', ''),
1413
'error', ('UnlockableTransport',))
1414
transport.mkdir('quack')
1415
transport = transport.clone('quack')
1416
branch = self.make_remote_branch(transport, client)
1417
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1418
client.finished_test()
1421
class TestTransportIsReadonly(tests.TestCase):
1423
def test_true(self):
1424
client = FakeClient()
1425
client.add_success_response('yes')
1426
transport = RemoteTransport('bzr://example.com/', medium=False,
1428
self.assertEqual(True, transport.is_readonly())
1430
[('call', 'Transport.is_readonly', ())],
1433
def test_false(self):
1434
client = FakeClient()
1435
client.add_success_response('no')
1436
transport = RemoteTransport('bzr://example.com/', medium=False,
1438
self.assertEqual(False, transport.is_readonly())
1440
[('call', 'Transport.is_readonly', ())],
1443
def test_error_from_old_server(self):
1444
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1446
Clients should treat it as a "no" response, because is_readonly is only
1447
advisory anyway (a transport could be read-write, but then the
1448
underlying filesystem could be readonly anyway).
1450
client = FakeClient()
1451
client.add_unknown_method_response('Transport.is_readonly')
1452
transport = RemoteTransport('bzr://example.com/', medium=False,
1454
self.assertEqual(False, transport.is_readonly())
1456
[('call', 'Transport.is_readonly', ())],
1460
class TestTransportMkdir(tests.TestCase):
1462
def test_permissiondenied(self):
1463
client = FakeClient()
1464
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1465
transport = RemoteTransport('bzr://example.com/', medium=False,
1467
exc = self.assertRaises(
1468
errors.PermissionDenied, transport.mkdir, 'client path')
1469
expected_error = errors.PermissionDenied('/client path', 'extra')
1470
self.assertEqual(expected_error, exc)
1473
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1475
def test_defaults_to_none(self):
1476
t = RemoteSSHTransport('bzr+ssh://example.com')
1477
self.assertIs(None, t._get_credentials()[0])
1479
def test_uses_authentication_config(self):
1480
conf = config.AuthenticationConfig()
1481
conf._get_config().update(
1482
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1485
t = RemoteSSHTransport('bzr+ssh://example.com')
1486
self.assertEqual('bar', t._get_credentials()[0])
1489
class TestRemoteRepository(TestRemote):
1490
"""Base for testing RemoteRepository protocol usage.
1492
These tests contain frozen requests and responses. We want any changes to
1493
what is sent or expected to be require a thoughtful update to these tests
1494
because they might break compatibility with different-versioned servers.
1497
def setup_fake_client_and_repository(self, transport_path):
1498
"""Create the fake client and repository for testing with.
1500
There's no real server here; we just have canned responses sent
1503
:param transport_path: Path below the root of the MemoryTransport
1504
where the repository will be created.
1506
transport = MemoryTransport()
1507
transport.mkdir(transport_path)
1508
client = FakeClient(transport.base)
1509
transport = transport.clone(transport_path)
1510
# we do not want bzrdir to make any remote calls
1511
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1513
repo = RemoteRepository(bzrdir, None, _client=client)
1517
class TestRepositoryFormat(TestRemoteRepository):
1519
def test_fast_delta(self):
1520
true_name = pack_repo.RepositoryFormatPackDevelopment2().network_name()
1521
true_format = RemoteRepositoryFormat()
1522
true_format._network_name = true_name
1523
self.assertEqual(True, true_format.fast_deltas)
1524
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1525
false_format = RemoteRepositoryFormat()
1526
false_format._network_name = false_name
1527
self.assertEqual(False, false_format.fast_deltas)
1530
class TestRepositoryGatherStats(TestRemoteRepository):
1532
def test_revid_none(self):
1533
# ('ok',), body with revisions and size
1534
transport_path = 'quack'
1535
repo, client = self.setup_fake_client_and_repository(transport_path)
1536
client.add_success_response_with_body(
1537
'revisions: 2\nsize: 18\n', 'ok')
1538
result = repo.gather_stats(None)
1540
[('call_expecting_body', 'Repository.gather_stats',
1541
('quack/','','no'))],
1543
self.assertEqual({'revisions': 2, 'size': 18}, result)
1545
def test_revid_no_committers(self):
1546
# ('ok',), body without committers
1547
body = ('firstrev: 123456.300 3600\n'
1548
'latestrev: 654231.400 0\n'
1551
transport_path = 'quick'
1552
revid = u'\xc8'.encode('utf8')
1553
repo, client = self.setup_fake_client_and_repository(transport_path)
1554
client.add_success_response_with_body(body, 'ok')
1555
result = repo.gather_stats(revid)
1557
[('call_expecting_body', 'Repository.gather_stats',
1558
('quick/', revid, 'no'))],
1560
self.assertEqual({'revisions': 2, 'size': 18,
1561
'firstrev': (123456.300, 3600),
1562
'latestrev': (654231.400, 0),},
1565
def test_revid_with_committers(self):
1566
# ('ok',), body with committers
1567
body = ('committers: 128\n'
1568
'firstrev: 123456.300 3600\n'
1569
'latestrev: 654231.400 0\n'
1572
transport_path = 'buick'
1573
revid = u'\xc8'.encode('utf8')
1574
repo, client = self.setup_fake_client_and_repository(transport_path)
1575
client.add_success_response_with_body(body, 'ok')
1576
result = repo.gather_stats(revid, True)
1578
[('call_expecting_body', 'Repository.gather_stats',
1579
('buick/', revid, 'yes'))],
1581
self.assertEqual({'revisions': 2, 'size': 18,
1583
'firstrev': (123456.300, 3600),
1584
'latestrev': (654231.400, 0),},
1588
class TestRepositoryGetGraph(TestRemoteRepository):
1590
def test_get_graph(self):
1591
# get_graph returns a graph with a custom parents provider.
1592
transport_path = 'quack'
1593
repo, client = self.setup_fake_client_and_repository(transport_path)
1594
graph = repo.get_graph()
1595
self.assertNotEqual(graph._parents_provider, repo)
1598
class TestRepositoryGetParentMap(TestRemoteRepository):
1600
def test_get_parent_map_caching(self):
1601
# get_parent_map returns from cache until unlock()
1602
# setup a reponse with two revisions
1603
r1 = u'\u0e33'.encode('utf8')
1604
r2 = u'\u0dab'.encode('utf8')
1605
lines = [' '.join([r2, r1]), r1]
1606
encoded_body = bz2.compress('\n'.join(lines))
1608
transport_path = 'quack'
1609
repo, client = self.setup_fake_client_and_repository(transport_path)
1610
client.add_success_response_with_body(encoded_body, 'ok')
1611
client.add_success_response_with_body(encoded_body, 'ok')
1613
graph = repo.get_graph()
1614
parents = graph.get_parent_map([r2])
1615
self.assertEqual({r2: (r1,)}, parents)
1616
# locking and unlocking deeper should not reset
1619
parents = graph.get_parent_map([r1])
1620
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1622
[('call_with_body_bytes_expecting_body',
1623
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1627
# now we call again, and it should use the second response.
1629
graph = repo.get_graph()
1630
parents = graph.get_parent_map([r1])
1631
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1633
[('call_with_body_bytes_expecting_body',
1634
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1636
('call_with_body_bytes_expecting_body',
1637
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1643
def test_get_parent_map_reconnects_if_unknown_method(self):
1644
transport_path = 'quack'
1645
rev_id = 'revision-id'
1646
repo, client = self.setup_fake_client_and_repository(transport_path)
1647
client.add_unknown_method_response('Repository.get_parent_map')
1648
client.add_success_response_with_body(rev_id, 'ok')
1649
self.assertFalse(client._medium._is_remote_before((1, 2)))
1650
parents = repo.get_parent_map([rev_id])
1652
[('call_with_body_bytes_expecting_body',
1653
'Repository.get_parent_map', ('quack/', 'include-missing:',
1655
('disconnect medium',),
1656
('call_expecting_body', 'Repository.get_revision_graph',
1659
# The medium is now marked as being connected to an older server
1660
self.assertTrue(client._medium._is_remote_before((1, 2)))
1661
self.assertEqual({rev_id: ('null:',)}, parents)
1663
def test_get_parent_map_fallback_parentless_node(self):
1664
"""get_parent_map falls back to get_revision_graph on old servers. The
1665
results from get_revision_graph are tweaked to match the get_parent_map
1668
Specifically, a {key: ()} result from get_revision_graph means "no
1669
parents" for that key, which in get_parent_map results should be
1670
represented as {key: ('null:',)}.
1672
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1674
rev_id = 'revision-id'
1675
transport_path = 'quack'
1676
repo, client = self.setup_fake_client_and_repository(transport_path)
1677
client.add_success_response_with_body(rev_id, 'ok')
1678
client._medium._remember_remote_is_before((1, 2))
1679
parents = repo.get_parent_map([rev_id])
1681
[('call_expecting_body', 'Repository.get_revision_graph',
1684
self.assertEqual({rev_id: ('null:',)}, parents)
1686
def test_get_parent_map_unexpected_response(self):
1687
repo, client = self.setup_fake_client_and_repository('path')
1688
client.add_success_response('something unexpected!')
1690
errors.UnexpectedSmartServerResponse,
1691
repo.get_parent_map, ['a-revision-id'])
1693
def test_get_parent_map_negative_caches_missing_keys(self):
1694
self.setup_smart_server_with_call_log()
1695
repo = self.make_repository('foo')
1696
self.assertIsInstance(repo, RemoteRepository)
1698
self.addCleanup(repo.unlock)
1699
self.reset_smart_call_log()
1700
graph = repo.get_graph()
1701
self.assertEqual({},
1702
graph.get_parent_map(['some-missing', 'other-missing']))
1703
self.assertLength(1, self.hpss_calls)
1704
# No call if we repeat this
1705
self.reset_smart_call_log()
1706
graph = repo.get_graph()
1707
self.assertEqual({},
1708
graph.get_parent_map(['some-missing', 'other-missing']))
1709
self.assertLength(0, self.hpss_calls)
1710
# Asking for more unknown keys makes a request.
1711
self.reset_smart_call_log()
1712
graph = repo.get_graph()
1713
self.assertEqual({},
1714
graph.get_parent_map(['some-missing', 'other-missing',
1716
self.assertLength(1, self.hpss_calls)
1718
def disableExtraResults(self):
1719
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1720
SmartServerRepositoryGetParentMap.no_extra_results = True
1722
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1723
self.addCleanup(reset_values)
1725
def test_null_cached_missing_and_stop_key(self):
1726
self.setup_smart_server_with_call_log()
1727
# Make a branch with a single revision.
1728
builder = self.make_branch_builder('foo')
1729
builder.start_series()
1730
builder.build_snapshot('first', None, [
1731
('add', ('', 'root-id', 'directory', ''))])
1732
builder.finish_series()
1733
branch = builder.get_branch()
1734
repo = branch.repository
1735
self.assertIsInstance(repo, RemoteRepository)
1736
# Stop the server from sending extra results.
1737
self.disableExtraResults()
1739
self.addCleanup(repo.unlock)
1740
self.reset_smart_call_log()
1741
graph = repo.get_graph()
1742
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1743
# 'first' it will be a candidate for the stop_keys of subsequent
1744
# requests, and because 'null:' was queried but not returned it will be
1745
# cached as missing.
1746
self.assertEqual({'first': ('null:',)},
1747
graph.get_parent_map(['first', 'null:']))
1748
# Now query for another key. This request will pass along a recipe of
1749
# start and stop keys describing the already cached results, and this
1750
# recipe's revision count must be correct (or else it will trigger an
1751
# error from the server).
1752
self.assertEqual({}, graph.get_parent_map(['another-key']))
1753
# This assertion guards against disableExtraResults silently failing to
1754
# work, thus invalidating the test.
1755
self.assertLength(2, self.hpss_calls)
1757
def test_get_parent_map_gets_ghosts_from_result(self):
1758
# asking for a revision should negatively cache close ghosts in its
1760
self.setup_smart_server_with_call_log()
1761
tree = self.make_branch_and_memory_tree('foo')
1764
builder = treebuilder.TreeBuilder()
1765
builder.start_tree(tree)
1767
builder.finish_tree()
1768
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1769
rev_id = tree.commit('')
1773
self.addCleanup(tree.unlock)
1774
repo = tree.branch.repository
1775
self.assertIsInstance(repo, RemoteRepository)
1777
repo.get_parent_map([rev_id])
1778
self.reset_smart_call_log()
1779
# Now asking for rev_id's ghost parent should not make calls
1780
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1781
self.assertLength(0, self.hpss_calls)
1784
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1786
def test_allows_new_revisions(self):
1787
"""get_parent_map's results can be updated by commit."""
1788
smart_server = server.SmartTCPServer_for_testing()
1789
smart_server.setUp()
1790
self.addCleanup(smart_server.tearDown)
1791
self.make_branch('branch')
1792
branch = Branch.open(smart_server.get_url() + '/branch')
1793
tree = branch.create_checkout('tree', lightweight=True)
1795
self.addCleanup(tree.unlock)
1796
graph = tree.branch.repository.get_graph()
1797
# This provides an opportunity for the missing rev-id to be cached.
1798
self.assertEqual({}, graph.get_parent_map(['rev1']))
1799
tree.commit('message', rev_id='rev1')
1800
graph = tree.branch.repository.get_graph()
1801
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1804
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1806
def test_null_revision(self):
1807
# a null revision has the predictable result {}, we should have no wire
1808
# traffic when calling it with this argument
1809
transport_path = 'empty'
1810
repo, client = self.setup_fake_client_and_repository(transport_path)
1811
client.add_success_response('notused')
1812
# actual RemoteRepository.get_revision_graph is gone, but there's an
1813
# equivalent private method for testing
1814
result = repo._get_revision_graph(NULL_REVISION)
1815
self.assertEqual([], client._calls)
1816
self.assertEqual({}, result)
1818
def test_none_revision(self):
1819
# with none we want the entire graph
1820
r1 = u'\u0e33'.encode('utf8')
1821
r2 = u'\u0dab'.encode('utf8')
1822
lines = [' '.join([r2, r1]), r1]
1823
encoded_body = '\n'.join(lines)
1825
transport_path = 'sinhala'
1826
repo, client = self.setup_fake_client_and_repository(transport_path)
1827
client.add_success_response_with_body(encoded_body, 'ok')
1828
# actual RemoteRepository.get_revision_graph is gone, but there's an
1829
# equivalent private method for testing
1830
result = repo._get_revision_graph(None)
1832
[('call_expecting_body', 'Repository.get_revision_graph',
1835
self.assertEqual({r1: (), r2: (r1, )}, result)
1837
def test_specific_revision(self):
1838
# with a specific revision we want the graph for that
1839
# with none we want the entire graph
1840
r11 = u'\u0e33'.encode('utf8')
1841
r12 = u'\xc9'.encode('utf8')
1842
r2 = u'\u0dab'.encode('utf8')
1843
lines = [' '.join([r2, r11, r12]), r11, r12]
1844
encoded_body = '\n'.join(lines)
1846
transport_path = 'sinhala'
1847
repo, client = self.setup_fake_client_and_repository(transport_path)
1848
client.add_success_response_with_body(encoded_body, 'ok')
1849
result = repo._get_revision_graph(r2)
1851
[('call_expecting_body', 'Repository.get_revision_graph',
1854
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1856
def test_no_such_revision(self):
1858
transport_path = 'sinhala'
1859
repo, client = self.setup_fake_client_and_repository(transport_path)
1860
client.add_error_response('nosuchrevision', revid)
1861
# also check that the right revision is reported in the error
1862
self.assertRaises(errors.NoSuchRevision,
1863
repo._get_revision_graph, revid)
1865
[('call_expecting_body', 'Repository.get_revision_graph',
1866
('sinhala/', revid))],
1869
def test_unexpected_error(self):
1871
transport_path = 'sinhala'
1872
repo, client = self.setup_fake_client_and_repository(transport_path)
1873
client.add_error_response('AnUnexpectedError')
1874
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1875
repo._get_revision_graph, revid)
1876
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1879
class TestRepositoryIsShared(TestRemoteRepository):
1881
def test_is_shared(self):
1882
# ('yes', ) for Repository.is_shared -> 'True'.
1883
transport_path = 'quack'
1884
repo, client = self.setup_fake_client_and_repository(transport_path)
1885
client.add_success_response('yes')
1886
result = repo.is_shared()
1888
[('call', 'Repository.is_shared', ('quack/',))],
1890
self.assertEqual(True, result)
1892
def test_is_not_shared(self):
1893
# ('no', ) for Repository.is_shared -> 'False'.
1894
transport_path = 'qwack'
1895
repo, client = self.setup_fake_client_and_repository(transport_path)
1896
client.add_success_response('no')
1897
result = repo.is_shared()
1899
[('call', 'Repository.is_shared', ('qwack/',))],
1901
self.assertEqual(False, result)
1904
class TestRepositoryLockWrite(TestRemoteRepository):
1906
def test_lock_write(self):
1907
transport_path = 'quack'
1908
repo, client = self.setup_fake_client_and_repository(transport_path)
1909
client.add_success_response('ok', 'a token')
1910
result = repo.lock_write()
1912
[('call', 'Repository.lock_write', ('quack/', ''))],
1914
self.assertEqual('a token', result)
1916
def test_lock_write_already_locked(self):
1917
transport_path = 'quack'
1918
repo, client = self.setup_fake_client_and_repository(transport_path)
1919
client.add_error_response('LockContention')
1920
self.assertRaises(errors.LockContention, repo.lock_write)
1922
[('call', 'Repository.lock_write', ('quack/', ''))],
1925
def test_lock_write_unlockable(self):
1926
transport_path = 'quack'
1927
repo, client = self.setup_fake_client_and_repository(transport_path)
1928
client.add_error_response('UnlockableTransport')
1929
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1931
[('call', 'Repository.lock_write', ('quack/', ''))],
1935
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1937
def test_backwards_compat(self):
1938
self.setup_smart_server_with_call_log()
1939
repo = self.make_repository('.')
1940
self.reset_smart_call_log()
1941
verb = 'Repository.set_make_working_trees'
1942
self.disable_verb(verb)
1943
repo.set_make_working_trees(True)
1944
call_count = len([call for call in self.hpss_calls if
1945
call.call.method == verb])
1946
self.assertEqual(1, call_count)
1948
def test_current(self):
1949
transport_path = 'quack'
1950
repo, client = self.setup_fake_client_and_repository(transport_path)
1951
client.add_expected_call(
1952
'Repository.set_make_working_trees', ('quack/', 'True'),
1954
client.add_expected_call(
1955
'Repository.set_make_working_trees', ('quack/', 'False'),
1957
repo.set_make_working_trees(True)
1958
repo.set_make_working_trees(False)
1961
class TestRepositoryUnlock(TestRemoteRepository):
1963
def test_unlock(self):
1964
transport_path = 'quack'
1965
repo, client = self.setup_fake_client_and_repository(transport_path)
1966
client.add_success_response('ok', 'a token')
1967
client.add_success_response('ok')
1971
[('call', 'Repository.lock_write', ('quack/', '')),
1972
('call', 'Repository.unlock', ('quack/', 'a token'))],
1975
def test_unlock_wrong_token(self):
1976
# If somehow the token is wrong, unlock will raise TokenMismatch.
1977
transport_path = 'quack'
1978
repo, client = self.setup_fake_client_and_repository(transport_path)
1979
client.add_success_response('ok', 'a token')
1980
client.add_error_response('TokenMismatch')
1982
self.assertRaises(errors.TokenMismatch, repo.unlock)
1985
class TestRepositoryHasRevision(TestRemoteRepository):
1987
def test_none(self):
1988
# repo.has_revision(None) should not cause any traffic.
1989
transport_path = 'quack'
1990
repo, client = self.setup_fake_client_and_repository(transport_path)
1992
# The null revision is always there, so has_revision(None) == True.
1993
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1995
# The remote repo shouldn't be accessed.
1996
self.assertEqual([], client._calls)
1999
class TestRepositoryInsertStream(TestRemoteRepository):
2001
def test_unlocked_repo(self):
2002
transport_path = 'quack'
2003
repo, client = self.setup_fake_client_and_repository(transport_path)
2004
client.add_expected_call(
2005
'Repository.insert_stream', ('quack/', ''),
2007
client.add_expected_call(
2008
'Repository.insert_stream', ('quack/', ''),
2010
sink = repo._get_sink()
2011
fmt = repository.RepositoryFormat.get_default_format()
2012
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2013
self.assertEqual([], resume_tokens)
2014
self.assertEqual(set(), missing_keys)
2015
client.finished_test()
2017
def test_locked_repo_with_no_lock_token(self):
2018
transport_path = 'quack'
2019
repo, client = self.setup_fake_client_and_repository(transport_path)
2020
client.add_expected_call(
2021
'Repository.lock_write', ('quack/', ''),
2022
'success', ('ok', ''))
2023
client.add_expected_call(
2024
'Repository.insert_stream', ('quack/', ''),
2026
client.add_expected_call(
2027
'Repository.insert_stream', ('quack/', ''),
2030
sink = repo._get_sink()
2031
fmt = repository.RepositoryFormat.get_default_format()
2032
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2033
self.assertEqual([], resume_tokens)
2034
self.assertEqual(set(), missing_keys)
2035
client.finished_test()
2037
def test_locked_repo_with_lock_token(self):
2038
transport_path = 'quack'
2039
repo, client = self.setup_fake_client_and_repository(transport_path)
2040
client.add_expected_call(
2041
'Repository.lock_write', ('quack/', ''),
2042
'success', ('ok', 'a token'))
2043
client.add_expected_call(
2044
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2046
client.add_expected_call(
2047
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2050
sink = repo._get_sink()
2051
fmt = repository.RepositoryFormat.get_default_format()
2052
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2053
self.assertEqual([], resume_tokens)
2054
self.assertEqual(set(), missing_keys)
2055
client.finished_test()
2058
class TestRepositoryTarball(TestRemoteRepository):
2060
# This is a canned tarball reponse we can validate against
2062
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2063
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2064
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2065
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2066
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2067
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2068
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2069
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2070
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2071
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2072
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2073
'nWQ7QH/F3JFOFCQ0aSPfA='
2076
def test_repository_tarball(self):
2077
# Test that Repository.tarball generates the right operations
2078
transport_path = 'repo'
2079
expected_calls = [('call_expecting_body', 'Repository.tarball',
2080
('repo/', 'bz2',),),
2082
repo, client = self.setup_fake_client_and_repository(transport_path)
2083
client.add_success_response_with_body(self.tarball_content, 'ok')
2084
# Now actually ask for the tarball
2085
tarball_file = repo._get_tarball('bz2')
2087
self.assertEqual(expected_calls, client._calls)
2088
self.assertEqual(self.tarball_content, tarball_file.read())
2090
tarball_file.close()
2093
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2094
"""RemoteRepository.copy_content_into optimizations"""
2096
def test_copy_content_remote_to_local(self):
2097
self.transport_server = server.SmartTCPServer_for_testing
2098
src_repo = self.make_repository('repo1')
2099
src_repo = repository.Repository.open(self.get_url('repo1'))
2100
# At the moment the tarball-based copy_content_into can't write back
2101
# into a smart server. It would be good if it could upload the
2102
# tarball; once that works we'd have to create repositories of
2103
# different formats. -- mbp 20070410
2104
dest_url = self.get_vfs_only_url('repo2')
2105
dest_bzrdir = BzrDir.create(dest_url)
2106
dest_repo = dest_bzrdir.create_repository()
2107
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2108
self.assertTrue(isinstance(src_repo, RemoteRepository))
2109
src_repo.copy_content_into(dest_repo)
2112
class _StubRealPackRepository(object):
2114
def __init__(self, calls):
2116
self._pack_collection = _StubPackCollection(calls)
2118
def is_in_write_group(self):
2121
def refresh_data(self):
2122
self.calls.append(('pack collection reload_pack_names',))
2125
class _StubPackCollection(object):
2127
def __init__(self, calls):
2131
self.calls.append(('pack collection autopack',))
2134
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2135
"""Tests for RemoteRepository.autopack implementation."""
2138
"""When the server returns 'ok' and there's no _real_repository, then
2139
nothing else happens: the autopack method is done.
2141
transport_path = 'quack'
2142
repo, client = self.setup_fake_client_and_repository(transport_path)
2143
client.add_expected_call(
2144
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2146
client.finished_test()
2148
def test_ok_with_real_repo(self):
2149
"""When the server returns 'ok' and there is a _real_repository, then
2150
the _real_repository's reload_pack_name's method will be called.
2152
transport_path = 'quack'
2153
repo, client = self.setup_fake_client_and_repository(transport_path)
2154
client.add_expected_call(
2155
'PackRepository.autopack', ('quack/',),
2157
repo._real_repository = _StubRealPackRepository(client._calls)
2160
[('call', 'PackRepository.autopack', ('quack/',)),
2161
('pack collection reload_pack_names',)],
2164
def test_backwards_compatibility(self):
2165
"""If the server does not recognise the PackRepository.autopack verb,
2166
fallback to the real_repository's implementation.
2168
transport_path = 'quack'
2169
repo, client = self.setup_fake_client_and_repository(transport_path)
2170
client.add_unknown_method_response('PackRepository.autopack')
2171
def stub_ensure_real():
2172
client._calls.append(('_ensure_real',))
2173
repo._real_repository = _StubRealPackRepository(client._calls)
2174
repo._ensure_real = stub_ensure_real
2177
[('call', 'PackRepository.autopack', ('quack/',)),
2179
('pack collection autopack',)],
2183
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2184
"""Base class for unit tests for bzrlib.remote._translate_error."""
2186
def translateTuple(self, error_tuple, **context):
2187
"""Call _translate_error with an ErrorFromSmartServer built from the
2190
:param error_tuple: A tuple of a smart server response, as would be
2191
passed to an ErrorFromSmartServer.
2192
:kwargs context: context items to call _translate_error with.
2194
:returns: The error raised by _translate_error.
2196
# Raise the ErrorFromSmartServer before passing it as an argument,
2197
# because _translate_error may need to re-raise it with a bare 'raise'
2199
server_error = errors.ErrorFromSmartServer(error_tuple)
2200
translated_error = self.translateErrorFromSmartServer(
2201
server_error, **context)
2202
return translated_error
2204
def translateErrorFromSmartServer(self, error_object, **context):
2205
"""Like translateTuple, but takes an already constructed
2206
ErrorFromSmartServer rather than a tuple.
2210
except errors.ErrorFromSmartServer, server_error:
2211
translated_error = self.assertRaises(
2212
errors.BzrError, remote._translate_error, server_error,
2214
return translated_error
2217
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2218
"""Unit tests for bzrlib.remote._translate_error.
2220
Given an ErrorFromSmartServer (which has an error tuple from a smart
2221
server) and some context, _translate_error raises more specific errors from
2224
This test case covers the cases where _translate_error succeeds in
2225
translating an ErrorFromSmartServer to something better. See
2226
TestErrorTranslationRobustness for other cases.
2229
def test_NoSuchRevision(self):
2230
branch = self.make_branch('')
2232
translated_error = self.translateTuple(
2233
('NoSuchRevision', revid), branch=branch)
2234
expected_error = errors.NoSuchRevision(branch, revid)
2235
self.assertEqual(expected_error, translated_error)
2237
def test_nosuchrevision(self):
2238
repository = self.make_repository('')
2240
translated_error = self.translateTuple(
2241
('nosuchrevision', revid), repository=repository)
2242
expected_error = errors.NoSuchRevision(repository, revid)
2243
self.assertEqual(expected_error, translated_error)
2245
def test_nobranch(self):
2246
bzrdir = self.make_bzrdir('')
2247
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2248
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2249
self.assertEqual(expected_error, translated_error)
2251
def test_LockContention(self):
2252
translated_error = self.translateTuple(('LockContention',))
2253
expected_error = errors.LockContention('(remote lock)')
2254
self.assertEqual(expected_error, translated_error)
2256
def test_UnlockableTransport(self):
2257
bzrdir = self.make_bzrdir('')
2258
translated_error = self.translateTuple(
2259
('UnlockableTransport',), bzrdir=bzrdir)
2260
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2261
self.assertEqual(expected_error, translated_error)
2263
def test_LockFailed(self):
2264
lock = 'str() of a server lock'
2265
why = 'str() of why'
2266
translated_error = self.translateTuple(('LockFailed', lock, why))
2267
expected_error = errors.LockFailed(lock, why)
2268
self.assertEqual(expected_error, translated_error)
2270
def test_TokenMismatch(self):
2271
token = 'a lock token'
2272
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2273
expected_error = errors.TokenMismatch(token, '(remote token)')
2274
self.assertEqual(expected_error, translated_error)
2276
def test_Diverged(self):
2277
branch = self.make_branch('a')
2278
other_branch = self.make_branch('b')
2279
translated_error = self.translateTuple(
2280
('Diverged',), branch=branch, other_branch=other_branch)
2281
expected_error = errors.DivergedBranches(branch, other_branch)
2282
self.assertEqual(expected_error, translated_error)
2284
def test_ReadError_no_args(self):
2286
translated_error = self.translateTuple(('ReadError',), path=path)
2287
expected_error = errors.ReadError(path)
2288
self.assertEqual(expected_error, translated_error)
2290
def test_ReadError(self):
2292
translated_error = self.translateTuple(('ReadError', path))
2293
expected_error = errors.ReadError(path)
2294
self.assertEqual(expected_error, translated_error)
2296
def test_PermissionDenied_no_args(self):
2298
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2299
expected_error = errors.PermissionDenied(path)
2300
self.assertEqual(expected_error, translated_error)
2302
def test_PermissionDenied_one_arg(self):
2304
translated_error = self.translateTuple(('PermissionDenied', path))
2305
expected_error = errors.PermissionDenied(path)
2306
self.assertEqual(expected_error, translated_error)
2308
def test_PermissionDenied_one_arg_and_context(self):
2309
"""Given a choice between a path from the local context and a path on
2310
the wire, _translate_error prefers the path from the local context.
2312
local_path = 'local path'
2313
remote_path = 'remote path'
2314
translated_error = self.translateTuple(
2315
('PermissionDenied', remote_path), path=local_path)
2316
expected_error = errors.PermissionDenied(local_path)
2317
self.assertEqual(expected_error, translated_error)
2319
def test_PermissionDenied_two_args(self):
2321
extra = 'a string with extra info'
2322
translated_error = self.translateTuple(
2323
('PermissionDenied', path, extra))
2324
expected_error = errors.PermissionDenied(path, extra)
2325
self.assertEqual(expected_error, translated_error)
2328
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2329
"""Unit tests for bzrlib.remote._translate_error's robustness.
2331
TestErrorTranslationSuccess is for cases where _translate_error can
2332
translate successfully. This class about how _translate_err behaves when
2333
it fails to translate: it re-raises the original error.
2336
def test_unrecognised_server_error(self):
2337
"""If the error code from the server is not recognised, the original
2338
ErrorFromSmartServer is propagated unmodified.
2340
error_tuple = ('An unknown error tuple',)
2341
server_error = errors.ErrorFromSmartServer(error_tuple)
2342
translated_error = self.translateErrorFromSmartServer(server_error)
2343
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2344
self.assertEqual(expected_error, translated_error)
2346
def test_context_missing_a_key(self):
2347
"""In case of a bug in the client, or perhaps an unexpected response
2348
from a server, _translate_error returns the original error tuple from
2349
the server and mutters a warning.
2351
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2352
# in the context dict. So let's give it an empty context dict instead
2353
# to exercise its error recovery.
2355
error_tuple = ('NoSuchRevision', 'revid')
2356
server_error = errors.ErrorFromSmartServer(error_tuple)
2357
translated_error = self.translateErrorFromSmartServer(server_error)
2358
self.assertEqual(server_error, translated_error)
2359
# In addition to re-raising ErrorFromSmartServer, some debug info has
2360
# been muttered to the log file for developer to look at.
2361
self.assertContainsRe(
2362
self._get_log(keep_log_file=True),
2363
"Missing key 'branch' in context")
2365
def test_path_missing(self):
2366
"""Some translations (PermissionDenied, ReadError) can determine the
2367
'path' variable from either the wire or the local context. If neither
2368
has it, then an error is raised.
2370
error_tuple = ('ReadError',)
2371
server_error = errors.ErrorFromSmartServer(error_tuple)
2372
translated_error = self.translateErrorFromSmartServer(server_error)
2373
self.assertEqual(server_error, translated_error)
2374
# In addition to re-raising ErrorFromSmartServer, some debug info has
2375
# been muttered to the log file for developer to look at.
2376
self.assertContainsRe(
2377
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2380
class TestStacking(tests.TestCaseWithTransport):
2381
"""Tests for operations on stacked remote repositories.
2383
The underlying format type must support stacking.
2386
def test_access_stacked_remote(self):
2387
# based on <http://launchpad.net/bugs/261315>
2388
# make a branch stacked on another repository containing an empty
2389
# revision, then open it over hpss - we should be able to see that
2391
base_transport = self.get_transport()
2392
base_builder = self.make_branch_builder('base', format='1.9')
2393
base_builder.start_series()
2394
base_revid = base_builder.build_snapshot('rev-id', None,
2395
[('add', ('', None, 'directory', None))],
2397
base_builder.finish_series()
2398
stacked_branch = self.make_branch('stacked', format='1.9')
2399
stacked_branch.set_stacked_on_url('../base')
2400
# start a server looking at this
2401
smart_server = server.SmartTCPServer_for_testing()
2402
smart_server.setUp()
2403
self.addCleanup(smart_server.tearDown)
2404
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2405
# can get its branch and repository
2406
remote_branch = remote_bzrdir.open_branch()
2407
remote_repo = remote_branch.repository
2408
remote_repo.lock_read()
2410
# it should have an appropriate fallback repository, which should also
2411
# be a RemoteRepository
2412
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2413
self.assertIsInstance(remote_repo._fallback_repositories[0],
2415
# and it has the revision committed to the underlying repository;
2416
# these have varying implementations so we try several of them
2417
self.assertTrue(remote_repo.has_revisions([base_revid]))
2418
self.assertTrue(remote_repo.has_revision(base_revid))
2419
self.assertEqual(remote_repo.get_revision(base_revid).message,
2422
remote_repo.unlock()
2424
def prepare_stacked_remote_branch(self):
2425
"""Get stacked_upon and stacked branches with content in each."""
2426
self.setup_smart_server_with_call_log()
2427
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2428
tree1.commit('rev1', rev_id='rev1')
2429
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2430
).open_workingtree()
2431
tree2.commit('local changes make me feel good.')
2432
branch2 = Branch.open(self.get_url('tree2'))
2434
self.addCleanup(branch2.unlock)
2435
return tree1.branch, branch2
2437
def test_stacked_get_parent_map(self):
2438
# the public implementation of get_parent_map obeys stacking
2439
_, branch = self.prepare_stacked_remote_branch()
2440
repo = branch.repository
2441
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2443
def test_unstacked_get_parent_map(self):
2444
# _unstacked_provider.get_parent_map ignores stacking
2445
_, branch = self.prepare_stacked_remote_branch()
2446
provider = branch.repository._unstacked_provider
2447
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2449
def fetch_stream_to_rev_order(self, stream):
2451
for kind, substream in stream:
2452
if not kind == 'revisions':
2455
for content in substream:
2456
result.append(content.key[-1])
2459
def get_ordered_revs(self, format, order):
2460
"""Get a list of the revisions in a stream to format format.
2462
:param format: The format of the target.
2463
:param order: the order that target should have requested.
2464
:result: The revision ids in the stream, in the order seen,
2465
the topological order of revisions in the source.
2467
unordered_format = bzrdir.format_registry.get(format)()
2468
target_repository_format = unordered_format.repository_format
2470
self.assertEqual(order, target_repository_format._fetch_order)
2471
trunk, stacked = self.prepare_stacked_remote_branch()
2472
source = stacked.repository._get_source(target_repository_format)
2473
tip = stacked.last_revision()
2474
revs = stacked.repository.get_ancestry(tip)
2475
search = graph.PendingAncestryResult([tip], stacked.repository)
2476
self.reset_smart_call_log()
2477
stream = source.get_stream(search)
2480
# We trust that if a revision is in the stream the rest of the new
2481
# content for it is too, as per our main fetch tests; here we are
2482
# checking that the revisions are actually included at all, and their
2484
return self.fetch_stream_to_rev_order(stream), revs
2486
def test_stacked_get_stream_unordered(self):
2487
# Repository._get_source.get_stream() from a stacked repository with
2488
# unordered yields the full data from both stacked and stacked upon
2490
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2491
self.assertEqual(set(expected_revs), set(rev_ord))
2492
# Getting unordered results should have made a streaming data request
2493
# from the server, then one from the backing branch.
2494
self.assertLength(2, self.hpss_calls)
2496
def test_stacked_get_stream_topological(self):
2497
# Repository._get_source.get_stream() from a stacked repository with
2498
# topological sorting yields the full data from both stacked and
2499
# stacked upon sources in topological order.
2500
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2501
self.assertEqual(expected_revs, rev_ord)
2502
# Getting topological sort requires VFS calls still
2503
self.assertLength(12, self.hpss_calls)
2505
def test_stacked_get_stream_groupcompress(self):
2506
# Repository._get_source.get_stream() from a stacked repository with
2507
# groupcompress sorting yields the full data from both stacked and
2508
# stacked upon sources in groupcompress order.
2509
raise tests.TestSkipped('No groupcompress ordered format available')
2510
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2511
self.assertEqual(expected_revs, reversed(rev_ord))
2512
# Getting unordered results should have made a streaming data request
2513
# from the backing branch, and one from the stacked on branch.
2514
self.assertLength(2, self.hpss_calls)
2517
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2520
super(TestRemoteBranchEffort, self).setUp()
2521
# Create a smart server that publishes whatever the backing VFS server
2523
self.smart_server = server.SmartTCPServer_for_testing()
2524
self.smart_server.setUp(self.get_server())
2525
self.addCleanup(self.smart_server.tearDown)
2526
# Log all HPSS calls into self.hpss_calls.
2527
_SmartClient.hooks.install_named_hook(
2528
'call', self.capture_hpss_call, None)
2529
self.hpss_calls = []
2531
def capture_hpss_call(self, params):
2532
self.hpss_calls.append(params.method)
2534
def test_copy_content_into_avoids_revision_history(self):
2535
local = self.make_branch('local')
2536
remote_backing_tree = self.make_branch_and_tree('remote')
2537
remote_backing_tree.commit("Commit.")
2538
remote_branch_url = self.smart_server.get_url() + 'remote'
2539
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2540
local.repository.fetch(remote_branch.repository)
2541
self.hpss_calls = []
2542
remote_branch.copy_content_into(local)
2543
self.assertFalse('Branch.revision_history' in self.hpss_calls)