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 groupcompress_repo, 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 TestBzrDirFormatInitializeEx(TestRemote):
746
def test_success(self):
747
"""Simple test for typical successful call."""
748
fmt = bzrdir.RemoteBzrDirFormat()
749
default_format_name = BzrDirFormat.get_default_format().network_name()
750
transport = self.get_transport()
751
client = FakeClient(transport.base)
752
client.add_expected_call(
753
'BzrDirFormat.initialize_ex_1.16',
754
(default_format_name, 'path', 'False', 'False', 'False', '',
755
'', '', '', 'False'),
757
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
758
'bzrdir fmt', 'False', '', '', 'repo lock token'))
759
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
760
# it's currently hard to test that without supplying a real remote
761
# transport connected to a real server.
762
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
763
transport, False, False, False, None, None, None, None, False)
764
client.finished_test()
766
def test_error(self):
767
"""Error responses are translated, e.g. 'PermissionDenied' raises the
768
corresponding error from the client.
770
fmt = bzrdir.RemoteBzrDirFormat()
771
default_format_name = BzrDirFormat.get_default_format().network_name()
772
transport = self.get_transport()
773
client = FakeClient(transport.base)
774
client.add_expected_call(
775
'BzrDirFormat.initialize_ex_1.16',
776
(default_format_name, 'path', 'False', 'False', 'False', '',
777
'', '', '', 'False'),
779
('PermissionDenied', 'path', 'extra info'))
780
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
781
# it's currently hard to test that without supplying a real remote
782
# transport connected to a real server.
783
err = self.assertRaises(errors.PermissionDenied,
784
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
785
False, False, False, None, None, None, None, False)
786
self.assertEqual('path', err.path)
787
self.assertEqual(': extra info', err.extra)
788
client.finished_test()
790
def test_error_from_real_server(self):
791
"""Integration test for error translation."""
792
transport = self.make_smart_server('foo')
793
transport = transport.clone('no-such-path')
794
fmt = bzrdir.RemoteBzrDirFormat()
795
err = self.assertRaises(errors.NoSuchFile,
796
fmt.initialize_on_transport_ex, transport, create_prefix=False)
799
class OldSmartClient(object):
800
"""A fake smart client for test_old_version that just returns a version one
801
response to the 'hello' (query version) command.
804
def get_request(self):
805
input_file = StringIO('ok\x011\n')
806
output_file = StringIO()
807
client_medium = medium.SmartSimplePipesClientMedium(
808
input_file, output_file)
809
return medium.SmartClientStreamMediumRequest(client_medium)
811
def protocol_version(self):
815
class OldServerTransport(object):
816
"""A fake transport for test_old_server that reports it's smart server
817
protocol version as version one.
823
def get_smart_client(self):
824
return OldSmartClient()
827
class RemoteBzrDirTestCase(TestRemote):
829
def make_remote_bzrdir(self, transport, client):
830
"""Make a RemotebzrDir using 'client' as the _client."""
831
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
835
class RemoteBranchTestCase(RemoteBzrDirTestCase):
837
def make_remote_branch(self, transport, client):
838
"""Make a RemoteBranch using 'client' as its _SmartClient.
840
A RemoteBzrDir and RemoteRepository will also be created to fill out
841
the RemoteBranch, albeit with stub values for some of their attributes.
843
# we do not want bzrdir to make any remote calls, so use False as its
844
# _client. If it tries to make a remote call, this will fail
846
bzrdir = self.make_remote_bzrdir(transport, False)
847
repo = RemoteRepository(bzrdir, None, _client=client)
848
branch_format = self.get_branch_format()
849
format = RemoteBranchFormat(network_name=branch_format.network_name())
850
return RemoteBranch(bzrdir, repo, _client=client, format=format)
853
class TestBranchGetParent(RemoteBranchTestCase):
855
def test_no_parent(self):
856
# in an empty branch we decode the response properly
857
transport = MemoryTransport()
858
client = FakeClient(transport.base)
859
client.add_expected_call(
860
'Branch.get_stacked_on_url', ('quack/',),
861
'error', ('NotStacked',))
862
client.add_expected_call(
863
'Branch.get_parent', ('quack/',),
865
transport.mkdir('quack')
866
transport = transport.clone('quack')
867
branch = self.make_remote_branch(transport, client)
868
result = branch.get_parent()
869
client.finished_test()
870
self.assertEqual(None, result)
872
def test_parent_relative(self):
873
transport = MemoryTransport()
874
client = FakeClient(transport.base)
875
client.add_expected_call(
876
'Branch.get_stacked_on_url', ('kwaak/',),
877
'error', ('NotStacked',))
878
client.add_expected_call(
879
'Branch.get_parent', ('kwaak/',),
880
'success', ('../foo/',))
881
transport.mkdir('kwaak')
882
transport = transport.clone('kwaak')
883
branch = self.make_remote_branch(transport, client)
884
result = branch.get_parent()
885
self.assertEqual(transport.clone('../foo').base, result)
887
def test_parent_absolute(self):
888
transport = MemoryTransport()
889
client = FakeClient(transport.base)
890
client.add_expected_call(
891
'Branch.get_stacked_on_url', ('kwaak/',),
892
'error', ('NotStacked',))
893
client.add_expected_call(
894
'Branch.get_parent', ('kwaak/',),
895
'success', ('http://foo/',))
896
transport.mkdir('kwaak')
897
transport = transport.clone('kwaak')
898
branch = self.make_remote_branch(transport, client)
899
result = branch.get_parent()
900
self.assertEqual('http://foo/', result)
901
client.finished_test()
904
class TestBranchSetParentLocation(RemoteBranchTestCase):
906
def test_no_parent(self):
907
# We call the verb when setting parent to None
908
transport = MemoryTransport()
909
client = FakeClient(transport.base)
910
client.add_expected_call(
911
'Branch.get_stacked_on_url', ('quack/',),
912
'error', ('NotStacked',))
913
client.add_expected_call(
914
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
916
transport.mkdir('quack')
917
transport = transport.clone('quack')
918
branch = self.make_remote_branch(transport, client)
919
branch._lock_token = 'b'
920
branch._repo_lock_token = 'r'
921
branch._set_parent_location(None)
922
client.finished_test()
924
def test_parent(self):
925
transport = MemoryTransport()
926
client = FakeClient(transport.base)
927
client.add_expected_call(
928
'Branch.get_stacked_on_url', ('kwaak/',),
929
'error', ('NotStacked',))
930
client.add_expected_call(
931
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
933
transport.mkdir('kwaak')
934
transport = transport.clone('kwaak')
935
branch = self.make_remote_branch(transport, client)
936
branch._lock_token = 'b'
937
branch._repo_lock_token = 'r'
938
branch._set_parent_location('foo')
939
client.finished_test()
941
def test_backwards_compat(self):
942
self.setup_smart_server_with_call_log()
943
branch = self.make_branch('.')
944
self.reset_smart_call_log()
945
verb = 'Branch.set_parent_location'
946
self.disable_verb(verb)
947
branch.set_parent('http://foo/')
948
self.assertLength(12, self.hpss_calls)
951
class TestBranchGetTagsBytes(RemoteBranchTestCase):
953
def test_backwards_compat(self):
954
self.setup_smart_server_with_call_log()
955
branch = self.make_branch('.')
956
self.reset_smart_call_log()
957
verb = 'Branch.get_tags_bytes'
958
self.disable_verb(verb)
959
branch.tags.get_tag_dict()
960
call_count = len([call for call in self.hpss_calls if
961
call.call.method == verb])
962
self.assertEqual(1, call_count)
964
def test_trivial(self):
965
transport = MemoryTransport()
966
client = FakeClient(transport.base)
967
client.add_expected_call(
968
'Branch.get_stacked_on_url', ('quack/',),
969
'error', ('NotStacked',))
970
client.add_expected_call(
971
'Branch.get_tags_bytes', ('quack/',),
973
transport.mkdir('quack')
974
transport = transport.clone('quack')
975
branch = self.make_remote_branch(transport, client)
976
result = branch.tags.get_tag_dict()
977
client.finished_test()
978
self.assertEqual({}, result)
981
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
983
def test_empty_branch(self):
984
# in an empty branch we decode the response properly
985
transport = MemoryTransport()
986
client = FakeClient(transport.base)
987
client.add_expected_call(
988
'Branch.get_stacked_on_url', ('quack/',),
989
'error', ('NotStacked',))
990
client.add_expected_call(
991
'Branch.last_revision_info', ('quack/',),
992
'success', ('ok', '0', 'null:'))
993
transport.mkdir('quack')
994
transport = transport.clone('quack')
995
branch = self.make_remote_branch(transport, client)
996
result = branch.last_revision_info()
997
client.finished_test()
998
self.assertEqual((0, NULL_REVISION), result)
1000
def test_non_empty_branch(self):
1001
# in a non-empty branch we also decode the response properly
1002
revid = u'\xc8'.encode('utf8')
1003
transport = MemoryTransport()
1004
client = FakeClient(transport.base)
1005
client.add_expected_call(
1006
'Branch.get_stacked_on_url', ('kwaak/',),
1007
'error', ('NotStacked',))
1008
client.add_expected_call(
1009
'Branch.last_revision_info', ('kwaak/',),
1010
'success', ('ok', '2', revid))
1011
transport.mkdir('kwaak')
1012
transport = transport.clone('kwaak')
1013
branch = self.make_remote_branch(transport, client)
1014
result = branch.last_revision_info()
1015
self.assertEqual((2, revid), result)
1018
class TestBranch_get_stacked_on_url(TestRemote):
1019
"""Test Branch._get_stacked_on_url rpc"""
1021
def test_get_stacked_on_invalid_url(self):
1022
# test that asking for a stacked on url the server can't access works.
1023
# This isn't perfect, but then as we're in the same process there
1024
# really isn't anything we can do to be 100% sure that the server
1025
# doesn't just open in - this test probably needs to be rewritten using
1026
# a spawn()ed server.
1027
stacked_branch = self.make_branch('stacked', format='1.9')
1028
memory_branch = self.make_branch('base', format='1.9')
1029
vfs_url = self.get_vfs_only_url('base')
1030
stacked_branch.set_stacked_on_url(vfs_url)
1031
transport = stacked_branch.bzrdir.root_transport
1032
client = FakeClient(transport.base)
1033
client.add_expected_call(
1034
'Branch.get_stacked_on_url', ('stacked/',),
1035
'success', ('ok', vfs_url))
1036
# XXX: Multiple calls are bad, this second call documents what is
1038
client.add_expected_call(
1039
'Branch.get_stacked_on_url', ('stacked/',),
1040
'success', ('ok', vfs_url))
1041
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1043
repo_fmt = remote.RemoteRepositoryFormat()
1044
repo_fmt._custom_format = stacked_branch.repository._format
1045
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1047
result = branch.get_stacked_on_url()
1048
self.assertEqual(vfs_url, result)
1050
def test_backwards_compatible(self):
1051
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1052
base_branch = self.make_branch('base', format='1.6')
1053
stacked_branch = self.make_branch('stacked', format='1.6')
1054
stacked_branch.set_stacked_on_url('../base')
1055
client = FakeClient(self.get_url())
1056
branch_network_name = self.get_branch_format().network_name()
1057
client.add_expected_call(
1058
'BzrDir.open_branchV2', ('stacked/',),
1059
'success', ('branch', branch_network_name))
1060
client.add_expected_call(
1061
'BzrDir.find_repositoryV3', ('stacked/',),
1062
'success', ('ok', '', 'no', 'no', 'yes',
1063
stacked_branch.repository._format.network_name()))
1064
# called twice, once from constructor and then again by us
1065
client.add_expected_call(
1066
'Branch.get_stacked_on_url', ('stacked/',),
1067
'unknown', ('Branch.get_stacked_on_url',))
1068
client.add_expected_call(
1069
'Branch.get_stacked_on_url', ('stacked/',),
1070
'unknown', ('Branch.get_stacked_on_url',))
1071
# this will also do vfs access, but that goes direct to the transport
1072
# and isn't seen by the FakeClient.
1073
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1074
remote.RemoteBzrDirFormat(), _client=client)
1075
branch = bzrdir.open_branch()
1076
result = branch.get_stacked_on_url()
1077
self.assertEqual('../base', result)
1078
client.finished_test()
1079
# it's in the fallback list both for the RemoteRepository and its vfs
1081
self.assertEqual(1, len(branch.repository._fallback_repositories))
1083
len(branch.repository._real_repository._fallback_repositories))
1085
def test_get_stacked_on_real_branch(self):
1086
base_branch = self.make_branch('base', format='1.6')
1087
stacked_branch = self.make_branch('stacked', format='1.6')
1088
stacked_branch.set_stacked_on_url('../base')
1089
reference_format = self.get_repo_format()
1090
network_name = reference_format.network_name()
1091
client = FakeClient(self.get_url())
1092
branch_network_name = self.get_branch_format().network_name()
1093
client.add_expected_call(
1094
'BzrDir.open_branchV2', ('stacked/',),
1095
'success', ('branch', branch_network_name))
1096
client.add_expected_call(
1097
'BzrDir.find_repositoryV3', ('stacked/',),
1098
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1099
# called twice, once from constructor and then again by us
1100
client.add_expected_call(
1101
'Branch.get_stacked_on_url', ('stacked/',),
1102
'success', ('ok', '../base'))
1103
client.add_expected_call(
1104
'Branch.get_stacked_on_url', ('stacked/',),
1105
'success', ('ok', '../base'))
1106
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1107
remote.RemoteBzrDirFormat(), _client=client)
1108
branch = bzrdir.open_branch()
1109
result = branch.get_stacked_on_url()
1110
self.assertEqual('../base', result)
1111
client.finished_test()
1112
# it's in the fallback list both for the RemoteRepository.
1113
self.assertEqual(1, len(branch.repository._fallback_repositories))
1114
# And we haven't had to construct a real repository.
1115
self.assertEqual(None, branch.repository._real_repository)
1118
class TestBranchSetLastRevision(RemoteBranchTestCase):
1120
def test_set_empty(self):
1121
# set_revision_history([]) is translated to calling
1122
# Branch.set_last_revision(path, '') on the wire.
1123
transport = MemoryTransport()
1124
transport.mkdir('branch')
1125
transport = transport.clone('branch')
1127
client = FakeClient(transport.base)
1128
client.add_expected_call(
1129
'Branch.get_stacked_on_url', ('branch/',),
1130
'error', ('NotStacked',))
1131
client.add_expected_call(
1132
'Branch.lock_write', ('branch/', '', ''),
1133
'success', ('ok', 'branch token', 'repo token'))
1134
client.add_expected_call(
1135
'Branch.last_revision_info',
1137
'success', ('ok', '0', 'null:'))
1138
client.add_expected_call(
1139
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1141
client.add_expected_call(
1142
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1144
branch = self.make_remote_branch(transport, client)
1145
# This is a hack to work around the problem that RemoteBranch currently
1146
# unnecessarily invokes _ensure_real upon a call to lock_write.
1147
branch._ensure_real = lambda: None
1149
result = branch.set_revision_history([])
1151
self.assertEqual(None, result)
1152
client.finished_test()
1154
def test_set_nonempty(self):
1155
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1156
# Branch.set_last_revision(path, rev-idN) on the wire.
1157
transport = MemoryTransport()
1158
transport.mkdir('branch')
1159
transport = transport.clone('branch')
1161
client = FakeClient(transport.base)
1162
client.add_expected_call(
1163
'Branch.get_stacked_on_url', ('branch/',),
1164
'error', ('NotStacked',))
1165
client.add_expected_call(
1166
'Branch.lock_write', ('branch/', '', ''),
1167
'success', ('ok', 'branch token', 'repo token'))
1168
client.add_expected_call(
1169
'Branch.last_revision_info',
1171
'success', ('ok', '0', 'null:'))
1173
encoded_body = bz2.compress('\n'.join(lines))
1174
client.add_success_response_with_body(encoded_body, 'ok')
1175
client.add_expected_call(
1176
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1178
client.add_expected_call(
1179
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1181
branch = self.make_remote_branch(transport, client)
1182
# This is a hack to work around the problem that RemoteBranch currently
1183
# unnecessarily invokes _ensure_real upon a call to lock_write.
1184
branch._ensure_real = lambda: None
1185
# Lock the branch, reset the record of remote calls.
1187
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1189
self.assertEqual(None, result)
1190
client.finished_test()
1192
def test_no_such_revision(self):
1193
transport = MemoryTransport()
1194
transport.mkdir('branch')
1195
transport = transport.clone('branch')
1196
# A response of 'NoSuchRevision' is translated into an exception.
1197
client = FakeClient(transport.base)
1198
client.add_expected_call(
1199
'Branch.get_stacked_on_url', ('branch/',),
1200
'error', ('NotStacked',))
1201
client.add_expected_call(
1202
'Branch.lock_write', ('branch/', '', ''),
1203
'success', ('ok', 'branch token', 'repo token'))
1204
client.add_expected_call(
1205
'Branch.last_revision_info',
1207
'success', ('ok', '0', 'null:'))
1208
# get_graph calls to construct the revision history, for the set_rh
1211
encoded_body = bz2.compress('\n'.join(lines))
1212
client.add_success_response_with_body(encoded_body, 'ok')
1213
client.add_expected_call(
1214
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1215
'error', ('NoSuchRevision', 'rev-id'))
1216
client.add_expected_call(
1217
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1220
branch = self.make_remote_branch(transport, client)
1223
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1225
client.finished_test()
1227
def test_tip_change_rejected(self):
1228
"""TipChangeRejected responses cause a TipChangeRejected exception to
1231
transport = MemoryTransport()
1232
transport.mkdir('branch')
1233
transport = transport.clone('branch')
1234
client = FakeClient(transport.base)
1235
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1236
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1237
client.add_expected_call(
1238
'Branch.get_stacked_on_url', ('branch/',),
1239
'error', ('NotStacked',))
1240
client.add_expected_call(
1241
'Branch.lock_write', ('branch/', '', ''),
1242
'success', ('ok', 'branch token', 'repo token'))
1243
client.add_expected_call(
1244
'Branch.last_revision_info',
1246
'success', ('ok', '0', 'null:'))
1248
encoded_body = bz2.compress('\n'.join(lines))
1249
client.add_success_response_with_body(encoded_body, 'ok')
1250
client.add_expected_call(
1251
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1252
'error', ('TipChangeRejected', rejection_msg_utf8))
1253
client.add_expected_call(
1254
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1256
branch = self.make_remote_branch(transport, client)
1257
branch._ensure_real = lambda: None
1259
# The 'TipChangeRejected' error response triggered by calling
1260
# set_revision_history causes a TipChangeRejected exception.
1261
err = self.assertRaises(
1262
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1263
# The UTF-8 message from the response has been decoded into a unicode
1265
self.assertIsInstance(err.msg, unicode)
1266
self.assertEqual(rejection_msg_unicode, err.msg)
1268
client.finished_test()
1271
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1273
def test_set_last_revision_info(self):
1274
# set_last_revision_info(num, 'rev-id') is translated to calling
1275
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1276
transport = MemoryTransport()
1277
transport.mkdir('branch')
1278
transport = transport.clone('branch')
1279
client = FakeClient(transport.base)
1280
# get_stacked_on_url
1281
client.add_error_response('NotStacked')
1283
client.add_success_response('ok', 'branch token', 'repo token')
1284
# query the current revision
1285
client.add_success_response('ok', '0', 'null:')
1287
client.add_success_response('ok')
1289
client.add_success_response('ok')
1291
branch = self.make_remote_branch(transport, client)
1292
# Lock the branch, reset the record of remote calls.
1295
result = branch.set_last_revision_info(1234, 'a-revision-id')
1297
[('call', 'Branch.last_revision_info', ('branch/',)),
1298
('call', 'Branch.set_last_revision_info',
1299
('branch/', 'branch token', 'repo token',
1300
'1234', 'a-revision-id'))],
1302
self.assertEqual(None, result)
1304
def test_no_such_revision(self):
1305
# A response of 'NoSuchRevision' is translated into an exception.
1306
transport = MemoryTransport()
1307
transport.mkdir('branch')
1308
transport = transport.clone('branch')
1309
client = FakeClient(transport.base)
1310
# get_stacked_on_url
1311
client.add_error_response('NotStacked')
1313
client.add_success_response('ok', 'branch token', 'repo token')
1315
client.add_error_response('NoSuchRevision', 'revid')
1317
client.add_success_response('ok')
1319
branch = self.make_remote_branch(transport, client)
1320
# Lock the branch, reset the record of remote calls.
1325
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1328
def lock_remote_branch(self, branch):
1329
"""Trick a RemoteBranch into thinking it is locked."""
1330
branch._lock_mode = 'w'
1331
branch._lock_count = 2
1332
branch._lock_token = 'branch token'
1333
branch._repo_lock_token = 'repo token'
1334
branch.repository._lock_mode = 'w'
1335
branch.repository._lock_count = 2
1336
branch.repository._lock_token = 'repo token'
1338
def test_backwards_compatibility(self):
1339
"""If the server does not support the Branch.set_last_revision_info
1340
verb (which is new in 1.4), then the client falls back to VFS methods.
1342
# This test is a little messy. Unlike most tests in this file, it
1343
# doesn't purely test what a Remote* object sends over the wire, and
1344
# how it reacts to responses from the wire. It instead relies partly
1345
# on asserting that the RemoteBranch will call
1346
# self._real_branch.set_last_revision_info(...).
1348
# First, set up our RemoteBranch with a FakeClient that raises
1349
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1350
transport = MemoryTransport()
1351
transport.mkdir('branch')
1352
transport = transport.clone('branch')
1353
client = FakeClient(transport.base)
1354
client.add_expected_call(
1355
'Branch.get_stacked_on_url', ('branch/',),
1356
'error', ('NotStacked',))
1357
client.add_expected_call(
1358
'Branch.last_revision_info',
1360
'success', ('ok', '0', 'null:'))
1361
client.add_expected_call(
1362
'Branch.set_last_revision_info',
1363
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1364
'unknown', 'Branch.set_last_revision_info')
1366
branch = self.make_remote_branch(transport, client)
1367
class StubRealBranch(object):
1370
def set_last_revision_info(self, revno, revision_id):
1372
('set_last_revision_info', revno, revision_id))
1373
def _clear_cached_state(self):
1375
real_branch = StubRealBranch()
1376
branch._real_branch = real_branch
1377
self.lock_remote_branch(branch)
1379
# Call set_last_revision_info, and verify it behaved as expected.
1380
result = branch.set_last_revision_info(1234, 'a-revision-id')
1382
[('set_last_revision_info', 1234, 'a-revision-id')],
1384
client.finished_test()
1386
def test_unexpected_error(self):
1387
# If the server sends an error the client doesn't understand, it gets
1388
# turned into an UnknownErrorFromSmartServer, which is presented as a
1389
# non-internal error to the user.
1390
transport = MemoryTransport()
1391
transport.mkdir('branch')
1392
transport = transport.clone('branch')
1393
client = FakeClient(transport.base)
1394
# get_stacked_on_url
1395
client.add_error_response('NotStacked')
1397
client.add_success_response('ok', 'branch token', 'repo token')
1399
client.add_error_response('UnexpectedError')
1401
client.add_success_response('ok')
1403
branch = self.make_remote_branch(transport, client)
1404
# Lock the branch, reset the record of remote calls.
1408
err = self.assertRaises(
1409
errors.UnknownErrorFromSmartServer,
1410
branch.set_last_revision_info, 123, 'revid')
1411
self.assertEqual(('UnexpectedError',), err.error_tuple)
1414
def test_tip_change_rejected(self):
1415
"""TipChangeRejected responses cause a TipChangeRejected exception to
1418
transport = MemoryTransport()
1419
transport.mkdir('branch')
1420
transport = transport.clone('branch')
1421
client = FakeClient(transport.base)
1422
# get_stacked_on_url
1423
client.add_error_response('NotStacked')
1425
client.add_success_response('ok', 'branch token', 'repo token')
1427
client.add_error_response('TipChangeRejected', 'rejection message')
1429
client.add_success_response('ok')
1431
branch = self.make_remote_branch(transport, client)
1432
# Lock the branch, reset the record of remote calls.
1434
self.addCleanup(branch.unlock)
1437
# The 'TipChangeRejected' error response triggered by calling
1438
# set_last_revision_info causes a TipChangeRejected exception.
1439
err = self.assertRaises(
1440
errors.TipChangeRejected,
1441
branch.set_last_revision_info, 123, 'revid')
1442
self.assertEqual('rejection message', err.msg)
1445
class TestBranchGetSetConfig(RemoteBranchTestCase):
1447
def test_get_branch_conf(self):
1448
# in an empty branch we decode the response properly
1449
client = FakeClient()
1450
client.add_expected_call(
1451
'Branch.get_stacked_on_url', ('memory:///',),
1452
'error', ('NotStacked',),)
1453
client.add_success_response_with_body('# config file body', 'ok')
1454
transport = MemoryTransport()
1455
branch = self.make_remote_branch(transport, client)
1456
config = branch.get_config()
1457
config.has_explicit_nickname()
1459
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1460
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1463
def test_get_multi_line_branch_conf(self):
1464
# Make sure that multiple-line branch.conf files are supported
1466
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1467
client = FakeClient()
1468
client.add_expected_call(
1469
'Branch.get_stacked_on_url', ('memory:///',),
1470
'error', ('NotStacked',),)
1471
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1472
transport = MemoryTransport()
1473
branch = self.make_remote_branch(transport, client)
1474
config = branch.get_config()
1475
self.assertEqual(u'2', config.get_user_option('b'))
1477
def test_set_option(self):
1478
client = FakeClient()
1479
client.add_expected_call(
1480
'Branch.get_stacked_on_url', ('memory:///',),
1481
'error', ('NotStacked',),)
1482
client.add_expected_call(
1483
'Branch.lock_write', ('memory:///', '', ''),
1484
'success', ('ok', 'branch token', 'repo token'))
1485
client.add_expected_call(
1486
'Branch.set_config_option', ('memory:///', 'branch token',
1487
'repo token', 'foo', 'bar', ''),
1489
client.add_expected_call(
1490
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1492
transport = MemoryTransport()
1493
branch = self.make_remote_branch(transport, client)
1495
config = branch._get_config()
1496
config.set_option('foo', 'bar')
1498
client.finished_test()
1500
def test_backwards_compat_set_option(self):
1501
self.setup_smart_server_with_call_log()
1502
branch = self.make_branch('.')
1503
verb = 'Branch.set_config_option'
1504
self.disable_verb(verb)
1506
self.addCleanup(branch.unlock)
1507
self.reset_smart_call_log()
1508
branch._get_config().set_option('value', 'name')
1509
self.assertLength(10, self.hpss_calls)
1510
self.assertEqual('value', branch._get_config().get_option('name'))
1513
class TestBranchLockWrite(RemoteBranchTestCase):
1515
def test_lock_write_unlockable(self):
1516
transport = MemoryTransport()
1517
client = FakeClient(transport.base)
1518
client.add_expected_call(
1519
'Branch.get_stacked_on_url', ('quack/',),
1520
'error', ('NotStacked',),)
1521
client.add_expected_call(
1522
'Branch.lock_write', ('quack/', '', ''),
1523
'error', ('UnlockableTransport',))
1524
transport.mkdir('quack')
1525
transport = transport.clone('quack')
1526
branch = self.make_remote_branch(transport, client)
1527
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1528
client.finished_test()
1531
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1533
def test__get_config(self):
1534
client = FakeClient()
1535
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1536
transport = MemoryTransport()
1537
bzrdir = self.make_remote_bzrdir(transport, client)
1538
config = bzrdir.get_config()
1539
self.assertEqual('/', config.get_default_stack_on())
1541
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1544
def test_set_option_uses_vfs(self):
1545
self.setup_smart_server_with_call_log()
1546
bzrdir = self.make_bzrdir('.')
1547
self.reset_smart_call_log()
1548
config = bzrdir.get_config()
1549
config.set_default_stack_on('/')
1550
self.assertLength(3, self.hpss_calls)
1552
def test_backwards_compat_get_option(self):
1553
self.setup_smart_server_with_call_log()
1554
bzrdir = self.make_bzrdir('.')
1555
verb = 'BzrDir.get_config_file'
1556
self.disable_verb(verb)
1557
self.reset_smart_call_log()
1558
self.assertEqual(None,
1559
bzrdir._get_config().get_option('default_stack_on'))
1560
self.assertLength(3, self.hpss_calls)
1563
class TestTransportIsReadonly(tests.TestCase):
1565
def test_true(self):
1566
client = FakeClient()
1567
client.add_success_response('yes')
1568
transport = RemoteTransport('bzr://example.com/', medium=False,
1570
self.assertEqual(True, transport.is_readonly())
1572
[('call', 'Transport.is_readonly', ())],
1575
def test_false(self):
1576
client = FakeClient()
1577
client.add_success_response('no')
1578
transport = RemoteTransport('bzr://example.com/', medium=False,
1580
self.assertEqual(False, transport.is_readonly())
1582
[('call', 'Transport.is_readonly', ())],
1585
def test_error_from_old_server(self):
1586
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1588
Clients should treat it as a "no" response, because is_readonly is only
1589
advisory anyway (a transport could be read-write, but then the
1590
underlying filesystem could be readonly anyway).
1592
client = FakeClient()
1593
client.add_unknown_method_response('Transport.is_readonly')
1594
transport = RemoteTransport('bzr://example.com/', medium=False,
1596
self.assertEqual(False, transport.is_readonly())
1598
[('call', 'Transport.is_readonly', ())],
1602
class TestTransportMkdir(tests.TestCase):
1604
def test_permissiondenied(self):
1605
client = FakeClient()
1606
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1607
transport = RemoteTransport('bzr://example.com/', medium=False,
1609
exc = self.assertRaises(
1610
errors.PermissionDenied, transport.mkdir, 'client path')
1611
expected_error = errors.PermissionDenied('/client path', 'extra')
1612
self.assertEqual(expected_error, exc)
1615
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1617
def test_defaults_to_none(self):
1618
t = RemoteSSHTransport('bzr+ssh://example.com')
1619
self.assertIs(None, t._get_credentials()[0])
1621
def test_uses_authentication_config(self):
1622
conf = config.AuthenticationConfig()
1623
conf._get_config().update(
1624
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1627
t = RemoteSSHTransport('bzr+ssh://example.com')
1628
self.assertEqual('bar', t._get_credentials()[0])
1631
class TestRemoteRepository(TestRemote):
1632
"""Base for testing RemoteRepository protocol usage.
1634
These tests contain frozen requests and responses. We want any changes to
1635
what is sent or expected to be require a thoughtful update to these tests
1636
because they might break compatibility with different-versioned servers.
1639
def setup_fake_client_and_repository(self, transport_path):
1640
"""Create the fake client and repository for testing with.
1642
There's no real server here; we just have canned responses sent
1645
:param transport_path: Path below the root of the MemoryTransport
1646
where the repository will be created.
1648
transport = MemoryTransport()
1649
transport.mkdir(transport_path)
1650
client = FakeClient(transport.base)
1651
transport = transport.clone(transport_path)
1652
# we do not want bzrdir to make any remote calls
1653
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1655
repo = RemoteRepository(bzrdir, None, _client=client)
1659
class TestRepositoryFormat(TestRemoteRepository):
1661
def test_fast_delta(self):
1662
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1663
true_format = RemoteRepositoryFormat()
1664
true_format._network_name = true_name
1665
self.assertEqual(True, true_format.fast_deltas)
1666
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1667
false_format = RemoteRepositoryFormat()
1668
false_format._network_name = false_name
1669
self.assertEqual(False, false_format.fast_deltas)
1672
class TestRepositoryGatherStats(TestRemoteRepository):
1674
def test_revid_none(self):
1675
# ('ok',), body with revisions and size
1676
transport_path = 'quack'
1677
repo, client = self.setup_fake_client_and_repository(transport_path)
1678
client.add_success_response_with_body(
1679
'revisions: 2\nsize: 18\n', 'ok')
1680
result = repo.gather_stats(None)
1682
[('call_expecting_body', 'Repository.gather_stats',
1683
('quack/','','no'))],
1685
self.assertEqual({'revisions': 2, 'size': 18}, result)
1687
def test_revid_no_committers(self):
1688
# ('ok',), body without committers
1689
body = ('firstrev: 123456.300 3600\n'
1690
'latestrev: 654231.400 0\n'
1693
transport_path = 'quick'
1694
revid = u'\xc8'.encode('utf8')
1695
repo, client = self.setup_fake_client_and_repository(transport_path)
1696
client.add_success_response_with_body(body, 'ok')
1697
result = repo.gather_stats(revid)
1699
[('call_expecting_body', 'Repository.gather_stats',
1700
('quick/', revid, 'no'))],
1702
self.assertEqual({'revisions': 2, 'size': 18,
1703
'firstrev': (123456.300, 3600),
1704
'latestrev': (654231.400, 0),},
1707
def test_revid_with_committers(self):
1708
# ('ok',), body with committers
1709
body = ('committers: 128\n'
1710
'firstrev: 123456.300 3600\n'
1711
'latestrev: 654231.400 0\n'
1714
transport_path = 'buick'
1715
revid = u'\xc8'.encode('utf8')
1716
repo, client = self.setup_fake_client_and_repository(transport_path)
1717
client.add_success_response_with_body(body, 'ok')
1718
result = repo.gather_stats(revid, True)
1720
[('call_expecting_body', 'Repository.gather_stats',
1721
('buick/', revid, 'yes'))],
1723
self.assertEqual({'revisions': 2, 'size': 18,
1725
'firstrev': (123456.300, 3600),
1726
'latestrev': (654231.400, 0),},
1730
class TestRepositoryGetGraph(TestRemoteRepository):
1732
def test_get_graph(self):
1733
# get_graph returns a graph with a custom parents provider.
1734
transport_path = 'quack'
1735
repo, client = self.setup_fake_client_and_repository(transport_path)
1736
graph = repo.get_graph()
1737
self.assertNotEqual(graph._parents_provider, repo)
1740
class TestRepositoryGetParentMap(TestRemoteRepository):
1742
def test_get_parent_map_caching(self):
1743
# get_parent_map returns from cache until unlock()
1744
# setup a reponse with two revisions
1745
r1 = u'\u0e33'.encode('utf8')
1746
r2 = u'\u0dab'.encode('utf8')
1747
lines = [' '.join([r2, r1]), r1]
1748
encoded_body = bz2.compress('\n'.join(lines))
1750
transport_path = 'quack'
1751
repo, client = self.setup_fake_client_and_repository(transport_path)
1752
client.add_success_response_with_body(encoded_body, 'ok')
1753
client.add_success_response_with_body(encoded_body, 'ok')
1755
graph = repo.get_graph()
1756
parents = graph.get_parent_map([r2])
1757
self.assertEqual({r2: (r1,)}, parents)
1758
# locking and unlocking deeper should not reset
1761
parents = graph.get_parent_map([r1])
1762
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1764
[('call_with_body_bytes_expecting_body',
1765
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1769
# now we call again, and it should use the second response.
1771
graph = repo.get_graph()
1772
parents = graph.get_parent_map([r1])
1773
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1775
[('call_with_body_bytes_expecting_body',
1776
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1778
('call_with_body_bytes_expecting_body',
1779
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1785
def test_get_parent_map_reconnects_if_unknown_method(self):
1786
transport_path = 'quack'
1787
rev_id = 'revision-id'
1788
repo, client = self.setup_fake_client_and_repository(transport_path)
1789
client.add_unknown_method_response('Repository.get_parent_map')
1790
client.add_success_response_with_body(rev_id, 'ok')
1791
self.assertFalse(client._medium._is_remote_before((1, 2)))
1792
parents = repo.get_parent_map([rev_id])
1794
[('call_with_body_bytes_expecting_body',
1795
'Repository.get_parent_map', ('quack/', 'include-missing:',
1797
('disconnect medium',),
1798
('call_expecting_body', 'Repository.get_revision_graph',
1801
# The medium is now marked as being connected to an older server
1802
self.assertTrue(client._medium._is_remote_before((1, 2)))
1803
self.assertEqual({rev_id: ('null:',)}, parents)
1805
def test_get_parent_map_fallback_parentless_node(self):
1806
"""get_parent_map falls back to get_revision_graph on old servers. The
1807
results from get_revision_graph are tweaked to match the get_parent_map
1810
Specifically, a {key: ()} result from get_revision_graph means "no
1811
parents" for that key, which in get_parent_map results should be
1812
represented as {key: ('null:',)}.
1814
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1816
rev_id = 'revision-id'
1817
transport_path = 'quack'
1818
repo, client = self.setup_fake_client_and_repository(transport_path)
1819
client.add_success_response_with_body(rev_id, 'ok')
1820
client._medium._remember_remote_is_before((1, 2))
1821
parents = repo.get_parent_map([rev_id])
1823
[('call_expecting_body', 'Repository.get_revision_graph',
1826
self.assertEqual({rev_id: ('null:',)}, parents)
1828
def test_get_parent_map_unexpected_response(self):
1829
repo, client = self.setup_fake_client_and_repository('path')
1830
client.add_success_response('something unexpected!')
1832
errors.UnexpectedSmartServerResponse,
1833
repo.get_parent_map, ['a-revision-id'])
1835
def test_get_parent_map_negative_caches_missing_keys(self):
1836
self.setup_smart_server_with_call_log()
1837
repo = self.make_repository('foo')
1838
self.assertIsInstance(repo, RemoteRepository)
1840
self.addCleanup(repo.unlock)
1841
self.reset_smart_call_log()
1842
graph = repo.get_graph()
1843
self.assertEqual({},
1844
graph.get_parent_map(['some-missing', 'other-missing']))
1845
self.assertLength(1, self.hpss_calls)
1846
# No call if we repeat this
1847
self.reset_smart_call_log()
1848
graph = repo.get_graph()
1849
self.assertEqual({},
1850
graph.get_parent_map(['some-missing', 'other-missing']))
1851
self.assertLength(0, self.hpss_calls)
1852
# Asking for more unknown keys makes a request.
1853
self.reset_smart_call_log()
1854
graph = repo.get_graph()
1855
self.assertEqual({},
1856
graph.get_parent_map(['some-missing', 'other-missing',
1858
self.assertLength(1, self.hpss_calls)
1860
def disableExtraResults(self):
1861
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1862
SmartServerRepositoryGetParentMap.no_extra_results = True
1864
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1865
self.addCleanup(reset_values)
1867
def test_null_cached_missing_and_stop_key(self):
1868
self.setup_smart_server_with_call_log()
1869
# Make a branch with a single revision.
1870
builder = self.make_branch_builder('foo')
1871
builder.start_series()
1872
builder.build_snapshot('first', None, [
1873
('add', ('', 'root-id', 'directory', ''))])
1874
builder.finish_series()
1875
branch = builder.get_branch()
1876
repo = branch.repository
1877
self.assertIsInstance(repo, RemoteRepository)
1878
# Stop the server from sending extra results.
1879
self.disableExtraResults()
1881
self.addCleanup(repo.unlock)
1882
self.reset_smart_call_log()
1883
graph = repo.get_graph()
1884
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1885
# 'first' it will be a candidate for the stop_keys of subsequent
1886
# requests, and because 'null:' was queried but not returned it will be
1887
# cached as missing.
1888
self.assertEqual({'first': ('null:',)},
1889
graph.get_parent_map(['first', 'null:']))
1890
# Now query for another key. This request will pass along a recipe of
1891
# start and stop keys describing the already cached results, and this
1892
# recipe's revision count must be correct (or else it will trigger an
1893
# error from the server).
1894
self.assertEqual({}, graph.get_parent_map(['another-key']))
1895
# This assertion guards against disableExtraResults silently failing to
1896
# work, thus invalidating the test.
1897
self.assertLength(2, self.hpss_calls)
1899
def test_get_parent_map_gets_ghosts_from_result(self):
1900
# asking for a revision should negatively cache close ghosts in its
1902
self.setup_smart_server_with_call_log()
1903
tree = self.make_branch_and_memory_tree('foo')
1906
builder = treebuilder.TreeBuilder()
1907
builder.start_tree(tree)
1909
builder.finish_tree()
1910
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1911
rev_id = tree.commit('')
1915
self.addCleanup(tree.unlock)
1916
repo = tree.branch.repository
1917
self.assertIsInstance(repo, RemoteRepository)
1919
repo.get_parent_map([rev_id])
1920
self.reset_smart_call_log()
1921
# Now asking for rev_id's ghost parent should not make calls
1922
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1923
self.assertLength(0, self.hpss_calls)
1926
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1928
def test_allows_new_revisions(self):
1929
"""get_parent_map's results can be updated by commit."""
1930
smart_server = server.SmartTCPServer_for_testing()
1931
smart_server.setUp()
1932
self.addCleanup(smart_server.tearDown)
1933
self.make_branch('branch')
1934
branch = Branch.open(smart_server.get_url() + '/branch')
1935
tree = branch.create_checkout('tree', lightweight=True)
1937
self.addCleanup(tree.unlock)
1938
graph = tree.branch.repository.get_graph()
1939
# This provides an opportunity for the missing rev-id to be cached.
1940
self.assertEqual({}, graph.get_parent_map(['rev1']))
1941
tree.commit('message', rev_id='rev1')
1942
graph = tree.branch.repository.get_graph()
1943
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1946
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1948
def test_null_revision(self):
1949
# a null revision has the predictable result {}, we should have no wire
1950
# traffic when calling it with this argument
1951
transport_path = 'empty'
1952
repo, client = self.setup_fake_client_and_repository(transport_path)
1953
client.add_success_response('notused')
1954
# actual RemoteRepository.get_revision_graph is gone, but there's an
1955
# equivalent private method for testing
1956
result = repo._get_revision_graph(NULL_REVISION)
1957
self.assertEqual([], client._calls)
1958
self.assertEqual({}, result)
1960
def test_none_revision(self):
1961
# with none we want the entire graph
1962
r1 = u'\u0e33'.encode('utf8')
1963
r2 = u'\u0dab'.encode('utf8')
1964
lines = [' '.join([r2, r1]), r1]
1965
encoded_body = '\n'.join(lines)
1967
transport_path = 'sinhala'
1968
repo, client = self.setup_fake_client_and_repository(transport_path)
1969
client.add_success_response_with_body(encoded_body, 'ok')
1970
# actual RemoteRepository.get_revision_graph is gone, but there's an
1971
# equivalent private method for testing
1972
result = repo._get_revision_graph(None)
1974
[('call_expecting_body', 'Repository.get_revision_graph',
1977
self.assertEqual({r1: (), r2: (r1, )}, result)
1979
def test_specific_revision(self):
1980
# with a specific revision we want the graph for that
1981
# with none we want the entire graph
1982
r11 = u'\u0e33'.encode('utf8')
1983
r12 = u'\xc9'.encode('utf8')
1984
r2 = u'\u0dab'.encode('utf8')
1985
lines = [' '.join([r2, r11, r12]), r11, r12]
1986
encoded_body = '\n'.join(lines)
1988
transport_path = 'sinhala'
1989
repo, client = self.setup_fake_client_and_repository(transport_path)
1990
client.add_success_response_with_body(encoded_body, 'ok')
1991
result = repo._get_revision_graph(r2)
1993
[('call_expecting_body', 'Repository.get_revision_graph',
1996
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1998
def test_no_such_revision(self):
2000
transport_path = 'sinhala'
2001
repo, client = self.setup_fake_client_and_repository(transport_path)
2002
client.add_error_response('nosuchrevision', revid)
2003
# also check that the right revision is reported in the error
2004
self.assertRaises(errors.NoSuchRevision,
2005
repo._get_revision_graph, revid)
2007
[('call_expecting_body', 'Repository.get_revision_graph',
2008
('sinhala/', revid))],
2011
def test_unexpected_error(self):
2013
transport_path = 'sinhala'
2014
repo, client = self.setup_fake_client_and_repository(transport_path)
2015
client.add_error_response('AnUnexpectedError')
2016
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2017
repo._get_revision_graph, revid)
2018
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2021
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2024
repo, client = self.setup_fake_client_and_repository('quack')
2025
client.add_expected_call(
2026
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2027
'success', ('ok', 'rev-five'))
2028
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2029
self.assertEqual((True, 'rev-five'), result)
2030
client.finished_test()
2032
def test_history_incomplete(self):
2033
repo, client = self.setup_fake_client_and_repository('quack')
2034
client.add_expected_call(
2035
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2036
'success', ('history-incomplete', 10, 'rev-ten'))
2037
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2038
self.assertEqual((False, (10, 'rev-ten')), result)
2039
client.finished_test()
2041
def test_history_incomplete_with_fallback(self):
2042
"""A 'history-incomplete' response causes the fallback repository to be
2043
queried too, if one is set.
2045
# Make a repo with a fallback repo, both using a FakeClient.
2046
format = remote.response_tuple_to_repo_format(
2047
('yes', 'no', 'yes', 'fake-network-name'))
2048
repo, client = self.setup_fake_client_and_repository('quack')
2049
repo._format = format
2050
fallback_repo, ignored = self.setup_fake_client_and_repository(
2052
fallback_repo._client = client
2053
repo.add_fallback_repository(fallback_repo)
2054
# First the client should ask the primary repo
2055
client.add_expected_call(
2056
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2057
'success', ('history-incomplete', 2, 'rev-two'))
2058
# Then it should ask the fallback, using revno/revid from the
2059
# history-incomplete response as the known revno/revid.
2060
client.add_expected_call(
2061
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2062
'success', ('ok', 'rev-one'))
2063
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2064
self.assertEqual((True, 'rev-one'), result)
2065
client.finished_test()
2067
def test_nosuchrevision(self):
2068
# 'nosuchrevision' is returned when the known-revid is not found in the
2069
# remote repo. The client translates that response to NoSuchRevision.
2070
repo, client = self.setup_fake_client_and_repository('quack')
2071
client.add_expected_call(
2072
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2073
'error', ('nosuchrevision', 'rev-foo'))
2075
errors.NoSuchRevision,
2076
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2077
client.finished_test()
2080
class TestRepositoryIsShared(TestRemoteRepository):
2082
def test_is_shared(self):
2083
# ('yes', ) for Repository.is_shared -> 'True'.
2084
transport_path = 'quack'
2085
repo, client = self.setup_fake_client_and_repository(transport_path)
2086
client.add_success_response('yes')
2087
result = repo.is_shared()
2089
[('call', 'Repository.is_shared', ('quack/',))],
2091
self.assertEqual(True, result)
2093
def test_is_not_shared(self):
2094
# ('no', ) for Repository.is_shared -> 'False'.
2095
transport_path = 'qwack'
2096
repo, client = self.setup_fake_client_and_repository(transport_path)
2097
client.add_success_response('no')
2098
result = repo.is_shared()
2100
[('call', 'Repository.is_shared', ('qwack/',))],
2102
self.assertEqual(False, result)
2105
class TestRepositoryLockWrite(TestRemoteRepository):
2107
def test_lock_write(self):
2108
transport_path = 'quack'
2109
repo, client = self.setup_fake_client_and_repository(transport_path)
2110
client.add_success_response('ok', 'a token')
2111
result = repo.lock_write()
2113
[('call', 'Repository.lock_write', ('quack/', ''))],
2115
self.assertEqual('a token', result)
2117
def test_lock_write_already_locked(self):
2118
transport_path = 'quack'
2119
repo, client = self.setup_fake_client_and_repository(transport_path)
2120
client.add_error_response('LockContention')
2121
self.assertRaises(errors.LockContention, repo.lock_write)
2123
[('call', 'Repository.lock_write', ('quack/', ''))],
2126
def test_lock_write_unlockable(self):
2127
transport_path = 'quack'
2128
repo, client = self.setup_fake_client_and_repository(transport_path)
2129
client.add_error_response('UnlockableTransport')
2130
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2132
[('call', 'Repository.lock_write', ('quack/', ''))],
2136
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2138
def test_backwards_compat(self):
2139
self.setup_smart_server_with_call_log()
2140
repo = self.make_repository('.')
2141
self.reset_smart_call_log()
2142
verb = 'Repository.set_make_working_trees'
2143
self.disable_verb(verb)
2144
repo.set_make_working_trees(True)
2145
call_count = len([call for call in self.hpss_calls if
2146
call.call.method == verb])
2147
self.assertEqual(1, call_count)
2149
def test_current(self):
2150
transport_path = 'quack'
2151
repo, client = self.setup_fake_client_and_repository(transport_path)
2152
client.add_expected_call(
2153
'Repository.set_make_working_trees', ('quack/', 'True'),
2155
client.add_expected_call(
2156
'Repository.set_make_working_trees', ('quack/', 'False'),
2158
repo.set_make_working_trees(True)
2159
repo.set_make_working_trees(False)
2162
class TestRepositoryUnlock(TestRemoteRepository):
2164
def test_unlock(self):
2165
transport_path = 'quack'
2166
repo, client = self.setup_fake_client_and_repository(transport_path)
2167
client.add_success_response('ok', 'a token')
2168
client.add_success_response('ok')
2172
[('call', 'Repository.lock_write', ('quack/', '')),
2173
('call', 'Repository.unlock', ('quack/', 'a token'))],
2176
def test_unlock_wrong_token(self):
2177
# If somehow the token is wrong, unlock will raise TokenMismatch.
2178
transport_path = 'quack'
2179
repo, client = self.setup_fake_client_and_repository(transport_path)
2180
client.add_success_response('ok', 'a token')
2181
client.add_error_response('TokenMismatch')
2183
self.assertRaises(errors.TokenMismatch, repo.unlock)
2186
class TestRepositoryHasRevision(TestRemoteRepository):
2188
def test_none(self):
2189
# repo.has_revision(None) should not cause any traffic.
2190
transport_path = 'quack'
2191
repo, client = self.setup_fake_client_and_repository(transport_path)
2193
# The null revision is always there, so has_revision(None) == True.
2194
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2196
# The remote repo shouldn't be accessed.
2197
self.assertEqual([], client._calls)
2200
class TestRepositoryInsertStream(TestRemoteRepository):
2202
def test_unlocked_repo(self):
2203
transport_path = 'quack'
2204
repo, client = self.setup_fake_client_and_repository(transport_path)
2205
client.add_expected_call(
2206
'Repository.insert_stream', ('quack/', ''),
2208
client.add_expected_call(
2209
'Repository.insert_stream', ('quack/', ''),
2211
sink = repo._get_sink()
2212
fmt = repository.RepositoryFormat.get_default_format()
2213
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2214
self.assertEqual([], resume_tokens)
2215
self.assertEqual(set(), missing_keys)
2216
client.finished_test()
2218
def test_locked_repo_with_no_lock_token(self):
2219
transport_path = 'quack'
2220
repo, client = self.setup_fake_client_and_repository(transport_path)
2221
client.add_expected_call(
2222
'Repository.lock_write', ('quack/', ''),
2223
'success', ('ok', ''))
2224
client.add_expected_call(
2225
'Repository.insert_stream', ('quack/', ''),
2227
client.add_expected_call(
2228
'Repository.insert_stream', ('quack/', ''),
2231
sink = repo._get_sink()
2232
fmt = repository.RepositoryFormat.get_default_format()
2233
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2234
self.assertEqual([], resume_tokens)
2235
self.assertEqual(set(), missing_keys)
2236
client.finished_test()
2238
def test_locked_repo_with_lock_token(self):
2239
transport_path = 'quack'
2240
repo, client = self.setup_fake_client_and_repository(transport_path)
2241
client.add_expected_call(
2242
'Repository.lock_write', ('quack/', ''),
2243
'success', ('ok', 'a token'))
2244
client.add_expected_call(
2245
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2247
client.add_expected_call(
2248
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2251
sink = repo._get_sink()
2252
fmt = repository.RepositoryFormat.get_default_format()
2253
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2254
self.assertEqual([], resume_tokens)
2255
self.assertEqual(set(), missing_keys)
2256
client.finished_test()
2259
class TestRepositoryTarball(TestRemoteRepository):
2261
# This is a canned tarball reponse we can validate against
2263
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2264
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2265
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2266
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2267
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2268
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2269
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2270
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2271
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2272
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2273
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2274
'nWQ7QH/F3JFOFCQ0aSPfA='
2277
def test_repository_tarball(self):
2278
# Test that Repository.tarball generates the right operations
2279
transport_path = 'repo'
2280
expected_calls = [('call_expecting_body', 'Repository.tarball',
2281
('repo/', 'bz2',),),
2283
repo, client = self.setup_fake_client_and_repository(transport_path)
2284
client.add_success_response_with_body(self.tarball_content, 'ok')
2285
# Now actually ask for the tarball
2286
tarball_file = repo._get_tarball('bz2')
2288
self.assertEqual(expected_calls, client._calls)
2289
self.assertEqual(self.tarball_content, tarball_file.read())
2291
tarball_file.close()
2294
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2295
"""RemoteRepository.copy_content_into optimizations"""
2297
def test_copy_content_remote_to_local(self):
2298
self.transport_server = server.SmartTCPServer_for_testing
2299
src_repo = self.make_repository('repo1')
2300
src_repo = repository.Repository.open(self.get_url('repo1'))
2301
# At the moment the tarball-based copy_content_into can't write back
2302
# into a smart server. It would be good if it could upload the
2303
# tarball; once that works we'd have to create repositories of
2304
# different formats. -- mbp 20070410
2305
dest_url = self.get_vfs_only_url('repo2')
2306
dest_bzrdir = BzrDir.create(dest_url)
2307
dest_repo = dest_bzrdir.create_repository()
2308
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2309
self.assertTrue(isinstance(src_repo, RemoteRepository))
2310
src_repo.copy_content_into(dest_repo)
2313
class _StubRealPackRepository(object):
2315
def __init__(self, calls):
2317
self._pack_collection = _StubPackCollection(calls)
2319
def is_in_write_group(self):
2322
def refresh_data(self):
2323
self.calls.append(('pack collection reload_pack_names',))
2326
class _StubPackCollection(object):
2328
def __init__(self, calls):
2332
self.calls.append(('pack collection autopack',))
2335
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2336
"""Tests for RemoteRepository.autopack implementation."""
2339
"""When the server returns 'ok' and there's no _real_repository, then
2340
nothing else happens: the autopack method is done.
2342
transport_path = 'quack'
2343
repo, client = self.setup_fake_client_and_repository(transport_path)
2344
client.add_expected_call(
2345
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2347
client.finished_test()
2349
def test_ok_with_real_repo(self):
2350
"""When the server returns 'ok' and there is a _real_repository, then
2351
the _real_repository's reload_pack_name's method will be called.
2353
transport_path = 'quack'
2354
repo, client = self.setup_fake_client_and_repository(transport_path)
2355
client.add_expected_call(
2356
'PackRepository.autopack', ('quack/',),
2358
repo._real_repository = _StubRealPackRepository(client._calls)
2361
[('call', 'PackRepository.autopack', ('quack/',)),
2362
('pack collection reload_pack_names',)],
2365
def test_backwards_compatibility(self):
2366
"""If the server does not recognise the PackRepository.autopack verb,
2367
fallback to the real_repository's implementation.
2369
transport_path = 'quack'
2370
repo, client = self.setup_fake_client_and_repository(transport_path)
2371
client.add_unknown_method_response('PackRepository.autopack')
2372
def stub_ensure_real():
2373
client._calls.append(('_ensure_real',))
2374
repo._real_repository = _StubRealPackRepository(client._calls)
2375
repo._ensure_real = stub_ensure_real
2378
[('call', 'PackRepository.autopack', ('quack/',)),
2380
('pack collection autopack',)],
2384
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2385
"""Base class for unit tests for bzrlib.remote._translate_error."""
2387
def translateTuple(self, error_tuple, **context):
2388
"""Call _translate_error with an ErrorFromSmartServer built from the
2391
:param error_tuple: A tuple of a smart server response, as would be
2392
passed to an ErrorFromSmartServer.
2393
:kwargs context: context items to call _translate_error with.
2395
:returns: The error raised by _translate_error.
2397
# Raise the ErrorFromSmartServer before passing it as an argument,
2398
# because _translate_error may need to re-raise it with a bare 'raise'
2400
server_error = errors.ErrorFromSmartServer(error_tuple)
2401
translated_error = self.translateErrorFromSmartServer(
2402
server_error, **context)
2403
return translated_error
2405
def translateErrorFromSmartServer(self, error_object, **context):
2406
"""Like translateTuple, but takes an already constructed
2407
ErrorFromSmartServer rather than a tuple.
2411
except errors.ErrorFromSmartServer, server_error:
2412
translated_error = self.assertRaises(
2413
errors.BzrError, remote._translate_error, server_error,
2415
return translated_error
2418
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2419
"""Unit tests for bzrlib.remote._translate_error.
2421
Given an ErrorFromSmartServer (which has an error tuple from a smart
2422
server) and some context, _translate_error raises more specific errors from
2425
This test case covers the cases where _translate_error succeeds in
2426
translating an ErrorFromSmartServer to something better. See
2427
TestErrorTranslationRobustness for other cases.
2430
def test_NoSuchRevision(self):
2431
branch = self.make_branch('')
2433
translated_error = self.translateTuple(
2434
('NoSuchRevision', revid), branch=branch)
2435
expected_error = errors.NoSuchRevision(branch, revid)
2436
self.assertEqual(expected_error, translated_error)
2438
def test_nosuchrevision(self):
2439
repository = self.make_repository('')
2441
translated_error = self.translateTuple(
2442
('nosuchrevision', revid), repository=repository)
2443
expected_error = errors.NoSuchRevision(repository, revid)
2444
self.assertEqual(expected_error, translated_error)
2446
def test_nobranch(self):
2447
bzrdir = self.make_bzrdir('')
2448
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2449
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2450
self.assertEqual(expected_error, translated_error)
2452
def test_LockContention(self):
2453
translated_error = self.translateTuple(('LockContention',))
2454
expected_error = errors.LockContention('(remote lock)')
2455
self.assertEqual(expected_error, translated_error)
2457
def test_UnlockableTransport(self):
2458
bzrdir = self.make_bzrdir('')
2459
translated_error = self.translateTuple(
2460
('UnlockableTransport',), bzrdir=bzrdir)
2461
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2462
self.assertEqual(expected_error, translated_error)
2464
def test_LockFailed(self):
2465
lock = 'str() of a server lock'
2466
why = 'str() of why'
2467
translated_error = self.translateTuple(('LockFailed', lock, why))
2468
expected_error = errors.LockFailed(lock, why)
2469
self.assertEqual(expected_error, translated_error)
2471
def test_TokenMismatch(self):
2472
token = 'a lock token'
2473
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2474
expected_error = errors.TokenMismatch(token, '(remote token)')
2475
self.assertEqual(expected_error, translated_error)
2477
def test_Diverged(self):
2478
branch = self.make_branch('a')
2479
other_branch = self.make_branch('b')
2480
translated_error = self.translateTuple(
2481
('Diverged',), branch=branch, other_branch=other_branch)
2482
expected_error = errors.DivergedBranches(branch, other_branch)
2483
self.assertEqual(expected_error, translated_error)
2485
def test_ReadError_no_args(self):
2487
translated_error = self.translateTuple(('ReadError',), path=path)
2488
expected_error = errors.ReadError(path)
2489
self.assertEqual(expected_error, translated_error)
2491
def test_ReadError(self):
2493
translated_error = self.translateTuple(('ReadError', path))
2494
expected_error = errors.ReadError(path)
2495
self.assertEqual(expected_error, translated_error)
2497
def test_PermissionDenied_no_args(self):
2499
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2500
expected_error = errors.PermissionDenied(path)
2501
self.assertEqual(expected_error, translated_error)
2503
def test_PermissionDenied_one_arg(self):
2505
translated_error = self.translateTuple(('PermissionDenied', path))
2506
expected_error = errors.PermissionDenied(path)
2507
self.assertEqual(expected_error, translated_error)
2509
def test_PermissionDenied_one_arg_and_context(self):
2510
"""Given a choice between a path from the local context and a path on
2511
the wire, _translate_error prefers the path from the local context.
2513
local_path = 'local path'
2514
remote_path = 'remote path'
2515
translated_error = self.translateTuple(
2516
('PermissionDenied', remote_path), path=local_path)
2517
expected_error = errors.PermissionDenied(local_path)
2518
self.assertEqual(expected_error, translated_error)
2520
def test_PermissionDenied_two_args(self):
2522
extra = 'a string with extra info'
2523
translated_error = self.translateTuple(
2524
('PermissionDenied', path, extra))
2525
expected_error = errors.PermissionDenied(path, extra)
2526
self.assertEqual(expected_error, translated_error)
2529
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2530
"""Unit tests for bzrlib.remote._translate_error's robustness.
2532
TestErrorTranslationSuccess is for cases where _translate_error can
2533
translate successfully. This class about how _translate_err behaves when
2534
it fails to translate: it re-raises the original error.
2537
def test_unrecognised_server_error(self):
2538
"""If the error code from the server is not recognised, the original
2539
ErrorFromSmartServer is propagated unmodified.
2541
error_tuple = ('An unknown error tuple',)
2542
server_error = errors.ErrorFromSmartServer(error_tuple)
2543
translated_error = self.translateErrorFromSmartServer(server_error)
2544
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2545
self.assertEqual(expected_error, translated_error)
2547
def test_context_missing_a_key(self):
2548
"""In case of a bug in the client, or perhaps an unexpected response
2549
from a server, _translate_error returns the original error tuple from
2550
the server and mutters a warning.
2552
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2553
# in the context dict. So let's give it an empty context dict instead
2554
# to exercise its error recovery.
2556
error_tuple = ('NoSuchRevision', 'revid')
2557
server_error = errors.ErrorFromSmartServer(error_tuple)
2558
translated_error = self.translateErrorFromSmartServer(server_error)
2559
self.assertEqual(server_error, translated_error)
2560
# In addition to re-raising ErrorFromSmartServer, some debug info has
2561
# been muttered to the log file for developer to look at.
2562
self.assertContainsRe(
2563
self._get_log(keep_log_file=True),
2564
"Missing key 'branch' in context")
2566
def test_path_missing(self):
2567
"""Some translations (PermissionDenied, ReadError) can determine the
2568
'path' variable from either the wire or the local context. If neither
2569
has it, then an error is raised.
2571
error_tuple = ('ReadError',)
2572
server_error = errors.ErrorFromSmartServer(error_tuple)
2573
translated_error = self.translateErrorFromSmartServer(server_error)
2574
self.assertEqual(server_error, translated_error)
2575
# In addition to re-raising ErrorFromSmartServer, some debug info has
2576
# been muttered to the log file for developer to look at.
2577
self.assertContainsRe(
2578
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2581
class TestStacking(tests.TestCaseWithTransport):
2582
"""Tests for operations on stacked remote repositories.
2584
The underlying format type must support stacking.
2587
def test_access_stacked_remote(self):
2588
# based on <http://launchpad.net/bugs/261315>
2589
# make a branch stacked on another repository containing an empty
2590
# revision, then open it over hpss - we should be able to see that
2592
base_transport = self.get_transport()
2593
base_builder = self.make_branch_builder('base', format='1.9')
2594
base_builder.start_series()
2595
base_revid = base_builder.build_snapshot('rev-id', None,
2596
[('add', ('', None, 'directory', None))],
2598
base_builder.finish_series()
2599
stacked_branch = self.make_branch('stacked', format='1.9')
2600
stacked_branch.set_stacked_on_url('../base')
2601
# start a server looking at this
2602
smart_server = server.SmartTCPServer_for_testing()
2603
smart_server.setUp()
2604
self.addCleanup(smart_server.tearDown)
2605
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2606
# can get its branch and repository
2607
remote_branch = remote_bzrdir.open_branch()
2608
remote_repo = remote_branch.repository
2609
remote_repo.lock_read()
2611
# it should have an appropriate fallback repository, which should also
2612
# be a RemoteRepository
2613
self.assertLength(1, remote_repo._fallback_repositories)
2614
self.assertIsInstance(remote_repo._fallback_repositories[0],
2616
# and it has the revision committed to the underlying repository;
2617
# these have varying implementations so we try several of them
2618
self.assertTrue(remote_repo.has_revisions([base_revid]))
2619
self.assertTrue(remote_repo.has_revision(base_revid))
2620
self.assertEqual(remote_repo.get_revision(base_revid).message,
2623
remote_repo.unlock()
2625
def prepare_stacked_remote_branch(self):
2626
"""Get stacked_upon and stacked branches with content in each."""
2627
self.setup_smart_server_with_call_log()
2628
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2629
tree1.commit('rev1', rev_id='rev1')
2630
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2631
).open_workingtree()
2632
tree2.commit('local changes make me feel good.')
2633
branch2 = Branch.open(self.get_url('tree2'))
2635
self.addCleanup(branch2.unlock)
2636
return tree1.branch, branch2
2638
def test_stacked_get_parent_map(self):
2639
# the public implementation of get_parent_map obeys stacking
2640
_, branch = self.prepare_stacked_remote_branch()
2641
repo = branch.repository
2642
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2644
def test_unstacked_get_parent_map(self):
2645
# _unstacked_provider.get_parent_map ignores stacking
2646
_, branch = self.prepare_stacked_remote_branch()
2647
provider = branch.repository._unstacked_provider
2648
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2650
def fetch_stream_to_rev_order(self, stream):
2652
for kind, substream in stream:
2653
if not kind == 'revisions':
2656
for content in substream:
2657
result.append(content.key[-1])
2660
def get_ordered_revs(self, format, order):
2661
"""Get a list of the revisions in a stream to format format.
2663
:param format: The format of the target.
2664
:param order: the order that target should have requested.
2665
:result: The revision ids in the stream, in the order seen,
2666
the topological order of revisions in the source.
2668
unordered_format = bzrdir.format_registry.get(format)()
2669
target_repository_format = unordered_format.repository_format
2671
self.assertEqual(order, target_repository_format._fetch_order)
2672
trunk, stacked = self.prepare_stacked_remote_branch()
2673
source = stacked.repository._get_source(target_repository_format)
2674
tip = stacked.last_revision()
2675
revs = stacked.repository.get_ancestry(tip)
2676
search = graph.PendingAncestryResult([tip], stacked.repository)
2677
self.reset_smart_call_log()
2678
stream = source.get_stream(search)
2681
# We trust that if a revision is in the stream the rest of the new
2682
# content for it is too, as per our main fetch tests; here we are
2683
# checking that the revisions are actually included at all, and their
2685
return self.fetch_stream_to_rev_order(stream), revs
2687
def test_stacked_get_stream_unordered(self):
2688
# Repository._get_source.get_stream() from a stacked repository with
2689
# unordered yields the full data from both stacked and stacked upon
2691
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2692
self.assertEqual(set(expected_revs), set(rev_ord))
2693
# Getting unordered results should have made a streaming data request
2694
# from the server, then one from the backing branch.
2695
self.assertLength(2, self.hpss_calls)
2697
def test_stacked_get_stream_topological(self):
2698
# Repository._get_source.get_stream() from a stacked repository with
2699
# topological sorting yields the full data from both stacked and
2700
# stacked upon sources in topological order.
2701
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2702
self.assertEqual(expected_revs, rev_ord)
2703
# Getting topological sort requires VFS calls still
2704
self.assertLength(12, self.hpss_calls)
2706
def test_stacked_get_stream_groupcompress(self):
2707
# Repository._get_source.get_stream() from a stacked repository with
2708
# groupcompress sorting yields the full data from both stacked and
2709
# stacked upon sources in groupcompress order.
2710
raise tests.TestSkipped('No groupcompress ordered format available')
2711
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2712
self.assertEqual(expected_revs, reversed(rev_ord))
2713
# Getting unordered results should have made a streaming data request
2714
# from the backing branch, and one from the stacked on branch.
2715
self.assertLength(2, self.hpss_calls)
2717
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
2718
# When pulling some fixed amount of content that is more than the
2719
# source has (because some is coming from a fallback branch, no error
2720
# should be received. This was reported as bug 360791.
2721
# Need three branches: a trunk, a stacked branch, and a preexisting
2722
# branch pulling content from stacked and trunk.
2723
self.setup_smart_server_with_call_log()
2724
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
2725
r1 = trunk.commit('start')
2726
stacked_branch = trunk.branch.create_clone_on_transport(
2727
self.get_transport('stacked'), stacked_on=trunk.branch.base)
2728
local = self.make_branch('local', format='1.9-rich-root')
2729
local.repository.fetch(stacked_branch.repository,
2730
stacked_branch.last_revision())
2733
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2736
super(TestRemoteBranchEffort, self).setUp()
2737
# Create a smart server that publishes whatever the backing VFS server
2739
self.smart_server = server.SmartTCPServer_for_testing()
2740
self.smart_server.setUp(self.get_server())
2741
self.addCleanup(self.smart_server.tearDown)
2742
# Log all HPSS calls into self.hpss_calls.
2743
_SmartClient.hooks.install_named_hook(
2744
'call', self.capture_hpss_call, None)
2745
self.hpss_calls = []
2747
def capture_hpss_call(self, params):
2748
self.hpss_calls.append(params.method)
2750
def test_copy_content_into_avoids_revision_history(self):
2751
local = self.make_branch('local')
2752
remote_backing_tree = self.make_branch_and_tree('remote')
2753
remote_backing_tree.commit("Commit.")
2754
remote_branch_url = self.smart_server.get_url() + 'remote'
2755
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2756
local.repository.fetch(remote_branch.repository)
2757
self.hpss_calls = []
2758
remote_branch.copy_content_into(local)
2759
self.assertFalse('Branch.revision_history' in self.hpss_calls)