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',
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',
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 TestRepositoryIsShared(TestRemoteRepository):
2023
def test_is_shared(self):
2024
# ('yes', ) for Repository.is_shared -> 'True'.
2025
transport_path = 'quack'
2026
repo, client = self.setup_fake_client_and_repository(transport_path)
2027
client.add_success_response('yes')
2028
result = repo.is_shared()
2030
[('call', 'Repository.is_shared', ('quack/',))],
2032
self.assertEqual(True, result)
2034
def test_is_not_shared(self):
2035
# ('no', ) for Repository.is_shared -> 'False'.
2036
transport_path = 'qwack'
2037
repo, client = self.setup_fake_client_and_repository(transport_path)
2038
client.add_success_response('no')
2039
result = repo.is_shared()
2041
[('call', 'Repository.is_shared', ('qwack/',))],
2043
self.assertEqual(False, result)
2046
class TestRepositoryLockWrite(TestRemoteRepository):
2048
def test_lock_write(self):
2049
transport_path = 'quack'
2050
repo, client = self.setup_fake_client_and_repository(transport_path)
2051
client.add_success_response('ok', 'a token')
2052
result = repo.lock_write()
2054
[('call', 'Repository.lock_write', ('quack/', ''))],
2056
self.assertEqual('a token', result)
2058
def test_lock_write_already_locked(self):
2059
transport_path = 'quack'
2060
repo, client = self.setup_fake_client_and_repository(transport_path)
2061
client.add_error_response('LockContention')
2062
self.assertRaises(errors.LockContention, repo.lock_write)
2064
[('call', 'Repository.lock_write', ('quack/', ''))],
2067
def test_lock_write_unlockable(self):
2068
transport_path = 'quack'
2069
repo, client = self.setup_fake_client_and_repository(transport_path)
2070
client.add_error_response('UnlockableTransport')
2071
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2073
[('call', 'Repository.lock_write', ('quack/', ''))],
2077
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2079
def test_backwards_compat(self):
2080
self.setup_smart_server_with_call_log()
2081
repo = self.make_repository('.')
2082
self.reset_smart_call_log()
2083
verb = 'Repository.set_make_working_trees'
2084
self.disable_verb(verb)
2085
repo.set_make_working_trees(True)
2086
call_count = len([call for call in self.hpss_calls if
2087
call.call.method == verb])
2088
self.assertEqual(1, call_count)
2090
def test_current(self):
2091
transport_path = 'quack'
2092
repo, client = self.setup_fake_client_and_repository(transport_path)
2093
client.add_expected_call(
2094
'Repository.set_make_working_trees', ('quack/', 'True'),
2096
client.add_expected_call(
2097
'Repository.set_make_working_trees', ('quack/', 'False'),
2099
repo.set_make_working_trees(True)
2100
repo.set_make_working_trees(False)
2103
class TestRepositoryUnlock(TestRemoteRepository):
2105
def test_unlock(self):
2106
transport_path = 'quack'
2107
repo, client = self.setup_fake_client_and_repository(transport_path)
2108
client.add_success_response('ok', 'a token')
2109
client.add_success_response('ok')
2113
[('call', 'Repository.lock_write', ('quack/', '')),
2114
('call', 'Repository.unlock', ('quack/', 'a token'))],
2117
def test_unlock_wrong_token(self):
2118
# If somehow the token is wrong, unlock will raise TokenMismatch.
2119
transport_path = 'quack'
2120
repo, client = self.setup_fake_client_and_repository(transport_path)
2121
client.add_success_response('ok', 'a token')
2122
client.add_error_response('TokenMismatch')
2124
self.assertRaises(errors.TokenMismatch, repo.unlock)
2127
class TestRepositoryHasRevision(TestRemoteRepository):
2129
def test_none(self):
2130
# repo.has_revision(None) should not cause any traffic.
2131
transport_path = 'quack'
2132
repo, client = self.setup_fake_client_and_repository(transport_path)
2134
# The null revision is always there, so has_revision(None) == True.
2135
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2137
# The remote repo shouldn't be accessed.
2138
self.assertEqual([], client._calls)
2141
class TestRepositoryInsertStream(TestRemoteRepository):
2143
def test_unlocked_repo(self):
2144
transport_path = 'quack'
2145
repo, client = self.setup_fake_client_and_repository(transport_path)
2146
client.add_expected_call(
2147
'Repository.insert_stream', ('quack/', ''),
2149
client.add_expected_call(
2150
'Repository.insert_stream', ('quack/', ''),
2152
sink = repo._get_sink()
2153
fmt = repository.RepositoryFormat.get_default_format()
2154
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2155
self.assertEqual([], resume_tokens)
2156
self.assertEqual(set(), missing_keys)
2157
client.finished_test()
2159
def test_locked_repo_with_no_lock_token(self):
2160
transport_path = 'quack'
2161
repo, client = self.setup_fake_client_and_repository(transport_path)
2162
client.add_expected_call(
2163
'Repository.lock_write', ('quack/', ''),
2164
'success', ('ok', ''))
2165
client.add_expected_call(
2166
'Repository.insert_stream', ('quack/', ''),
2168
client.add_expected_call(
2169
'Repository.insert_stream', ('quack/', ''),
2172
sink = repo._get_sink()
2173
fmt = repository.RepositoryFormat.get_default_format()
2174
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2175
self.assertEqual([], resume_tokens)
2176
self.assertEqual(set(), missing_keys)
2177
client.finished_test()
2179
def test_locked_repo_with_lock_token(self):
2180
transport_path = 'quack'
2181
repo, client = self.setup_fake_client_and_repository(transport_path)
2182
client.add_expected_call(
2183
'Repository.lock_write', ('quack/', ''),
2184
'success', ('ok', 'a token'))
2185
client.add_expected_call(
2186
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2188
client.add_expected_call(
2189
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2192
sink = repo._get_sink()
2193
fmt = repository.RepositoryFormat.get_default_format()
2194
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2195
self.assertEqual([], resume_tokens)
2196
self.assertEqual(set(), missing_keys)
2197
client.finished_test()
2200
class TestRepositoryTarball(TestRemoteRepository):
2202
# This is a canned tarball reponse we can validate against
2204
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2205
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2206
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2207
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2208
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2209
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2210
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2211
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2212
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2213
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2214
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2215
'nWQ7QH/F3JFOFCQ0aSPfA='
2218
def test_repository_tarball(self):
2219
# Test that Repository.tarball generates the right operations
2220
transport_path = 'repo'
2221
expected_calls = [('call_expecting_body', 'Repository.tarball',
2222
('repo/', 'bz2',),),
2224
repo, client = self.setup_fake_client_and_repository(transport_path)
2225
client.add_success_response_with_body(self.tarball_content, 'ok')
2226
# Now actually ask for the tarball
2227
tarball_file = repo._get_tarball('bz2')
2229
self.assertEqual(expected_calls, client._calls)
2230
self.assertEqual(self.tarball_content, tarball_file.read())
2232
tarball_file.close()
2235
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2236
"""RemoteRepository.copy_content_into optimizations"""
2238
def test_copy_content_remote_to_local(self):
2239
self.transport_server = server.SmartTCPServer_for_testing
2240
src_repo = self.make_repository('repo1')
2241
src_repo = repository.Repository.open(self.get_url('repo1'))
2242
# At the moment the tarball-based copy_content_into can't write back
2243
# into a smart server. It would be good if it could upload the
2244
# tarball; once that works we'd have to create repositories of
2245
# different formats. -- mbp 20070410
2246
dest_url = self.get_vfs_only_url('repo2')
2247
dest_bzrdir = BzrDir.create(dest_url)
2248
dest_repo = dest_bzrdir.create_repository()
2249
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2250
self.assertTrue(isinstance(src_repo, RemoteRepository))
2251
src_repo.copy_content_into(dest_repo)
2254
class _StubRealPackRepository(object):
2256
def __init__(self, calls):
2258
self._pack_collection = _StubPackCollection(calls)
2260
def is_in_write_group(self):
2263
def refresh_data(self):
2264
self.calls.append(('pack collection reload_pack_names',))
2267
class _StubPackCollection(object):
2269
def __init__(self, calls):
2273
self.calls.append(('pack collection autopack',))
2276
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2277
"""Tests for RemoteRepository.autopack implementation."""
2280
"""When the server returns 'ok' and there's no _real_repository, then
2281
nothing else happens: the autopack method is done.
2283
transport_path = 'quack'
2284
repo, client = self.setup_fake_client_and_repository(transport_path)
2285
client.add_expected_call(
2286
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2288
client.finished_test()
2290
def test_ok_with_real_repo(self):
2291
"""When the server returns 'ok' and there is a _real_repository, then
2292
the _real_repository's reload_pack_name's method will be called.
2294
transport_path = 'quack'
2295
repo, client = self.setup_fake_client_and_repository(transport_path)
2296
client.add_expected_call(
2297
'PackRepository.autopack', ('quack/',),
2299
repo._real_repository = _StubRealPackRepository(client._calls)
2302
[('call', 'PackRepository.autopack', ('quack/',)),
2303
('pack collection reload_pack_names',)],
2306
def test_backwards_compatibility(self):
2307
"""If the server does not recognise the PackRepository.autopack verb,
2308
fallback to the real_repository's implementation.
2310
transport_path = 'quack'
2311
repo, client = self.setup_fake_client_and_repository(transport_path)
2312
client.add_unknown_method_response('PackRepository.autopack')
2313
def stub_ensure_real():
2314
client._calls.append(('_ensure_real',))
2315
repo._real_repository = _StubRealPackRepository(client._calls)
2316
repo._ensure_real = stub_ensure_real
2319
[('call', 'PackRepository.autopack', ('quack/',)),
2321
('pack collection autopack',)],
2325
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2326
"""Base class for unit tests for bzrlib.remote._translate_error."""
2328
def translateTuple(self, error_tuple, **context):
2329
"""Call _translate_error with an ErrorFromSmartServer built from the
2332
:param error_tuple: A tuple of a smart server response, as would be
2333
passed to an ErrorFromSmartServer.
2334
:kwargs context: context items to call _translate_error with.
2336
:returns: The error raised by _translate_error.
2338
# Raise the ErrorFromSmartServer before passing it as an argument,
2339
# because _translate_error may need to re-raise it with a bare 'raise'
2341
server_error = errors.ErrorFromSmartServer(error_tuple)
2342
translated_error = self.translateErrorFromSmartServer(
2343
server_error, **context)
2344
return translated_error
2346
def translateErrorFromSmartServer(self, error_object, **context):
2347
"""Like translateTuple, but takes an already constructed
2348
ErrorFromSmartServer rather than a tuple.
2352
except errors.ErrorFromSmartServer, server_error:
2353
translated_error = self.assertRaises(
2354
errors.BzrError, remote._translate_error, server_error,
2356
return translated_error
2359
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2360
"""Unit tests for bzrlib.remote._translate_error.
2362
Given an ErrorFromSmartServer (which has an error tuple from a smart
2363
server) and some context, _translate_error raises more specific errors from
2366
This test case covers the cases where _translate_error succeeds in
2367
translating an ErrorFromSmartServer to something better. See
2368
TestErrorTranslationRobustness for other cases.
2371
def test_NoSuchRevision(self):
2372
branch = self.make_branch('')
2374
translated_error = self.translateTuple(
2375
('NoSuchRevision', revid), branch=branch)
2376
expected_error = errors.NoSuchRevision(branch, revid)
2377
self.assertEqual(expected_error, translated_error)
2379
def test_nosuchrevision(self):
2380
repository = self.make_repository('')
2382
translated_error = self.translateTuple(
2383
('nosuchrevision', revid), repository=repository)
2384
expected_error = errors.NoSuchRevision(repository, revid)
2385
self.assertEqual(expected_error, translated_error)
2387
def test_nobranch(self):
2388
bzrdir = self.make_bzrdir('')
2389
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2390
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2391
self.assertEqual(expected_error, translated_error)
2393
def test_LockContention(self):
2394
translated_error = self.translateTuple(('LockContention',))
2395
expected_error = errors.LockContention('(remote lock)')
2396
self.assertEqual(expected_error, translated_error)
2398
def test_UnlockableTransport(self):
2399
bzrdir = self.make_bzrdir('')
2400
translated_error = self.translateTuple(
2401
('UnlockableTransport',), bzrdir=bzrdir)
2402
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2403
self.assertEqual(expected_error, translated_error)
2405
def test_LockFailed(self):
2406
lock = 'str() of a server lock'
2407
why = 'str() of why'
2408
translated_error = self.translateTuple(('LockFailed', lock, why))
2409
expected_error = errors.LockFailed(lock, why)
2410
self.assertEqual(expected_error, translated_error)
2412
def test_TokenMismatch(self):
2413
token = 'a lock token'
2414
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2415
expected_error = errors.TokenMismatch(token, '(remote token)')
2416
self.assertEqual(expected_error, translated_error)
2418
def test_Diverged(self):
2419
branch = self.make_branch('a')
2420
other_branch = self.make_branch('b')
2421
translated_error = self.translateTuple(
2422
('Diverged',), branch=branch, other_branch=other_branch)
2423
expected_error = errors.DivergedBranches(branch, other_branch)
2424
self.assertEqual(expected_error, translated_error)
2426
def test_ReadError_no_args(self):
2428
translated_error = self.translateTuple(('ReadError',), path=path)
2429
expected_error = errors.ReadError(path)
2430
self.assertEqual(expected_error, translated_error)
2432
def test_ReadError(self):
2434
translated_error = self.translateTuple(('ReadError', path))
2435
expected_error = errors.ReadError(path)
2436
self.assertEqual(expected_error, translated_error)
2438
def test_PermissionDenied_no_args(self):
2440
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2441
expected_error = errors.PermissionDenied(path)
2442
self.assertEqual(expected_error, translated_error)
2444
def test_PermissionDenied_one_arg(self):
2446
translated_error = self.translateTuple(('PermissionDenied', path))
2447
expected_error = errors.PermissionDenied(path)
2448
self.assertEqual(expected_error, translated_error)
2450
def test_PermissionDenied_one_arg_and_context(self):
2451
"""Given a choice between a path from the local context and a path on
2452
the wire, _translate_error prefers the path from the local context.
2454
local_path = 'local path'
2455
remote_path = 'remote path'
2456
translated_error = self.translateTuple(
2457
('PermissionDenied', remote_path), path=local_path)
2458
expected_error = errors.PermissionDenied(local_path)
2459
self.assertEqual(expected_error, translated_error)
2461
def test_PermissionDenied_two_args(self):
2463
extra = 'a string with extra info'
2464
translated_error = self.translateTuple(
2465
('PermissionDenied', path, extra))
2466
expected_error = errors.PermissionDenied(path, extra)
2467
self.assertEqual(expected_error, translated_error)
2470
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2471
"""Unit tests for bzrlib.remote._translate_error's robustness.
2473
TestErrorTranslationSuccess is for cases where _translate_error can
2474
translate successfully. This class about how _translate_err behaves when
2475
it fails to translate: it re-raises the original error.
2478
def test_unrecognised_server_error(self):
2479
"""If the error code from the server is not recognised, the original
2480
ErrorFromSmartServer is propagated unmodified.
2482
error_tuple = ('An unknown error tuple',)
2483
server_error = errors.ErrorFromSmartServer(error_tuple)
2484
translated_error = self.translateErrorFromSmartServer(server_error)
2485
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2486
self.assertEqual(expected_error, translated_error)
2488
def test_context_missing_a_key(self):
2489
"""In case of a bug in the client, or perhaps an unexpected response
2490
from a server, _translate_error returns the original error tuple from
2491
the server and mutters a warning.
2493
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2494
# in the context dict. So let's give it an empty context dict instead
2495
# to exercise its error recovery.
2497
error_tuple = ('NoSuchRevision', 'revid')
2498
server_error = errors.ErrorFromSmartServer(error_tuple)
2499
translated_error = self.translateErrorFromSmartServer(server_error)
2500
self.assertEqual(server_error, translated_error)
2501
# In addition to re-raising ErrorFromSmartServer, some debug info has
2502
# been muttered to the log file for developer to look at.
2503
self.assertContainsRe(
2504
self._get_log(keep_log_file=True),
2505
"Missing key 'branch' in context")
2507
def test_path_missing(self):
2508
"""Some translations (PermissionDenied, ReadError) can determine the
2509
'path' variable from either the wire or the local context. If neither
2510
has it, then an error is raised.
2512
error_tuple = ('ReadError',)
2513
server_error = errors.ErrorFromSmartServer(error_tuple)
2514
translated_error = self.translateErrorFromSmartServer(server_error)
2515
self.assertEqual(server_error, translated_error)
2516
# In addition to re-raising ErrorFromSmartServer, some debug info has
2517
# been muttered to the log file for developer to look at.
2518
self.assertContainsRe(
2519
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2522
class TestStacking(tests.TestCaseWithTransport):
2523
"""Tests for operations on stacked remote repositories.
2525
The underlying format type must support stacking.
2528
def test_access_stacked_remote(self):
2529
# based on <http://launchpad.net/bugs/261315>
2530
# make a branch stacked on another repository containing an empty
2531
# revision, then open it over hpss - we should be able to see that
2533
base_transport = self.get_transport()
2534
base_builder = self.make_branch_builder('base', format='1.9')
2535
base_builder.start_series()
2536
base_revid = base_builder.build_snapshot('rev-id', None,
2537
[('add', ('', None, 'directory', None))],
2539
base_builder.finish_series()
2540
stacked_branch = self.make_branch('stacked', format='1.9')
2541
stacked_branch.set_stacked_on_url('../base')
2542
# start a server looking at this
2543
smart_server = server.SmartTCPServer_for_testing()
2544
smart_server.setUp()
2545
self.addCleanup(smart_server.tearDown)
2546
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2547
# can get its branch and repository
2548
remote_branch = remote_bzrdir.open_branch()
2549
remote_repo = remote_branch.repository
2550
remote_repo.lock_read()
2552
# it should have an appropriate fallback repository, which should also
2553
# be a RemoteRepository
2554
self.assertLength(1, remote_repo._fallback_repositories)
2555
self.assertIsInstance(remote_repo._fallback_repositories[0],
2557
# and it has the revision committed to the underlying repository;
2558
# these have varying implementations so we try several of them
2559
self.assertTrue(remote_repo.has_revisions([base_revid]))
2560
self.assertTrue(remote_repo.has_revision(base_revid))
2561
self.assertEqual(remote_repo.get_revision(base_revid).message,
2564
remote_repo.unlock()
2566
def prepare_stacked_remote_branch(self):
2567
"""Get stacked_upon and stacked branches with content in each."""
2568
self.setup_smart_server_with_call_log()
2569
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2570
tree1.commit('rev1', rev_id='rev1')
2571
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2572
).open_workingtree()
2573
tree2.commit('local changes make me feel good.')
2574
branch2 = Branch.open(self.get_url('tree2'))
2576
self.addCleanup(branch2.unlock)
2577
return tree1.branch, branch2
2579
def test_stacked_get_parent_map(self):
2580
# the public implementation of get_parent_map obeys stacking
2581
_, branch = self.prepare_stacked_remote_branch()
2582
repo = branch.repository
2583
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2585
def test_unstacked_get_parent_map(self):
2586
# _unstacked_provider.get_parent_map ignores stacking
2587
_, branch = self.prepare_stacked_remote_branch()
2588
provider = branch.repository._unstacked_provider
2589
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2591
def fetch_stream_to_rev_order(self, stream):
2593
for kind, substream in stream:
2594
if not kind == 'revisions':
2597
for content in substream:
2598
result.append(content.key[-1])
2601
def get_ordered_revs(self, format, order):
2602
"""Get a list of the revisions in a stream to format format.
2604
:param format: The format of the target.
2605
:param order: the order that target should have requested.
2606
:result: The revision ids in the stream, in the order seen,
2607
the topological order of revisions in the source.
2609
unordered_format = bzrdir.format_registry.get(format)()
2610
target_repository_format = unordered_format.repository_format
2612
self.assertEqual(order, target_repository_format._fetch_order)
2613
trunk, stacked = self.prepare_stacked_remote_branch()
2614
source = stacked.repository._get_source(target_repository_format)
2615
tip = stacked.last_revision()
2616
revs = stacked.repository.get_ancestry(tip)
2617
search = graph.PendingAncestryResult([tip], stacked.repository)
2618
self.reset_smart_call_log()
2619
stream = source.get_stream(search)
2622
# We trust that if a revision is in the stream the rest of the new
2623
# content for it is too, as per our main fetch tests; here we are
2624
# checking that the revisions are actually included at all, and their
2626
return self.fetch_stream_to_rev_order(stream), revs
2628
def test_stacked_get_stream_unordered(self):
2629
# Repository._get_source.get_stream() from a stacked repository with
2630
# unordered yields the full data from both stacked and stacked upon
2632
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2633
self.assertEqual(set(expected_revs), set(rev_ord))
2634
# Getting unordered results should have made a streaming data request
2635
# from the server, then one from the backing branch.
2636
self.assertLength(2, self.hpss_calls)
2638
def test_stacked_get_stream_topological(self):
2639
# Repository._get_source.get_stream() from a stacked repository with
2640
# topological sorting yields the full data from both stacked and
2641
# stacked upon sources in topological order.
2642
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2643
self.assertEqual(expected_revs, rev_ord)
2644
# Getting topological sort requires VFS calls still
2645
self.assertLength(12, self.hpss_calls)
2647
def test_stacked_get_stream_groupcompress(self):
2648
# Repository._get_source.get_stream() from a stacked repository with
2649
# groupcompress sorting yields the full data from both stacked and
2650
# stacked upon sources in groupcompress order.
2651
raise tests.TestSkipped('No groupcompress ordered format available')
2652
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2653
self.assertEqual(expected_revs, reversed(rev_ord))
2654
# Getting unordered results should have made a streaming data request
2655
# from the backing branch, and one from the stacked on branch.
2656
self.assertLength(2, self.hpss_calls)
2658
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
2659
# When pulling some fixed amount of content that is more than the
2660
# source has (because some is coming from a fallback branch, no error
2661
# should be received. This was reported as bug 360791.
2662
# Need three branches: a trunk, a stacked branch, and a preexisting
2663
# branch pulling content from stacked and trunk.
2664
self.setup_smart_server_with_call_log()
2665
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
2666
r1 = trunk.commit('start')
2667
stacked_branch = trunk.branch.create_clone_on_transport(
2668
self.get_transport('stacked'), stacked_on=trunk.branch.base)
2669
local = self.make_branch('local', format='1.9-rich-root')
2670
local.repository.fetch(stacked_branch.repository,
2671
stacked_branch.last_revision())
2674
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2677
super(TestRemoteBranchEffort, self).setUp()
2678
# Create a smart server that publishes whatever the backing VFS server
2680
self.smart_server = server.SmartTCPServer_for_testing()
2681
self.smart_server.setUp(self.get_server())
2682
self.addCleanup(self.smart_server.tearDown)
2683
# Log all HPSS calls into self.hpss_calls.
2684
_SmartClient.hooks.install_named_hook(
2685
'call', self.capture_hpss_call, None)
2686
self.hpss_calls = []
2688
def capture_hpss_call(self, params):
2689
self.hpss_calls.append(params.method)
2691
def test_copy_content_into_avoids_revision_history(self):
2692
local = self.make_branch('local')
2693
remote_backing_tree = self.make_branch_and_tree('remote')
2694
remote_backing_tree.commit("Commit.")
2695
remote_branch_url = self.smart_server.get_url() + 'remote'
2696
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2697
local.repository.fetch(remote_branch.repository)
2698
self.hpss_calls = []
2699
remote_branch.copy_content_into(local)
2700
self.assertFalse('Branch.revision_history' in self.hpss_calls)