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
43
from bzrlib.branch import Branch
44
from bzrlib.bzrdir import BzrDir, BzrDirFormat
45
from bzrlib.remote import (
51
RemoteRepositoryFormat,
53
from bzrlib.repofmt import groupcompress_repo, pack_repo
54
from bzrlib.revision import NULL_REVISION
55
from bzrlib.smart import server, medium
56
from bzrlib.smart.client import _SmartClient
57
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
58
from bzrlib.tests import (
60
split_suite_by_condition,
63
from bzrlib.transport import get_transport, http
64
from bzrlib.transport.memory import MemoryTransport
65
from bzrlib.transport.remote import (
71
def load_tests(standard_tests, module, loader):
72
to_adapt, result = split_suite_by_condition(
73
standard_tests, condition_isinstance(BasicRemoteObjectTests))
74
smart_server_version_scenarios = [
76
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
78
{'transport_server': server.SmartTCPServer_for_testing})]
79
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
82
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
85
super(BasicRemoteObjectTests, self).setUp()
86
self.transport = self.get_transport()
87
# make a branch that can be opened over the smart transport
88
self.local_wt = BzrDir.create_standalone_workingtree('.')
91
self.transport.disconnect()
92
tests.TestCaseWithTransport.tearDown(self)
94
def test_create_remote_bzrdir(self):
95
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
96
self.assertIsInstance(b, BzrDir)
98
def test_open_remote_branch(self):
99
# open a standalone branch in the working directory
100
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
101
branch = b.open_branch()
102
self.assertIsInstance(branch, Branch)
104
def test_remote_repository(self):
105
b = BzrDir.open_from_transport(self.transport)
106
repo = b.open_repository()
107
revid = u'\xc823123123'.encode('utf8')
108
self.assertFalse(repo.has_revision(revid))
109
self.local_wt.commit(message='test commit', rev_id=revid)
110
self.assertTrue(repo.has_revision(revid))
112
def test_remote_branch_revision_history(self):
113
b = BzrDir.open_from_transport(self.transport).open_branch()
114
self.assertEqual([], b.revision_history())
115
r1 = self.local_wt.commit('1st commit')
116
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
117
self.assertEqual([r1, r2], b.revision_history())
119
def test_find_correct_format(self):
120
"""Should open a RemoteBzrDir over a RemoteTransport"""
121
fmt = BzrDirFormat.find_format(self.transport)
122
self.assertTrue(RemoteBzrDirFormat
123
in BzrDirFormat._control_server_formats)
124
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
126
def test_open_detected_smart_format(self):
127
fmt = BzrDirFormat.find_format(self.transport)
128
d = fmt.open(self.transport)
129
self.assertIsInstance(d, BzrDir)
131
def test_remote_branch_repr(self):
132
b = BzrDir.open_from_transport(self.transport).open_branch()
133
self.assertStartsWith(str(b), 'RemoteBranch(')
135
def test_remote_branch_format_supports_stacking(self):
137
self.make_branch('unstackable', format='pack-0.92')
138
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
139
self.assertFalse(b._format.supports_stacking())
140
self.make_branch('stackable', format='1.9')
141
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
142
self.assertTrue(b._format.supports_stacking())
144
def test_remote_repo_format_supports_external_references(self):
146
bd = self.make_bzrdir('unstackable', format='pack-0.92')
147
r = bd.create_repository()
148
self.assertFalse(r._format.supports_external_lookups)
149
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
150
self.assertFalse(r._format.supports_external_lookups)
151
bd = self.make_bzrdir('stackable', format='1.9')
152
r = bd.create_repository()
153
self.assertTrue(r._format.supports_external_lookups)
154
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
155
self.assertTrue(r._format.supports_external_lookups)
158
class FakeProtocol(object):
159
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
161
def __init__(self, body, fake_client):
163
self._body_buffer = None
164
self._fake_client = fake_client
166
def read_body_bytes(self, count=-1):
167
if self._body_buffer is None:
168
self._body_buffer = StringIO(self.body)
169
bytes = self._body_buffer.read(count)
170
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
171
self._fake_client.expecting_body = False
174
def cancel_read_body(self):
175
self._fake_client.expecting_body = False
177
def read_streamed_body(self):
181
class FakeClient(_SmartClient):
182
"""Lookalike for _SmartClient allowing testing."""
184
def __init__(self, fake_medium_base='fake base'):
185
"""Create a FakeClient."""
188
self.expecting_body = False
189
# if non-None, this is the list of expected calls, with only the
190
# method name and arguments included. the body might be hard to
191
# compute so is not included. If a call is None, that call can
193
self._expected_calls = None
194
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
196
def add_expected_call(self, call_name, call_args, response_type,
197
response_args, response_body=None):
198
if self._expected_calls is None:
199
self._expected_calls = []
200
self._expected_calls.append((call_name, call_args))
201
self.responses.append((response_type, response_args, response_body))
203
def add_success_response(self, *args):
204
self.responses.append(('success', args, None))
206
def add_success_response_with_body(self, body, *args):
207
self.responses.append(('success', args, body))
208
if self._expected_calls is not None:
209
self._expected_calls.append(None)
211
def add_error_response(self, *args):
212
self.responses.append(('error', args))
214
def add_unknown_method_response(self, verb):
215
self.responses.append(('unknown', verb))
217
def finished_test(self):
218
if self._expected_calls:
219
raise AssertionError("%r finished but was still expecting %r"
220
% (self, self._expected_calls[0]))
222
def _get_next_response(self):
224
response_tuple = self.responses.pop(0)
225
except IndexError, e:
226
raise AssertionError("%r didn't expect any more calls"
228
if response_tuple[0] == 'unknown':
229
raise errors.UnknownSmartMethod(response_tuple[1])
230
elif response_tuple[0] == 'error':
231
raise errors.ErrorFromSmartServer(response_tuple[1])
232
return response_tuple
234
def _check_call(self, method, args):
235
if self._expected_calls is None:
236
# the test should be updated to say what it expects
239
next_call = self._expected_calls.pop(0)
241
raise AssertionError("%r didn't expect any more calls "
243
% (self, method, args,))
244
if next_call is None:
246
if method != next_call[0] or args != next_call[1]:
247
raise AssertionError("%r expected %r%r "
249
% (self, next_call[0], next_call[1], method, args,))
251
def call(self, method, *args):
252
self._check_call(method, args)
253
self._calls.append(('call', method, args))
254
return self._get_next_response()[1]
256
def call_expecting_body(self, method, *args):
257
self._check_call(method, args)
258
self._calls.append(('call_expecting_body', method, args))
259
result = self._get_next_response()
260
self.expecting_body = True
261
return result[1], FakeProtocol(result[2], self)
263
def call_with_body_bytes_expecting_body(self, method, args, body):
264
self._check_call(method, args)
265
self._calls.append(('call_with_body_bytes_expecting_body', method,
267
result = self._get_next_response()
268
self.expecting_body = True
269
return result[1], FakeProtocol(result[2], self)
271
def call_with_body_stream(self, args, stream):
272
# Explicitly consume the stream before checking for an error, because
273
# that's what happens a real medium.
274
stream = list(stream)
275
self._check_call(args[0], args[1:])
276
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
277
result = self._get_next_response()
278
# The second value returned from call_with_body_stream is supposed to
279
# be a response_handler object, but so far no tests depend on that.
280
response_handler = None
281
return result[1], response_handler
284
class FakeMedium(medium.SmartClientMedium):
286
def __init__(self, client_calls, base):
287
medium.SmartClientMedium.__init__(self, base)
288
self._client_calls = client_calls
290
def disconnect(self):
291
self._client_calls.append(('disconnect medium',))
294
class TestVfsHas(tests.TestCase):
296
def test_unicode_path(self):
297
client = FakeClient('/')
298
client.add_success_response('yes',)
299
transport = RemoteTransport('bzr://localhost/', _client=client)
300
filename = u'/hell\u00d8'.encode('utf8')
301
result = transport.has(filename)
303
[('call', 'has', (filename,))],
305
self.assertTrue(result)
308
class TestRemote(tests.TestCaseWithMemoryTransport):
310
def get_branch_format(self):
311
reference_bzrdir_format = bzrdir.format_registry.get('default')()
312
return reference_bzrdir_format.get_branch_format()
314
def get_repo_format(self):
315
reference_bzrdir_format = bzrdir.format_registry.get('default')()
316
return reference_bzrdir_format.repository_format
318
def disable_verb(self, verb):
319
"""Disable a verb for one test."""
320
request_handlers = smart.request.request_handlers
321
orig_method = request_handlers.get(verb)
322
request_handlers.remove(verb)
324
request_handlers.register(verb, orig_method)
325
self.addCleanup(restoreVerb)
328
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
329
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
331
def assertRemotePath(self, expected, client_base, transport_base):
332
"""Assert that the result of
333
SmartClientMedium.remote_path_from_transport is the expected value for
334
a given client_base and transport_base.
336
client_medium = medium.SmartClientMedium(client_base)
337
transport = get_transport(transport_base)
338
result = client_medium.remote_path_from_transport(transport)
339
self.assertEqual(expected, result)
341
def test_remote_path_from_transport(self):
342
"""SmartClientMedium.remote_path_from_transport calculates a URL for
343
the given transport relative to the root of the client base URL.
345
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
346
self.assertRemotePath(
347
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
349
def assertRemotePathHTTP(self, expected, transport_base, relpath):
350
"""Assert that the result of
351
HttpTransportBase.remote_path_from_transport is the expected value for
352
a given transport_base and relpath of that transport. (Note that
353
HttpTransportBase is a subclass of SmartClientMedium)
355
base_transport = get_transport(transport_base)
356
client_medium = base_transport.get_smart_medium()
357
cloned_transport = base_transport.clone(relpath)
358
result = client_medium.remote_path_from_transport(cloned_transport)
359
self.assertEqual(expected, result)
361
def test_remote_path_from_transport_http(self):
362
"""Remote paths for HTTP transports are calculated differently to other
363
transports. They are just relative to the client base, not the root
364
directory of the host.
366
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
367
self.assertRemotePathHTTP(
368
'../xyz/', scheme + '//host/path', '../xyz/')
369
self.assertRemotePathHTTP(
370
'xyz/', scheme + '//host/path', 'xyz/')
373
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
374
"""Tests for the behaviour of client_medium.remote_is_at_least."""
376
def test_initially_unlimited(self):
377
"""A fresh medium assumes that the remote side supports all
380
client_medium = medium.SmartClientMedium('dummy base')
381
self.assertFalse(client_medium._is_remote_before((99, 99)))
383
def test__remember_remote_is_before(self):
384
"""Calling _remember_remote_is_before ratchets down the known remote
387
client_medium = medium.SmartClientMedium('dummy base')
388
# Mark the remote side as being less than 1.6. The remote side may
390
client_medium._remember_remote_is_before((1, 6))
391
self.assertTrue(client_medium._is_remote_before((1, 6)))
392
self.assertFalse(client_medium._is_remote_before((1, 5)))
393
# Calling _remember_remote_is_before again with a lower value works.
394
client_medium._remember_remote_is_before((1, 5))
395
self.assertTrue(client_medium._is_remote_before((1, 5)))
396
# You cannot call _remember_remote_is_before with a larger value.
398
AssertionError, client_medium._remember_remote_is_before, (1, 9))
401
class TestBzrDirCloningMetaDir(TestRemote):
403
def test_backwards_compat(self):
404
self.setup_smart_server_with_call_log()
405
a_dir = self.make_bzrdir('.')
406
self.reset_smart_call_log()
407
verb = 'BzrDir.cloning_metadir'
408
self.disable_verb(verb)
409
format = a_dir.cloning_metadir()
410
call_count = len([call for call in self.hpss_calls if
411
call.call.method == verb])
412
self.assertEqual(1, call_count)
414
def test_branch_reference(self):
415
transport = self.get_transport('quack')
416
referenced = self.make_branch('referenced')
417
expected = referenced.bzrdir.cloning_metadir()
418
client = FakeClient(transport.base)
419
client.add_expected_call(
420
'BzrDir.cloning_metadir', ('quack/', 'False'),
421
'error', ('BranchReference',)),
422
client.add_expected_call(
423
'BzrDir.open_branchV2', ('quack/',),
424
'success', ('ref', self.get_url('referenced'))),
425
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
427
result = a_bzrdir.cloning_metadir()
428
# We should have got a control dir matching the referenced branch.
429
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
430
self.assertEqual(expected._repository_format, result._repository_format)
431
self.assertEqual(expected._branch_format, result._branch_format)
432
client.finished_test()
434
def test_current_server(self):
435
transport = self.get_transport('.')
436
transport = transport.clone('quack')
437
self.make_bzrdir('quack')
438
client = FakeClient(transport.base)
439
reference_bzrdir_format = bzrdir.format_registry.get('default')()
440
control_name = reference_bzrdir_format.network_name()
441
client.add_expected_call(
442
'BzrDir.cloning_metadir', ('quack/', 'False'),
443
'success', (control_name, '', ('branch', ''))),
444
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
446
result = a_bzrdir.cloning_metadir()
447
# We should have got a reference control dir with default branch and
448
# repository formats.
449
# This pokes a little, just to be sure.
450
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
451
self.assertEqual(None, result._repository_format)
452
self.assertEqual(None, result._branch_format)
453
client.finished_test()
456
class TestBzrDirOpenBranch(TestRemote):
458
def test_backwards_compat(self):
459
self.setup_smart_server_with_call_log()
460
self.make_branch('.')
461
a_dir = BzrDir.open(self.get_url('.'))
462
self.reset_smart_call_log()
463
verb = 'BzrDir.open_branchV2'
464
self.disable_verb(verb)
465
format = a_dir.open_branch()
466
call_count = len([call for call in self.hpss_calls if
467
call.call.method == verb])
468
self.assertEqual(1, call_count)
470
def test_branch_present(self):
471
reference_format = self.get_repo_format()
472
network_name = reference_format.network_name()
473
branch_network_name = self.get_branch_format().network_name()
474
transport = MemoryTransport()
475
transport.mkdir('quack')
476
transport = transport.clone('quack')
477
client = FakeClient(transport.base)
478
client.add_expected_call(
479
'BzrDir.open_branchV2', ('quack/',),
480
'success', ('branch', branch_network_name))
481
client.add_expected_call(
482
'BzrDir.find_repositoryV3', ('quack/',),
483
'success', ('ok', '', 'no', 'no', 'no', network_name))
484
client.add_expected_call(
485
'Branch.get_stacked_on_url', ('quack/',),
486
'error', ('NotStacked',))
487
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
489
result = bzrdir.open_branch()
490
self.assertIsInstance(result, RemoteBranch)
491
self.assertEqual(bzrdir, result.bzrdir)
492
client.finished_test()
494
def test_branch_missing(self):
495
transport = MemoryTransport()
496
transport.mkdir('quack')
497
transport = transport.clone('quack')
498
client = FakeClient(transport.base)
499
client.add_error_response('nobranch')
500
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
502
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
504
[('call', 'BzrDir.open_branchV2', ('quack/',))],
507
def test__get_tree_branch(self):
508
# _get_tree_branch is a form of open_branch, but it should only ask for
509
# branch opening, not any other network requests.
512
calls.append("Called")
514
transport = MemoryTransport()
515
# no requests on the network - catches other api calls being made.
516
client = FakeClient(transport.base)
517
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
519
# patch the open_branch call to record that it was called.
520
bzrdir.open_branch = open_branch
521
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
522
self.assertEqual(["Called"], calls)
523
self.assertEqual([], client._calls)
525
def test_url_quoting_of_path(self):
526
# Relpaths on the wire should not be URL-escaped. So "~" should be
527
# transmitted as "~", not "%7E".
528
transport = RemoteTCPTransport('bzr://localhost/~hello/')
529
client = FakeClient(transport.base)
530
reference_format = self.get_repo_format()
531
network_name = reference_format.network_name()
532
branch_network_name = self.get_branch_format().network_name()
533
client.add_expected_call(
534
'BzrDir.open_branchV2', ('~hello/',),
535
'success', ('branch', branch_network_name))
536
client.add_expected_call(
537
'BzrDir.find_repositoryV3', ('~hello/',),
538
'success', ('ok', '', 'no', 'no', 'no', network_name))
539
client.add_expected_call(
540
'Branch.get_stacked_on_url', ('~hello/',),
541
'error', ('NotStacked',))
542
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
544
result = bzrdir.open_branch()
545
client.finished_test()
547
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
548
reference_format = self.get_repo_format()
549
network_name = reference_format.network_name()
550
transport = MemoryTransport()
551
transport.mkdir('quack')
552
transport = transport.clone('quack')
554
rich_response = 'yes'
558
subtree_response = 'yes'
560
subtree_response = 'no'
561
client = FakeClient(transport.base)
562
client.add_success_response(
563
'ok', '', rich_response, subtree_response, external_lookup,
565
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
567
result = bzrdir.open_repository()
569
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
571
self.assertIsInstance(result, RemoteRepository)
572
self.assertEqual(bzrdir, result.bzrdir)
573
self.assertEqual(rich_root, result._format.rich_root_data)
574
self.assertEqual(subtrees, result._format.supports_tree_reference)
576
def test_open_repository_sets_format_attributes(self):
577
self.check_open_repository(True, True)
578
self.check_open_repository(False, True)
579
self.check_open_repository(True, False)
580
self.check_open_repository(False, False)
581
self.check_open_repository(False, False, 'yes')
583
def test_old_server(self):
584
"""RemoteBzrDirFormat should fail to probe if the server version is too
587
self.assertRaises(errors.NotBranchError,
588
RemoteBzrDirFormat.probe_transport, OldServerTransport())
591
class TestBzrDirCreateBranch(TestRemote):
593
def test_backwards_compat(self):
594
self.setup_smart_server_with_call_log()
595
repo = self.make_repository('.')
596
self.reset_smart_call_log()
597
self.disable_verb('BzrDir.create_branch')
598
branch = repo.bzrdir.create_branch()
599
create_branch_call_count = len([call for call in self.hpss_calls if
600
call.call.method == 'BzrDir.create_branch'])
601
self.assertEqual(1, create_branch_call_count)
603
def test_current_server(self):
604
transport = self.get_transport('.')
605
transport = transport.clone('quack')
606
self.make_repository('quack')
607
client = FakeClient(transport.base)
608
reference_bzrdir_format = bzrdir.format_registry.get('default')()
609
reference_format = reference_bzrdir_format.get_branch_format()
610
network_name = reference_format.network_name()
611
reference_repo_fmt = reference_bzrdir_format.repository_format
612
reference_repo_name = reference_repo_fmt.network_name()
613
client.add_expected_call(
614
'BzrDir.create_branch', ('quack/', network_name),
615
'success', ('ok', network_name, '', 'no', 'no', 'yes',
616
reference_repo_name))
617
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
619
branch = a_bzrdir.create_branch()
620
# We should have got a remote branch
621
self.assertIsInstance(branch, remote.RemoteBranch)
622
# its format should have the settings from the response
623
format = branch._format
624
self.assertEqual(network_name, format.network_name())
627
class TestBzrDirCreateRepository(TestRemote):
629
def test_backwards_compat(self):
630
self.setup_smart_server_with_call_log()
631
bzrdir = self.make_bzrdir('.')
632
self.reset_smart_call_log()
633
self.disable_verb('BzrDir.create_repository')
634
repo = bzrdir.create_repository()
635
create_repo_call_count = len([call for call in self.hpss_calls if
636
call.call.method == 'BzrDir.create_repository'])
637
self.assertEqual(1, create_repo_call_count)
639
def test_current_server(self):
640
transport = self.get_transport('.')
641
transport = transport.clone('quack')
642
self.make_bzrdir('quack')
643
client = FakeClient(transport.base)
644
reference_bzrdir_format = bzrdir.format_registry.get('default')()
645
reference_format = reference_bzrdir_format.repository_format
646
network_name = reference_format.network_name()
647
client.add_expected_call(
648
'BzrDir.create_repository', ('quack/',
649
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
650
'success', ('ok', 'no', 'no', 'no', network_name))
651
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
653
repo = a_bzrdir.create_repository()
654
# We should have got a remote repository
655
self.assertIsInstance(repo, remote.RemoteRepository)
656
# its format should have the settings from the response
657
format = repo._format
658
self.assertFalse(format.rich_root_data)
659
self.assertFalse(format.supports_tree_reference)
660
self.assertFalse(format.supports_external_lookups)
661
self.assertEqual(network_name, format.network_name())
664
class TestBzrDirOpenRepository(TestRemote):
666
def test_backwards_compat_1_2_3(self):
667
# fallback all the way to the first version.
668
reference_format = self.get_repo_format()
669
network_name = reference_format.network_name()
670
client = FakeClient('bzr://example.com/')
671
client.add_unknown_method_response('BzrDir.find_repositoryV3')
672
client.add_unknown_method_response('BzrDir.find_repositoryV2')
673
client.add_success_response('ok', '', 'no', 'no')
674
# A real repository instance will be created to determine the network
676
client.add_success_response_with_body(
677
"Bazaar-NG meta directory, format 1\n", 'ok')
678
client.add_success_response_with_body(
679
reference_format.get_format_string(), 'ok')
680
# PackRepository wants to do a stat
681
client.add_success_response('stat', '0', '65535')
682
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
684
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
686
repo = bzrdir.open_repository()
688
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
689
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
690
('call', 'BzrDir.find_repository', ('quack/',)),
691
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
692
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
693
('call', 'stat', ('/quack/.bzr/repository',)),
696
self.assertEqual(network_name, repo._format.network_name())
698
def test_backwards_compat_2(self):
699
# fallback to find_repositoryV2
700
reference_format = self.get_repo_format()
701
network_name = reference_format.network_name()
702
client = FakeClient('bzr://example.com/')
703
client.add_unknown_method_response('BzrDir.find_repositoryV3')
704
client.add_success_response('ok', '', 'no', 'no', 'no')
705
# A real repository instance will be created to determine the network
707
client.add_success_response_with_body(
708
"Bazaar-NG meta directory, format 1\n", 'ok')
709
client.add_success_response_with_body(
710
reference_format.get_format_string(), 'ok')
711
# PackRepository wants to do a stat
712
client.add_success_response('stat', '0', '65535')
713
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
715
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
717
repo = bzrdir.open_repository()
719
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
720
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
721
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
722
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
723
('call', 'stat', ('/quack/.bzr/repository',)),
726
self.assertEqual(network_name, repo._format.network_name())
728
def test_current_server(self):
729
reference_format = self.get_repo_format()
730
network_name = reference_format.network_name()
731
transport = MemoryTransport()
732
transport.mkdir('quack')
733
transport = transport.clone('quack')
734
client = FakeClient(transport.base)
735
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
736
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
738
repo = bzrdir.open_repository()
740
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
742
self.assertEqual(network_name, repo._format.network_name())
745
class OldSmartClient(object):
746
"""A fake smart client for test_old_version that just returns a version one
747
response to the 'hello' (query version) command.
750
def get_request(self):
751
input_file = StringIO('ok\x011\n')
752
output_file = StringIO()
753
client_medium = medium.SmartSimplePipesClientMedium(
754
input_file, output_file)
755
return medium.SmartClientStreamMediumRequest(client_medium)
757
def protocol_version(self):
761
class OldServerTransport(object):
762
"""A fake transport for test_old_server that reports it's smart server
763
protocol version as version one.
769
def get_smart_client(self):
770
return OldSmartClient()
773
class RemoteBzrDirTestCase(TestRemote):
775
def make_remote_bzrdir(self, transport, client):
776
"""Make a RemotebzrDir using 'client' as the _client."""
777
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
781
class RemoteBranchTestCase(RemoteBzrDirTestCase):
783
def make_remote_branch(self, transport, client):
784
"""Make a RemoteBranch using 'client' as its _SmartClient.
786
A RemoteBzrDir and RemoteRepository will also be created to fill out
787
the RemoteBranch, albeit with stub values for some of their attributes.
789
# we do not want bzrdir to make any remote calls, so use False as its
790
# _client. If it tries to make a remote call, this will fail
792
bzrdir = self.make_remote_bzrdir(transport, False)
793
repo = RemoteRepository(bzrdir, None, _client=client)
794
branch_format = self.get_branch_format()
795
format = RemoteBranchFormat(network_name=branch_format.network_name())
796
return RemoteBranch(bzrdir, repo, _client=client, format=format)
799
class TestBranchGetParent(RemoteBranchTestCase):
801
def test_no_parent(self):
802
# in an empty branch we decode the response properly
803
transport = MemoryTransport()
804
client = FakeClient(transport.base)
805
client.add_expected_call(
806
'Branch.get_stacked_on_url', ('quack/',),
807
'error', ('NotStacked',))
808
client.add_expected_call(
809
'Branch.get_parent', ('quack/',),
811
transport.mkdir('quack')
812
transport = transport.clone('quack')
813
branch = self.make_remote_branch(transport, client)
814
result = branch.get_parent()
815
client.finished_test()
816
self.assertEqual(None, result)
818
def test_parent_relative(self):
819
transport = MemoryTransport()
820
client = FakeClient(transport.base)
821
client.add_expected_call(
822
'Branch.get_stacked_on_url', ('kwaak/',),
823
'error', ('NotStacked',))
824
client.add_expected_call(
825
'Branch.get_parent', ('kwaak/',),
826
'success', ('../foo/',))
827
transport.mkdir('kwaak')
828
transport = transport.clone('kwaak')
829
branch = self.make_remote_branch(transport, client)
830
result = branch.get_parent()
831
self.assertEqual(transport.clone('../foo').base, result)
833
def test_parent_absolute(self):
834
transport = MemoryTransport()
835
client = FakeClient(transport.base)
836
client.add_expected_call(
837
'Branch.get_stacked_on_url', ('kwaak/',),
838
'error', ('NotStacked',))
839
client.add_expected_call(
840
'Branch.get_parent', ('kwaak/',),
841
'success', ('http://foo/',))
842
transport.mkdir('kwaak')
843
transport = transport.clone('kwaak')
844
branch = self.make_remote_branch(transport, client)
845
result = branch.get_parent()
846
self.assertEqual('http://foo/', result)
847
client.finished_test()
850
class TestBranchSetParentLocation(RemoteBranchTestCase):
852
def test_no_parent(self):
853
# We call the verb when setting parent to None
854
transport = MemoryTransport()
855
client = FakeClient(transport.base)
856
client.add_expected_call(
857
'Branch.get_stacked_on_url', ('quack/',),
858
'error', ('NotStacked',))
859
client.add_expected_call(
860
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
862
transport.mkdir('quack')
863
transport = transport.clone('quack')
864
branch = self.make_remote_branch(transport, client)
865
branch._lock_token = 'b'
866
branch._repo_lock_token = 'r'
867
branch._set_parent_location(None)
868
client.finished_test()
870
def test_parent(self):
871
transport = MemoryTransport()
872
client = FakeClient(transport.base)
873
client.add_expected_call(
874
'Branch.get_stacked_on_url', ('kwaak/',),
875
'error', ('NotStacked',))
876
client.add_expected_call(
877
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
879
transport.mkdir('kwaak')
880
transport = transport.clone('kwaak')
881
branch = self.make_remote_branch(transport, client)
882
branch._lock_token = 'b'
883
branch._repo_lock_token = 'r'
884
branch._set_parent_location('foo')
885
client.finished_test()
887
def test_backwards_compat(self):
888
self.setup_smart_server_with_call_log()
889
branch = self.make_branch('.')
890
self.reset_smart_call_log()
891
verb = 'Branch.set_parent_location'
892
self.disable_verb(verb)
893
branch.set_parent('http://foo/')
894
self.assertLength(12, self.hpss_calls)
897
class TestBranchGetTagsBytes(RemoteBranchTestCase):
899
def test_backwards_compat(self):
900
self.setup_smart_server_with_call_log()
901
branch = self.make_branch('.')
902
self.reset_smart_call_log()
903
verb = 'Branch.get_tags_bytes'
904
self.disable_verb(verb)
905
branch.tags.get_tag_dict()
906
call_count = len([call for call in self.hpss_calls if
907
call.call.method == verb])
908
self.assertEqual(1, call_count)
910
def test_trivial(self):
911
transport = MemoryTransport()
912
client = FakeClient(transport.base)
913
client.add_expected_call(
914
'Branch.get_stacked_on_url', ('quack/',),
915
'error', ('NotStacked',))
916
client.add_expected_call(
917
'Branch.get_tags_bytes', ('quack/',),
919
transport.mkdir('quack')
920
transport = transport.clone('quack')
921
branch = self.make_remote_branch(transport, client)
922
result = branch.tags.get_tag_dict()
923
client.finished_test()
924
self.assertEqual({}, result)
927
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
929
def test_empty_branch(self):
930
# in an empty branch we decode the response properly
931
transport = MemoryTransport()
932
client = FakeClient(transport.base)
933
client.add_expected_call(
934
'Branch.get_stacked_on_url', ('quack/',),
935
'error', ('NotStacked',))
936
client.add_expected_call(
937
'Branch.last_revision_info', ('quack/',),
938
'success', ('ok', '0', 'null:'))
939
transport.mkdir('quack')
940
transport = transport.clone('quack')
941
branch = self.make_remote_branch(transport, client)
942
result = branch.last_revision_info()
943
client.finished_test()
944
self.assertEqual((0, NULL_REVISION), result)
946
def test_non_empty_branch(self):
947
# in a non-empty branch we also decode the response properly
948
revid = u'\xc8'.encode('utf8')
949
transport = MemoryTransport()
950
client = FakeClient(transport.base)
951
client.add_expected_call(
952
'Branch.get_stacked_on_url', ('kwaak/',),
953
'error', ('NotStacked',))
954
client.add_expected_call(
955
'Branch.last_revision_info', ('kwaak/',),
956
'success', ('ok', '2', revid))
957
transport.mkdir('kwaak')
958
transport = transport.clone('kwaak')
959
branch = self.make_remote_branch(transport, client)
960
result = branch.last_revision_info()
961
self.assertEqual((2, revid), result)
964
class TestBranch_get_stacked_on_url(TestRemote):
965
"""Test Branch._get_stacked_on_url rpc"""
967
def test_get_stacked_on_invalid_url(self):
968
# test that asking for a stacked on url the server can't access works.
969
# This isn't perfect, but then as we're in the same process there
970
# really isn't anything we can do to be 100% sure that the server
971
# doesn't just open in - this test probably needs to be rewritten using
972
# a spawn()ed server.
973
stacked_branch = self.make_branch('stacked', format='1.9')
974
memory_branch = self.make_branch('base', format='1.9')
975
vfs_url = self.get_vfs_only_url('base')
976
stacked_branch.set_stacked_on_url(vfs_url)
977
transport = stacked_branch.bzrdir.root_transport
978
client = FakeClient(transport.base)
979
client.add_expected_call(
980
'Branch.get_stacked_on_url', ('stacked/',),
981
'success', ('ok', vfs_url))
982
# XXX: Multiple calls are bad, this second call documents what is
984
client.add_expected_call(
985
'Branch.get_stacked_on_url', ('stacked/',),
986
'success', ('ok', vfs_url))
987
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
989
repo_fmt = remote.RemoteRepositoryFormat()
990
repo_fmt._custom_format = stacked_branch.repository._format
991
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
993
result = branch.get_stacked_on_url()
994
self.assertEqual(vfs_url, result)
996
def test_backwards_compatible(self):
997
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
998
base_branch = self.make_branch('base', format='1.6')
999
stacked_branch = self.make_branch('stacked', format='1.6')
1000
stacked_branch.set_stacked_on_url('../base')
1001
client = FakeClient(self.get_url())
1002
branch_network_name = self.get_branch_format().network_name()
1003
client.add_expected_call(
1004
'BzrDir.open_branchV2', ('stacked/',),
1005
'success', ('branch', branch_network_name))
1006
client.add_expected_call(
1007
'BzrDir.find_repositoryV3', ('stacked/',),
1008
'success', ('ok', '', 'no', 'no', 'yes',
1009
stacked_branch.repository._format.network_name()))
1010
# called twice, once from constructor and then again by us
1011
client.add_expected_call(
1012
'Branch.get_stacked_on_url', ('stacked/',),
1013
'unknown', ('Branch.get_stacked_on_url',))
1014
client.add_expected_call(
1015
'Branch.get_stacked_on_url', ('stacked/',),
1016
'unknown', ('Branch.get_stacked_on_url',))
1017
# this will also do vfs access, but that goes direct to the transport
1018
# and isn't seen by the FakeClient.
1019
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1020
remote.RemoteBzrDirFormat(), _client=client)
1021
branch = bzrdir.open_branch()
1022
result = branch.get_stacked_on_url()
1023
self.assertEqual('../base', result)
1024
client.finished_test()
1025
# it's in the fallback list both for the RemoteRepository and its vfs
1027
self.assertEqual(1, len(branch.repository._fallback_repositories))
1029
len(branch.repository._real_repository._fallback_repositories))
1031
def test_get_stacked_on_real_branch(self):
1032
base_branch = self.make_branch('base', format='1.6')
1033
stacked_branch = self.make_branch('stacked', format='1.6')
1034
stacked_branch.set_stacked_on_url('../base')
1035
reference_format = self.get_repo_format()
1036
network_name = reference_format.network_name()
1037
client = FakeClient(self.get_url())
1038
branch_network_name = self.get_branch_format().network_name()
1039
client.add_expected_call(
1040
'BzrDir.open_branchV2', ('stacked/',),
1041
'success', ('branch', branch_network_name))
1042
client.add_expected_call(
1043
'BzrDir.find_repositoryV3', ('stacked/',),
1044
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1045
# called twice, once from constructor and then again by us
1046
client.add_expected_call(
1047
'Branch.get_stacked_on_url', ('stacked/',),
1048
'success', ('ok', '../base'))
1049
client.add_expected_call(
1050
'Branch.get_stacked_on_url', ('stacked/',),
1051
'success', ('ok', '../base'))
1052
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1053
remote.RemoteBzrDirFormat(), _client=client)
1054
branch = bzrdir.open_branch()
1055
result = branch.get_stacked_on_url()
1056
self.assertEqual('../base', result)
1057
client.finished_test()
1058
# it's in the fallback list both for the RemoteRepository.
1059
self.assertEqual(1, len(branch.repository._fallback_repositories))
1060
# And we haven't had to construct a real repository.
1061
self.assertEqual(None, branch.repository._real_repository)
1064
class TestBranchSetLastRevision(RemoteBranchTestCase):
1066
def test_set_empty(self):
1067
# set_revision_history([]) is translated to calling
1068
# Branch.set_last_revision(path, '') on the wire.
1069
transport = MemoryTransport()
1070
transport.mkdir('branch')
1071
transport = transport.clone('branch')
1073
client = FakeClient(transport.base)
1074
client.add_expected_call(
1075
'Branch.get_stacked_on_url', ('branch/',),
1076
'error', ('NotStacked',))
1077
client.add_expected_call(
1078
'Branch.lock_write', ('branch/', '', ''),
1079
'success', ('ok', 'branch token', 'repo token'))
1080
client.add_expected_call(
1081
'Branch.last_revision_info',
1083
'success', ('ok', '0', 'null:'))
1084
client.add_expected_call(
1085
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1087
client.add_expected_call(
1088
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1090
branch = self.make_remote_branch(transport, client)
1091
# This is a hack to work around the problem that RemoteBranch currently
1092
# unnecessarily invokes _ensure_real upon a call to lock_write.
1093
branch._ensure_real = lambda: None
1095
result = branch.set_revision_history([])
1097
self.assertEqual(None, result)
1098
client.finished_test()
1100
def test_set_nonempty(self):
1101
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1102
# Branch.set_last_revision(path, rev-idN) on the wire.
1103
transport = MemoryTransport()
1104
transport.mkdir('branch')
1105
transport = transport.clone('branch')
1107
client = FakeClient(transport.base)
1108
client.add_expected_call(
1109
'Branch.get_stacked_on_url', ('branch/',),
1110
'error', ('NotStacked',))
1111
client.add_expected_call(
1112
'Branch.lock_write', ('branch/', '', ''),
1113
'success', ('ok', 'branch token', 'repo token'))
1114
client.add_expected_call(
1115
'Branch.last_revision_info',
1117
'success', ('ok', '0', 'null:'))
1119
encoded_body = bz2.compress('\n'.join(lines))
1120
client.add_success_response_with_body(encoded_body, 'ok')
1121
client.add_expected_call(
1122
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1124
client.add_expected_call(
1125
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1127
branch = self.make_remote_branch(transport, client)
1128
# This is a hack to work around the problem that RemoteBranch currently
1129
# unnecessarily invokes _ensure_real upon a call to lock_write.
1130
branch._ensure_real = lambda: None
1131
# Lock the branch, reset the record of remote calls.
1133
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1135
self.assertEqual(None, result)
1136
client.finished_test()
1138
def test_no_such_revision(self):
1139
transport = MemoryTransport()
1140
transport.mkdir('branch')
1141
transport = transport.clone('branch')
1142
# A response of 'NoSuchRevision' is translated into an exception.
1143
client = FakeClient(transport.base)
1144
client.add_expected_call(
1145
'Branch.get_stacked_on_url', ('branch/',),
1146
'error', ('NotStacked',))
1147
client.add_expected_call(
1148
'Branch.lock_write', ('branch/', '', ''),
1149
'success', ('ok', 'branch token', 'repo token'))
1150
client.add_expected_call(
1151
'Branch.last_revision_info',
1153
'success', ('ok', '0', 'null:'))
1154
# get_graph calls to construct the revision history, for the set_rh
1157
encoded_body = bz2.compress('\n'.join(lines))
1158
client.add_success_response_with_body(encoded_body, 'ok')
1159
client.add_expected_call(
1160
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1161
'error', ('NoSuchRevision', 'rev-id'))
1162
client.add_expected_call(
1163
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1166
branch = self.make_remote_branch(transport, client)
1169
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1171
client.finished_test()
1173
def test_tip_change_rejected(self):
1174
"""TipChangeRejected responses cause a TipChangeRejected exception to
1177
transport = MemoryTransport()
1178
transport.mkdir('branch')
1179
transport = transport.clone('branch')
1180
client = FakeClient(transport.base)
1181
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1182
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1183
client.add_expected_call(
1184
'Branch.get_stacked_on_url', ('branch/',),
1185
'error', ('NotStacked',))
1186
client.add_expected_call(
1187
'Branch.lock_write', ('branch/', '', ''),
1188
'success', ('ok', 'branch token', 'repo token'))
1189
client.add_expected_call(
1190
'Branch.last_revision_info',
1192
'success', ('ok', '0', 'null:'))
1194
encoded_body = bz2.compress('\n'.join(lines))
1195
client.add_success_response_with_body(encoded_body, 'ok')
1196
client.add_expected_call(
1197
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1198
'error', ('TipChangeRejected', rejection_msg_utf8))
1199
client.add_expected_call(
1200
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1202
branch = self.make_remote_branch(transport, client)
1203
branch._ensure_real = lambda: None
1205
# The 'TipChangeRejected' error response triggered by calling
1206
# set_revision_history causes a TipChangeRejected exception.
1207
err = self.assertRaises(
1208
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1209
# The UTF-8 message from the response has been decoded into a unicode
1211
self.assertIsInstance(err.msg, unicode)
1212
self.assertEqual(rejection_msg_unicode, err.msg)
1214
client.finished_test()
1217
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1219
def test_set_last_revision_info(self):
1220
# set_last_revision_info(num, 'rev-id') is translated to calling
1221
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1222
transport = MemoryTransport()
1223
transport.mkdir('branch')
1224
transport = transport.clone('branch')
1225
client = FakeClient(transport.base)
1226
# get_stacked_on_url
1227
client.add_error_response('NotStacked')
1229
client.add_success_response('ok', 'branch token', 'repo token')
1230
# query the current revision
1231
client.add_success_response('ok', '0', 'null:')
1233
client.add_success_response('ok')
1235
client.add_success_response('ok')
1237
branch = self.make_remote_branch(transport, client)
1238
# Lock the branch, reset the record of remote calls.
1241
result = branch.set_last_revision_info(1234, 'a-revision-id')
1243
[('call', 'Branch.last_revision_info', ('branch/',)),
1244
('call', 'Branch.set_last_revision_info',
1245
('branch/', 'branch token', 'repo token',
1246
'1234', 'a-revision-id'))],
1248
self.assertEqual(None, result)
1250
def test_no_such_revision(self):
1251
# A response of 'NoSuchRevision' is translated into an exception.
1252
transport = MemoryTransport()
1253
transport.mkdir('branch')
1254
transport = transport.clone('branch')
1255
client = FakeClient(transport.base)
1256
# get_stacked_on_url
1257
client.add_error_response('NotStacked')
1259
client.add_success_response('ok', 'branch token', 'repo token')
1261
client.add_error_response('NoSuchRevision', 'revid')
1263
client.add_success_response('ok')
1265
branch = self.make_remote_branch(transport, client)
1266
# Lock the branch, reset the record of remote calls.
1271
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1274
def lock_remote_branch(self, branch):
1275
"""Trick a RemoteBranch into thinking it is locked."""
1276
branch._lock_mode = 'w'
1277
branch._lock_count = 2
1278
branch._lock_token = 'branch token'
1279
branch._repo_lock_token = 'repo token'
1280
branch.repository._lock_mode = 'w'
1281
branch.repository._lock_count = 2
1282
branch.repository._lock_token = 'repo token'
1284
def test_backwards_compatibility(self):
1285
"""If the server does not support the Branch.set_last_revision_info
1286
verb (which is new in 1.4), then the client falls back to VFS methods.
1288
# This test is a little messy. Unlike most tests in this file, it
1289
# doesn't purely test what a Remote* object sends over the wire, and
1290
# how it reacts to responses from the wire. It instead relies partly
1291
# on asserting that the RemoteBranch will call
1292
# self._real_branch.set_last_revision_info(...).
1294
# First, set up our RemoteBranch with a FakeClient that raises
1295
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1296
transport = MemoryTransport()
1297
transport.mkdir('branch')
1298
transport = transport.clone('branch')
1299
client = FakeClient(transport.base)
1300
client.add_expected_call(
1301
'Branch.get_stacked_on_url', ('branch/',),
1302
'error', ('NotStacked',))
1303
client.add_expected_call(
1304
'Branch.last_revision_info',
1306
'success', ('ok', '0', 'null:'))
1307
client.add_expected_call(
1308
'Branch.set_last_revision_info',
1309
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1310
'unknown', 'Branch.set_last_revision_info')
1312
branch = self.make_remote_branch(transport, client)
1313
class StubRealBranch(object):
1316
def set_last_revision_info(self, revno, revision_id):
1318
('set_last_revision_info', revno, revision_id))
1319
def _clear_cached_state(self):
1321
real_branch = StubRealBranch()
1322
branch._real_branch = real_branch
1323
self.lock_remote_branch(branch)
1325
# Call set_last_revision_info, and verify it behaved as expected.
1326
result = branch.set_last_revision_info(1234, 'a-revision-id')
1328
[('set_last_revision_info', 1234, 'a-revision-id')],
1330
client.finished_test()
1332
def test_unexpected_error(self):
1333
# If the server sends an error the client doesn't understand, it gets
1334
# turned into an UnknownErrorFromSmartServer, which is presented as a
1335
# non-internal error to the user.
1336
transport = MemoryTransport()
1337
transport.mkdir('branch')
1338
transport = transport.clone('branch')
1339
client = FakeClient(transport.base)
1340
# get_stacked_on_url
1341
client.add_error_response('NotStacked')
1343
client.add_success_response('ok', 'branch token', 'repo token')
1345
client.add_error_response('UnexpectedError')
1347
client.add_success_response('ok')
1349
branch = self.make_remote_branch(transport, client)
1350
# Lock the branch, reset the record of remote calls.
1354
err = self.assertRaises(
1355
errors.UnknownErrorFromSmartServer,
1356
branch.set_last_revision_info, 123, 'revid')
1357
self.assertEqual(('UnexpectedError',), err.error_tuple)
1360
def test_tip_change_rejected(self):
1361
"""TipChangeRejected responses cause a TipChangeRejected exception to
1364
transport = MemoryTransport()
1365
transport.mkdir('branch')
1366
transport = transport.clone('branch')
1367
client = FakeClient(transport.base)
1368
# get_stacked_on_url
1369
client.add_error_response('NotStacked')
1371
client.add_success_response('ok', 'branch token', 'repo token')
1373
client.add_error_response('TipChangeRejected', 'rejection message')
1375
client.add_success_response('ok')
1377
branch = self.make_remote_branch(transport, client)
1378
# Lock the branch, reset the record of remote calls.
1380
self.addCleanup(branch.unlock)
1383
# The 'TipChangeRejected' error response triggered by calling
1384
# set_last_revision_info causes a TipChangeRejected exception.
1385
err = self.assertRaises(
1386
errors.TipChangeRejected,
1387
branch.set_last_revision_info, 123, 'revid')
1388
self.assertEqual('rejection message', err.msg)
1391
class TestBranchGetSetConfig(RemoteBranchTestCase):
1393
def test_get_branch_conf(self):
1394
# in an empty branch we decode the response properly
1395
client = FakeClient()
1396
client.add_expected_call(
1397
'Branch.get_stacked_on_url', ('memory:///',),
1398
'error', ('NotStacked',),)
1399
client.add_success_response_with_body('# config file body', 'ok')
1400
transport = MemoryTransport()
1401
branch = self.make_remote_branch(transport, client)
1402
config = branch.get_config()
1403
config.has_explicit_nickname()
1405
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1406
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1409
def test_get_multi_line_branch_conf(self):
1410
# Make sure that multiple-line branch.conf files are supported
1412
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1413
client = FakeClient()
1414
client.add_expected_call(
1415
'Branch.get_stacked_on_url', ('memory:///',),
1416
'error', ('NotStacked',),)
1417
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1418
transport = MemoryTransport()
1419
branch = self.make_remote_branch(transport, client)
1420
config = branch.get_config()
1421
self.assertEqual(u'2', config.get_user_option('b'))
1423
def test_set_option(self):
1424
client = FakeClient()
1425
client.add_expected_call(
1426
'Branch.get_stacked_on_url', ('memory:///',),
1427
'error', ('NotStacked',),)
1428
client.add_expected_call(
1429
'Branch.lock_write', ('memory:///', '', ''),
1430
'success', ('ok', 'branch token', 'repo token'))
1431
client.add_expected_call(
1432
'Branch.set_config_option', ('memory:///', 'branch token',
1433
'repo token', 'foo', 'bar', ''),
1435
client.add_expected_call(
1436
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1438
transport = MemoryTransport()
1439
branch = self.make_remote_branch(transport, client)
1441
config = branch._get_config()
1442
config.set_option('foo', 'bar')
1444
client.finished_test()
1446
def test_backwards_compat_set_option(self):
1447
self.setup_smart_server_with_call_log()
1448
branch = self.make_branch('.')
1449
verb = 'Branch.set_config_option'
1450
self.disable_verb(verb)
1452
self.addCleanup(branch.unlock)
1453
self.reset_smart_call_log()
1454
branch._get_config().set_option('value', 'name')
1455
self.assertLength(10, self.hpss_calls)
1456
self.assertEqual('value', branch._get_config().get_option('name'))
1459
class TestBranchLockWrite(RemoteBranchTestCase):
1461
def test_lock_write_unlockable(self):
1462
transport = MemoryTransport()
1463
client = FakeClient(transport.base)
1464
client.add_expected_call(
1465
'Branch.get_stacked_on_url', ('quack/',),
1466
'error', ('NotStacked',),)
1467
client.add_expected_call(
1468
'Branch.lock_write', ('quack/', '', ''),
1469
'error', ('UnlockableTransport',))
1470
transport.mkdir('quack')
1471
transport = transport.clone('quack')
1472
branch = self.make_remote_branch(transport, client)
1473
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1474
client.finished_test()
1477
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1479
def test__get_config(self):
1480
client = FakeClient()
1481
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1482
transport = MemoryTransport()
1483
bzrdir = self.make_remote_bzrdir(transport, client)
1484
config = bzrdir.get_config()
1485
self.assertEqual('/', config.get_default_stack_on())
1487
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1490
def test_set_option_uses_vfs(self):
1491
self.setup_smart_server_with_call_log()
1492
bzrdir = self.make_bzrdir('.')
1493
self.reset_smart_call_log()
1494
config = bzrdir.get_config()
1495
config.set_default_stack_on('/')
1496
self.assertLength(3, self.hpss_calls)
1498
def test_backwards_compat_get_option(self):
1499
self.setup_smart_server_with_call_log()
1500
bzrdir = self.make_bzrdir('.')
1501
verb = 'BzrDir.get_config_file'
1502
self.disable_verb(verb)
1503
self.reset_smart_call_log()
1504
self.assertEqual(None,
1505
bzrdir._get_config().get_option('default_stack_on'))
1506
self.assertLength(3, self.hpss_calls)
1509
class TestTransportIsReadonly(tests.TestCase):
1511
def test_true(self):
1512
client = FakeClient()
1513
client.add_success_response('yes')
1514
transport = RemoteTransport('bzr://example.com/', medium=False,
1516
self.assertEqual(True, transport.is_readonly())
1518
[('call', 'Transport.is_readonly', ())],
1521
def test_false(self):
1522
client = FakeClient()
1523
client.add_success_response('no')
1524
transport = RemoteTransport('bzr://example.com/', medium=False,
1526
self.assertEqual(False, transport.is_readonly())
1528
[('call', 'Transport.is_readonly', ())],
1531
def test_error_from_old_server(self):
1532
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1534
Clients should treat it as a "no" response, because is_readonly is only
1535
advisory anyway (a transport could be read-write, but then the
1536
underlying filesystem could be readonly anyway).
1538
client = FakeClient()
1539
client.add_unknown_method_response('Transport.is_readonly')
1540
transport = RemoteTransport('bzr://example.com/', medium=False,
1542
self.assertEqual(False, transport.is_readonly())
1544
[('call', 'Transport.is_readonly', ())],
1548
class TestTransportMkdir(tests.TestCase):
1550
def test_permissiondenied(self):
1551
client = FakeClient()
1552
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1553
transport = RemoteTransport('bzr://example.com/', medium=False,
1555
exc = self.assertRaises(
1556
errors.PermissionDenied, transport.mkdir, 'client path')
1557
expected_error = errors.PermissionDenied('/client path', 'extra')
1558
self.assertEqual(expected_error, exc)
1561
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1563
def test_defaults_to_getuser(self):
1564
t = RemoteSSHTransport('bzr+ssh://example.com')
1565
self.assertIs(getpass.getuser(), t._get_credentials()[0])
1567
def test_uses_authentication_config(self):
1568
conf = config.AuthenticationConfig()
1569
conf._get_config().update(
1570
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1573
t = RemoteSSHTransport('bzr+ssh://example.com')
1574
self.assertEqual('bar', t._get_credentials()[0])
1577
class TestRemoteRepository(TestRemote):
1578
"""Base for testing RemoteRepository protocol usage.
1580
These tests contain frozen requests and responses. We want any changes to
1581
what is sent or expected to be require a thoughtful update to these tests
1582
because they might break compatibility with different-versioned servers.
1585
def setup_fake_client_and_repository(self, transport_path):
1586
"""Create the fake client and repository for testing with.
1588
There's no real server here; we just have canned responses sent
1591
:param transport_path: Path below the root of the MemoryTransport
1592
where the repository will be created.
1594
transport = MemoryTransport()
1595
transport.mkdir(transport_path)
1596
client = FakeClient(transport.base)
1597
transport = transport.clone(transport_path)
1598
# we do not want bzrdir to make any remote calls
1599
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1601
repo = RemoteRepository(bzrdir, None, _client=client)
1605
class TestRepositoryFormat(TestRemoteRepository):
1607
def test_fast_delta(self):
1608
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1609
true_format = RemoteRepositoryFormat()
1610
true_format._network_name = true_name
1611
self.assertEqual(True, true_format.fast_deltas)
1612
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1613
false_format = RemoteRepositoryFormat()
1614
false_format._network_name = false_name
1615
self.assertEqual(False, false_format.fast_deltas)
1618
class TestRepositoryGatherStats(TestRemoteRepository):
1620
def test_revid_none(self):
1621
# ('ok',), body with revisions and size
1622
transport_path = 'quack'
1623
repo, client = self.setup_fake_client_and_repository(transport_path)
1624
client.add_success_response_with_body(
1625
'revisions: 2\nsize: 18\n', 'ok')
1626
result = repo.gather_stats(None)
1628
[('call_expecting_body', 'Repository.gather_stats',
1629
('quack/','','no'))],
1631
self.assertEqual({'revisions': 2, 'size': 18}, result)
1633
def test_revid_no_committers(self):
1634
# ('ok',), body without committers
1635
body = ('firstrev: 123456.300 3600\n'
1636
'latestrev: 654231.400 0\n'
1639
transport_path = 'quick'
1640
revid = u'\xc8'.encode('utf8')
1641
repo, client = self.setup_fake_client_and_repository(transport_path)
1642
client.add_success_response_with_body(body, 'ok')
1643
result = repo.gather_stats(revid)
1645
[('call_expecting_body', 'Repository.gather_stats',
1646
('quick/', revid, 'no'))],
1648
self.assertEqual({'revisions': 2, 'size': 18,
1649
'firstrev': (123456.300, 3600),
1650
'latestrev': (654231.400, 0),},
1653
def test_revid_with_committers(self):
1654
# ('ok',), body with committers
1655
body = ('committers: 128\n'
1656
'firstrev: 123456.300 3600\n'
1657
'latestrev: 654231.400 0\n'
1660
transport_path = 'buick'
1661
revid = u'\xc8'.encode('utf8')
1662
repo, client = self.setup_fake_client_and_repository(transport_path)
1663
client.add_success_response_with_body(body, 'ok')
1664
result = repo.gather_stats(revid, True)
1666
[('call_expecting_body', 'Repository.gather_stats',
1667
('buick/', revid, 'yes'))],
1669
self.assertEqual({'revisions': 2, 'size': 18,
1671
'firstrev': (123456.300, 3600),
1672
'latestrev': (654231.400, 0),},
1676
class TestRepositoryGetGraph(TestRemoteRepository):
1678
def test_get_graph(self):
1679
# get_graph returns a graph with a custom parents provider.
1680
transport_path = 'quack'
1681
repo, client = self.setup_fake_client_and_repository(transport_path)
1682
graph = repo.get_graph()
1683
self.assertNotEqual(graph._parents_provider, repo)
1686
class TestRepositoryGetParentMap(TestRemoteRepository):
1688
def test_get_parent_map_caching(self):
1689
# get_parent_map returns from cache until unlock()
1690
# setup a reponse with two revisions
1691
r1 = u'\u0e33'.encode('utf8')
1692
r2 = u'\u0dab'.encode('utf8')
1693
lines = [' '.join([r2, r1]), r1]
1694
encoded_body = bz2.compress('\n'.join(lines))
1696
transport_path = 'quack'
1697
repo, client = self.setup_fake_client_and_repository(transport_path)
1698
client.add_success_response_with_body(encoded_body, 'ok')
1699
client.add_success_response_with_body(encoded_body, 'ok')
1701
graph = repo.get_graph()
1702
parents = graph.get_parent_map([r2])
1703
self.assertEqual({r2: (r1,)}, parents)
1704
# locking and unlocking deeper should not reset
1707
parents = graph.get_parent_map([r1])
1708
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1710
[('call_with_body_bytes_expecting_body',
1711
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1715
# now we call again, and it should use the second response.
1717
graph = repo.get_graph()
1718
parents = graph.get_parent_map([r1])
1719
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1721
[('call_with_body_bytes_expecting_body',
1722
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1724
('call_with_body_bytes_expecting_body',
1725
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1731
def test_get_parent_map_reconnects_if_unknown_method(self):
1732
transport_path = 'quack'
1733
rev_id = 'revision-id'
1734
repo, client = self.setup_fake_client_and_repository(transport_path)
1735
client.add_unknown_method_response('Repository.get_parent_map')
1736
client.add_success_response_with_body(rev_id, 'ok')
1737
self.assertFalse(client._medium._is_remote_before((1, 2)))
1738
parents = repo.get_parent_map([rev_id])
1740
[('call_with_body_bytes_expecting_body',
1741
'Repository.get_parent_map', ('quack/', 'include-missing:',
1743
('disconnect medium',),
1744
('call_expecting_body', 'Repository.get_revision_graph',
1747
# The medium is now marked as being connected to an older server
1748
self.assertTrue(client._medium._is_remote_before((1, 2)))
1749
self.assertEqual({rev_id: ('null:',)}, parents)
1751
def test_get_parent_map_fallback_parentless_node(self):
1752
"""get_parent_map falls back to get_revision_graph on old servers. The
1753
results from get_revision_graph are tweaked to match the get_parent_map
1756
Specifically, a {key: ()} result from get_revision_graph means "no
1757
parents" for that key, which in get_parent_map results should be
1758
represented as {key: ('null:',)}.
1760
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1762
rev_id = 'revision-id'
1763
transport_path = 'quack'
1764
repo, client = self.setup_fake_client_and_repository(transport_path)
1765
client.add_success_response_with_body(rev_id, 'ok')
1766
client._medium._remember_remote_is_before((1, 2))
1767
parents = repo.get_parent_map([rev_id])
1769
[('call_expecting_body', 'Repository.get_revision_graph',
1772
self.assertEqual({rev_id: ('null:',)}, parents)
1774
def test_get_parent_map_unexpected_response(self):
1775
repo, client = self.setup_fake_client_and_repository('path')
1776
client.add_success_response('something unexpected!')
1778
errors.UnexpectedSmartServerResponse,
1779
repo.get_parent_map, ['a-revision-id'])
1781
def test_get_parent_map_negative_caches_missing_keys(self):
1782
self.setup_smart_server_with_call_log()
1783
repo = self.make_repository('foo')
1784
self.assertIsInstance(repo, RemoteRepository)
1786
self.addCleanup(repo.unlock)
1787
self.reset_smart_call_log()
1788
graph = repo.get_graph()
1789
self.assertEqual({},
1790
graph.get_parent_map(['some-missing', 'other-missing']))
1791
self.assertLength(1, self.hpss_calls)
1792
# No call if we repeat this
1793
self.reset_smart_call_log()
1794
graph = repo.get_graph()
1795
self.assertEqual({},
1796
graph.get_parent_map(['some-missing', 'other-missing']))
1797
self.assertLength(0, self.hpss_calls)
1798
# Asking for more unknown keys makes a request.
1799
self.reset_smart_call_log()
1800
graph = repo.get_graph()
1801
self.assertEqual({},
1802
graph.get_parent_map(['some-missing', 'other-missing',
1804
self.assertLength(1, self.hpss_calls)
1806
def disableExtraResults(self):
1807
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1808
SmartServerRepositoryGetParentMap.no_extra_results = True
1810
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1811
self.addCleanup(reset_values)
1813
def test_null_cached_missing_and_stop_key(self):
1814
self.setup_smart_server_with_call_log()
1815
# Make a branch with a single revision.
1816
builder = self.make_branch_builder('foo')
1817
builder.start_series()
1818
builder.build_snapshot('first', None, [
1819
('add', ('', 'root-id', 'directory', ''))])
1820
builder.finish_series()
1821
branch = builder.get_branch()
1822
repo = branch.repository
1823
self.assertIsInstance(repo, RemoteRepository)
1824
# Stop the server from sending extra results.
1825
self.disableExtraResults()
1827
self.addCleanup(repo.unlock)
1828
self.reset_smart_call_log()
1829
graph = repo.get_graph()
1830
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1831
# 'first' it will be a candidate for the stop_keys of subsequent
1832
# requests, and because 'null:' was queried but not returned it will be
1833
# cached as missing.
1834
self.assertEqual({'first': ('null:',)},
1835
graph.get_parent_map(['first', 'null:']))
1836
# Now query for another key. This request will pass along a recipe of
1837
# start and stop keys describing the already cached results, and this
1838
# recipe's revision count must be correct (or else it will trigger an
1839
# error from the server).
1840
self.assertEqual({}, graph.get_parent_map(['another-key']))
1841
# This assertion guards against disableExtraResults silently failing to
1842
# work, thus invalidating the test.
1843
self.assertLength(2, self.hpss_calls)
1845
def test_get_parent_map_gets_ghosts_from_result(self):
1846
# asking for a revision should negatively cache close ghosts in its
1848
self.setup_smart_server_with_call_log()
1849
tree = self.make_branch_and_memory_tree('foo')
1852
builder = treebuilder.TreeBuilder()
1853
builder.start_tree(tree)
1855
builder.finish_tree()
1856
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1857
rev_id = tree.commit('')
1861
self.addCleanup(tree.unlock)
1862
repo = tree.branch.repository
1863
self.assertIsInstance(repo, RemoteRepository)
1865
repo.get_parent_map([rev_id])
1866
self.reset_smart_call_log()
1867
# Now asking for rev_id's ghost parent should not make calls
1868
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1869
self.assertLength(0, self.hpss_calls)
1872
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1874
def test_allows_new_revisions(self):
1875
"""get_parent_map's results can be updated by commit."""
1876
smart_server = server.SmartTCPServer_for_testing()
1877
smart_server.setUp()
1878
self.addCleanup(smart_server.tearDown)
1879
self.make_branch('branch')
1880
branch = Branch.open(smart_server.get_url() + '/branch')
1881
tree = branch.create_checkout('tree', lightweight=True)
1883
self.addCleanup(tree.unlock)
1884
graph = tree.branch.repository.get_graph()
1885
# This provides an opportunity for the missing rev-id to be cached.
1886
self.assertEqual({}, graph.get_parent_map(['rev1']))
1887
tree.commit('message', rev_id='rev1')
1888
graph = tree.branch.repository.get_graph()
1889
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1892
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1894
def test_null_revision(self):
1895
# a null revision has the predictable result {}, we should have no wire
1896
# traffic when calling it with this argument
1897
transport_path = 'empty'
1898
repo, client = self.setup_fake_client_and_repository(transport_path)
1899
client.add_success_response('notused')
1900
# actual RemoteRepository.get_revision_graph is gone, but there's an
1901
# equivalent private method for testing
1902
result = repo._get_revision_graph(NULL_REVISION)
1903
self.assertEqual([], client._calls)
1904
self.assertEqual({}, result)
1906
def test_none_revision(self):
1907
# with none we want the entire graph
1908
r1 = u'\u0e33'.encode('utf8')
1909
r2 = u'\u0dab'.encode('utf8')
1910
lines = [' '.join([r2, r1]), r1]
1911
encoded_body = '\n'.join(lines)
1913
transport_path = 'sinhala'
1914
repo, client = self.setup_fake_client_and_repository(transport_path)
1915
client.add_success_response_with_body(encoded_body, 'ok')
1916
# actual RemoteRepository.get_revision_graph is gone, but there's an
1917
# equivalent private method for testing
1918
result = repo._get_revision_graph(None)
1920
[('call_expecting_body', 'Repository.get_revision_graph',
1923
self.assertEqual({r1: (), r2: (r1, )}, result)
1925
def test_specific_revision(self):
1926
# with a specific revision we want the graph for that
1927
# with none we want the entire graph
1928
r11 = u'\u0e33'.encode('utf8')
1929
r12 = u'\xc9'.encode('utf8')
1930
r2 = u'\u0dab'.encode('utf8')
1931
lines = [' '.join([r2, r11, r12]), r11, r12]
1932
encoded_body = '\n'.join(lines)
1934
transport_path = 'sinhala'
1935
repo, client = self.setup_fake_client_and_repository(transport_path)
1936
client.add_success_response_with_body(encoded_body, 'ok')
1937
result = repo._get_revision_graph(r2)
1939
[('call_expecting_body', 'Repository.get_revision_graph',
1942
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1944
def test_no_such_revision(self):
1946
transport_path = 'sinhala'
1947
repo, client = self.setup_fake_client_and_repository(transport_path)
1948
client.add_error_response('nosuchrevision', revid)
1949
# also check that the right revision is reported in the error
1950
self.assertRaises(errors.NoSuchRevision,
1951
repo._get_revision_graph, revid)
1953
[('call_expecting_body', 'Repository.get_revision_graph',
1954
('sinhala/', revid))],
1957
def test_unexpected_error(self):
1959
transport_path = 'sinhala'
1960
repo, client = self.setup_fake_client_and_repository(transport_path)
1961
client.add_error_response('AnUnexpectedError')
1962
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1963
repo._get_revision_graph, revid)
1964
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1967
class TestRepositoryIsShared(TestRemoteRepository):
1969
def test_is_shared(self):
1970
# ('yes', ) for Repository.is_shared -> 'True'.
1971
transport_path = 'quack'
1972
repo, client = self.setup_fake_client_and_repository(transport_path)
1973
client.add_success_response('yes')
1974
result = repo.is_shared()
1976
[('call', 'Repository.is_shared', ('quack/',))],
1978
self.assertEqual(True, result)
1980
def test_is_not_shared(self):
1981
# ('no', ) for Repository.is_shared -> 'False'.
1982
transport_path = 'qwack'
1983
repo, client = self.setup_fake_client_and_repository(transport_path)
1984
client.add_success_response('no')
1985
result = repo.is_shared()
1987
[('call', 'Repository.is_shared', ('qwack/',))],
1989
self.assertEqual(False, result)
1992
class TestRepositoryLockWrite(TestRemoteRepository):
1994
def test_lock_write(self):
1995
transport_path = 'quack'
1996
repo, client = self.setup_fake_client_and_repository(transport_path)
1997
client.add_success_response('ok', 'a token')
1998
result = repo.lock_write()
2000
[('call', 'Repository.lock_write', ('quack/', ''))],
2002
self.assertEqual('a token', result)
2004
def test_lock_write_already_locked(self):
2005
transport_path = 'quack'
2006
repo, client = self.setup_fake_client_and_repository(transport_path)
2007
client.add_error_response('LockContention')
2008
self.assertRaises(errors.LockContention, repo.lock_write)
2010
[('call', 'Repository.lock_write', ('quack/', ''))],
2013
def test_lock_write_unlockable(self):
2014
transport_path = 'quack'
2015
repo, client = self.setup_fake_client_and_repository(transport_path)
2016
client.add_error_response('UnlockableTransport')
2017
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2019
[('call', 'Repository.lock_write', ('quack/', ''))],
2023
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2025
def test_backwards_compat(self):
2026
self.setup_smart_server_with_call_log()
2027
repo = self.make_repository('.')
2028
self.reset_smart_call_log()
2029
verb = 'Repository.set_make_working_trees'
2030
self.disable_verb(verb)
2031
repo.set_make_working_trees(True)
2032
call_count = len([call for call in self.hpss_calls if
2033
call.call.method == verb])
2034
self.assertEqual(1, call_count)
2036
def test_current(self):
2037
transport_path = 'quack'
2038
repo, client = self.setup_fake_client_and_repository(transport_path)
2039
client.add_expected_call(
2040
'Repository.set_make_working_trees', ('quack/', 'True'),
2042
client.add_expected_call(
2043
'Repository.set_make_working_trees', ('quack/', 'False'),
2045
repo.set_make_working_trees(True)
2046
repo.set_make_working_trees(False)
2049
class TestRepositoryUnlock(TestRemoteRepository):
2051
def test_unlock(self):
2052
transport_path = 'quack'
2053
repo, client = self.setup_fake_client_and_repository(transport_path)
2054
client.add_success_response('ok', 'a token')
2055
client.add_success_response('ok')
2059
[('call', 'Repository.lock_write', ('quack/', '')),
2060
('call', 'Repository.unlock', ('quack/', 'a token'))],
2063
def test_unlock_wrong_token(self):
2064
# If somehow the token is wrong, unlock will raise TokenMismatch.
2065
transport_path = 'quack'
2066
repo, client = self.setup_fake_client_and_repository(transport_path)
2067
client.add_success_response('ok', 'a token')
2068
client.add_error_response('TokenMismatch')
2070
self.assertRaises(errors.TokenMismatch, repo.unlock)
2073
class TestRepositoryHasRevision(TestRemoteRepository):
2075
def test_none(self):
2076
# repo.has_revision(None) should not cause any traffic.
2077
transport_path = 'quack'
2078
repo, client = self.setup_fake_client_and_repository(transport_path)
2080
# The null revision is always there, so has_revision(None) == True.
2081
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2083
# The remote repo shouldn't be accessed.
2084
self.assertEqual([], client._calls)
2087
class TestRepositoryInsertStream(TestRemoteRepository):
2089
def test_unlocked_repo(self):
2090
transport_path = 'quack'
2091
repo, client = self.setup_fake_client_and_repository(transport_path)
2092
client.add_expected_call(
2093
'Repository.insert_stream', ('quack/', ''),
2095
client.add_expected_call(
2096
'Repository.insert_stream', ('quack/', ''),
2098
sink = repo._get_sink()
2099
fmt = repository.RepositoryFormat.get_default_format()
2100
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2101
self.assertEqual([], resume_tokens)
2102
self.assertEqual(set(), missing_keys)
2103
client.finished_test()
2105
def test_locked_repo_with_no_lock_token(self):
2106
transport_path = 'quack'
2107
repo, client = self.setup_fake_client_and_repository(transport_path)
2108
client.add_expected_call(
2109
'Repository.lock_write', ('quack/', ''),
2110
'success', ('ok', ''))
2111
client.add_expected_call(
2112
'Repository.insert_stream', ('quack/', ''),
2114
client.add_expected_call(
2115
'Repository.insert_stream', ('quack/', ''),
2118
sink = repo._get_sink()
2119
fmt = repository.RepositoryFormat.get_default_format()
2120
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2121
self.assertEqual([], resume_tokens)
2122
self.assertEqual(set(), missing_keys)
2123
client.finished_test()
2125
def test_locked_repo_with_lock_token(self):
2126
transport_path = 'quack'
2127
repo, client = self.setup_fake_client_and_repository(transport_path)
2128
client.add_expected_call(
2129
'Repository.lock_write', ('quack/', ''),
2130
'success', ('ok', 'a token'))
2131
client.add_expected_call(
2132
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2134
client.add_expected_call(
2135
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2138
sink = repo._get_sink()
2139
fmt = repository.RepositoryFormat.get_default_format()
2140
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2141
self.assertEqual([], resume_tokens)
2142
self.assertEqual(set(), missing_keys)
2143
client.finished_test()
2146
class TestRepositoryTarball(TestRemoteRepository):
2148
# This is a canned tarball reponse we can validate against
2150
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2151
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2152
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2153
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2154
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2155
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2156
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2157
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2158
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2159
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2160
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2161
'nWQ7QH/F3JFOFCQ0aSPfA='
2164
def test_repository_tarball(self):
2165
# Test that Repository.tarball generates the right operations
2166
transport_path = 'repo'
2167
expected_calls = [('call_expecting_body', 'Repository.tarball',
2168
('repo/', 'bz2',),),
2170
repo, client = self.setup_fake_client_and_repository(transport_path)
2171
client.add_success_response_with_body(self.tarball_content, 'ok')
2172
# Now actually ask for the tarball
2173
tarball_file = repo._get_tarball('bz2')
2175
self.assertEqual(expected_calls, client._calls)
2176
self.assertEqual(self.tarball_content, tarball_file.read())
2178
tarball_file.close()
2181
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2182
"""RemoteRepository.copy_content_into optimizations"""
2184
def test_copy_content_remote_to_local(self):
2185
self.transport_server = server.SmartTCPServer_for_testing
2186
src_repo = self.make_repository('repo1')
2187
src_repo = repository.Repository.open(self.get_url('repo1'))
2188
# At the moment the tarball-based copy_content_into can't write back
2189
# into a smart server. It would be good if it could upload the
2190
# tarball; once that works we'd have to create repositories of
2191
# different formats. -- mbp 20070410
2192
dest_url = self.get_vfs_only_url('repo2')
2193
dest_bzrdir = BzrDir.create(dest_url)
2194
dest_repo = dest_bzrdir.create_repository()
2195
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2196
self.assertTrue(isinstance(src_repo, RemoteRepository))
2197
src_repo.copy_content_into(dest_repo)
2200
class _StubRealPackRepository(object):
2202
def __init__(self, calls):
2204
self._pack_collection = _StubPackCollection(calls)
2206
def is_in_write_group(self):
2209
def refresh_data(self):
2210
self.calls.append(('pack collection reload_pack_names',))
2213
class _StubPackCollection(object):
2215
def __init__(self, calls):
2219
self.calls.append(('pack collection autopack',))
2222
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2223
"""Tests for RemoteRepository.autopack implementation."""
2226
"""When the server returns 'ok' and there's no _real_repository, then
2227
nothing else happens: the autopack method is done.
2229
transport_path = 'quack'
2230
repo, client = self.setup_fake_client_and_repository(transport_path)
2231
client.add_expected_call(
2232
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2234
client.finished_test()
2236
def test_ok_with_real_repo(self):
2237
"""When the server returns 'ok' and there is a _real_repository, then
2238
the _real_repository's reload_pack_name's method will be called.
2240
transport_path = 'quack'
2241
repo, client = self.setup_fake_client_and_repository(transport_path)
2242
client.add_expected_call(
2243
'PackRepository.autopack', ('quack/',),
2245
repo._real_repository = _StubRealPackRepository(client._calls)
2248
[('call', 'PackRepository.autopack', ('quack/',)),
2249
('pack collection reload_pack_names',)],
2252
def test_backwards_compatibility(self):
2253
"""If the server does not recognise the PackRepository.autopack verb,
2254
fallback to the real_repository's implementation.
2256
transport_path = 'quack'
2257
repo, client = self.setup_fake_client_and_repository(transport_path)
2258
client.add_unknown_method_response('PackRepository.autopack')
2259
def stub_ensure_real():
2260
client._calls.append(('_ensure_real',))
2261
repo._real_repository = _StubRealPackRepository(client._calls)
2262
repo._ensure_real = stub_ensure_real
2265
[('call', 'PackRepository.autopack', ('quack/',)),
2267
('pack collection autopack',)],
2271
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2272
"""Base class for unit tests for bzrlib.remote._translate_error."""
2274
def translateTuple(self, error_tuple, **context):
2275
"""Call _translate_error with an ErrorFromSmartServer built from the
2278
:param error_tuple: A tuple of a smart server response, as would be
2279
passed to an ErrorFromSmartServer.
2280
:kwargs context: context items to call _translate_error with.
2282
:returns: The error raised by _translate_error.
2284
# Raise the ErrorFromSmartServer before passing it as an argument,
2285
# because _translate_error may need to re-raise it with a bare 'raise'
2287
server_error = errors.ErrorFromSmartServer(error_tuple)
2288
translated_error = self.translateErrorFromSmartServer(
2289
server_error, **context)
2290
return translated_error
2292
def translateErrorFromSmartServer(self, error_object, **context):
2293
"""Like translateTuple, but takes an already constructed
2294
ErrorFromSmartServer rather than a tuple.
2298
except errors.ErrorFromSmartServer, server_error:
2299
translated_error = self.assertRaises(
2300
errors.BzrError, remote._translate_error, server_error,
2302
return translated_error
2305
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2306
"""Unit tests for bzrlib.remote._translate_error.
2308
Given an ErrorFromSmartServer (which has an error tuple from a smart
2309
server) and some context, _translate_error raises more specific errors from
2312
This test case covers the cases where _translate_error succeeds in
2313
translating an ErrorFromSmartServer to something better. See
2314
TestErrorTranslationRobustness for other cases.
2317
def test_NoSuchRevision(self):
2318
branch = self.make_branch('')
2320
translated_error = self.translateTuple(
2321
('NoSuchRevision', revid), branch=branch)
2322
expected_error = errors.NoSuchRevision(branch, revid)
2323
self.assertEqual(expected_error, translated_error)
2325
def test_nosuchrevision(self):
2326
repository = self.make_repository('')
2328
translated_error = self.translateTuple(
2329
('nosuchrevision', revid), repository=repository)
2330
expected_error = errors.NoSuchRevision(repository, revid)
2331
self.assertEqual(expected_error, translated_error)
2333
def test_nobranch(self):
2334
bzrdir = self.make_bzrdir('')
2335
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2336
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2337
self.assertEqual(expected_error, translated_error)
2339
def test_LockContention(self):
2340
translated_error = self.translateTuple(('LockContention',))
2341
expected_error = errors.LockContention('(remote lock)')
2342
self.assertEqual(expected_error, translated_error)
2344
def test_UnlockableTransport(self):
2345
bzrdir = self.make_bzrdir('')
2346
translated_error = self.translateTuple(
2347
('UnlockableTransport',), bzrdir=bzrdir)
2348
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2349
self.assertEqual(expected_error, translated_error)
2351
def test_LockFailed(self):
2352
lock = 'str() of a server lock'
2353
why = 'str() of why'
2354
translated_error = self.translateTuple(('LockFailed', lock, why))
2355
expected_error = errors.LockFailed(lock, why)
2356
self.assertEqual(expected_error, translated_error)
2358
def test_TokenMismatch(self):
2359
token = 'a lock token'
2360
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2361
expected_error = errors.TokenMismatch(token, '(remote token)')
2362
self.assertEqual(expected_error, translated_error)
2364
def test_Diverged(self):
2365
branch = self.make_branch('a')
2366
other_branch = self.make_branch('b')
2367
translated_error = self.translateTuple(
2368
('Diverged',), branch=branch, other_branch=other_branch)
2369
expected_error = errors.DivergedBranches(branch, other_branch)
2370
self.assertEqual(expected_error, translated_error)
2372
def test_ReadError_no_args(self):
2374
translated_error = self.translateTuple(('ReadError',), path=path)
2375
expected_error = errors.ReadError(path)
2376
self.assertEqual(expected_error, translated_error)
2378
def test_ReadError(self):
2380
translated_error = self.translateTuple(('ReadError', path))
2381
expected_error = errors.ReadError(path)
2382
self.assertEqual(expected_error, translated_error)
2384
def test_PermissionDenied_no_args(self):
2386
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2387
expected_error = errors.PermissionDenied(path)
2388
self.assertEqual(expected_error, translated_error)
2390
def test_PermissionDenied_one_arg(self):
2392
translated_error = self.translateTuple(('PermissionDenied', path))
2393
expected_error = errors.PermissionDenied(path)
2394
self.assertEqual(expected_error, translated_error)
2396
def test_PermissionDenied_one_arg_and_context(self):
2397
"""Given a choice between a path from the local context and a path on
2398
the wire, _translate_error prefers the path from the local context.
2400
local_path = 'local path'
2401
remote_path = 'remote path'
2402
translated_error = self.translateTuple(
2403
('PermissionDenied', remote_path), path=local_path)
2404
expected_error = errors.PermissionDenied(local_path)
2405
self.assertEqual(expected_error, translated_error)
2407
def test_PermissionDenied_two_args(self):
2409
extra = 'a string with extra info'
2410
translated_error = self.translateTuple(
2411
('PermissionDenied', path, extra))
2412
expected_error = errors.PermissionDenied(path, extra)
2413
self.assertEqual(expected_error, translated_error)
2416
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2417
"""Unit tests for bzrlib.remote._translate_error's robustness.
2419
TestErrorTranslationSuccess is for cases where _translate_error can
2420
translate successfully. This class about how _translate_err behaves when
2421
it fails to translate: it re-raises the original error.
2424
def test_unrecognised_server_error(self):
2425
"""If the error code from the server is not recognised, the original
2426
ErrorFromSmartServer is propagated unmodified.
2428
error_tuple = ('An unknown error tuple',)
2429
server_error = errors.ErrorFromSmartServer(error_tuple)
2430
translated_error = self.translateErrorFromSmartServer(server_error)
2431
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2432
self.assertEqual(expected_error, translated_error)
2434
def test_context_missing_a_key(self):
2435
"""In case of a bug in the client, or perhaps an unexpected response
2436
from a server, _translate_error returns the original error tuple from
2437
the server and mutters a warning.
2439
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2440
# in the context dict. So let's give it an empty context dict instead
2441
# to exercise its error recovery.
2443
error_tuple = ('NoSuchRevision', 'revid')
2444
server_error = errors.ErrorFromSmartServer(error_tuple)
2445
translated_error = self.translateErrorFromSmartServer(server_error)
2446
self.assertEqual(server_error, translated_error)
2447
# In addition to re-raising ErrorFromSmartServer, some debug info has
2448
# been muttered to the log file for developer to look at.
2449
self.assertContainsRe(
2450
self._get_log(keep_log_file=True),
2451
"Missing key 'branch' in context")
2453
def test_path_missing(self):
2454
"""Some translations (PermissionDenied, ReadError) can determine the
2455
'path' variable from either the wire or the local context. If neither
2456
has it, then an error is raised.
2458
error_tuple = ('ReadError',)
2459
server_error = errors.ErrorFromSmartServer(error_tuple)
2460
translated_error = self.translateErrorFromSmartServer(server_error)
2461
self.assertEqual(server_error, translated_error)
2462
# In addition to re-raising ErrorFromSmartServer, some debug info has
2463
# been muttered to the log file for developer to look at.
2464
self.assertContainsRe(
2465
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2468
class TestStacking(tests.TestCaseWithTransport):
2469
"""Tests for operations on stacked remote repositories.
2471
The underlying format type must support stacking.
2474
def test_access_stacked_remote(self):
2475
# based on <http://launchpad.net/bugs/261315>
2476
# make a branch stacked on another repository containing an empty
2477
# revision, then open it over hpss - we should be able to see that
2479
base_transport = self.get_transport()
2480
base_builder = self.make_branch_builder('base', format='1.9')
2481
base_builder.start_series()
2482
base_revid = base_builder.build_snapshot('rev-id', None,
2483
[('add', ('', None, 'directory', None))],
2485
base_builder.finish_series()
2486
stacked_branch = self.make_branch('stacked', format='1.9')
2487
stacked_branch.set_stacked_on_url('../base')
2488
# start a server looking at this
2489
smart_server = server.SmartTCPServer_for_testing()
2490
smart_server.setUp()
2491
self.addCleanup(smart_server.tearDown)
2492
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2493
# can get its branch and repository
2494
remote_branch = remote_bzrdir.open_branch()
2495
remote_repo = remote_branch.repository
2496
remote_repo.lock_read()
2498
# it should have an appropriate fallback repository, which should also
2499
# be a RemoteRepository
2500
self.assertLength(1, remote_repo._fallback_repositories)
2501
self.assertIsInstance(remote_repo._fallback_repositories[0],
2503
# and it has the revision committed to the underlying repository;
2504
# these have varying implementations so we try several of them
2505
self.assertTrue(remote_repo.has_revisions([base_revid]))
2506
self.assertTrue(remote_repo.has_revision(base_revid))
2507
self.assertEqual(remote_repo.get_revision(base_revid).message,
2510
remote_repo.unlock()
2512
def prepare_stacked_remote_branch(self):
2513
"""Get stacked_upon and stacked branches with content in each."""
2514
self.setup_smart_server_with_call_log()
2515
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2516
tree1.commit('rev1', rev_id='rev1')
2517
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2518
).open_workingtree()
2519
tree2.commit('local changes make me feel good.')
2520
branch2 = Branch.open(self.get_url('tree2'))
2522
self.addCleanup(branch2.unlock)
2523
return tree1.branch, branch2
2525
def test_stacked_get_parent_map(self):
2526
# the public implementation of get_parent_map obeys stacking
2527
_, branch = self.prepare_stacked_remote_branch()
2528
repo = branch.repository
2529
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2531
def test_unstacked_get_parent_map(self):
2532
# _unstacked_provider.get_parent_map ignores stacking
2533
_, branch = self.prepare_stacked_remote_branch()
2534
provider = branch.repository._unstacked_provider
2535
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2537
def fetch_stream_to_rev_order(self, stream):
2539
for kind, substream in stream:
2540
if not kind == 'revisions':
2543
for content in substream:
2544
result.append(content.key[-1])
2547
def get_ordered_revs(self, format, order):
2548
"""Get a list of the revisions in a stream to format format.
2550
:param format: The format of the target.
2551
:param order: the order that target should have requested.
2552
:result: The revision ids in the stream, in the order seen,
2553
the topological order of revisions in the source.
2555
unordered_format = bzrdir.format_registry.get(format)()
2556
target_repository_format = unordered_format.repository_format
2558
self.assertEqual(order, target_repository_format._fetch_order)
2559
trunk, stacked = self.prepare_stacked_remote_branch()
2560
source = stacked.repository._get_source(target_repository_format)
2561
tip = stacked.last_revision()
2562
revs = stacked.repository.get_ancestry(tip)
2563
search = graph.PendingAncestryResult([tip], stacked.repository)
2564
self.reset_smart_call_log()
2565
stream = source.get_stream(search)
2568
# We trust that if a revision is in the stream the rest of the new
2569
# content for it is too, as per our main fetch tests; here we are
2570
# checking that the revisions are actually included at all, and their
2572
return self.fetch_stream_to_rev_order(stream), revs
2574
def test_stacked_get_stream_unordered(self):
2575
# Repository._get_source.get_stream() from a stacked repository with
2576
# unordered yields the full data from both stacked and stacked upon
2578
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2579
self.assertEqual(set(expected_revs), set(rev_ord))
2580
# Getting unordered results should have made a streaming data request
2581
# from the server, then one from the backing branch.
2582
self.assertLength(2, self.hpss_calls)
2584
def test_stacked_get_stream_topological(self):
2585
# Repository._get_source.get_stream() from a stacked repository with
2586
# topological sorting yields the full data from both stacked and
2587
# stacked upon sources in topological order.
2588
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2589
self.assertEqual(expected_revs, rev_ord)
2590
# Getting topological sort requires VFS calls still
2591
self.assertLength(12, self.hpss_calls)
2593
def test_stacked_get_stream_groupcompress(self):
2594
# Repository._get_source.get_stream() from a stacked repository with
2595
# groupcompress sorting yields the full data from both stacked and
2596
# stacked upon sources in groupcompress order.
2597
raise tests.TestSkipped('No groupcompress ordered format available')
2598
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2599
self.assertEqual(expected_revs, reversed(rev_ord))
2600
# Getting unordered results should have made a streaming data request
2601
# from the backing branch, and one from the stacked on branch.
2602
self.assertLength(2, self.hpss_calls)
2605
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2608
super(TestRemoteBranchEffort, self).setUp()
2609
# Create a smart server that publishes whatever the backing VFS server
2611
self.smart_server = server.SmartTCPServer_for_testing()
2612
self.smart_server.setUp(self.get_server())
2613
self.addCleanup(self.smart_server.tearDown)
2614
# Log all HPSS calls into self.hpss_calls.
2615
_SmartClient.hooks.install_named_hook(
2616
'call', self.capture_hpss_call, None)
2617
self.hpss_calls = []
2619
def capture_hpss_call(self, params):
2620
self.hpss_calls.append(params.method)
2622
def test_copy_content_into_avoids_revision_history(self):
2623
local = self.make_branch('local')
2624
remote_backing_tree = self.make_branch_and_tree('remote')
2625
remote_backing_tree.commit("Commit.")
2626
remote_branch_url = self.smart_server.get_url() + 'remote'
2627
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2628
local.repository.fetch(remote_branch.repository)
2629
self.hpss_calls = []
2630
remote_branch.copy_content_into(local)
2631
self.assertFalse('Branch.revision_history' in self.hpss_calls)