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 OldSmartClient(object):
745
"""A fake smart client for test_old_version that just returns a version one
746
response to the 'hello' (query version) command.
749
def get_request(self):
750
input_file = StringIO('ok\x011\n')
751
output_file = StringIO()
752
client_medium = medium.SmartSimplePipesClientMedium(
753
input_file, output_file)
754
return medium.SmartClientStreamMediumRequest(client_medium)
756
def protocol_version(self):
760
class OldServerTransport(object):
761
"""A fake transport for test_old_server that reports it's smart server
762
protocol version as version one.
768
def get_smart_client(self):
769
return OldSmartClient()
772
class RemoteBzrDirTestCase(TestRemote):
774
def make_remote_bzrdir(self, transport, client):
775
"""Make a RemotebzrDir using 'client' as the _client."""
776
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
780
class RemoteBranchTestCase(RemoteBzrDirTestCase):
782
def make_remote_branch(self, transport, client):
783
"""Make a RemoteBranch using 'client' as its _SmartClient.
785
A RemoteBzrDir and RemoteRepository will also be created to fill out
786
the RemoteBranch, albeit with stub values for some of their attributes.
788
# we do not want bzrdir to make any remote calls, so use False as its
789
# _client. If it tries to make a remote call, this will fail
791
bzrdir = self.make_remote_bzrdir(transport, False)
792
repo = RemoteRepository(bzrdir, None, _client=client)
793
branch_format = self.get_branch_format()
794
format = RemoteBranchFormat(network_name=branch_format.network_name())
795
return RemoteBranch(bzrdir, repo, _client=client, format=format)
798
class TestBranchGetParent(RemoteBranchTestCase):
800
def test_no_parent(self):
801
# in an empty branch we decode the response properly
802
transport = MemoryTransport()
803
client = FakeClient(transport.base)
804
client.add_expected_call(
805
'Branch.get_stacked_on_url', ('quack/',),
806
'error', ('NotStacked',))
807
client.add_expected_call(
808
'Branch.get_parent', ('quack/',),
810
transport.mkdir('quack')
811
transport = transport.clone('quack')
812
branch = self.make_remote_branch(transport, client)
813
result = branch.get_parent()
814
client.finished_test()
815
self.assertEqual(None, result)
817
def test_parent_relative(self):
818
transport = MemoryTransport()
819
client = FakeClient(transport.base)
820
client.add_expected_call(
821
'Branch.get_stacked_on_url', ('kwaak/',),
822
'error', ('NotStacked',))
823
client.add_expected_call(
824
'Branch.get_parent', ('kwaak/',),
825
'success', ('../foo/',))
826
transport.mkdir('kwaak')
827
transport = transport.clone('kwaak')
828
branch = self.make_remote_branch(transport, client)
829
result = branch.get_parent()
830
self.assertEqual(transport.clone('../foo').base, result)
832
def test_parent_absolute(self):
833
transport = MemoryTransport()
834
client = FakeClient(transport.base)
835
client.add_expected_call(
836
'Branch.get_stacked_on_url', ('kwaak/',),
837
'error', ('NotStacked',))
838
client.add_expected_call(
839
'Branch.get_parent', ('kwaak/',),
840
'success', ('http://foo/',))
841
transport.mkdir('kwaak')
842
transport = transport.clone('kwaak')
843
branch = self.make_remote_branch(transport, client)
844
result = branch.get_parent()
845
self.assertEqual('http://foo/', result)
846
client.finished_test()
849
class TestBranchSetParentLocation(RemoteBranchTestCase):
851
def test_no_parent(self):
852
# We call the verb when setting parent to None
853
transport = MemoryTransport()
854
client = FakeClient(transport.base)
855
client.add_expected_call(
856
'Branch.get_stacked_on_url', ('quack/',),
857
'error', ('NotStacked',))
858
client.add_expected_call(
859
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
861
transport.mkdir('quack')
862
transport = transport.clone('quack')
863
branch = self.make_remote_branch(transport, client)
864
branch._lock_token = 'b'
865
branch._repo_lock_token = 'r'
866
branch._set_parent_location(None)
867
client.finished_test()
869
def test_parent(self):
870
transport = MemoryTransport()
871
client = FakeClient(transport.base)
872
client.add_expected_call(
873
'Branch.get_stacked_on_url', ('kwaak/',),
874
'error', ('NotStacked',))
875
client.add_expected_call(
876
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
878
transport.mkdir('kwaak')
879
transport = transport.clone('kwaak')
880
branch = self.make_remote_branch(transport, client)
881
branch._lock_token = 'b'
882
branch._repo_lock_token = 'r'
883
branch._set_parent_location('foo')
884
client.finished_test()
886
def test_backwards_compat(self):
887
self.setup_smart_server_with_call_log()
888
branch = self.make_branch('.')
889
self.reset_smart_call_log()
890
verb = 'Branch.set_parent_location'
891
self.disable_verb(verb)
892
branch.set_parent('http://foo/')
893
self.assertLength(12, self.hpss_calls)
896
class TestBranchGetTagsBytes(RemoteBranchTestCase):
898
def test_backwards_compat(self):
899
self.setup_smart_server_with_call_log()
900
branch = self.make_branch('.')
901
self.reset_smart_call_log()
902
verb = 'Branch.get_tags_bytes'
903
self.disable_verb(verb)
904
branch.tags.get_tag_dict()
905
call_count = len([call for call in self.hpss_calls if
906
call.call.method == verb])
907
self.assertEqual(1, call_count)
909
def test_trivial(self):
910
transport = MemoryTransport()
911
client = FakeClient(transport.base)
912
client.add_expected_call(
913
'Branch.get_stacked_on_url', ('quack/',),
914
'error', ('NotStacked',))
915
client.add_expected_call(
916
'Branch.get_tags_bytes', ('quack/',),
918
transport.mkdir('quack')
919
transport = transport.clone('quack')
920
branch = self.make_remote_branch(transport, client)
921
result = branch.tags.get_tag_dict()
922
client.finished_test()
923
self.assertEqual({}, result)
926
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
928
def test_empty_branch(self):
929
# in an empty branch we decode the response properly
930
transport = MemoryTransport()
931
client = FakeClient(transport.base)
932
client.add_expected_call(
933
'Branch.get_stacked_on_url', ('quack/',),
934
'error', ('NotStacked',))
935
client.add_expected_call(
936
'Branch.last_revision_info', ('quack/',),
937
'success', ('ok', '0', 'null:'))
938
transport.mkdir('quack')
939
transport = transport.clone('quack')
940
branch = self.make_remote_branch(transport, client)
941
result = branch.last_revision_info()
942
client.finished_test()
943
self.assertEqual((0, NULL_REVISION), result)
945
def test_non_empty_branch(self):
946
# in a non-empty branch we also decode the response properly
947
revid = u'\xc8'.encode('utf8')
948
transport = MemoryTransport()
949
client = FakeClient(transport.base)
950
client.add_expected_call(
951
'Branch.get_stacked_on_url', ('kwaak/',),
952
'error', ('NotStacked',))
953
client.add_expected_call(
954
'Branch.last_revision_info', ('kwaak/',),
955
'success', ('ok', '2', revid))
956
transport.mkdir('kwaak')
957
transport = transport.clone('kwaak')
958
branch = self.make_remote_branch(transport, client)
959
result = branch.last_revision_info()
960
self.assertEqual((2, revid), result)
963
class TestBranch_get_stacked_on_url(TestRemote):
964
"""Test Branch._get_stacked_on_url rpc"""
966
def test_get_stacked_on_invalid_url(self):
967
# test that asking for a stacked on url the server can't access works.
968
# This isn't perfect, but then as we're in the same process there
969
# really isn't anything we can do to be 100% sure that the server
970
# doesn't just open in - this test probably needs to be rewritten using
971
# a spawn()ed server.
972
stacked_branch = self.make_branch('stacked', format='1.9')
973
memory_branch = self.make_branch('base', format='1.9')
974
vfs_url = self.get_vfs_only_url('base')
975
stacked_branch.set_stacked_on_url(vfs_url)
976
transport = stacked_branch.bzrdir.root_transport
977
client = FakeClient(transport.base)
978
client.add_expected_call(
979
'Branch.get_stacked_on_url', ('stacked/',),
980
'success', ('ok', vfs_url))
981
# XXX: Multiple calls are bad, this second call documents what is
983
client.add_expected_call(
984
'Branch.get_stacked_on_url', ('stacked/',),
985
'success', ('ok', vfs_url))
986
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
988
repo_fmt = remote.RemoteRepositoryFormat()
989
repo_fmt._custom_format = stacked_branch.repository._format
990
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
992
result = branch.get_stacked_on_url()
993
self.assertEqual(vfs_url, result)
995
def test_backwards_compatible(self):
996
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
997
base_branch = self.make_branch('base', format='1.6')
998
stacked_branch = self.make_branch('stacked', format='1.6')
999
stacked_branch.set_stacked_on_url('../base')
1000
client = FakeClient(self.get_url())
1001
branch_network_name = self.get_branch_format().network_name()
1002
client.add_expected_call(
1003
'BzrDir.open_branchV2', ('stacked/',),
1004
'success', ('branch', branch_network_name))
1005
client.add_expected_call(
1006
'BzrDir.find_repositoryV3', ('stacked/',),
1007
'success', ('ok', '', 'no', 'no', 'yes',
1008
stacked_branch.repository._format.network_name()))
1009
# called twice, once from constructor and then again by us
1010
client.add_expected_call(
1011
'Branch.get_stacked_on_url', ('stacked/',),
1012
'unknown', ('Branch.get_stacked_on_url',))
1013
client.add_expected_call(
1014
'Branch.get_stacked_on_url', ('stacked/',),
1015
'unknown', ('Branch.get_stacked_on_url',))
1016
# this will also do vfs access, but that goes direct to the transport
1017
# and isn't seen by the FakeClient.
1018
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1019
remote.RemoteBzrDirFormat(), _client=client)
1020
branch = bzrdir.open_branch()
1021
result = branch.get_stacked_on_url()
1022
self.assertEqual('../base', result)
1023
client.finished_test()
1024
# it's in the fallback list both for the RemoteRepository and its vfs
1026
self.assertEqual(1, len(branch.repository._fallback_repositories))
1028
len(branch.repository._real_repository._fallback_repositories))
1030
def test_get_stacked_on_real_branch(self):
1031
base_branch = self.make_branch('base', format='1.6')
1032
stacked_branch = self.make_branch('stacked', format='1.6')
1033
stacked_branch.set_stacked_on_url('../base')
1034
reference_format = self.get_repo_format()
1035
network_name = reference_format.network_name()
1036
client = FakeClient(self.get_url())
1037
branch_network_name = self.get_branch_format().network_name()
1038
client.add_expected_call(
1039
'BzrDir.open_branchV2', ('stacked/',),
1040
'success', ('branch', branch_network_name))
1041
client.add_expected_call(
1042
'BzrDir.find_repositoryV3', ('stacked/',),
1043
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1044
# called twice, once from constructor and then again by us
1045
client.add_expected_call(
1046
'Branch.get_stacked_on_url', ('stacked/',),
1047
'success', ('ok', '../base'))
1048
client.add_expected_call(
1049
'Branch.get_stacked_on_url', ('stacked/',),
1050
'success', ('ok', '../base'))
1051
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1052
remote.RemoteBzrDirFormat(), _client=client)
1053
branch = bzrdir.open_branch()
1054
result = branch.get_stacked_on_url()
1055
self.assertEqual('../base', result)
1056
client.finished_test()
1057
# it's in the fallback list both for the RemoteRepository.
1058
self.assertEqual(1, len(branch.repository._fallback_repositories))
1059
# And we haven't had to construct a real repository.
1060
self.assertEqual(None, branch.repository._real_repository)
1063
class TestBranchSetLastRevision(RemoteBranchTestCase):
1065
def test_set_empty(self):
1066
# set_revision_history([]) is translated to calling
1067
# Branch.set_last_revision(path, '') on the wire.
1068
transport = MemoryTransport()
1069
transport.mkdir('branch')
1070
transport = transport.clone('branch')
1072
client = FakeClient(transport.base)
1073
client.add_expected_call(
1074
'Branch.get_stacked_on_url', ('branch/',),
1075
'error', ('NotStacked',))
1076
client.add_expected_call(
1077
'Branch.lock_write', ('branch/', '', ''),
1078
'success', ('ok', 'branch token', 'repo token'))
1079
client.add_expected_call(
1080
'Branch.last_revision_info',
1082
'success', ('ok', '0', 'null:'))
1083
client.add_expected_call(
1084
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1086
client.add_expected_call(
1087
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1089
branch = self.make_remote_branch(transport, client)
1090
# This is a hack to work around the problem that RemoteBranch currently
1091
# unnecessarily invokes _ensure_real upon a call to lock_write.
1092
branch._ensure_real = lambda: None
1094
result = branch.set_revision_history([])
1096
self.assertEqual(None, result)
1097
client.finished_test()
1099
def test_set_nonempty(self):
1100
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1101
# Branch.set_last_revision(path, rev-idN) on the wire.
1102
transport = MemoryTransport()
1103
transport.mkdir('branch')
1104
transport = transport.clone('branch')
1106
client = FakeClient(transport.base)
1107
client.add_expected_call(
1108
'Branch.get_stacked_on_url', ('branch/',),
1109
'error', ('NotStacked',))
1110
client.add_expected_call(
1111
'Branch.lock_write', ('branch/', '', ''),
1112
'success', ('ok', 'branch token', 'repo token'))
1113
client.add_expected_call(
1114
'Branch.last_revision_info',
1116
'success', ('ok', '0', 'null:'))
1118
encoded_body = bz2.compress('\n'.join(lines))
1119
client.add_success_response_with_body(encoded_body, 'ok')
1120
client.add_expected_call(
1121
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1123
client.add_expected_call(
1124
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1126
branch = self.make_remote_branch(transport, client)
1127
# This is a hack to work around the problem that RemoteBranch currently
1128
# unnecessarily invokes _ensure_real upon a call to lock_write.
1129
branch._ensure_real = lambda: None
1130
# Lock the branch, reset the record of remote calls.
1132
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1134
self.assertEqual(None, result)
1135
client.finished_test()
1137
def test_no_such_revision(self):
1138
transport = MemoryTransport()
1139
transport.mkdir('branch')
1140
transport = transport.clone('branch')
1141
# A response of 'NoSuchRevision' is translated into an exception.
1142
client = FakeClient(transport.base)
1143
client.add_expected_call(
1144
'Branch.get_stacked_on_url', ('branch/',),
1145
'error', ('NotStacked',))
1146
client.add_expected_call(
1147
'Branch.lock_write', ('branch/', '', ''),
1148
'success', ('ok', 'branch token', 'repo token'))
1149
client.add_expected_call(
1150
'Branch.last_revision_info',
1152
'success', ('ok', '0', 'null:'))
1153
# get_graph calls to construct the revision history, for the set_rh
1156
encoded_body = bz2.compress('\n'.join(lines))
1157
client.add_success_response_with_body(encoded_body, 'ok')
1158
client.add_expected_call(
1159
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1160
'error', ('NoSuchRevision', 'rev-id'))
1161
client.add_expected_call(
1162
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1165
branch = self.make_remote_branch(transport, client)
1168
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1170
client.finished_test()
1172
def test_tip_change_rejected(self):
1173
"""TipChangeRejected responses cause a TipChangeRejected exception to
1176
transport = MemoryTransport()
1177
transport.mkdir('branch')
1178
transport = transport.clone('branch')
1179
client = FakeClient(transport.base)
1180
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1181
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1182
client.add_expected_call(
1183
'Branch.get_stacked_on_url', ('branch/',),
1184
'error', ('NotStacked',))
1185
client.add_expected_call(
1186
'Branch.lock_write', ('branch/', '', ''),
1187
'success', ('ok', 'branch token', 'repo token'))
1188
client.add_expected_call(
1189
'Branch.last_revision_info',
1191
'success', ('ok', '0', 'null:'))
1193
encoded_body = bz2.compress('\n'.join(lines))
1194
client.add_success_response_with_body(encoded_body, 'ok')
1195
client.add_expected_call(
1196
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1197
'error', ('TipChangeRejected', rejection_msg_utf8))
1198
client.add_expected_call(
1199
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1201
branch = self.make_remote_branch(transport, client)
1202
branch._ensure_real = lambda: None
1204
# The 'TipChangeRejected' error response triggered by calling
1205
# set_revision_history causes a TipChangeRejected exception.
1206
err = self.assertRaises(
1207
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1208
# The UTF-8 message from the response has been decoded into a unicode
1210
self.assertIsInstance(err.msg, unicode)
1211
self.assertEqual(rejection_msg_unicode, err.msg)
1213
client.finished_test()
1216
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1218
def test_set_last_revision_info(self):
1219
# set_last_revision_info(num, 'rev-id') is translated to calling
1220
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1221
transport = MemoryTransport()
1222
transport.mkdir('branch')
1223
transport = transport.clone('branch')
1224
client = FakeClient(transport.base)
1225
# get_stacked_on_url
1226
client.add_error_response('NotStacked')
1228
client.add_success_response('ok', 'branch token', 'repo token')
1229
# query the current revision
1230
client.add_success_response('ok', '0', 'null:')
1232
client.add_success_response('ok')
1234
client.add_success_response('ok')
1236
branch = self.make_remote_branch(transport, client)
1237
# Lock the branch, reset the record of remote calls.
1240
result = branch.set_last_revision_info(1234, 'a-revision-id')
1242
[('call', 'Branch.last_revision_info', ('branch/',)),
1243
('call', 'Branch.set_last_revision_info',
1244
('branch/', 'branch token', 'repo token',
1245
'1234', 'a-revision-id'))],
1247
self.assertEqual(None, result)
1249
def test_no_such_revision(self):
1250
# A response of 'NoSuchRevision' is translated into an exception.
1251
transport = MemoryTransport()
1252
transport.mkdir('branch')
1253
transport = transport.clone('branch')
1254
client = FakeClient(transport.base)
1255
# get_stacked_on_url
1256
client.add_error_response('NotStacked')
1258
client.add_success_response('ok', 'branch token', 'repo token')
1260
client.add_error_response('NoSuchRevision', 'revid')
1262
client.add_success_response('ok')
1264
branch = self.make_remote_branch(transport, client)
1265
# Lock the branch, reset the record of remote calls.
1270
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1273
def lock_remote_branch(self, branch):
1274
"""Trick a RemoteBranch into thinking it is locked."""
1275
branch._lock_mode = 'w'
1276
branch._lock_count = 2
1277
branch._lock_token = 'branch token'
1278
branch._repo_lock_token = 'repo token'
1279
branch.repository._lock_mode = 'w'
1280
branch.repository._lock_count = 2
1281
branch.repository._lock_token = 'repo token'
1283
def test_backwards_compatibility(self):
1284
"""If the server does not support the Branch.set_last_revision_info
1285
verb (which is new in 1.4), then the client falls back to VFS methods.
1287
# This test is a little messy. Unlike most tests in this file, it
1288
# doesn't purely test what a Remote* object sends over the wire, and
1289
# how it reacts to responses from the wire. It instead relies partly
1290
# on asserting that the RemoteBranch will call
1291
# self._real_branch.set_last_revision_info(...).
1293
# First, set up our RemoteBranch with a FakeClient that raises
1294
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1295
transport = MemoryTransport()
1296
transport.mkdir('branch')
1297
transport = transport.clone('branch')
1298
client = FakeClient(transport.base)
1299
client.add_expected_call(
1300
'Branch.get_stacked_on_url', ('branch/',),
1301
'error', ('NotStacked',))
1302
client.add_expected_call(
1303
'Branch.last_revision_info',
1305
'success', ('ok', '0', 'null:'))
1306
client.add_expected_call(
1307
'Branch.set_last_revision_info',
1308
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1309
'unknown', 'Branch.set_last_revision_info')
1311
branch = self.make_remote_branch(transport, client)
1312
class StubRealBranch(object):
1315
def set_last_revision_info(self, revno, revision_id):
1317
('set_last_revision_info', revno, revision_id))
1318
def _clear_cached_state(self):
1320
real_branch = StubRealBranch()
1321
branch._real_branch = real_branch
1322
self.lock_remote_branch(branch)
1324
# Call set_last_revision_info, and verify it behaved as expected.
1325
result = branch.set_last_revision_info(1234, 'a-revision-id')
1327
[('set_last_revision_info', 1234, 'a-revision-id')],
1329
client.finished_test()
1331
def test_unexpected_error(self):
1332
# If the server sends an error the client doesn't understand, it gets
1333
# turned into an UnknownErrorFromSmartServer, which is presented as a
1334
# non-internal error to the user.
1335
transport = MemoryTransport()
1336
transport.mkdir('branch')
1337
transport = transport.clone('branch')
1338
client = FakeClient(transport.base)
1339
# get_stacked_on_url
1340
client.add_error_response('NotStacked')
1342
client.add_success_response('ok', 'branch token', 'repo token')
1344
client.add_error_response('UnexpectedError')
1346
client.add_success_response('ok')
1348
branch = self.make_remote_branch(transport, client)
1349
# Lock the branch, reset the record of remote calls.
1353
err = self.assertRaises(
1354
errors.UnknownErrorFromSmartServer,
1355
branch.set_last_revision_info, 123, 'revid')
1356
self.assertEqual(('UnexpectedError',), err.error_tuple)
1359
def test_tip_change_rejected(self):
1360
"""TipChangeRejected responses cause a TipChangeRejected exception to
1363
transport = MemoryTransport()
1364
transport.mkdir('branch')
1365
transport = transport.clone('branch')
1366
client = FakeClient(transport.base)
1367
# get_stacked_on_url
1368
client.add_error_response('NotStacked')
1370
client.add_success_response('ok', 'branch token', 'repo token')
1372
client.add_error_response('TipChangeRejected', 'rejection message')
1374
client.add_success_response('ok')
1376
branch = self.make_remote_branch(transport, client)
1377
# Lock the branch, reset the record of remote calls.
1379
self.addCleanup(branch.unlock)
1382
# The 'TipChangeRejected' error response triggered by calling
1383
# set_last_revision_info causes a TipChangeRejected exception.
1384
err = self.assertRaises(
1385
errors.TipChangeRejected,
1386
branch.set_last_revision_info, 123, 'revid')
1387
self.assertEqual('rejection message', err.msg)
1390
class TestBranchGetSetConfig(RemoteBranchTestCase):
1392
def test_get_branch_conf(self):
1393
# in an empty branch we decode the response properly
1394
client = FakeClient()
1395
client.add_expected_call(
1396
'Branch.get_stacked_on_url', ('memory:///',),
1397
'error', ('NotStacked',),)
1398
client.add_success_response_with_body('# config file body', 'ok')
1399
transport = MemoryTransport()
1400
branch = self.make_remote_branch(transport, client)
1401
config = branch.get_config()
1402
config.has_explicit_nickname()
1404
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1405
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1408
def test_get_multi_line_branch_conf(self):
1409
# Make sure that multiple-line branch.conf files are supported
1411
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1412
client = FakeClient()
1413
client.add_expected_call(
1414
'Branch.get_stacked_on_url', ('memory:///',),
1415
'error', ('NotStacked',),)
1416
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1417
transport = MemoryTransport()
1418
branch = self.make_remote_branch(transport, client)
1419
config = branch.get_config()
1420
self.assertEqual(u'2', config.get_user_option('b'))
1422
def test_set_option(self):
1423
client = FakeClient()
1424
client.add_expected_call(
1425
'Branch.get_stacked_on_url', ('memory:///',),
1426
'error', ('NotStacked',),)
1427
client.add_expected_call(
1428
'Branch.lock_write', ('memory:///', '', ''),
1429
'success', ('ok', 'branch token', 'repo token'))
1430
client.add_expected_call(
1431
'Branch.set_config_option', ('memory:///', 'branch token',
1432
'repo token', 'foo', 'bar', ''),
1434
client.add_expected_call(
1435
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1437
transport = MemoryTransport()
1438
branch = self.make_remote_branch(transport, client)
1440
config = branch._get_config()
1441
config.set_option('foo', 'bar')
1443
client.finished_test()
1445
def test_backwards_compat_set_option(self):
1446
self.setup_smart_server_with_call_log()
1447
branch = self.make_branch('.')
1448
verb = 'Branch.set_config_option'
1449
self.disable_verb(verb)
1451
self.addCleanup(branch.unlock)
1452
self.reset_smart_call_log()
1453
branch._get_config().set_option('value', 'name')
1454
self.assertLength(10, self.hpss_calls)
1455
self.assertEqual('value', branch._get_config().get_option('name'))
1458
class TestBranchLockWrite(RemoteBranchTestCase):
1460
def test_lock_write_unlockable(self):
1461
transport = MemoryTransport()
1462
client = FakeClient(transport.base)
1463
client.add_expected_call(
1464
'Branch.get_stacked_on_url', ('quack/',),
1465
'error', ('NotStacked',),)
1466
client.add_expected_call(
1467
'Branch.lock_write', ('quack/', '', ''),
1468
'error', ('UnlockableTransport',))
1469
transport.mkdir('quack')
1470
transport = transport.clone('quack')
1471
branch = self.make_remote_branch(transport, client)
1472
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1473
client.finished_test()
1476
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1478
def test__get_config(self):
1479
client = FakeClient()
1480
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1481
transport = MemoryTransport()
1482
bzrdir = self.make_remote_bzrdir(transport, client)
1483
config = bzrdir.get_config()
1484
self.assertEqual('/', config.get_default_stack_on())
1486
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1489
def test_set_option_uses_vfs(self):
1490
self.setup_smart_server_with_call_log()
1491
bzrdir = self.make_bzrdir('.')
1492
self.reset_smart_call_log()
1493
config = bzrdir.get_config()
1494
config.set_default_stack_on('/')
1495
self.assertLength(3, self.hpss_calls)
1497
def test_backwards_compat_get_option(self):
1498
self.setup_smart_server_with_call_log()
1499
bzrdir = self.make_bzrdir('.')
1500
verb = 'BzrDir.get_config_file'
1501
self.disable_verb(verb)
1502
self.reset_smart_call_log()
1503
self.assertEqual(None,
1504
bzrdir._get_config().get_option('default_stack_on'))
1505
self.assertLength(3, self.hpss_calls)
1508
class TestTransportIsReadonly(tests.TestCase):
1510
def test_true(self):
1511
client = FakeClient()
1512
client.add_success_response('yes')
1513
transport = RemoteTransport('bzr://example.com/', medium=False,
1515
self.assertEqual(True, transport.is_readonly())
1517
[('call', 'Transport.is_readonly', ())],
1520
def test_false(self):
1521
client = FakeClient()
1522
client.add_success_response('no')
1523
transport = RemoteTransport('bzr://example.com/', medium=False,
1525
self.assertEqual(False, transport.is_readonly())
1527
[('call', 'Transport.is_readonly', ())],
1530
def test_error_from_old_server(self):
1531
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1533
Clients should treat it as a "no" response, because is_readonly is only
1534
advisory anyway (a transport could be read-write, but then the
1535
underlying filesystem could be readonly anyway).
1537
client = FakeClient()
1538
client.add_unknown_method_response('Transport.is_readonly')
1539
transport = RemoteTransport('bzr://example.com/', medium=False,
1541
self.assertEqual(False, transport.is_readonly())
1543
[('call', 'Transport.is_readonly', ())],
1547
class TestTransportMkdir(tests.TestCase):
1549
def test_permissiondenied(self):
1550
client = FakeClient()
1551
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1552
transport = RemoteTransport('bzr://example.com/', medium=False,
1554
exc = self.assertRaises(
1555
errors.PermissionDenied, transport.mkdir, 'client path')
1556
expected_error = errors.PermissionDenied('/client path', 'extra')
1557
self.assertEqual(expected_error, exc)
1560
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1562
def test_defaults_to_none(self):
1563
t = RemoteSSHTransport('bzr+ssh://example.com')
1564
self.assertIs(None, t._get_credentials()[0])
1566
def test_uses_authentication_config(self):
1567
conf = config.AuthenticationConfig()
1568
conf._get_config().update(
1569
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1572
t = RemoteSSHTransport('bzr+ssh://example.com')
1573
self.assertEqual('bar', t._get_credentials()[0])
1576
class TestRemoteRepository(TestRemote):
1577
"""Base for testing RemoteRepository protocol usage.
1579
These tests contain frozen requests and responses. We want any changes to
1580
what is sent or expected to be require a thoughtful update to these tests
1581
because they might break compatibility with different-versioned servers.
1584
def setup_fake_client_and_repository(self, transport_path):
1585
"""Create the fake client and repository for testing with.
1587
There's no real server here; we just have canned responses sent
1590
:param transport_path: Path below the root of the MemoryTransport
1591
where the repository will be created.
1593
transport = MemoryTransport()
1594
transport.mkdir(transport_path)
1595
client = FakeClient(transport.base)
1596
transport = transport.clone(transport_path)
1597
# we do not want bzrdir to make any remote calls
1598
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1600
repo = RemoteRepository(bzrdir, None, _client=client)
1604
class TestRepositoryFormat(TestRemoteRepository):
1606
def test_fast_delta(self):
1607
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1608
true_format = RemoteRepositoryFormat()
1609
true_format._network_name = true_name
1610
self.assertEqual(True, true_format.fast_deltas)
1611
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1612
false_format = RemoteRepositoryFormat()
1613
false_format._network_name = false_name
1614
self.assertEqual(False, false_format.fast_deltas)
1617
class TestRepositoryGatherStats(TestRemoteRepository):
1619
def test_revid_none(self):
1620
# ('ok',), body with revisions and size
1621
transport_path = 'quack'
1622
repo, client = self.setup_fake_client_and_repository(transport_path)
1623
client.add_success_response_with_body(
1624
'revisions: 2\nsize: 18\n', 'ok')
1625
result = repo.gather_stats(None)
1627
[('call_expecting_body', 'Repository.gather_stats',
1628
('quack/','','no'))],
1630
self.assertEqual({'revisions': 2, 'size': 18}, result)
1632
def test_revid_no_committers(self):
1633
# ('ok',), body without committers
1634
body = ('firstrev: 123456.300 3600\n'
1635
'latestrev: 654231.400 0\n'
1638
transport_path = 'quick'
1639
revid = u'\xc8'.encode('utf8')
1640
repo, client = self.setup_fake_client_and_repository(transport_path)
1641
client.add_success_response_with_body(body, 'ok')
1642
result = repo.gather_stats(revid)
1644
[('call_expecting_body', 'Repository.gather_stats',
1645
('quick/', revid, 'no'))],
1647
self.assertEqual({'revisions': 2, 'size': 18,
1648
'firstrev': (123456.300, 3600),
1649
'latestrev': (654231.400, 0),},
1652
def test_revid_with_committers(self):
1653
# ('ok',), body with committers
1654
body = ('committers: 128\n'
1655
'firstrev: 123456.300 3600\n'
1656
'latestrev: 654231.400 0\n'
1659
transport_path = 'buick'
1660
revid = u'\xc8'.encode('utf8')
1661
repo, client = self.setup_fake_client_and_repository(transport_path)
1662
client.add_success_response_with_body(body, 'ok')
1663
result = repo.gather_stats(revid, True)
1665
[('call_expecting_body', 'Repository.gather_stats',
1666
('buick/', revid, 'yes'))],
1668
self.assertEqual({'revisions': 2, 'size': 18,
1670
'firstrev': (123456.300, 3600),
1671
'latestrev': (654231.400, 0),},
1675
class TestRepositoryGetGraph(TestRemoteRepository):
1677
def test_get_graph(self):
1678
# get_graph returns a graph with a custom parents provider.
1679
transport_path = 'quack'
1680
repo, client = self.setup_fake_client_and_repository(transport_path)
1681
graph = repo.get_graph()
1682
self.assertNotEqual(graph._parents_provider, repo)
1685
class TestRepositoryGetParentMap(TestRemoteRepository):
1687
def test_get_parent_map_caching(self):
1688
# get_parent_map returns from cache until unlock()
1689
# setup a reponse with two revisions
1690
r1 = u'\u0e33'.encode('utf8')
1691
r2 = u'\u0dab'.encode('utf8')
1692
lines = [' '.join([r2, r1]), r1]
1693
encoded_body = bz2.compress('\n'.join(lines))
1695
transport_path = 'quack'
1696
repo, client = self.setup_fake_client_and_repository(transport_path)
1697
client.add_success_response_with_body(encoded_body, 'ok')
1698
client.add_success_response_with_body(encoded_body, 'ok')
1700
graph = repo.get_graph()
1701
parents = graph.get_parent_map([r2])
1702
self.assertEqual({r2: (r1,)}, parents)
1703
# locking and unlocking deeper should not reset
1706
parents = graph.get_parent_map([r1])
1707
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1709
[('call_with_body_bytes_expecting_body',
1710
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1714
# now we call again, and it should use the second response.
1716
graph = repo.get_graph()
1717
parents = graph.get_parent_map([r1])
1718
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1720
[('call_with_body_bytes_expecting_body',
1721
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1723
('call_with_body_bytes_expecting_body',
1724
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1730
def test_get_parent_map_reconnects_if_unknown_method(self):
1731
transport_path = 'quack'
1732
rev_id = 'revision-id'
1733
repo, client = self.setup_fake_client_and_repository(transport_path)
1734
client.add_unknown_method_response('Repository.get_parent_map')
1735
client.add_success_response_with_body(rev_id, 'ok')
1736
self.assertFalse(client._medium._is_remote_before((1, 2)))
1737
parents = repo.get_parent_map([rev_id])
1739
[('call_with_body_bytes_expecting_body',
1740
'Repository.get_parent_map', ('quack/', 'include-missing:',
1742
('disconnect medium',),
1743
('call_expecting_body', 'Repository.get_revision_graph',
1746
# The medium is now marked as being connected to an older server
1747
self.assertTrue(client._medium._is_remote_before((1, 2)))
1748
self.assertEqual({rev_id: ('null:',)}, parents)
1750
def test_get_parent_map_fallback_parentless_node(self):
1751
"""get_parent_map falls back to get_revision_graph on old servers. The
1752
results from get_revision_graph are tweaked to match the get_parent_map
1755
Specifically, a {key: ()} result from get_revision_graph means "no
1756
parents" for that key, which in get_parent_map results should be
1757
represented as {key: ('null:',)}.
1759
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1761
rev_id = 'revision-id'
1762
transport_path = 'quack'
1763
repo, client = self.setup_fake_client_and_repository(transport_path)
1764
client.add_success_response_with_body(rev_id, 'ok')
1765
client._medium._remember_remote_is_before((1, 2))
1766
parents = repo.get_parent_map([rev_id])
1768
[('call_expecting_body', 'Repository.get_revision_graph',
1771
self.assertEqual({rev_id: ('null:',)}, parents)
1773
def test_get_parent_map_unexpected_response(self):
1774
repo, client = self.setup_fake_client_and_repository('path')
1775
client.add_success_response('something unexpected!')
1777
errors.UnexpectedSmartServerResponse,
1778
repo.get_parent_map, ['a-revision-id'])
1780
def test_get_parent_map_negative_caches_missing_keys(self):
1781
self.setup_smart_server_with_call_log()
1782
repo = self.make_repository('foo')
1783
self.assertIsInstance(repo, RemoteRepository)
1785
self.addCleanup(repo.unlock)
1786
self.reset_smart_call_log()
1787
graph = repo.get_graph()
1788
self.assertEqual({},
1789
graph.get_parent_map(['some-missing', 'other-missing']))
1790
self.assertLength(1, self.hpss_calls)
1791
# No call if we repeat this
1792
self.reset_smart_call_log()
1793
graph = repo.get_graph()
1794
self.assertEqual({},
1795
graph.get_parent_map(['some-missing', 'other-missing']))
1796
self.assertLength(0, self.hpss_calls)
1797
# Asking for more unknown keys makes a request.
1798
self.reset_smart_call_log()
1799
graph = repo.get_graph()
1800
self.assertEqual({},
1801
graph.get_parent_map(['some-missing', 'other-missing',
1803
self.assertLength(1, self.hpss_calls)
1805
def disableExtraResults(self):
1806
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1807
SmartServerRepositoryGetParentMap.no_extra_results = True
1809
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1810
self.addCleanup(reset_values)
1812
def test_null_cached_missing_and_stop_key(self):
1813
self.setup_smart_server_with_call_log()
1814
# Make a branch with a single revision.
1815
builder = self.make_branch_builder('foo')
1816
builder.start_series()
1817
builder.build_snapshot('first', None, [
1818
('add', ('', 'root-id', 'directory', ''))])
1819
builder.finish_series()
1820
branch = builder.get_branch()
1821
repo = branch.repository
1822
self.assertIsInstance(repo, RemoteRepository)
1823
# Stop the server from sending extra results.
1824
self.disableExtraResults()
1826
self.addCleanup(repo.unlock)
1827
self.reset_smart_call_log()
1828
graph = repo.get_graph()
1829
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1830
# 'first' it will be a candidate for the stop_keys of subsequent
1831
# requests, and because 'null:' was queried but not returned it will be
1832
# cached as missing.
1833
self.assertEqual({'first': ('null:',)},
1834
graph.get_parent_map(['first', 'null:']))
1835
# Now query for another key. This request will pass along a recipe of
1836
# start and stop keys describing the already cached results, and this
1837
# recipe's revision count must be correct (or else it will trigger an
1838
# error from the server).
1839
self.assertEqual({}, graph.get_parent_map(['another-key']))
1840
# This assertion guards against disableExtraResults silently failing to
1841
# work, thus invalidating the test.
1842
self.assertLength(2, self.hpss_calls)
1844
def test_get_parent_map_gets_ghosts_from_result(self):
1845
# asking for a revision should negatively cache close ghosts in its
1847
self.setup_smart_server_with_call_log()
1848
tree = self.make_branch_and_memory_tree('foo')
1851
builder = treebuilder.TreeBuilder()
1852
builder.start_tree(tree)
1854
builder.finish_tree()
1855
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1856
rev_id = tree.commit('')
1860
self.addCleanup(tree.unlock)
1861
repo = tree.branch.repository
1862
self.assertIsInstance(repo, RemoteRepository)
1864
repo.get_parent_map([rev_id])
1865
self.reset_smart_call_log()
1866
# Now asking for rev_id's ghost parent should not make calls
1867
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1868
self.assertLength(0, self.hpss_calls)
1871
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1873
def test_allows_new_revisions(self):
1874
"""get_parent_map's results can be updated by commit."""
1875
smart_server = server.SmartTCPServer_for_testing()
1876
smart_server.setUp()
1877
self.addCleanup(smart_server.tearDown)
1878
self.make_branch('branch')
1879
branch = Branch.open(smart_server.get_url() + '/branch')
1880
tree = branch.create_checkout('tree', lightweight=True)
1882
self.addCleanup(tree.unlock)
1883
graph = tree.branch.repository.get_graph()
1884
# This provides an opportunity for the missing rev-id to be cached.
1885
self.assertEqual({}, graph.get_parent_map(['rev1']))
1886
tree.commit('message', rev_id='rev1')
1887
graph = tree.branch.repository.get_graph()
1888
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1891
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1893
def test_null_revision(self):
1894
# a null revision has the predictable result {}, we should have no wire
1895
# traffic when calling it with this argument
1896
transport_path = 'empty'
1897
repo, client = self.setup_fake_client_and_repository(transport_path)
1898
client.add_success_response('notused')
1899
# actual RemoteRepository.get_revision_graph is gone, but there's an
1900
# equivalent private method for testing
1901
result = repo._get_revision_graph(NULL_REVISION)
1902
self.assertEqual([], client._calls)
1903
self.assertEqual({}, result)
1905
def test_none_revision(self):
1906
# with none we want the entire graph
1907
r1 = u'\u0e33'.encode('utf8')
1908
r2 = u'\u0dab'.encode('utf8')
1909
lines = [' '.join([r2, r1]), r1]
1910
encoded_body = '\n'.join(lines)
1912
transport_path = 'sinhala'
1913
repo, client = self.setup_fake_client_and_repository(transport_path)
1914
client.add_success_response_with_body(encoded_body, 'ok')
1915
# actual RemoteRepository.get_revision_graph is gone, but there's an
1916
# equivalent private method for testing
1917
result = repo._get_revision_graph(None)
1919
[('call_expecting_body', 'Repository.get_revision_graph',
1922
self.assertEqual({r1: (), r2: (r1, )}, result)
1924
def test_specific_revision(self):
1925
# with a specific revision we want the graph for that
1926
# with none we want the entire graph
1927
r11 = u'\u0e33'.encode('utf8')
1928
r12 = u'\xc9'.encode('utf8')
1929
r2 = u'\u0dab'.encode('utf8')
1930
lines = [' '.join([r2, r11, r12]), r11, r12]
1931
encoded_body = '\n'.join(lines)
1933
transport_path = 'sinhala'
1934
repo, client = self.setup_fake_client_and_repository(transport_path)
1935
client.add_success_response_with_body(encoded_body, 'ok')
1936
result = repo._get_revision_graph(r2)
1938
[('call_expecting_body', 'Repository.get_revision_graph',
1941
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1943
def test_no_such_revision(self):
1945
transport_path = 'sinhala'
1946
repo, client = self.setup_fake_client_and_repository(transport_path)
1947
client.add_error_response('nosuchrevision', revid)
1948
# also check that the right revision is reported in the error
1949
self.assertRaises(errors.NoSuchRevision,
1950
repo._get_revision_graph, revid)
1952
[('call_expecting_body', 'Repository.get_revision_graph',
1953
('sinhala/', revid))],
1956
def test_unexpected_error(self):
1958
transport_path = 'sinhala'
1959
repo, client = self.setup_fake_client_and_repository(transport_path)
1960
client.add_error_response('AnUnexpectedError')
1961
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1962
repo._get_revision_graph, revid)
1963
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1966
class TestRepositoryIsShared(TestRemoteRepository):
1968
def test_is_shared(self):
1969
# ('yes', ) for Repository.is_shared -> 'True'.
1970
transport_path = 'quack'
1971
repo, client = self.setup_fake_client_and_repository(transport_path)
1972
client.add_success_response('yes')
1973
result = repo.is_shared()
1975
[('call', 'Repository.is_shared', ('quack/',))],
1977
self.assertEqual(True, result)
1979
def test_is_not_shared(self):
1980
# ('no', ) for Repository.is_shared -> 'False'.
1981
transport_path = 'qwack'
1982
repo, client = self.setup_fake_client_and_repository(transport_path)
1983
client.add_success_response('no')
1984
result = repo.is_shared()
1986
[('call', 'Repository.is_shared', ('qwack/',))],
1988
self.assertEqual(False, result)
1991
class TestRepositoryLockWrite(TestRemoteRepository):
1993
def test_lock_write(self):
1994
transport_path = 'quack'
1995
repo, client = self.setup_fake_client_and_repository(transport_path)
1996
client.add_success_response('ok', 'a token')
1997
result = repo.lock_write()
1999
[('call', 'Repository.lock_write', ('quack/', ''))],
2001
self.assertEqual('a token', result)
2003
def test_lock_write_already_locked(self):
2004
transport_path = 'quack'
2005
repo, client = self.setup_fake_client_and_repository(transport_path)
2006
client.add_error_response('LockContention')
2007
self.assertRaises(errors.LockContention, repo.lock_write)
2009
[('call', 'Repository.lock_write', ('quack/', ''))],
2012
def test_lock_write_unlockable(self):
2013
transport_path = 'quack'
2014
repo, client = self.setup_fake_client_and_repository(transport_path)
2015
client.add_error_response('UnlockableTransport')
2016
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2018
[('call', 'Repository.lock_write', ('quack/', ''))],
2022
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2024
def test_backwards_compat(self):
2025
self.setup_smart_server_with_call_log()
2026
repo = self.make_repository('.')
2027
self.reset_smart_call_log()
2028
verb = 'Repository.set_make_working_trees'
2029
self.disable_verb(verb)
2030
repo.set_make_working_trees(True)
2031
call_count = len([call for call in self.hpss_calls if
2032
call.call.method == verb])
2033
self.assertEqual(1, call_count)
2035
def test_current(self):
2036
transport_path = 'quack'
2037
repo, client = self.setup_fake_client_and_repository(transport_path)
2038
client.add_expected_call(
2039
'Repository.set_make_working_trees', ('quack/', 'True'),
2041
client.add_expected_call(
2042
'Repository.set_make_working_trees', ('quack/', 'False'),
2044
repo.set_make_working_trees(True)
2045
repo.set_make_working_trees(False)
2048
class TestRepositoryUnlock(TestRemoteRepository):
2050
def test_unlock(self):
2051
transport_path = 'quack'
2052
repo, client = self.setup_fake_client_and_repository(transport_path)
2053
client.add_success_response('ok', 'a token')
2054
client.add_success_response('ok')
2058
[('call', 'Repository.lock_write', ('quack/', '')),
2059
('call', 'Repository.unlock', ('quack/', 'a token'))],
2062
def test_unlock_wrong_token(self):
2063
# If somehow the token is wrong, unlock will raise TokenMismatch.
2064
transport_path = 'quack'
2065
repo, client = self.setup_fake_client_and_repository(transport_path)
2066
client.add_success_response('ok', 'a token')
2067
client.add_error_response('TokenMismatch')
2069
self.assertRaises(errors.TokenMismatch, repo.unlock)
2072
class TestRepositoryHasRevision(TestRemoteRepository):
2074
def test_none(self):
2075
# repo.has_revision(None) should not cause any traffic.
2076
transport_path = 'quack'
2077
repo, client = self.setup_fake_client_and_repository(transport_path)
2079
# The null revision is always there, so has_revision(None) == True.
2080
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2082
# The remote repo shouldn't be accessed.
2083
self.assertEqual([], client._calls)
2086
class TestRepositoryInsertStream(TestRemoteRepository):
2088
def test_unlocked_repo(self):
2089
transport_path = 'quack'
2090
repo, client = self.setup_fake_client_and_repository(transport_path)
2091
client.add_expected_call(
2092
'Repository.insert_stream', ('quack/', ''),
2094
client.add_expected_call(
2095
'Repository.insert_stream', ('quack/', ''),
2097
sink = repo._get_sink()
2098
fmt = repository.RepositoryFormat.get_default_format()
2099
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2100
self.assertEqual([], resume_tokens)
2101
self.assertEqual(set(), missing_keys)
2102
client.finished_test()
2104
def test_locked_repo_with_no_lock_token(self):
2105
transport_path = 'quack'
2106
repo, client = self.setup_fake_client_and_repository(transport_path)
2107
client.add_expected_call(
2108
'Repository.lock_write', ('quack/', ''),
2109
'success', ('ok', ''))
2110
client.add_expected_call(
2111
'Repository.insert_stream', ('quack/', ''),
2113
client.add_expected_call(
2114
'Repository.insert_stream', ('quack/', ''),
2117
sink = repo._get_sink()
2118
fmt = repository.RepositoryFormat.get_default_format()
2119
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2120
self.assertEqual([], resume_tokens)
2121
self.assertEqual(set(), missing_keys)
2122
client.finished_test()
2124
def test_locked_repo_with_lock_token(self):
2125
transport_path = 'quack'
2126
repo, client = self.setup_fake_client_and_repository(transport_path)
2127
client.add_expected_call(
2128
'Repository.lock_write', ('quack/', ''),
2129
'success', ('ok', 'a token'))
2130
client.add_expected_call(
2131
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2133
client.add_expected_call(
2134
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2137
sink = repo._get_sink()
2138
fmt = repository.RepositoryFormat.get_default_format()
2139
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2140
self.assertEqual([], resume_tokens)
2141
self.assertEqual(set(), missing_keys)
2142
client.finished_test()
2145
class TestRepositoryTarball(TestRemoteRepository):
2147
# This is a canned tarball reponse we can validate against
2149
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2150
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2151
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2152
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2153
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2154
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2155
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2156
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2157
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2158
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2159
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2160
'nWQ7QH/F3JFOFCQ0aSPfA='
2163
def test_repository_tarball(self):
2164
# Test that Repository.tarball generates the right operations
2165
transport_path = 'repo'
2166
expected_calls = [('call_expecting_body', 'Repository.tarball',
2167
('repo/', 'bz2',),),
2169
repo, client = self.setup_fake_client_and_repository(transport_path)
2170
client.add_success_response_with_body(self.tarball_content, 'ok')
2171
# Now actually ask for the tarball
2172
tarball_file = repo._get_tarball('bz2')
2174
self.assertEqual(expected_calls, client._calls)
2175
self.assertEqual(self.tarball_content, tarball_file.read())
2177
tarball_file.close()
2180
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2181
"""RemoteRepository.copy_content_into optimizations"""
2183
def test_copy_content_remote_to_local(self):
2184
self.transport_server = server.SmartTCPServer_for_testing
2185
src_repo = self.make_repository('repo1')
2186
src_repo = repository.Repository.open(self.get_url('repo1'))
2187
# At the moment the tarball-based copy_content_into can't write back
2188
# into a smart server. It would be good if it could upload the
2189
# tarball; once that works we'd have to create repositories of
2190
# different formats. -- mbp 20070410
2191
dest_url = self.get_vfs_only_url('repo2')
2192
dest_bzrdir = BzrDir.create(dest_url)
2193
dest_repo = dest_bzrdir.create_repository()
2194
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2195
self.assertTrue(isinstance(src_repo, RemoteRepository))
2196
src_repo.copy_content_into(dest_repo)
2199
class _StubRealPackRepository(object):
2201
def __init__(self, calls):
2203
self._pack_collection = _StubPackCollection(calls)
2205
def is_in_write_group(self):
2208
def refresh_data(self):
2209
self.calls.append(('pack collection reload_pack_names',))
2212
class _StubPackCollection(object):
2214
def __init__(self, calls):
2218
self.calls.append(('pack collection autopack',))
2221
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2222
"""Tests for RemoteRepository.autopack implementation."""
2225
"""When the server returns 'ok' and there's no _real_repository, then
2226
nothing else happens: the autopack method is done.
2228
transport_path = 'quack'
2229
repo, client = self.setup_fake_client_and_repository(transport_path)
2230
client.add_expected_call(
2231
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2233
client.finished_test()
2235
def test_ok_with_real_repo(self):
2236
"""When the server returns 'ok' and there is a _real_repository, then
2237
the _real_repository's reload_pack_name's method will be called.
2239
transport_path = 'quack'
2240
repo, client = self.setup_fake_client_and_repository(transport_path)
2241
client.add_expected_call(
2242
'PackRepository.autopack', ('quack/',),
2244
repo._real_repository = _StubRealPackRepository(client._calls)
2247
[('call', 'PackRepository.autopack', ('quack/',)),
2248
('pack collection reload_pack_names',)],
2251
def test_backwards_compatibility(self):
2252
"""If the server does not recognise the PackRepository.autopack verb,
2253
fallback to the real_repository's implementation.
2255
transport_path = 'quack'
2256
repo, client = self.setup_fake_client_and_repository(transport_path)
2257
client.add_unknown_method_response('PackRepository.autopack')
2258
def stub_ensure_real():
2259
client._calls.append(('_ensure_real',))
2260
repo._real_repository = _StubRealPackRepository(client._calls)
2261
repo._ensure_real = stub_ensure_real
2264
[('call', 'PackRepository.autopack', ('quack/',)),
2266
('pack collection autopack',)],
2270
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2271
"""Base class for unit tests for bzrlib.remote._translate_error."""
2273
def translateTuple(self, error_tuple, **context):
2274
"""Call _translate_error with an ErrorFromSmartServer built from the
2277
:param error_tuple: A tuple of a smart server response, as would be
2278
passed to an ErrorFromSmartServer.
2279
:kwargs context: context items to call _translate_error with.
2281
:returns: The error raised by _translate_error.
2283
# Raise the ErrorFromSmartServer before passing it as an argument,
2284
# because _translate_error may need to re-raise it with a bare 'raise'
2286
server_error = errors.ErrorFromSmartServer(error_tuple)
2287
translated_error = self.translateErrorFromSmartServer(
2288
server_error, **context)
2289
return translated_error
2291
def translateErrorFromSmartServer(self, error_object, **context):
2292
"""Like translateTuple, but takes an already constructed
2293
ErrorFromSmartServer rather than a tuple.
2297
except errors.ErrorFromSmartServer, server_error:
2298
translated_error = self.assertRaises(
2299
errors.BzrError, remote._translate_error, server_error,
2301
return translated_error
2304
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2305
"""Unit tests for bzrlib.remote._translate_error.
2307
Given an ErrorFromSmartServer (which has an error tuple from a smart
2308
server) and some context, _translate_error raises more specific errors from
2311
This test case covers the cases where _translate_error succeeds in
2312
translating an ErrorFromSmartServer to something better. See
2313
TestErrorTranslationRobustness for other cases.
2316
def test_NoSuchRevision(self):
2317
branch = self.make_branch('')
2319
translated_error = self.translateTuple(
2320
('NoSuchRevision', revid), branch=branch)
2321
expected_error = errors.NoSuchRevision(branch, revid)
2322
self.assertEqual(expected_error, translated_error)
2324
def test_nosuchrevision(self):
2325
repository = self.make_repository('')
2327
translated_error = self.translateTuple(
2328
('nosuchrevision', revid), repository=repository)
2329
expected_error = errors.NoSuchRevision(repository, revid)
2330
self.assertEqual(expected_error, translated_error)
2332
def test_nobranch(self):
2333
bzrdir = self.make_bzrdir('')
2334
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2335
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2336
self.assertEqual(expected_error, translated_error)
2338
def test_LockContention(self):
2339
translated_error = self.translateTuple(('LockContention',))
2340
expected_error = errors.LockContention('(remote lock)')
2341
self.assertEqual(expected_error, translated_error)
2343
def test_UnlockableTransport(self):
2344
bzrdir = self.make_bzrdir('')
2345
translated_error = self.translateTuple(
2346
('UnlockableTransport',), bzrdir=bzrdir)
2347
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2348
self.assertEqual(expected_error, translated_error)
2350
def test_LockFailed(self):
2351
lock = 'str() of a server lock'
2352
why = 'str() of why'
2353
translated_error = self.translateTuple(('LockFailed', lock, why))
2354
expected_error = errors.LockFailed(lock, why)
2355
self.assertEqual(expected_error, translated_error)
2357
def test_TokenMismatch(self):
2358
token = 'a lock token'
2359
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2360
expected_error = errors.TokenMismatch(token, '(remote token)')
2361
self.assertEqual(expected_error, translated_error)
2363
def test_Diverged(self):
2364
branch = self.make_branch('a')
2365
other_branch = self.make_branch('b')
2366
translated_error = self.translateTuple(
2367
('Diverged',), branch=branch, other_branch=other_branch)
2368
expected_error = errors.DivergedBranches(branch, other_branch)
2369
self.assertEqual(expected_error, translated_error)
2371
def test_ReadError_no_args(self):
2373
translated_error = self.translateTuple(('ReadError',), path=path)
2374
expected_error = errors.ReadError(path)
2375
self.assertEqual(expected_error, translated_error)
2377
def test_ReadError(self):
2379
translated_error = self.translateTuple(('ReadError', path))
2380
expected_error = errors.ReadError(path)
2381
self.assertEqual(expected_error, translated_error)
2383
def test_PermissionDenied_no_args(self):
2385
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2386
expected_error = errors.PermissionDenied(path)
2387
self.assertEqual(expected_error, translated_error)
2389
def test_PermissionDenied_one_arg(self):
2391
translated_error = self.translateTuple(('PermissionDenied', path))
2392
expected_error = errors.PermissionDenied(path)
2393
self.assertEqual(expected_error, translated_error)
2395
def test_PermissionDenied_one_arg_and_context(self):
2396
"""Given a choice between a path from the local context and a path on
2397
the wire, _translate_error prefers the path from the local context.
2399
local_path = 'local path'
2400
remote_path = 'remote path'
2401
translated_error = self.translateTuple(
2402
('PermissionDenied', remote_path), path=local_path)
2403
expected_error = errors.PermissionDenied(local_path)
2404
self.assertEqual(expected_error, translated_error)
2406
def test_PermissionDenied_two_args(self):
2408
extra = 'a string with extra info'
2409
translated_error = self.translateTuple(
2410
('PermissionDenied', path, extra))
2411
expected_error = errors.PermissionDenied(path, extra)
2412
self.assertEqual(expected_error, translated_error)
2415
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2416
"""Unit tests for bzrlib.remote._translate_error's robustness.
2418
TestErrorTranslationSuccess is for cases where _translate_error can
2419
translate successfully. This class about how _translate_err behaves when
2420
it fails to translate: it re-raises the original error.
2423
def test_unrecognised_server_error(self):
2424
"""If the error code from the server is not recognised, the original
2425
ErrorFromSmartServer is propagated unmodified.
2427
error_tuple = ('An unknown error tuple',)
2428
server_error = errors.ErrorFromSmartServer(error_tuple)
2429
translated_error = self.translateErrorFromSmartServer(server_error)
2430
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2431
self.assertEqual(expected_error, translated_error)
2433
def test_context_missing_a_key(self):
2434
"""In case of a bug in the client, or perhaps an unexpected response
2435
from a server, _translate_error returns the original error tuple from
2436
the server and mutters a warning.
2438
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2439
# in the context dict. So let's give it an empty context dict instead
2440
# to exercise its error recovery.
2442
error_tuple = ('NoSuchRevision', 'revid')
2443
server_error = errors.ErrorFromSmartServer(error_tuple)
2444
translated_error = self.translateErrorFromSmartServer(server_error)
2445
self.assertEqual(server_error, translated_error)
2446
# In addition to re-raising ErrorFromSmartServer, some debug info has
2447
# been muttered to the log file for developer to look at.
2448
self.assertContainsRe(
2449
self._get_log(keep_log_file=True),
2450
"Missing key 'branch' in context")
2452
def test_path_missing(self):
2453
"""Some translations (PermissionDenied, ReadError) can determine the
2454
'path' variable from either the wire or the local context. If neither
2455
has it, then an error is raised.
2457
error_tuple = ('ReadError',)
2458
server_error = errors.ErrorFromSmartServer(error_tuple)
2459
translated_error = self.translateErrorFromSmartServer(server_error)
2460
self.assertEqual(server_error, translated_error)
2461
# In addition to re-raising ErrorFromSmartServer, some debug info has
2462
# been muttered to the log file for developer to look at.
2463
self.assertContainsRe(
2464
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2467
class TestStacking(tests.TestCaseWithTransport):
2468
"""Tests for operations on stacked remote repositories.
2470
The underlying format type must support stacking.
2473
def test_access_stacked_remote(self):
2474
# based on <http://launchpad.net/bugs/261315>
2475
# make a branch stacked on another repository containing an empty
2476
# revision, then open it over hpss - we should be able to see that
2478
base_transport = self.get_transport()
2479
base_builder = self.make_branch_builder('base', format='1.9')
2480
base_builder.start_series()
2481
base_revid = base_builder.build_snapshot('rev-id', None,
2482
[('add', ('', None, 'directory', None))],
2484
base_builder.finish_series()
2485
stacked_branch = self.make_branch('stacked', format='1.9')
2486
stacked_branch.set_stacked_on_url('../base')
2487
# start a server looking at this
2488
smart_server = server.SmartTCPServer_for_testing()
2489
smart_server.setUp()
2490
self.addCleanup(smart_server.tearDown)
2491
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2492
# can get its branch and repository
2493
remote_branch = remote_bzrdir.open_branch()
2494
remote_repo = remote_branch.repository
2495
remote_repo.lock_read()
2497
# it should have an appropriate fallback repository, which should also
2498
# be a RemoteRepository
2499
self.assertLength(1, remote_repo._fallback_repositories)
2500
self.assertIsInstance(remote_repo._fallback_repositories[0],
2502
# and it has the revision committed to the underlying repository;
2503
# these have varying implementations so we try several of them
2504
self.assertTrue(remote_repo.has_revisions([base_revid]))
2505
self.assertTrue(remote_repo.has_revision(base_revid))
2506
self.assertEqual(remote_repo.get_revision(base_revid).message,
2509
remote_repo.unlock()
2511
def prepare_stacked_remote_branch(self):
2512
"""Get stacked_upon and stacked branches with content in each."""
2513
self.setup_smart_server_with_call_log()
2514
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2515
tree1.commit('rev1', rev_id='rev1')
2516
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2517
).open_workingtree()
2518
tree2.commit('local changes make me feel good.')
2519
branch2 = Branch.open(self.get_url('tree2'))
2521
self.addCleanup(branch2.unlock)
2522
return tree1.branch, branch2
2524
def test_stacked_get_parent_map(self):
2525
# the public implementation of get_parent_map obeys stacking
2526
_, branch = self.prepare_stacked_remote_branch()
2527
repo = branch.repository
2528
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2530
def test_unstacked_get_parent_map(self):
2531
# _unstacked_provider.get_parent_map ignores stacking
2532
_, branch = self.prepare_stacked_remote_branch()
2533
provider = branch.repository._unstacked_provider
2534
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2536
def fetch_stream_to_rev_order(self, stream):
2538
for kind, substream in stream:
2539
if not kind == 'revisions':
2542
for content in substream:
2543
result.append(content.key[-1])
2546
def get_ordered_revs(self, format, order):
2547
"""Get a list of the revisions in a stream to format format.
2549
:param format: The format of the target.
2550
:param order: the order that target should have requested.
2551
:result: The revision ids in the stream, in the order seen,
2552
the topological order of revisions in the source.
2554
unordered_format = bzrdir.format_registry.get(format)()
2555
target_repository_format = unordered_format.repository_format
2557
self.assertEqual(order, target_repository_format._fetch_order)
2558
trunk, stacked = self.prepare_stacked_remote_branch()
2559
source = stacked.repository._get_source(target_repository_format)
2560
tip = stacked.last_revision()
2561
revs = stacked.repository.get_ancestry(tip)
2562
search = graph.PendingAncestryResult([tip], stacked.repository)
2563
self.reset_smart_call_log()
2564
stream = source.get_stream(search)
2567
# We trust that if a revision is in the stream the rest of the new
2568
# content for it is too, as per our main fetch tests; here we are
2569
# checking that the revisions are actually included at all, and their
2571
return self.fetch_stream_to_rev_order(stream), revs
2573
def test_stacked_get_stream_unordered(self):
2574
# Repository._get_source.get_stream() from a stacked repository with
2575
# unordered yields the full data from both stacked and stacked upon
2577
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2578
self.assertEqual(set(expected_revs), set(rev_ord))
2579
# Getting unordered results should have made a streaming data request
2580
# from the server, then one from the backing branch.
2581
self.assertLength(2, self.hpss_calls)
2583
def test_stacked_get_stream_topological(self):
2584
# Repository._get_source.get_stream() from a stacked repository with
2585
# topological sorting yields the full data from both stacked and
2586
# stacked upon sources in topological order.
2587
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2588
self.assertEqual(expected_revs, rev_ord)
2589
# Getting topological sort requires VFS calls still
2590
self.assertLength(12, self.hpss_calls)
2592
def test_stacked_get_stream_groupcompress(self):
2593
# Repository._get_source.get_stream() from a stacked repository with
2594
# groupcompress sorting yields the full data from both stacked and
2595
# stacked upon sources in groupcompress order.
2596
raise tests.TestSkipped('No groupcompress ordered format available')
2597
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2598
self.assertEqual(expected_revs, reversed(rev_ord))
2599
# Getting unordered results should have made a streaming data request
2600
# from the backing branch, and one from the stacked on branch.
2601
self.assertLength(2, self.hpss_calls)
2604
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2607
super(TestRemoteBranchEffort, self).setUp()
2608
# Create a smart server that publishes whatever the backing VFS server
2610
self.smart_server = server.SmartTCPServer_for_testing()
2611
self.smart_server.setUp(self.get_server())
2612
self.addCleanup(self.smart_server.tearDown)
2613
# Log all HPSS calls into self.hpss_calls.
2614
_SmartClient.hooks.install_named_hook(
2615
'call', self.capture_hpss_call, None)
2616
self.hpss_calls = []
2618
def capture_hpss_call(self, params):
2619
self.hpss_calls.append(params.method)
2621
def test_copy_content_into_avoids_revision_history(self):
2622
local = self.make_branch('local')
2623
remote_backing_tree = self.make_branch_and_tree('remote')
2624
remote_backing_tree.commit("Commit.")
2625
remote_branch_url = self.smart_server.get_url() + 'remote'
2626
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2627
local.repository.fetch(remote_branch.repository)
2628
self.hpss_calls = []
2629
remote_branch.copy_content_into(local)
2630
self.assertFalse('Branch.revision_history' in self.hpss_calls)