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()
791
class OldSmartClient(object):
792
"""A fake smart client for test_old_version that just returns a version one
793
response to the 'hello' (query version) command.
796
def get_request(self):
797
input_file = StringIO('ok\x011\n')
798
output_file = StringIO()
799
client_medium = medium.SmartSimplePipesClientMedium(
800
input_file, output_file)
801
return medium.SmartClientStreamMediumRequest(client_medium)
803
def protocol_version(self):
807
class OldServerTransport(object):
808
"""A fake transport for test_old_server that reports it's smart server
809
protocol version as version one.
815
def get_smart_client(self):
816
return OldSmartClient()
819
class RemoteBzrDirTestCase(TestRemote):
821
def make_remote_bzrdir(self, transport, client):
822
"""Make a RemotebzrDir using 'client' as the _client."""
823
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
827
class RemoteBranchTestCase(RemoteBzrDirTestCase):
829
def make_remote_branch(self, transport, client):
830
"""Make a RemoteBranch using 'client' as its _SmartClient.
832
A RemoteBzrDir and RemoteRepository will also be created to fill out
833
the RemoteBranch, albeit with stub values for some of their attributes.
835
# we do not want bzrdir to make any remote calls, so use False as its
836
# _client. If it tries to make a remote call, this will fail
838
bzrdir = self.make_remote_bzrdir(transport, False)
839
repo = RemoteRepository(bzrdir, None, _client=client)
840
branch_format = self.get_branch_format()
841
format = RemoteBranchFormat(network_name=branch_format.network_name())
842
return RemoteBranch(bzrdir, repo, _client=client, format=format)
845
class TestBranchGetParent(RemoteBranchTestCase):
847
def test_no_parent(self):
848
# in an empty branch we decode the response properly
849
transport = MemoryTransport()
850
client = FakeClient(transport.base)
851
client.add_expected_call(
852
'Branch.get_stacked_on_url', ('quack/',),
853
'error', ('NotStacked',))
854
client.add_expected_call(
855
'Branch.get_parent', ('quack/',),
857
transport.mkdir('quack')
858
transport = transport.clone('quack')
859
branch = self.make_remote_branch(transport, client)
860
result = branch.get_parent()
861
client.finished_test()
862
self.assertEqual(None, result)
864
def test_parent_relative(self):
865
transport = MemoryTransport()
866
client = FakeClient(transport.base)
867
client.add_expected_call(
868
'Branch.get_stacked_on_url', ('kwaak/',),
869
'error', ('NotStacked',))
870
client.add_expected_call(
871
'Branch.get_parent', ('kwaak/',),
872
'success', ('../foo/',))
873
transport.mkdir('kwaak')
874
transport = transport.clone('kwaak')
875
branch = self.make_remote_branch(transport, client)
876
result = branch.get_parent()
877
self.assertEqual(transport.clone('../foo').base, result)
879
def test_parent_absolute(self):
880
transport = MemoryTransport()
881
client = FakeClient(transport.base)
882
client.add_expected_call(
883
'Branch.get_stacked_on_url', ('kwaak/',),
884
'error', ('NotStacked',))
885
client.add_expected_call(
886
'Branch.get_parent', ('kwaak/',),
887
'success', ('http://foo/',))
888
transport.mkdir('kwaak')
889
transport = transport.clone('kwaak')
890
branch = self.make_remote_branch(transport, client)
891
result = branch.get_parent()
892
self.assertEqual('http://foo/', result)
893
client.finished_test()
896
class TestBranchSetParentLocation(RemoteBranchTestCase):
898
def test_no_parent(self):
899
# We call the verb when setting parent to None
900
transport = MemoryTransport()
901
client = FakeClient(transport.base)
902
client.add_expected_call(
903
'Branch.get_stacked_on_url', ('quack/',),
904
'error', ('NotStacked',))
905
client.add_expected_call(
906
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
908
transport.mkdir('quack')
909
transport = transport.clone('quack')
910
branch = self.make_remote_branch(transport, client)
911
branch._lock_token = 'b'
912
branch._repo_lock_token = 'r'
913
branch._set_parent_location(None)
914
client.finished_test()
916
def test_parent(self):
917
transport = MemoryTransport()
918
client = FakeClient(transport.base)
919
client.add_expected_call(
920
'Branch.get_stacked_on_url', ('kwaak/',),
921
'error', ('NotStacked',))
922
client.add_expected_call(
923
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
925
transport.mkdir('kwaak')
926
transport = transport.clone('kwaak')
927
branch = self.make_remote_branch(transport, client)
928
branch._lock_token = 'b'
929
branch._repo_lock_token = 'r'
930
branch._set_parent_location('foo')
931
client.finished_test()
933
def test_backwards_compat(self):
934
self.setup_smart_server_with_call_log()
935
branch = self.make_branch('.')
936
self.reset_smart_call_log()
937
verb = 'Branch.set_parent_location'
938
self.disable_verb(verb)
939
branch.set_parent('http://foo/')
940
self.assertLength(12, self.hpss_calls)
943
class TestBranchGetTagsBytes(RemoteBranchTestCase):
945
def test_backwards_compat(self):
946
self.setup_smart_server_with_call_log()
947
branch = self.make_branch('.')
948
self.reset_smart_call_log()
949
verb = 'Branch.get_tags_bytes'
950
self.disable_verb(verb)
951
branch.tags.get_tag_dict()
952
call_count = len([call for call in self.hpss_calls if
953
call.call.method == verb])
954
self.assertEqual(1, call_count)
956
def test_trivial(self):
957
transport = MemoryTransport()
958
client = FakeClient(transport.base)
959
client.add_expected_call(
960
'Branch.get_stacked_on_url', ('quack/',),
961
'error', ('NotStacked',))
962
client.add_expected_call(
963
'Branch.get_tags_bytes', ('quack/',),
965
transport.mkdir('quack')
966
transport = transport.clone('quack')
967
branch = self.make_remote_branch(transport, client)
968
result = branch.tags.get_tag_dict()
969
client.finished_test()
970
self.assertEqual({}, result)
973
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
975
def test_empty_branch(self):
976
# in an empty branch we decode the response properly
977
transport = MemoryTransport()
978
client = FakeClient(transport.base)
979
client.add_expected_call(
980
'Branch.get_stacked_on_url', ('quack/',),
981
'error', ('NotStacked',))
982
client.add_expected_call(
983
'Branch.last_revision_info', ('quack/',),
984
'success', ('ok', '0', 'null:'))
985
transport.mkdir('quack')
986
transport = transport.clone('quack')
987
branch = self.make_remote_branch(transport, client)
988
result = branch.last_revision_info()
989
client.finished_test()
990
self.assertEqual((0, NULL_REVISION), result)
992
def test_non_empty_branch(self):
993
# in a non-empty branch we also decode the response properly
994
revid = u'\xc8'.encode('utf8')
995
transport = MemoryTransport()
996
client = FakeClient(transport.base)
997
client.add_expected_call(
998
'Branch.get_stacked_on_url', ('kwaak/',),
999
'error', ('NotStacked',))
1000
client.add_expected_call(
1001
'Branch.last_revision_info', ('kwaak/',),
1002
'success', ('ok', '2', revid))
1003
transport.mkdir('kwaak')
1004
transport = transport.clone('kwaak')
1005
branch = self.make_remote_branch(transport, client)
1006
result = branch.last_revision_info()
1007
self.assertEqual((2, revid), result)
1010
class TestBranch_get_stacked_on_url(TestRemote):
1011
"""Test Branch._get_stacked_on_url rpc"""
1013
def test_get_stacked_on_invalid_url(self):
1014
# test that asking for a stacked on url the server can't access works.
1015
# This isn't perfect, but then as we're in the same process there
1016
# really isn't anything we can do to be 100% sure that the server
1017
# doesn't just open in - this test probably needs to be rewritten using
1018
# a spawn()ed server.
1019
stacked_branch = self.make_branch('stacked', format='1.9')
1020
memory_branch = self.make_branch('base', format='1.9')
1021
vfs_url = self.get_vfs_only_url('base')
1022
stacked_branch.set_stacked_on_url(vfs_url)
1023
transport = stacked_branch.bzrdir.root_transport
1024
client = FakeClient(transport.base)
1025
client.add_expected_call(
1026
'Branch.get_stacked_on_url', ('stacked/',),
1027
'success', ('ok', vfs_url))
1028
# XXX: Multiple calls are bad, this second call documents what is
1030
client.add_expected_call(
1031
'Branch.get_stacked_on_url', ('stacked/',),
1032
'success', ('ok', vfs_url))
1033
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1035
repo_fmt = remote.RemoteRepositoryFormat()
1036
repo_fmt._custom_format = stacked_branch.repository._format
1037
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1039
result = branch.get_stacked_on_url()
1040
self.assertEqual(vfs_url, result)
1042
def test_backwards_compatible(self):
1043
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1044
base_branch = self.make_branch('base', format='1.6')
1045
stacked_branch = self.make_branch('stacked', format='1.6')
1046
stacked_branch.set_stacked_on_url('../base')
1047
client = FakeClient(self.get_url())
1048
branch_network_name = self.get_branch_format().network_name()
1049
client.add_expected_call(
1050
'BzrDir.open_branchV2', ('stacked/',),
1051
'success', ('branch', branch_network_name))
1052
client.add_expected_call(
1053
'BzrDir.find_repositoryV3', ('stacked/',),
1054
'success', ('ok', '', 'no', 'no', 'yes',
1055
stacked_branch.repository._format.network_name()))
1056
# called twice, once from constructor and then again by us
1057
client.add_expected_call(
1058
'Branch.get_stacked_on_url', ('stacked/',),
1059
'unknown', ('Branch.get_stacked_on_url',))
1060
client.add_expected_call(
1061
'Branch.get_stacked_on_url', ('stacked/',),
1062
'unknown', ('Branch.get_stacked_on_url',))
1063
# this will also do vfs access, but that goes direct to the transport
1064
# and isn't seen by the FakeClient.
1065
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1066
remote.RemoteBzrDirFormat(), _client=client)
1067
branch = bzrdir.open_branch()
1068
result = branch.get_stacked_on_url()
1069
self.assertEqual('../base', result)
1070
client.finished_test()
1071
# it's in the fallback list both for the RemoteRepository and its vfs
1073
self.assertEqual(1, len(branch.repository._fallback_repositories))
1075
len(branch.repository._real_repository._fallback_repositories))
1077
def test_get_stacked_on_real_branch(self):
1078
base_branch = self.make_branch('base', format='1.6')
1079
stacked_branch = self.make_branch('stacked', format='1.6')
1080
stacked_branch.set_stacked_on_url('../base')
1081
reference_format = self.get_repo_format()
1082
network_name = reference_format.network_name()
1083
client = FakeClient(self.get_url())
1084
branch_network_name = self.get_branch_format().network_name()
1085
client.add_expected_call(
1086
'BzrDir.open_branchV2', ('stacked/',),
1087
'success', ('branch', branch_network_name))
1088
client.add_expected_call(
1089
'BzrDir.find_repositoryV3', ('stacked/',),
1090
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1091
# called twice, once from constructor and then again by us
1092
client.add_expected_call(
1093
'Branch.get_stacked_on_url', ('stacked/',),
1094
'success', ('ok', '../base'))
1095
client.add_expected_call(
1096
'Branch.get_stacked_on_url', ('stacked/',),
1097
'success', ('ok', '../base'))
1098
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1099
remote.RemoteBzrDirFormat(), _client=client)
1100
branch = bzrdir.open_branch()
1101
result = branch.get_stacked_on_url()
1102
self.assertEqual('../base', result)
1103
client.finished_test()
1104
# it's in the fallback list both for the RemoteRepository.
1105
self.assertEqual(1, len(branch.repository._fallback_repositories))
1106
# And we haven't had to construct a real repository.
1107
self.assertEqual(None, branch.repository._real_repository)
1110
class TestBranchSetLastRevision(RemoteBranchTestCase):
1112
def test_set_empty(self):
1113
# set_revision_history([]) is translated to calling
1114
# Branch.set_last_revision(path, '') on the wire.
1115
transport = MemoryTransport()
1116
transport.mkdir('branch')
1117
transport = transport.clone('branch')
1119
client = FakeClient(transport.base)
1120
client.add_expected_call(
1121
'Branch.get_stacked_on_url', ('branch/',),
1122
'error', ('NotStacked',))
1123
client.add_expected_call(
1124
'Branch.lock_write', ('branch/', '', ''),
1125
'success', ('ok', 'branch token', 'repo token'))
1126
client.add_expected_call(
1127
'Branch.last_revision_info',
1129
'success', ('ok', '0', 'null:'))
1130
client.add_expected_call(
1131
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1133
client.add_expected_call(
1134
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1136
branch = self.make_remote_branch(transport, client)
1137
# This is a hack to work around the problem that RemoteBranch currently
1138
# unnecessarily invokes _ensure_real upon a call to lock_write.
1139
branch._ensure_real = lambda: None
1141
result = branch.set_revision_history([])
1143
self.assertEqual(None, result)
1144
client.finished_test()
1146
def test_set_nonempty(self):
1147
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1148
# Branch.set_last_revision(path, rev-idN) on the wire.
1149
transport = MemoryTransport()
1150
transport.mkdir('branch')
1151
transport = transport.clone('branch')
1153
client = FakeClient(transport.base)
1154
client.add_expected_call(
1155
'Branch.get_stacked_on_url', ('branch/',),
1156
'error', ('NotStacked',))
1157
client.add_expected_call(
1158
'Branch.lock_write', ('branch/', '', ''),
1159
'success', ('ok', 'branch token', 'repo token'))
1160
client.add_expected_call(
1161
'Branch.last_revision_info',
1163
'success', ('ok', '0', 'null:'))
1165
encoded_body = bz2.compress('\n'.join(lines))
1166
client.add_success_response_with_body(encoded_body, 'ok')
1167
client.add_expected_call(
1168
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1170
client.add_expected_call(
1171
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1173
branch = self.make_remote_branch(transport, client)
1174
# This is a hack to work around the problem that RemoteBranch currently
1175
# unnecessarily invokes _ensure_real upon a call to lock_write.
1176
branch._ensure_real = lambda: None
1177
# Lock the branch, reset the record of remote calls.
1179
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1181
self.assertEqual(None, result)
1182
client.finished_test()
1184
def test_no_such_revision(self):
1185
transport = MemoryTransport()
1186
transport.mkdir('branch')
1187
transport = transport.clone('branch')
1188
# A response of 'NoSuchRevision' is translated into an exception.
1189
client = FakeClient(transport.base)
1190
client.add_expected_call(
1191
'Branch.get_stacked_on_url', ('branch/',),
1192
'error', ('NotStacked',))
1193
client.add_expected_call(
1194
'Branch.lock_write', ('branch/', '', ''),
1195
'success', ('ok', 'branch token', 'repo token'))
1196
client.add_expected_call(
1197
'Branch.last_revision_info',
1199
'success', ('ok', '0', 'null:'))
1200
# get_graph calls to construct the revision history, for the set_rh
1203
encoded_body = bz2.compress('\n'.join(lines))
1204
client.add_success_response_with_body(encoded_body, 'ok')
1205
client.add_expected_call(
1206
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1207
'error', ('NoSuchRevision', 'rev-id'))
1208
client.add_expected_call(
1209
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1212
branch = self.make_remote_branch(transport, client)
1215
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1217
client.finished_test()
1219
def test_tip_change_rejected(self):
1220
"""TipChangeRejected responses cause a TipChangeRejected exception to
1223
transport = MemoryTransport()
1224
transport.mkdir('branch')
1225
transport = transport.clone('branch')
1226
client = FakeClient(transport.base)
1227
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1228
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1229
client.add_expected_call(
1230
'Branch.get_stacked_on_url', ('branch/',),
1231
'error', ('NotStacked',))
1232
client.add_expected_call(
1233
'Branch.lock_write', ('branch/', '', ''),
1234
'success', ('ok', 'branch token', 'repo token'))
1235
client.add_expected_call(
1236
'Branch.last_revision_info',
1238
'success', ('ok', '0', 'null:'))
1240
encoded_body = bz2.compress('\n'.join(lines))
1241
client.add_success_response_with_body(encoded_body, 'ok')
1242
client.add_expected_call(
1243
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1244
'error', ('TipChangeRejected', rejection_msg_utf8))
1245
client.add_expected_call(
1246
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1248
branch = self.make_remote_branch(transport, client)
1249
branch._ensure_real = lambda: None
1251
# The 'TipChangeRejected' error response triggered by calling
1252
# set_revision_history causes a TipChangeRejected exception.
1253
err = self.assertRaises(
1254
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1255
# The UTF-8 message from the response has been decoded into a unicode
1257
self.assertIsInstance(err.msg, unicode)
1258
self.assertEqual(rejection_msg_unicode, err.msg)
1260
client.finished_test()
1263
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1265
def test_set_last_revision_info(self):
1266
# set_last_revision_info(num, 'rev-id') is translated to calling
1267
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1268
transport = MemoryTransport()
1269
transport.mkdir('branch')
1270
transport = transport.clone('branch')
1271
client = FakeClient(transport.base)
1272
# get_stacked_on_url
1273
client.add_error_response('NotStacked')
1275
client.add_success_response('ok', 'branch token', 'repo token')
1276
# query the current revision
1277
client.add_success_response('ok', '0', 'null:')
1279
client.add_success_response('ok')
1281
client.add_success_response('ok')
1283
branch = self.make_remote_branch(transport, client)
1284
# Lock the branch, reset the record of remote calls.
1287
result = branch.set_last_revision_info(1234, 'a-revision-id')
1289
[('call', 'Branch.last_revision_info', ('branch/',)),
1290
('call', 'Branch.set_last_revision_info',
1291
('branch/', 'branch token', 'repo token',
1292
'1234', 'a-revision-id'))],
1294
self.assertEqual(None, result)
1296
def test_no_such_revision(self):
1297
# A response of 'NoSuchRevision' is translated into an exception.
1298
transport = MemoryTransport()
1299
transport.mkdir('branch')
1300
transport = transport.clone('branch')
1301
client = FakeClient(transport.base)
1302
# get_stacked_on_url
1303
client.add_error_response('NotStacked')
1305
client.add_success_response('ok', 'branch token', 'repo token')
1307
client.add_error_response('NoSuchRevision', 'revid')
1309
client.add_success_response('ok')
1311
branch = self.make_remote_branch(transport, client)
1312
# Lock the branch, reset the record of remote calls.
1317
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1320
def lock_remote_branch(self, branch):
1321
"""Trick a RemoteBranch into thinking it is locked."""
1322
branch._lock_mode = 'w'
1323
branch._lock_count = 2
1324
branch._lock_token = 'branch token'
1325
branch._repo_lock_token = 'repo token'
1326
branch.repository._lock_mode = 'w'
1327
branch.repository._lock_count = 2
1328
branch.repository._lock_token = 'repo token'
1330
def test_backwards_compatibility(self):
1331
"""If the server does not support the Branch.set_last_revision_info
1332
verb (which is new in 1.4), then the client falls back to VFS methods.
1334
# This test is a little messy. Unlike most tests in this file, it
1335
# doesn't purely test what a Remote* object sends over the wire, and
1336
# how it reacts to responses from the wire. It instead relies partly
1337
# on asserting that the RemoteBranch will call
1338
# self._real_branch.set_last_revision_info(...).
1340
# First, set up our RemoteBranch with a FakeClient that raises
1341
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1342
transport = MemoryTransport()
1343
transport.mkdir('branch')
1344
transport = transport.clone('branch')
1345
client = FakeClient(transport.base)
1346
client.add_expected_call(
1347
'Branch.get_stacked_on_url', ('branch/',),
1348
'error', ('NotStacked',))
1349
client.add_expected_call(
1350
'Branch.last_revision_info',
1352
'success', ('ok', '0', 'null:'))
1353
client.add_expected_call(
1354
'Branch.set_last_revision_info',
1355
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1356
'unknown', 'Branch.set_last_revision_info')
1358
branch = self.make_remote_branch(transport, client)
1359
class StubRealBranch(object):
1362
def set_last_revision_info(self, revno, revision_id):
1364
('set_last_revision_info', revno, revision_id))
1365
def _clear_cached_state(self):
1367
real_branch = StubRealBranch()
1368
branch._real_branch = real_branch
1369
self.lock_remote_branch(branch)
1371
# Call set_last_revision_info, and verify it behaved as expected.
1372
result = branch.set_last_revision_info(1234, 'a-revision-id')
1374
[('set_last_revision_info', 1234, 'a-revision-id')],
1376
client.finished_test()
1378
def test_unexpected_error(self):
1379
# If the server sends an error the client doesn't understand, it gets
1380
# turned into an UnknownErrorFromSmartServer, which is presented as a
1381
# non-internal error to the user.
1382
transport = MemoryTransport()
1383
transport.mkdir('branch')
1384
transport = transport.clone('branch')
1385
client = FakeClient(transport.base)
1386
# get_stacked_on_url
1387
client.add_error_response('NotStacked')
1389
client.add_success_response('ok', 'branch token', 'repo token')
1391
client.add_error_response('UnexpectedError')
1393
client.add_success_response('ok')
1395
branch = self.make_remote_branch(transport, client)
1396
# Lock the branch, reset the record of remote calls.
1400
err = self.assertRaises(
1401
errors.UnknownErrorFromSmartServer,
1402
branch.set_last_revision_info, 123, 'revid')
1403
self.assertEqual(('UnexpectedError',), err.error_tuple)
1406
def test_tip_change_rejected(self):
1407
"""TipChangeRejected responses cause a TipChangeRejected exception to
1410
transport = MemoryTransport()
1411
transport.mkdir('branch')
1412
transport = transport.clone('branch')
1413
client = FakeClient(transport.base)
1414
# get_stacked_on_url
1415
client.add_error_response('NotStacked')
1417
client.add_success_response('ok', 'branch token', 'repo token')
1419
client.add_error_response('TipChangeRejected', 'rejection message')
1421
client.add_success_response('ok')
1423
branch = self.make_remote_branch(transport, client)
1424
# Lock the branch, reset the record of remote calls.
1426
self.addCleanup(branch.unlock)
1429
# The 'TipChangeRejected' error response triggered by calling
1430
# set_last_revision_info causes a TipChangeRejected exception.
1431
err = self.assertRaises(
1432
errors.TipChangeRejected,
1433
branch.set_last_revision_info, 123, 'revid')
1434
self.assertEqual('rejection message', err.msg)
1437
class TestBranchGetSetConfig(RemoteBranchTestCase):
1439
def test_get_branch_conf(self):
1440
# in an empty branch we decode the response properly
1441
client = FakeClient()
1442
client.add_expected_call(
1443
'Branch.get_stacked_on_url', ('memory:///',),
1444
'error', ('NotStacked',),)
1445
client.add_success_response_with_body('# config file body', 'ok')
1446
transport = MemoryTransport()
1447
branch = self.make_remote_branch(transport, client)
1448
config = branch.get_config()
1449
config.has_explicit_nickname()
1451
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1452
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1455
def test_get_multi_line_branch_conf(self):
1456
# Make sure that multiple-line branch.conf files are supported
1458
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1459
client = FakeClient()
1460
client.add_expected_call(
1461
'Branch.get_stacked_on_url', ('memory:///',),
1462
'error', ('NotStacked',),)
1463
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1464
transport = MemoryTransport()
1465
branch = self.make_remote_branch(transport, client)
1466
config = branch.get_config()
1467
self.assertEqual(u'2', config.get_user_option('b'))
1469
def test_set_option(self):
1470
client = FakeClient()
1471
client.add_expected_call(
1472
'Branch.get_stacked_on_url', ('memory:///',),
1473
'error', ('NotStacked',),)
1474
client.add_expected_call(
1475
'Branch.lock_write', ('memory:///', '', ''),
1476
'success', ('ok', 'branch token', 'repo token'))
1477
client.add_expected_call(
1478
'Branch.set_config_option', ('memory:///', 'branch token',
1479
'repo token', 'foo', 'bar', ''),
1481
client.add_expected_call(
1482
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1484
transport = MemoryTransport()
1485
branch = self.make_remote_branch(transport, client)
1487
config = branch._get_config()
1488
config.set_option('foo', 'bar')
1490
client.finished_test()
1492
def test_backwards_compat_set_option(self):
1493
self.setup_smart_server_with_call_log()
1494
branch = self.make_branch('.')
1495
verb = 'Branch.set_config_option'
1496
self.disable_verb(verb)
1498
self.addCleanup(branch.unlock)
1499
self.reset_smart_call_log()
1500
branch._get_config().set_option('value', 'name')
1501
self.assertLength(10, self.hpss_calls)
1502
self.assertEqual('value', branch._get_config().get_option('name'))
1505
class TestBranchLockWrite(RemoteBranchTestCase):
1507
def test_lock_write_unlockable(self):
1508
transport = MemoryTransport()
1509
client = FakeClient(transport.base)
1510
client.add_expected_call(
1511
'Branch.get_stacked_on_url', ('quack/',),
1512
'error', ('NotStacked',),)
1513
client.add_expected_call(
1514
'Branch.lock_write', ('quack/', '', ''),
1515
'error', ('UnlockableTransport',))
1516
transport.mkdir('quack')
1517
transport = transport.clone('quack')
1518
branch = self.make_remote_branch(transport, client)
1519
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1520
client.finished_test()
1523
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1525
def test__get_config(self):
1526
client = FakeClient()
1527
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1528
transport = MemoryTransport()
1529
bzrdir = self.make_remote_bzrdir(transport, client)
1530
config = bzrdir.get_config()
1531
self.assertEqual('/', config.get_default_stack_on())
1533
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1536
def test_set_option_uses_vfs(self):
1537
self.setup_smart_server_with_call_log()
1538
bzrdir = self.make_bzrdir('.')
1539
self.reset_smart_call_log()
1540
config = bzrdir.get_config()
1541
config.set_default_stack_on('/')
1542
self.assertLength(3, self.hpss_calls)
1544
def test_backwards_compat_get_option(self):
1545
self.setup_smart_server_with_call_log()
1546
bzrdir = self.make_bzrdir('.')
1547
verb = 'BzrDir.get_config_file'
1548
self.disable_verb(verb)
1549
self.reset_smart_call_log()
1550
self.assertEqual(None,
1551
bzrdir._get_config().get_option('default_stack_on'))
1552
self.assertLength(3, self.hpss_calls)
1555
class TestTransportIsReadonly(tests.TestCase):
1557
def test_true(self):
1558
client = FakeClient()
1559
client.add_success_response('yes')
1560
transport = RemoteTransport('bzr://example.com/', medium=False,
1562
self.assertEqual(True, transport.is_readonly())
1564
[('call', 'Transport.is_readonly', ())],
1567
def test_false(self):
1568
client = FakeClient()
1569
client.add_success_response('no')
1570
transport = RemoteTransport('bzr://example.com/', medium=False,
1572
self.assertEqual(False, transport.is_readonly())
1574
[('call', 'Transport.is_readonly', ())],
1577
def test_error_from_old_server(self):
1578
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1580
Clients should treat it as a "no" response, because is_readonly is only
1581
advisory anyway (a transport could be read-write, but then the
1582
underlying filesystem could be readonly anyway).
1584
client = FakeClient()
1585
client.add_unknown_method_response('Transport.is_readonly')
1586
transport = RemoteTransport('bzr://example.com/', medium=False,
1588
self.assertEqual(False, transport.is_readonly())
1590
[('call', 'Transport.is_readonly', ())],
1594
class TestTransportMkdir(tests.TestCase):
1596
def test_permissiondenied(self):
1597
client = FakeClient()
1598
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1599
transport = RemoteTransport('bzr://example.com/', medium=False,
1601
exc = self.assertRaises(
1602
errors.PermissionDenied, transport.mkdir, 'client path')
1603
expected_error = errors.PermissionDenied('/client path', 'extra')
1604
self.assertEqual(expected_error, exc)
1607
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1609
def test_defaults_to_none(self):
1610
t = RemoteSSHTransport('bzr+ssh://example.com')
1611
self.assertIs(None, t._get_credentials()[0])
1613
def test_uses_authentication_config(self):
1614
conf = config.AuthenticationConfig()
1615
conf._get_config().update(
1616
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1619
t = RemoteSSHTransport('bzr+ssh://example.com')
1620
self.assertEqual('bar', t._get_credentials()[0])
1623
class TestRemoteRepository(TestRemote):
1624
"""Base for testing RemoteRepository protocol usage.
1626
These tests contain frozen requests and responses. We want any changes to
1627
what is sent or expected to be require a thoughtful update to these tests
1628
because they might break compatibility with different-versioned servers.
1631
def setup_fake_client_and_repository(self, transport_path):
1632
"""Create the fake client and repository for testing with.
1634
There's no real server here; we just have canned responses sent
1637
:param transport_path: Path below the root of the MemoryTransport
1638
where the repository will be created.
1640
transport = MemoryTransport()
1641
transport.mkdir(transport_path)
1642
client = FakeClient(transport.base)
1643
transport = transport.clone(transport_path)
1644
# we do not want bzrdir to make any remote calls
1645
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1647
repo = RemoteRepository(bzrdir, None, _client=client)
1651
class TestRepositoryFormat(TestRemoteRepository):
1653
def test_fast_delta(self):
1654
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1655
true_format = RemoteRepositoryFormat()
1656
true_format._network_name = true_name
1657
self.assertEqual(True, true_format.fast_deltas)
1658
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1659
false_format = RemoteRepositoryFormat()
1660
false_format._network_name = false_name
1661
self.assertEqual(False, false_format.fast_deltas)
1664
class TestRepositoryGatherStats(TestRemoteRepository):
1666
def test_revid_none(self):
1667
# ('ok',), body with revisions and size
1668
transport_path = 'quack'
1669
repo, client = self.setup_fake_client_and_repository(transport_path)
1670
client.add_success_response_with_body(
1671
'revisions: 2\nsize: 18\n', 'ok')
1672
result = repo.gather_stats(None)
1674
[('call_expecting_body', 'Repository.gather_stats',
1675
('quack/','','no'))],
1677
self.assertEqual({'revisions': 2, 'size': 18}, result)
1679
def test_revid_no_committers(self):
1680
# ('ok',), body without committers
1681
body = ('firstrev: 123456.300 3600\n'
1682
'latestrev: 654231.400 0\n'
1685
transport_path = 'quick'
1686
revid = u'\xc8'.encode('utf8')
1687
repo, client = self.setup_fake_client_and_repository(transport_path)
1688
client.add_success_response_with_body(body, 'ok')
1689
result = repo.gather_stats(revid)
1691
[('call_expecting_body', 'Repository.gather_stats',
1692
('quick/', revid, 'no'))],
1694
self.assertEqual({'revisions': 2, 'size': 18,
1695
'firstrev': (123456.300, 3600),
1696
'latestrev': (654231.400, 0),},
1699
def test_revid_with_committers(self):
1700
# ('ok',), body with committers
1701
body = ('committers: 128\n'
1702
'firstrev: 123456.300 3600\n'
1703
'latestrev: 654231.400 0\n'
1706
transport_path = 'buick'
1707
revid = u'\xc8'.encode('utf8')
1708
repo, client = self.setup_fake_client_and_repository(transport_path)
1709
client.add_success_response_with_body(body, 'ok')
1710
result = repo.gather_stats(revid, True)
1712
[('call_expecting_body', 'Repository.gather_stats',
1713
('buick/', revid, 'yes'))],
1715
self.assertEqual({'revisions': 2, 'size': 18,
1717
'firstrev': (123456.300, 3600),
1718
'latestrev': (654231.400, 0),},
1722
class TestRepositoryGetGraph(TestRemoteRepository):
1724
def test_get_graph(self):
1725
# get_graph returns a graph with a custom parents provider.
1726
transport_path = 'quack'
1727
repo, client = self.setup_fake_client_and_repository(transport_path)
1728
graph = repo.get_graph()
1729
self.assertNotEqual(graph._parents_provider, repo)
1732
class TestRepositoryGetParentMap(TestRemoteRepository):
1734
def test_get_parent_map_caching(self):
1735
# get_parent_map returns from cache until unlock()
1736
# setup a reponse with two revisions
1737
r1 = u'\u0e33'.encode('utf8')
1738
r2 = u'\u0dab'.encode('utf8')
1739
lines = [' '.join([r2, r1]), r1]
1740
encoded_body = bz2.compress('\n'.join(lines))
1742
transport_path = 'quack'
1743
repo, client = self.setup_fake_client_and_repository(transport_path)
1744
client.add_success_response_with_body(encoded_body, 'ok')
1745
client.add_success_response_with_body(encoded_body, 'ok')
1747
graph = repo.get_graph()
1748
parents = graph.get_parent_map([r2])
1749
self.assertEqual({r2: (r1,)}, parents)
1750
# locking and unlocking deeper should not reset
1753
parents = graph.get_parent_map([r1])
1754
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1756
[('call_with_body_bytes_expecting_body',
1757
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1761
# now we call again, and it should use the second response.
1763
graph = repo.get_graph()
1764
parents = graph.get_parent_map([r1])
1765
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1767
[('call_with_body_bytes_expecting_body',
1768
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1770
('call_with_body_bytes_expecting_body',
1771
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1777
def test_get_parent_map_reconnects_if_unknown_method(self):
1778
transport_path = 'quack'
1779
rev_id = 'revision-id'
1780
repo, client = self.setup_fake_client_and_repository(transport_path)
1781
client.add_unknown_method_response('Repository.get_parent_map')
1782
client.add_success_response_with_body(rev_id, 'ok')
1783
self.assertFalse(client._medium._is_remote_before((1, 2)))
1784
parents = repo.get_parent_map([rev_id])
1786
[('call_with_body_bytes_expecting_body',
1787
'Repository.get_parent_map', ('quack/', 'include-missing:',
1789
('disconnect medium',),
1790
('call_expecting_body', 'Repository.get_revision_graph',
1793
# The medium is now marked as being connected to an older server
1794
self.assertTrue(client._medium._is_remote_before((1, 2)))
1795
self.assertEqual({rev_id: ('null:',)}, parents)
1797
def test_get_parent_map_fallback_parentless_node(self):
1798
"""get_parent_map falls back to get_revision_graph on old servers. The
1799
results from get_revision_graph are tweaked to match the get_parent_map
1802
Specifically, a {key: ()} result from get_revision_graph means "no
1803
parents" for that key, which in get_parent_map results should be
1804
represented as {key: ('null:',)}.
1806
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1808
rev_id = 'revision-id'
1809
transport_path = 'quack'
1810
repo, client = self.setup_fake_client_and_repository(transport_path)
1811
client.add_success_response_with_body(rev_id, 'ok')
1812
client._medium._remember_remote_is_before((1, 2))
1813
parents = repo.get_parent_map([rev_id])
1815
[('call_expecting_body', 'Repository.get_revision_graph',
1818
self.assertEqual({rev_id: ('null:',)}, parents)
1820
def test_get_parent_map_unexpected_response(self):
1821
repo, client = self.setup_fake_client_and_repository('path')
1822
client.add_success_response('something unexpected!')
1824
errors.UnexpectedSmartServerResponse,
1825
repo.get_parent_map, ['a-revision-id'])
1827
def test_get_parent_map_negative_caches_missing_keys(self):
1828
self.setup_smart_server_with_call_log()
1829
repo = self.make_repository('foo')
1830
self.assertIsInstance(repo, RemoteRepository)
1832
self.addCleanup(repo.unlock)
1833
self.reset_smart_call_log()
1834
graph = repo.get_graph()
1835
self.assertEqual({},
1836
graph.get_parent_map(['some-missing', 'other-missing']))
1837
self.assertLength(1, self.hpss_calls)
1838
# No call if we repeat this
1839
self.reset_smart_call_log()
1840
graph = repo.get_graph()
1841
self.assertEqual({},
1842
graph.get_parent_map(['some-missing', 'other-missing']))
1843
self.assertLength(0, self.hpss_calls)
1844
# Asking for more unknown keys makes a request.
1845
self.reset_smart_call_log()
1846
graph = repo.get_graph()
1847
self.assertEqual({},
1848
graph.get_parent_map(['some-missing', 'other-missing',
1850
self.assertLength(1, self.hpss_calls)
1852
def disableExtraResults(self):
1853
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1854
SmartServerRepositoryGetParentMap.no_extra_results = True
1856
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1857
self.addCleanup(reset_values)
1859
def test_null_cached_missing_and_stop_key(self):
1860
self.setup_smart_server_with_call_log()
1861
# Make a branch with a single revision.
1862
builder = self.make_branch_builder('foo')
1863
builder.start_series()
1864
builder.build_snapshot('first', None, [
1865
('add', ('', 'root-id', 'directory', ''))])
1866
builder.finish_series()
1867
branch = builder.get_branch()
1868
repo = branch.repository
1869
self.assertIsInstance(repo, RemoteRepository)
1870
# Stop the server from sending extra results.
1871
self.disableExtraResults()
1873
self.addCleanup(repo.unlock)
1874
self.reset_smart_call_log()
1875
graph = repo.get_graph()
1876
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1877
# 'first' it will be a candidate for the stop_keys of subsequent
1878
# requests, and because 'null:' was queried but not returned it will be
1879
# cached as missing.
1880
self.assertEqual({'first': ('null:',)},
1881
graph.get_parent_map(['first', 'null:']))
1882
# Now query for another key. This request will pass along a recipe of
1883
# start and stop keys describing the already cached results, and this
1884
# recipe's revision count must be correct (or else it will trigger an
1885
# error from the server).
1886
self.assertEqual({}, graph.get_parent_map(['another-key']))
1887
# This assertion guards against disableExtraResults silently failing to
1888
# work, thus invalidating the test.
1889
self.assertLength(2, self.hpss_calls)
1891
def test_get_parent_map_gets_ghosts_from_result(self):
1892
# asking for a revision should negatively cache close ghosts in its
1894
self.setup_smart_server_with_call_log()
1895
tree = self.make_branch_and_memory_tree('foo')
1898
builder = treebuilder.TreeBuilder()
1899
builder.start_tree(tree)
1901
builder.finish_tree()
1902
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1903
rev_id = tree.commit('')
1907
self.addCleanup(tree.unlock)
1908
repo = tree.branch.repository
1909
self.assertIsInstance(repo, RemoteRepository)
1911
repo.get_parent_map([rev_id])
1912
self.reset_smart_call_log()
1913
# Now asking for rev_id's ghost parent should not make calls
1914
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1915
self.assertLength(0, self.hpss_calls)
1918
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1920
def test_allows_new_revisions(self):
1921
"""get_parent_map's results can be updated by commit."""
1922
smart_server = server.SmartTCPServer_for_testing()
1923
smart_server.setUp()
1924
self.addCleanup(smart_server.tearDown)
1925
self.make_branch('branch')
1926
branch = Branch.open(smart_server.get_url() + '/branch')
1927
tree = branch.create_checkout('tree', lightweight=True)
1929
self.addCleanup(tree.unlock)
1930
graph = tree.branch.repository.get_graph()
1931
# This provides an opportunity for the missing rev-id to be cached.
1932
self.assertEqual({}, graph.get_parent_map(['rev1']))
1933
tree.commit('message', rev_id='rev1')
1934
graph = tree.branch.repository.get_graph()
1935
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1938
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1940
def test_null_revision(self):
1941
# a null revision has the predictable result {}, we should have no wire
1942
# traffic when calling it with this argument
1943
transport_path = 'empty'
1944
repo, client = self.setup_fake_client_and_repository(transport_path)
1945
client.add_success_response('notused')
1946
# actual RemoteRepository.get_revision_graph is gone, but there's an
1947
# equivalent private method for testing
1948
result = repo._get_revision_graph(NULL_REVISION)
1949
self.assertEqual([], client._calls)
1950
self.assertEqual({}, result)
1952
def test_none_revision(self):
1953
# with none we want the entire graph
1954
r1 = u'\u0e33'.encode('utf8')
1955
r2 = u'\u0dab'.encode('utf8')
1956
lines = [' '.join([r2, r1]), r1]
1957
encoded_body = '\n'.join(lines)
1959
transport_path = 'sinhala'
1960
repo, client = self.setup_fake_client_and_repository(transport_path)
1961
client.add_success_response_with_body(encoded_body, 'ok')
1962
# actual RemoteRepository.get_revision_graph is gone, but there's an
1963
# equivalent private method for testing
1964
result = repo._get_revision_graph(None)
1966
[('call_expecting_body', 'Repository.get_revision_graph',
1969
self.assertEqual({r1: (), r2: (r1, )}, result)
1971
def test_specific_revision(self):
1972
# with a specific revision we want the graph for that
1973
# with none we want the entire graph
1974
r11 = u'\u0e33'.encode('utf8')
1975
r12 = u'\xc9'.encode('utf8')
1976
r2 = u'\u0dab'.encode('utf8')
1977
lines = [' '.join([r2, r11, r12]), r11, r12]
1978
encoded_body = '\n'.join(lines)
1980
transport_path = 'sinhala'
1981
repo, client = self.setup_fake_client_and_repository(transport_path)
1982
client.add_success_response_with_body(encoded_body, 'ok')
1983
result = repo._get_revision_graph(r2)
1985
[('call_expecting_body', 'Repository.get_revision_graph',
1988
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1990
def test_no_such_revision(self):
1992
transport_path = 'sinhala'
1993
repo, client = self.setup_fake_client_and_repository(transport_path)
1994
client.add_error_response('nosuchrevision', revid)
1995
# also check that the right revision is reported in the error
1996
self.assertRaises(errors.NoSuchRevision,
1997
repo._get_revision_graph, revid)
1999
[('call_expecting_body', 'Repository.get_revision_graph',
2000
('sinhala/', revid))],
2003
def test_unexpected_error(self):
2005
transport_path = 'sinhala'
2006
repo, client = self.setup_fake_client_and_repository(transport_path)
2007
client.add_error_response('AnUnexpectedError')
2008
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2009
repo._get_revision_graph, revid)
2010
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2013
class TestRepositoryIsShared(TestRemoteRepository):
2015
def test_is_shared(self):
2016
# ('yes', ) for Repository.is_shared -> 'True'.
2017
transport_path = 'quack'
2018
repo, client = self.setup_fake_client_and_repository(transport_path)
2019
client.add_success_response('yes')
2020
result = repo.is_shared()
2022
[('call', 'Repository.is_shared', ('quack/',))],
2024
self.assertEqual(True, result)
2026
def test_is_not_shared(self):
2027
# ('no', ) for Repository.is_shared -> 'False'.
2028
transport_path = 'qwack'
2029
repo, client = self.setup_fake_client_and_repository(transport_path)
2030
client.add_success_response('no')
2031
result = repo.is_shared()
2033
[('call', 'Repository.is_shared', ('qwack/',))],
2035
self.assertEqual(False, result)
2038
class TestRepositoryLockWrite(TestRemoteRepository):
2040
def test_lock_write(self):
2041
transport_path = 'quack'
2042
repo, client = self.setup_fake_client_and_repository(transport_path)
2043
client.add_success_response('ok', 'a token')
2044
result = repo.lock_write()
2046
[('call', 'Repository.lock_write', ('quack/', ''))],
2048
self.assertEqual('a token', result)
2050
def test_lock_write_already_locked(self):
2051
transport_path = 'quack'
2052
repo, client = self.setup_fake_client_and_repository(transport_path)
2053
client.add_error_response('LockContention')
2054
self.assertRaises(errors.LockContention, repo.lock_write)
2056
[('call', 'Repository.lock_write', ('quack/', ''))],
2059
def test_lock_write_unlockable(self):
2060
transport_path = 'quack'
2061
repo, client = self.setup_fake_client_and_repository(transport_path)
2062
client.add_error_response('UnlockableTransport')
2063
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2065
[('call', 'Repository.lock_write', ('quack/', ''))],
2069
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2071
def test_backwards_compat(self):
2072
self.setup_smart_server_with_call_log()
2073
repo = self.make_repository('.')
2074
self.reset_smart_call_log()
2075
verb = 'Repository.set_make_working_trees'
2076
self.disable_verb(verb)
2077
repo.set_make_working_trees(True)
2078
call_count = len([call for call in self.hpss_calls if
2079
call.call.method == verb])
2080
self.assertEqual(1, call_count)
2082
def test_current(self):
2083
transport_path = 'quack'
2084
repo, client = self.setup_fake_client_and_repository(transport_path)
2085
client.add_expected_call(
2086
'Repository.set_make_working_trees', ('quack/', 'True'),
2088
client.add_expected_call(
2089
'Repository.set_make_working_trees', ('quack/', 'False'),
2091
repo.set_make_working_trees(True)
2092
repo.set_make_working_trees(False)
2095
class TestRepositoryUnlock(TestRemoteRepository):
2097
def test_unlock(self):
2098
transport_path = 'quack'
2099
repo, client = self.setup_fake_client_and_repository(transport_path)
2100
client.add_success_response('ok', 'a token')
2101
client.add_success_response('ok')
2105
[('call', 'Repository.lock_write', ('quack/', '')),
2106
('call', 'Repository.unlock', ('quack/', 'a token'))],
2109
def test_unlock_wrong_token(self):
2110
# If somehow the token is wrong, unlock will raise TokenMismatch.
2111
transport_path = 'quack'
2112
repo, client = self.setup_fake_client_and_repository(transport_path)
2113
client.add_success_response('ok', 'a token')
2114
client.add_error_response('TokenMismatch')
2116
self.assertRaises(errors.TokenMismatch, repo.unlock)
2119
class TestRepositoryHasRevision(TestRemoteRepository):
2121
def test_none(self):
2122
# repo.has_revision(None) should not cause any traffic.
2123
transport_path = 'quack'
2124
repo, client = self.setup_fake_client_and_repository(transport_path)
2126
# The null revision is always there, so has_revision(None) == True.
2127
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2129
# The remote repo shouldn't be accessed.
2130
self.assertEqual([], client._calls)
2133
class TestRepositoryInsertStream(TestRemoteRepository):
2135
def test_unlocked_repo(self):
2136
transport_path = 'quack'
2137
repo, client = self.setup_fake_client_and_repository(transport_path)
2138
client.add_expected_call(
2139
'Repository.insert_stream', ('quack/', ''),
2141
client.add_expected_call(
2142
'Repository.insert_stream', ('quack/', ''),
2144
sink = repo._get_sink()
2145
fmt = repository.RepositoryFormat.get_default_format()
2146
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2147
self.assertEqual([], resume_tokens)
2148
self.assertEqual(set(), missing_keys)
2149
client.finished_test()
2151
def test_locked_repo_with_no_lock_token(self):
2152
transport_path = 'quack'
2153
repo, client = self.setup_fake_client_and_repository(transport_path)
2154
client.add_expected_call(
2155
'Repository.lock_write', ('quack/', ''),
2156
'success', ('ok', ''))
2157
client.add_expected_call(
2158
'Repository.insert_stream', ('quack/', ''),
2160
client.add_expected_call(
2161
'Repository.insert_stream', ('quack/', ''),
2164
sink = repo._get_sink()
2165
fmt = repository.RepositoryFormat.get_default_format()
2166
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2167
self.assertEqual([], resume_tokens)
2168
self.assertEqual(set(), missing_keys)
2169
client.finished_test()
2171
def test_locked_repo_with_lock_token(self):
2172
transport_path = 'quack'
2173
repo, client = self.setup_fake_client_and_repository(transport_path)
2174
client.add_expected_call(
2175
'Repository.lock_write', ('quack/', ''),
2176
'success', ('ok', 'a token'))
2177
client.add_expected_call(
2178
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2180
client.add_expected_call(
2181
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2184
sink = repo._get_sink()
2185
fmt = repository.RepositoryFormat.get_default_format()
2186
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2187
self.assertEqual([], resume_tokens)
2188
self.assertEqual(set(), missing_keys)
2189
client.finished_test()
2192
class TestRepositoryTarball(TestRemoteRepository):
2194
# This is a canned tarball reponse we can validate against
2196
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2197
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2198
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2199
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2200
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2201
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2202
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2203
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2204
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2205
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2206
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2207
'nWQ7QH/F3JFOFCQ0aSPfA='
2210
def test_repository_tarball(self):
2211
# Test that Repository.tarball generates the right operations
2212
transport_path = 'repo'
2213
expected_calls = [('call_expecting_body', 'Repository.tarball',
2214
('repo/', 'bz2',),),
2216
repo, client = self.setup_fake_client_and_repository(transport_path)
2217
client.add_success_response_with_body(self.tarball_content, 'ok')
2218
# Now actually ask for the tarball
2219
tarball_file = repo._get_tarball('bz2')
2221
self.assertEqual(expected_calls, client._calls)
2222
self.assertEqual(self.tarball_content, tarball_file.read())
2224
tarball_file.close()
2227
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2228
"""RemoteRepository.copy_content_into optimizations"""
2230
def test_copy_content_remote_to_local(self):
2231
self.transport_server = server.SmartTCPServer_for_testing
2232
src_repo = self.make_repository('repo1')
2233
src_repo = repository.Repository.open(self.get_url('repo1'))
2234
# At the moment the tarball-based copy_content_into can't write back
2235
# into a smart server. It would be good if it could upload the
2236
# tarball; once that works we'd have to create repositories of
2237
# different formats. -- mbp 20070410
2238
dest_url = self.get_vfs_only_url('repo2')
2239
dest_bzrdir = BzrDir.create(dest_url)
2240
dest_repo = dest_bzrdir.create_repository()
2241
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2242
self.assertTrue(isinstance(src_repo, RemoteRepository))
2243
src_repo.copy_content_into(dest_repo)
2246
class _StubRealPackRepository(object):
2248
def __init__(self, calls):
2250
self._pack_collection = _StubPackCollection(calls)
2252
def is_in_write_group(self):
2255
def refresh_data(self):
2256
self.calls.append(('pack collection reload_pack_names',))
2259
class _StubPackCollection(object):
2261
def __init__(self, calls):
2265
self.calls.append(('pack collection autopack',))
2268
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2269
"""Tests for RemoteRepository.autopack implementation."""
2272
"""When the server returns 'ok' and there's no _real_repository, then
2273
nothing else happens: the autopack method is done.
2275
transport_path = 'quack'
2276
repo, client = self.setup_fake_client_and_repository(transport_path)
2277
client.add_expected_call(
2278
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2280
client.finished_test()
2282
def test_ok_with_real_repo(self):
2283
"""When the server returns 'ok' and there is a _real_repository, then
2284
the _real_repository's reload_pack_name's method will be called.
2286
transport_path = 'quack'
2287
repo, client = self.setup_fake_client_and_repository(transport_path)
2288
client.add_expected_call(
2289
'PackRepository.autopack', ('quack/',),
2291
repo._real_repository = _StubRealPackRepository(client._calls)
2294
[('call', 'PackRepository.autopack', ('quack/',)),
2295
('pack collection reload_pack_names',)],
2298
def test_backwards_compatibility(self):
2299
"""If the server does not recognise the PackRepository.autopack verb,
2300
fallback to the real_repository's implementation.
2302
transport_path = 'quack'
2303
repo, client = self.setup_fake_client_and_repository(transport_path)
2304
client.add_unknown_method_response('PackRepository.autopack')
2305
def stub_ensure_real():
2306
client._calls.append(('_ensure_real',))
2307
repo._real_repository = _StubRealPackRepository(client._calls)
2308
repo._ensure_real = stub_ensure_real
2311
[('call', 'PackRepository.autopack', ('quack/',)),
2313
('pack collection autopack',)],
2317
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2318
"""Base class for unit tests for bzrlib.remote._translate_error."""
2320
def translateTuple(self, error_tuple, **context):
2321
"""Call _translate_error with an ErrorFromSmartServer built from the
2324
:param error_tuple: A tuple of a smart server response, as would be
2325
passed to an ErrorFromSmartServer.
2326
:kwargs context: context items to call _translate_error with.
2328
:returns: The error raised by _translate_error.
2330
# Raise the ErrorFromSmartServer before passing it as an argument,
2331
# because _translate_error may need to re-raise it with a bare 'raise'
2333
server_error = errors.ErrorFromSmartServer(error_tuple)
2334
translated_error = self.translateErrorFromSmartServer(
2335
server_error, **context)
2336
return translated_error
2338
def translateErrorFromSmartServer(self, error_object, **context):
2339
"""Like translateTuple, but takes an already constructed
2340
ErrorFromSmartServer rather than a tuple.
2344
except errors.ErrorFromSmartServer, server_error:
2345
translated_error = self.assertRaises(
2346
errors.BzrError, remote._translate_error, server_error,
2348
return translated_error
2351
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2352
"""Unit tests for bzrlib.remote._translate_error.
2354
Given an ErrorFromSmartServer (which has an error tuple from a smart
2355
server) and some context, _translate_error raises more specific errors from
2358
This test case covers the cases where _translate_error succeeds in
2359
translating an ErrorFromSmartServer to something better. See
2360
TestErrorTranslationRobustness for other cases.
2363
def test_NoSuchRevision(self):
2364
branch = self.make_branch('')
2366
translated_error = self.translateTuple(
2367
('NoSuchRevision', revid), branch=branch)
2368
expected_error = errors.NoSuchRevision(branch, revid)
2369
self.assertEqual(expected_error, translated_error)
2371
def test_nosuchrevision(self):
2372
repository = self.make_repository('')
2374
translated_error = self.translateTuple(
2375
('nosuchrevision', revid), repository=repository)
2376
expected_error = errors.NoSuchRevision(repository, revid)
2377
self.assertEqual(expected_error, translated_error)
2379
def test_nobranch(self):
2380
bzrdir = self.make_bzrdir('')
2381
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2382
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2383
self.assertEqual(expected_error, translated_error)
2385
def test_LockContention(self):
2386
translated_error = self.translateTuple(('LockContention',))
2387
expected_error = errors.LockContention('(remote lock)')
2388
self.assertEqual(expected_error, translated_error)
2390
def test_UnlockableTransport(self):
2391
bzrdir = self.make_bzrdir('')
2392
translated_error = self.translateTuple(
2393
('UnlockableTransport',), bzrdir=bzrdir)
2394
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2395
self.assertEqual(expected_error, translated_error)
2397
def test_LockFailed(self):
2398
lock = 'str() of a server lock'
2399
why = 'str() of why'
2400
translated_error = self.translateTuple(('LockFailed', lock, why))
2401
expected_error = errors.LockFailed(lock, why)
2402
self.assertEqual(expected_error, translated_error)
2404
def test_TokenMismatch(self):
2405
token = 'a lock token'
2406
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2407
expected_error = errors.TokenMismatch(token, '(remote token)')
2408
self.assertEqual(expected_error, translated_error)
2410
def test_Diverged(self):
2411
branch = self.make_branch('a')
2412
other_branch = self.make_branch('b')
2413
translated_error = self.translateTuple(
2414
('Diverged',), branch=branch, other_branch=other_branch)
2415
expected_error = errors.DivergedBranches(branch, other_branch)
2416
self.assertEqual(expected_error, translated_error)
2418
def test_ReadError_no_args(self):
2420
translated_error = self.translateTuple(('ReadError',), path=path)
2421
expected_error = errors.ReadError(path)
2422
self.assertEqual(expected_error, translated_error)
2424
def test_ReadError(self):
2426
translated_error = self.translateTuple(('ReadError', path))
2427
expected_error = errors.ReadError(path)
2428
self.assertEqual(expected_error, translated_error)
2430
def test_PermissionDenied_no_args(self):
2432
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2433
expected_error = errors.PermissionDenied(path)
2434
self.assertEqual(expected_error, translated_error)
2436
def test_PermissionDenied_one_arg(self):
2438
translated_error = self.translateTuple(('PermissionDenied', path))
2439
expected_error = errors.PermissionDenied(path)
2440
self.assertEqual(expected_error, translated_error)
2442
def test_PermissionDenied_one_arg_and_context(self):
2443
"""Given a choice between a path from the local context and a path on
2444
the wire, _translate_error prefers the path from the local context.
2446
local_path = 'local path'
2447
remote_path = 'remote path'
2448
translated_error = self.translateTuple(
2449
('PermissionDenied', remote_path), path=local_path)
2450
expected_error = errors.PermissionDenied(local_path)
2451
self.assertEqual(expected_error, translated_error)
2453
def test_PermissionDenied_two_args(self):
2455
extra = 'a string with extra info'
2456
translated_error = self.translateTuple(
2457
('PermissionDenied', path, extra))
2458
expected_error = errors.PermissionDenied(path, extra)
2459
self.assertEqual(expected_error, translated_error)
2462
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2463
"""Unit tests for bzrlib.remote._translate_error's robustness.
2465
TestErrorTranslationSuccess is for cases where _translate_error can
2466
translate successfully. This class about how _translate_err behaves when
2467
it fails to translate: it re-raises the original error.
2470
def test_unrecognised_server_error(self):
2471
"""If the error code from the server is not recognised, the original
2472
ErrorFromSmartServer is propagated unmodified.
2474
error_tuple = ('An unknown error tuple',)
2475
server_error = errors.ErrorFromSmartServer(error_tuple)
2476
translated_error = self.translateErrorFromSmartServer(server_error)
2477
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2478
self.assertEqual(expected_error, translated_error)
2480
def test_context_missing_a_key(self):
2481
"""In case of a bug in the client, or perhaps an unexpected response
2482
from a server, _translate_error returns the original error tuple from
2483
the server and mutters a warning.
2485
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2486
# in the context dict. So let's give it an empty context dict instead
2487
# to exercise its error recovery.
2489
error_tuple = ('NoSuchRevision', 'revid')
2490
server_error = errors.ErrorFromSmartServer(error_tuple)
2491
translated_error = self.translateErrorFromSmartServer(server_error)
2492
self.assertEqual(server_error, translated_error)
2493
# In addition to re-raising ErrorFromSmartServer, some debug info has
2494
# been muttered to the log file for developer to look at.
2495
self.assertContainsRe(
2496
self._get_log(keep_log_file=True),
2497
"Missing key 'branch' in context")
2499
def test_path_missing(self):
2500
"""Some translations (PermissionDenied, ReadError) can determine the
2501
'path' variable from either the wire or the local context. If neither
2502
has it, then an error is raised.
2504
error_tuple = ('ReadError',)
2505
server_error = errors.ErrorFromSmartServer(error_tuple)
2506
translated_error = self.translateErrorFromSmartServer(server_error)
2507
self.assertEqual(server_error, translated_error)
2508
# In addition to re-raising ErrorFromSmartServer, some debug info has
2509
# been muttered to the log file for developer to look at.
2510
self.assertContainsRe(
2511
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2514
class TestStacking(tests.TestCaseWithTransport):
2515
"""Tests for operations on stacked remote repositories.
2517
The underlying format type must support stacking.
2520
def test_access_stacked_remote(self):
2521
# based on <http://launchpad.net/bugs/261315>
2522
# make a branch stacked on another repository containing an empty
2523
# revision, then open it over hpss - we should be able to see that
2525
base_transport = self.get_transport()
2526
base_builder = self.make_branch_builder('base', format='1.9')
2527
base_builder.start_series()
2528
base_revid = base_builder.build_snapshot('rev-id', None,
2529
[('add', ('', None, 'directory', None))],
2531
base_builder.finish_series()
2532
stacked_branch = self.make_branch('stacked', format='1.9')
2533
stacked_branch.set_stacked_on_url('../base')
2534
# start a server looking at this
2535
smart_server = server.SmartTCPServer_for_testing()
2536
smart_server.setUp()
2537
self.addCleanup(smart_server.tearDown)
2538
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2539
# can get its branch and repository
2540
remote_branch = remote_bzrdir.open_branch()
2541
remote_repo = remote_branch.repository
2542
remote_repo.lock_read()
2544
# it should have an appropriate fallback repository, which should also
2545
# be a RemoteRepository
2546
self.assertLength(1, remote_repo._fallback_repositories)
2547
self.assertIsInstance(remote_repo._fallback_repositories[0],
2549
# and it has the revision committed to the underlying repository;
2550
# these have varying implementations so we try several of them
2551
self.assertTrue(remote_repo.has_revisions([base_revid]))
2552
self.assertTrue(remote_repo.has_revision(base_revid))
2553
self.assertEqual(remote_repo.get_revision(base_revid).message,
2556
remote_repo.unlock()
2558
def prepare_stacked_remote_branch(self):
2559
"""Get stacked_upon and stacked branches with content in each."""
2560
self.setup_smart_server_with_call_log()
2561
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2562
tree1.commit('rev1', rev_id='rev1')
2563
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2564
).open_workingtree()
2565
tree2.commit('local changes make me feel good.')
2566
branch2 = Branch.open(self.get_url('tree2'))
2568
self.addCleanup(branch2.unlock)
2569
return tree1.branch, branch2
2571
def test_stacked_get_parent_map(self):
2572
# the public implementation of get_parent_map obeys stacking
2573
_, branch = self.prepare_stacked_remote_branch()
2574
repo = branch.repository
2575
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2577
def test_unstacked_get_parent_map(self):
2578
# _unstacked_provider.get_parent_map ignores stacking
2579
_, branch = self.prepare_stacked_remote_branch()
2580
provider = branch.repository._unstacked_provider
2581
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2583
def fetch_stream_to_rev_order(self, stream):
2585
for kind, substream in stream:
2586
if not kind == 'revisions':
2589
for content in substream:
2590
result.append(content.key[-1])
2593
def get_ordered_revs(self, format, order):
2594
"""Get a list of the revisions in a stream to format format.
2596
:param format: The format of the target.
2597
:param order: the order that target should have requested.
2598
:result: The revision ids in the stream, in the order seen,
2599
the topological order of revisions in the source.
2601
unordered_format = bzrdir.format_registry.get(format)()
2602
target_repository_format = unordered_format.repository_format
2604
self.assertEqual(order, target_repository_format._fetch_order)
2605
trunk, stacked = self.prepare_stacked_remote_branch()
2606
source = stacked.repository._get_source(target_repository_format)
2607
tip = stacked.last_revision()
2608
revs = stacked.repository.get_ancestry(tip)
2609
search = graph.PendingAncestryResult([tip], stacked.repository)
2610
self.reset_smart_call_log()
2611
stream = source.get_stream(search)
2614
# We trust that if a revision is in the stream the rest of the new
2615
# content for it is too, as per our main fetch tests; here we are
2616
# checking that the revisions are actually included at all, and their
2618
return self.fetch_stream_to_rev_order(stream), revs
2620
def test_stacked_get_stream_unordered(self):
2621
# Repository._get_source.get_stream() from a stacked repository with
2622
# unordered yields the full data from both stacked and stacked upon
2624
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2625
self.assertEqual(set(expected_revs), set(rev_ord))
2626
# Getting unordered results should have made a streaming data request
2627
# from the server, then one from the backing branch.
2628
self.assertLength(2, self.hpss_calls)
2630
def test_stacked_get_stream_topological(self):
2631
# Repository._get_source.get_stream() from a stacked repository with
2632
# topological sorting yields the full data from both stacked and
2633
# stacked upon sources in topological order.
2634
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2635
self.assertEqual(expected_revs, rev_ord)
2636
# Getting topological sort requires VFS calls still
2637
self.assertLength(12, self.hpss_calls)
2639
def test_stacked_get_stream_groupcompress(self):
2640
# Repository._get_source.get_stream() from a stacked repository with
2641
# groupcompress sorting yields the full data from both stacked and
2642
# stacked upon sources in groupcompress order.
2643
raise tests.TestSkipped('No groupcompress ordered format available')
2644
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2645
self.assertEqual(expected_revs, reversed(rev_ord))
2646
# Getting unordered results should have made a streaming data request
2647
# from the backing branch, and one from the stacked on branch.
2648
self.assertLength(2, self.hpss_calls)
2650
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
2651
# When pulling some fixed amount of content that is more than the
2652
# source has (because some is coming from a fallback branch, no error
2653
# should be received. This was reported as bug 360791.
2654
# Need three branches: a trunk, a stacked branch, and a preexisting
2655
# branch pulling content from stacked and trunk.
2656
self.setup_smart_server_with_call_log()
2657
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
2658
r1 = trunk.commit('start')
2659
stacked_branch = trunk.branch.create_clone_on_transport(
2660
self.get_transport('stacked'), stacked_on=trunk.branch.base)
2661
local = self.make_branch('local', format='1.9-rich-root')
2662
local.repository.fetch(stacked_branch.repository,
2663
stacked_branch.last_revision())
2666
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2669
super(TestRemoteBranchEffort, self).setUp()
2670
# Create a smart server that publishes whatever the backing VFS server
2672
self.smart_server = server.SmartTCPServer_for_testing()
2673
self.smart_server.setUp(self.get_server())
2674
self.addCleanup(self.smart_server.tearDown)
2675
# Log all HPSS calls into self.hpss_calls.
2676
_SmartClient.hooks.install_named_hook(
2677
'call', self.capture_hpss_call, None)
2678
self.hpss_calls = []
2680
def capture_hpss_call(self, params):
2681
self.hpss_calls.append(params.method)
2683
def test_copy_content_into_avoids_revision_history(self):
2684
local = self.make_branch('local')
2685
remote_backing_tree = self.make_branch_and_tree('remote')
2686
remote_backing_tree.commit("Commit.")
2687
remote_branch_url = self.smart_server.get_url() + 'remote'
2688
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2689
local.repository.fetch(remote_branch.repository)
2690
self.hpss_calls = []
2691
remote_branch.copy_content_into(local)
2692
self.assertFalse('Branch.revision_history' in self.hpss_calls)