1
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for remote bzrdir/branch/repo/etc
19
These are proxy objects which act on remote objects by sending messages
20
through a smart client. The proxies are to be created when attempting to open
21
the object given a transport that supports smartserver rpc operations.
23
These tests correspond to tests.test_smart, which exercises the server side.
27
from cStringIO import StringIO
42
from bzrlib.branch import Branch
43
from bzrlib.bzrdir import BzrDir, BzrDirFormat
44
from bzrlib.remote import (
50
RemoteRepositoryFormat,
52
from bzrlib.repofmt import groupcompress_repo, pack_repo
53
from bzrlib.revision import NULL_REVISION
54
from bzrlib.smart import server, medium
55
from bzrlib.smart.client import _SmartClient
56
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
57
from bzrlib.tests import (
59
split_suite_by_condition,
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)
157
def test_remote_branch_set_append_revisions_only(self):
158
# Make a format 1.9 branch, which supports append_revisions_only
159
branch = self.make_branch('branch', format='1.9')
160
config = branch.get_config()
161
branch.set_append_revisions_only(True)
163
'True', config.get_user_option('append_revisions_only'))
164
branch.set_append_revisions_only(False)
166
'False', config.get_user_option('append_revisions_only'))
168
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
169
branch = self.make_branch('branch', format='knit')
170
config = branch.get_config()
172
errors.UpgradeRequired, branch.set_append_revisions_only, True)
175
class FakeProtocol(object):
176
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
178
def __init__(self, body, fake_client):
180
self._body_buffer = None
181
self._fake_client = fake_client
183
def read_body_bytes(self, count=-1):
184
if self._body_buffer is None:
185
self._body_buffer = StringIO(self.body)
186
bytes = self._body_buffer.read(count)
187
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
188
self._fake_client.expecting_body = False
191
def cancel_read_body(self):
192
self._fake_client.expecting_body = False
194
def read_streamed_body(self):
198
class FakeClient(_SmartClient):
199
"""Lookalike for _SmartClient allowing testing."""
201
def __init__(self, fake_medium_base='fake base'):
202
"""Create a FakeClient."""
205
self.expecting_body = False
206
# if non-None, this is the list of expected calls, with only the
207
# method name and arguments included. the body might be hard to
208
# compute so is not included. If a call is None, that call can
210
self._expected_calls = None
211
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
213
def add_expected_call(self, call_name, call_args, response_type,
214
response_args, response_body=None):
215
if self._expected_calls is None:
216
self._expected_calls = []
217
self._expected_calls.append((call_name, call_args))
218
self.responses.append((response_type, response_args, response_body))
220
def add_success_response(self, *args):
221
self.responses.append(('success', args, None))
223
def add_success_response_with_body(self, body, *args):
224
self.responses.append(('success', args, body))
225
if self._expected_calls is not None:
226
self._expected_calls.append(None)
228
def add_error_response(self, *args):
229
self.responses.append(('error', args))
231
def add_unknown_method_response(self, verb):
232
self.responses.append(('unknown', verb))
234
def finished_test(self):
235
if self._expected_calls:
236
raise AssertionError("%r finished but was still expecting %r"
237
% (self, self._expected_calls[0]))
239
def _get_next_response(self):
241
response_tuple = self.responses.pop(0)
242
except IndexError, e:
243
raise AssertionError("%r didn't expect any more calls"
245
if response_tuple[0] == 'unknown':
246
raise errors.UnknownSmartMethod(response_tuple[1])
247
elif response_tuple[0] == 'error':
248
raise errors.ErrorFromSmartServer(response_tuple[1])
249
return response_tuple
251
def _check_call(self, method, args):
252
if self._expected_calls is None:
253
# the test should be updated to say what it expects
256
next_call = self._expected_calls.pop(0)
258
raise AssertionError("%r didn't expect any more calls "
260
% (self, method, args,))
261
if next_call is None:
263
if method != next_call[0] or args != next_call[1]:
264
raise AssertionError("%r expected %r%r "
266
% (self, next_call[0], next_call[1], method, args,))
268
def call(self, method, *args):
269
self._check_call(method, args)
270
self._calls.append(('call', method, args))
271
return self._get_next_response()[1]
273
def call_expecting_body(self, method, *args):
274
self._check_call(method, args)
275
self._calls.append(('call_expecting_body', method, args))
276
result = self._get_next_response()
277
self.expecting_body = True
278
return result[1], FakeProtocol(result[2], self)
280
def call_with_body_bytes_expecting_body(self, method, args, body):
281
self._check_call(method, args)
282
self._calls.append(('call_with_body_bytes_expecting_body', method,
284
result = self._get_next_response()
285
self.expecting_body = True
286
return result[1], FakeProtocol(result[2], self)
288
def call_with_body_stream(self, args, stream):
289
# Explicitly consume the stream before checking for an error, because
290
# that's what happens a real medium.
291
stream = list(stream)
292
self._check_call(args[0], args[1:])
293
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
294
result = self._get_next_response()
295
# The second value returned from call_with_body_stream is supposed to
296
# be a response_handler object, but so far no tests depend on that.
297
response_handler = None
298
return result[1], response_handler
301
class FakeMedium(medium.SmartClientMedium):
303
def __init__(self, client_calls, base):
304
medium.SmartClientMedium.__init__(self, base)
305
self._client_calls = client_calls
307
def disconnect(self):
308
self._client_calls.append(('disconnect medium',))
311
class TestVfsHas(tests.TestCase):
313
def test_unicode_path(self):
314
client = FakeClient('/')
315
client.add_success_response('yes',)
316
transport = RemoteTransport('bzr://localhost/', _client=client)
317
filename = u'/hell\u00d8'.encode('utf8')
318
result = transport.has(filename)
320
[('call', 'has', (filename,))],
322
self.assertTrue(result)
325
class TestRemote(tests.TestCaseWithMemoryTransport):
327
def get_branch_format(self):
328
reference_bzrdir_format = bzrdir.format_registry.get('default')()
329
return reference_bzrdir_format.get_branch_format()
331
def get_repo_format(self):
332
reference_bzrdir_format = bzrdir.format_registry.get('default')()
333
return reference_bzrdir_format.repository_format
335
def disable_verb(self, verb):
336
"""Disable a verb for one test."""
337
request_handlers = smart.request.request_handlers
338
orig_method = request_handlers.get(verb)
339
request_handlers.remove(verb)
341
request_handlers.register(verb, orig_method)
342
self.addCleanup(restoreVerb)
344
def assertFinished(self, fake_client):
345
"""Assert that all of a FakeClient's expected calls have occurred."""
346
fake_client.finished_test()
349
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
350
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
352
def assertRemotePath(self, expected, client_base, transport_base):
353
"""Assert that the result of
354
SmartClientMedium.remote_path_from_transport is the expected value for
355
a given client_base and transport_base.
357
client_medium = medium.SmartClientMedium(client_base)
358
transport = get_transport(transport_base)
359
result = client_medium.remote_path_from_transport(transport)
360
self.assertEqual(expected, result)
362
def test_remote_path_from_transport(self):
363
"""SmartClientMedium.remote_path_from_transport calculates a URL for
364
the given transport relative to the root of the client base URL.
366
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
367
self.assertRemotePath(
368
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
370
def assertRemotePathHTTP(self, expected, transport_base, relpath):
371
"""Assert that the result of
372
HttpTransportBase.remote_path_from_transport is the expected value for
373
a given transport_base and relpath of that transport. (Note that
374
HttpTransportBase is a subclass of SmartClientMedium)
376
base_transport = get_transport(transport_base)
377
client_medium = base_transport.get_smart_medium()
378
cloned_transport = base_transport.clone(relpath)
379
result = client_medium.remote_path_from_transport(cloned_transport)
380
self.assertEqual(expected, result)
382
def test_remote_path_from_transport_http(self):
383
"""Remote paths for HTTP transports are calculated differently to other
384
transports. They are just relative to the client base, not the root
385
directory of the host.
387
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
388
self.assertRemotePathHTTP(
389
'../xyz/', scheme + '//host/path', '../xyz/')
390
self.assertRemotePathHTTP(
391
'xyz/', scheme + '//host/path', 'xyz/')
394
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
395
"""Tests for the behaviour of client_medium.remote_is_at_least."""
397
def test_initially_unlimited(self):
398
"""A fresh medium assumes that the remote side supports all
401
client_medium = medium.SmartClientMedium('dummy base')
402
self.assertFalse(client_medium._is_remote_before((99, 99)))
404
def test__remember_remote_is_before(self):
405
"""Calling _remember_remote_is_before ratchets down the known remote
408
client_medium = medium.SmartClientMedium('dummy base')
409
# Mark the remote side as being less than 1.6. The remote side may
411
client_medium._remember_remote_is_before((1, 6))
412
self.assertTrue(client_medium._is_remote_before((1, 6)))
413
self.assertFalse(client_medium._is_remote_before((1, 5)))
414
# Calling _remember_remote_is_before again with a lower value works.
415
client_medium._remember_remote_is_before((1, 5))
416
self.assertTrue(client_medium._is_remote_before((1, 5)))
417
# You cannot call _remember_remote_is_before with a larger value.
419
AssertionError, client_medium._remember_remote_is_before, (1, 9))
422
class TestBzrDirCloningMetaDir(TestRemote):
424
def test_backwards_compat(self):
425
self.setup_smart_server_with_call_log()
426
a_dir = self.make_bzrdir('.')
427
self.reset_smart_call_log()
428
verb = 'BzrDir.cloning_metadir'
429
self.disable_verb(verb)
430
format = a_dir.cloning_metadir()
431
call_count = len([call for call in self.hpss_calls if
432
call.call.method == verb])
433
self.assertEqual(1, call_count)
435
def test_branch_reference(self):
436
transport = self.get_transport('quack')
437
referenced = self.make_branch('referenced')
438
expected = referenced.bzrdir.cloning_metadir()
439
client = FakeClient(transport.base)
440
client.add_expected_call(
441
'BzrDir.cloning_metadir', ('quack/', 'False'),
442
'error', ('BranchReference',)),
443
client.add_expected_call(
444
'BzrDir.open_branchV2', ('quack/',),
445
'success', ('ref', self.get_url('referenced'))),
446
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
448
result = a_bzrdir.cloning_metadir()
449
# We should have got a control dir matching the referenced branch.
450
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
451
self.assertEqual(expected._repository_format, result._repository_format)
452
self.assertEqual(expected._branch_format, result._branch_format)
453
self.assertFinished(client)
455
def test_current_server(self):
456
transport = self.get_transport('.')
457
transport = transport.clone('quack')
458
self.make_bzrdir('quack')
459
client = FakeClient(transport.base)
460
reference_bzrdir_format = bzrdir.format_registry.get('default')()
461
control_name = reference_bzrdir_format.network_name()
462
client.add_expected_call(
463
'BzrDir.cloning_metadir', ('quack/', 'False'),
464
'success', (control_name, '', ('branch', ''))),
465
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
467
result = a_bzrdir.cloning_metadir()
468
# We should have got a reference control dir with default branch and
469
# repository formats.
470
# This pokes a little, just to be sure.
471
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
472
self.assertEqual(None, result._repository_format)
473
self.assertEqual(None, result._branch_format)
474
self.assertFinished(client)
477
class TestBzrDirOpenBranch(TestRemote):
479
def test_backwards_compat(self):
480
self.setup_smart_server_with_call_log()
481
self.make_branch('.')
482
a_dir = BzrDir.open(self.get_url('.'))
483
self.reset_smart_call_log()
484
verb = 'BzrDir.open_branchV2'
485
self.disable_verb(verb)
486
format = a_dir.open_branch()
487
call_count = len([call for call in self.hpss_calls if
488
call.call.method == verb])
489
self.assertEqual(1, call_count)
491
def test_branch_present(self):
492
reference_format = self.get_repo_format()
493
network_name = reference_format.network_name()
494
branch_network_name = self.get_branch_format().network_name()
495
transport = MemoryTransport()
496
transport.mkdir('quack')
497
transport = transport.clone('quack')
498
client = FakeClient(transport.base)
499
client.add_expected_call(
500
'BzrDir.open_branchV2', ('quack/',),
501
'success', ('branch', branch_network_name))
502
client.add_expected_call(
503
'BzrDir.find_repositoryV3', ('quack/',),
504
'success', ('ok', '', 'no', 'no', 'no', network_name))
505
client.add_expected_call(
506
'Branch.get_stacked_on_url', ('quack/',),
507
'error', ('NotStacked',))
508
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
510
result = bzrdir.open_branch()
511
self.assertIsInstance(result, RemoteBranch)
512
self.assertEqual(bzrdir, result.bzrdir)
513
self.assertFinished(client)
515
def test_branch_missing(self):
516
transport = MemoryTransport()
517
transport.mkdir('quack')
518
transport = transport.clone('quack')
519
client = FakeClient(transport.base)
520
client.add_error_response('nobranch')
521
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
523
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
525
[('call', 'BzrDir.open_branchV2', ('quack/',))],
528
def test__get_tree_branch(self):
529
# _get_tree_branch is a form of open_branch, but it should only ask for
530
# branch opening, not any other network requests.
533
calls.append("Called")
535
transport = MemoryTransport()
536
# no requests on the network - catches other api calls being made.
537
client = FakeClient(transport.base)
538
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
540
# patch the open_branch call to record that it was called.
541
bzrdir.open_branch = open_branch
542
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
543
self.assertEqual(["Called"], calls)
544
self.assertEqual([], client._calls)
546
def test_url_quoting_of_path(self):
547
# Relpaths on the wire should not be URL-escaped. So "~" should be
548
# transmitted as "~", not "%7E".
549
transport = RemoteTCPTransport('bzr://localhost/~hello/')
550
client = FakeClient(transport.base)
551
reference_format = self.get_repo_format()
552
network_name = reference_format.network_name()
553
branch_network_name = self.get_branch_format().network_name()
554
client.add_expected_call(
555
'BzrDir.open_branchV2', ('~hello/',),
556
'success', ('branch', branch_network_name))
557
client.add_expected_call(
558
'BzrDir.find_repositoryV3', ('~hello/',),
559
'success', ('ok', '', 'no', 'no', 'no', network_name))
560
client.add_expected_call(
561
'Branch.get_stacked_on_url', ('~hello/',),
562
'error', ('NotStacked',))
563
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
565
result = bzrdir.open_branch()
566
self.assertFinished(client)
568
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
569
reference_format = self.get_repo_format()
570
network_name = reference_format.network_name()
571
transport = MemoryTransport()
572
transport.mkdir('quack')
573
transport = transport.clone('quack')
575
rich_response = 'yes'
579
subtree_response = 'yes'
581
subtree_response = 'no'
582
client = FakeClient(transport.base)
583
client.add_success_response(
584
'ok', '', rich_response, subtree_response, external_lookup,
586
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
588
result = bzrdir.open_repository()
590
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
592
self.assertIsInstance(result, RemoteRepository)
593
self.assertEqual(bzrdir, result.bzrdir)
594
self.assertEqual(rich_root, result._format.rich_root_data)
595
self.assertEqual(subtrees, result._format.supports_tree_reference)
597
def test_open_repository_sets_format_attributes(self):
598
self.check_open_repository(True, True)
599
self.check_open_repository(False, True)
600
self.check_open_repository(True, False)
601
self.check_open_repository(False, False)
602
self.check_open_repository(False, False, 'yes')
604
def test_old_server(self):
605
"""RemoteBzrDirFormat should fail to probe if the server version is too
608
self.assertRaises(errors.NotBranchError,
609
RemoteBzrDirFormat.probe_transport, OldServerTransport())
612
class TestBzrDirCreateBranch(TestRemote):
614
def test_backwards_compat(self):
615
self.setup_smart_server_with_call_log()
616
repo = self.make_repository('.')
617
self.reset_smart_call_log()
618
self.disable_verb('BzrDir.create_branch')
619
branch = repo.bzrdir.create_branch()
620
create_branch_call_count = len([call for call in self.hpss_calls if
621
call.call.method == 'BzrDir.create_branch'])
622
self.assertEqual(1, create_branch_call_count)
624
def test_current_server(self):
625
transport = self.get_transport('.')
626
transport = transport.clone('quack')
627
self.make_repository('quack')
628
client = FakeClient(transport.base)
629
reference_bzrdir_format = bzrdir.format_registry.get('default')()
630
reference_format = reference_bzrdir_format.get_branch_format()
631
network_name = reference_format.network_name()
632
reference_repo_fmt = reference_bzrdir_format.repository_format
633
reference_repo_name = reference_repo_fmt.network_name()
634
client.add_expected_call(
635
'BzrDir.create_branch', ('quack/', network_name),
636
'success', ('ok', network_name, '', 'no', 'no', 'yes',
637
reference_repo_name))
638
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
640
branch = a_bzrdir.create_branch()
641
# We should have got a remote branch
642
self.assertIsInstance(branch, remote.RemoteBranch)
643
# its format should have the settings from the response
644
format = branch._format
645
self.assertEqual(network_name, format.network_name())
648
class TestBzrDirCreateRepository(TestRemote):
650
def test_backwards_compat(self):
651
self.setup_smart_server_with_call_log()
652
bzrdir = self.make_bzrdir('.')
653
self.reset_smart_call_log()
654
self.disable_verb('BzrDir.create_repository')
655
repo = bzrdir.create_repository()
656
create_repo_call_count = len([call for call in self.hpss_calls if
657
call.call.method == 'BzrDir.create_repository'])
658
self.assertEqual(1, create_repo_call_count)
660
def test_current_server(self):
661
transport = self.get_transport('.')
662
transport = transport.clone('quack')
663
self.make_bzrdir('quack')
664
client = FakeClient(transport.base)
665
reference_bzrdir_format = bzrdir.format_registry.get('default')()
666
reference_format = reference_bzrdir_format.repository_format
667
network_name = reference_format.network_name()
668
client.add_expected_call(
669
'BzrDir.create_repository', ('quack/',
670
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
671
'success', ('ok', 'no', 'no', 'no', network_name))
672
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
674
repo = a_bzrdir.create_repository()
675
# We should have got a remote repository
676
self.assertIsInstance(repo, remote.RemoteRepository)
677
# its format should have the settings from the response
678
format = repo._format
679
self.assertFalse(format.rich_root_data)
680
self.assertFalse(format.supports_tree_reference)
681
self.assertFalse(format.supports_external_lookups)
682
self.assertEqual(network_name, format.network_name())
685
class TestBzrDirOpenRepository(TestRemote):
687
def test_backwards_compat_1_2_3(self):
688
# fallback all the way to the first version.
689
reference_format = self.get_repo_format()
690
network_name = reference_format.network_name()
691
client = FakeClient('bzr://example.com/')
692
client.add_unknown_method_response('BzrDir.find_repositoryV3')
693
client.add_unknown_method_response('BzrDir.find_repositoryV2')
694
client.add_success_response('ok', '', 'no', 'no')
695
# A real repository instance will be created to determine the network
697
client.add_success_response_with_body(
698
"Bazaar-NG meta directory, format 1\n", 'ok')
699
client.add_success_response_with_body(
700
reference_format.get_format_string(), 'ok')
701
# PackRepository wants to do a stat
702
client.add_success_response('stat', '0', '65535')
703
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
705
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
707
repo = bzrdir.open_repository()
709
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
710
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
711
('call', 'BzrDir.find_repository', ('quack/',)),
712
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
713
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
714
('call', 'stat', ('/quack/.bzr/repository',)),
717
self.assertEqual(network_name, repo._format.network_name())
719
def test_backwards_compat_2(self):
720
# fallback to find_repositoryV2
721
reference_format = self.get_repo_format()
722
network_name = reference_format.network_name()
723
client = FakeClient('bzr://example.com/')
724
client.add_unknown_method_response('BzrDir.find_repositoryV3')
725
client.add_success_response('ok', '', 'no', 'no', 'no')
726
# A real repository instance will be created to determine the network
728
client.add_success_response_with_body(
729
"Bazaar-NG meta directory, format 1\n", 'ok')
730
client.add_success_response_with_body(
731
reference_format.get_format_string(), 'ok')
732
# PackRepository wants to do a stat
733
client.add_success_response('stat', '0', '65535')
734
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
736
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
738
repo = bzrdir.open_repository()
740
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
741
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
742
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
743
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
744
('call', 'stat', ('/quack/.bzr/repository',)),
747
self.assertEqual(network_name, repo._format.network_name())
749
def test_current_server(self):
750
reference_format = self.get_repo_format()
751
network_name = reference_format.network_name()
752
transport = MemoryTransport()
753
transport.mkdir('quack')
754
transport = transport.clone('quack')
755
client = FakeClient(transport.base)
756
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
757
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
759
repo = bzrdir.open_repository()
761
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
763
self.assertEqual(network_name, repo._format.network_name())
766
class TestBzrDirFormatInitializeEx(TestRemote):
768
def test_success(self):
769
"""Simple test for typical successful call."""
770
fmt = bzrdir.RemoteBzrDirFormat()
771
default_format_name = BzrDirFormat.get_default_format().network_name()
772
transport = self.get_transport()
773
client = FakeClient(transport.base)
774
client.add_expected_call(
775
'BzrDirFormat.initialize_ex_1.16',
776
(default_format_name, 'path', 'False', 'False', 'False', '',
777
'', '', '', 'False'),
779
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
780
'bzrdir fmt', 'False', '', '', 'repo lock token'))
781
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
782
# it's currently hard to test that without supplying a real remote
783
# transport connected to a real server.
784
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
785
transport, False, False, False, None, None, None, None, False)
786
self.assertFinished(client)
788
def test_error(self):
789
"""Error responses are translated, e.g. 'PermissionDenied' raises the
790
corresponding error from the client.
792
fmt = bzrdir.RemoteBzrDirFormat()
793
default_format_name = BzrDirFormat.get_default_format().network_name()
794
transport = self.get_transport()
795
client = FakeClient(transport.base)
796
client.add_expected_call(
797
'BzrDirFormat.initialize_ex_1.16',
798
(default_format_name, 'path', 'False', 'False', 'False', '',
799
'', '', '', 'False'),
801
('PermissionDenied', 'path', 'extra info'))
802
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
803
# it's currently hard to test that without supplying a real remote
804
# transport connected to a real server.
805
err = self.assertRaises(errors.PermissionDenied,
806
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
807
False, False, False, None, None, None, None, False)
808
self.assertEqual('path', err.path)
809
self.assertEqual(': extra info', err.extra)
810
self.assertFinished(client)
812
def test_error_from_real_server(self):
813
"""Integration test for error translation."""
814
transport = self.make_smart_server('foo')
815
transport = transport.clone('no-such-path')
816
fmt = bzrdir.RemoteBzrDirFormat()
817
err = self.assertRaises(errors.NoSuchFile,
818
fmt.initialize_on_transport_ex, transport, create_prefix=False)
821
class OldSmartClient(object):
822
"""A fake smart client for test_old_version that just returns a version one
823
response to the 'hello' (query version) command.
826
def get_request(self):
827
input_file = StringIO('ok\x011\n')
828
output_file = StringIO()
829
client_medium = medium.SmartSimplePipesClientMedium(
830
input_file, output_file)
831
return medium.SmartClientStreamMediumRequest(client_medium)
833
def protocol_version(self):
837
class OldServerTransport(object):
838
"""A fake transport for test_old_server that reports it's smart server
839
protocol version as version one.
845
def get_smart_client(self):
846
return OldSmartClient()
849
class RemoteBzrDirTestCase(TestRemote):
851
def make_remote_bzrdir(self, transport, client):
852
"""Make a RemotebzrDir using 'client' as the _client."""
853
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
857
class RemoteBranchTestCase(RemoteBzrDirTestCase):
859
def make_remote_branch(self, transport, client):
860
"""Make a RemoteBranch using 'client' as its _SmartClient.
862
A RemoteBzrDir and RemoteRepository will also be created to fill out
863
the RemoteBranch, albeit with stub values for some of their attributes.
865
# we do not want bzrdir to make any remote calls, so use False as its
866
# _client. If it tries to make a remote call, this will fail
868
bzrdir = self.make_remote_bzrdir(transport, False)
869
repo = RemoteRepository(bzrdir, None, _client=client)
870
branch_format = self.get_branch_format()
871
format = RemoteBranchFormat(network_name=branch_format.network_name())
872
return RemoteBranch(bzrdir, repo, _client=client, format=format)
875
class TestBranchGetParent(RemoteBranchTestCase):
877
def test_no_parent(self):
878
# in an empty branch we decode the response properly
879
transport = MemoryTransport()
880
client = FakeClient(transport.base)
881
client.add_expected_call(
882
'Branch.get_stacked_on_url', ('quack/',),
883
'error', ('NotStacked',))
884
client.add_expected_call(
885
'Branch.get_parent', ('quack/',),
887
transport.mkdir('quack')
888
transport = transport.clone('quack')
889
branch = self.make_remote_branch(transport, client)
890
result = branch.get_parent()
891
self.assertFinished(client)
892
self.assertEqual(None, result)
894
def test_parent_relative(self):
895
transport = MemoryTransport()
896
client = FakeClient(transport.base)
897
client.add_expected_call(
898
'Branch.get_stacked_on_url', ('kwaak/',),
899
'error', ('NotStacked',))
900
client.add_expected_call(
901
'Branch.get_parent', ('kwaak/',),
902
'success', ('../foo/',))
903
transport.mkdir('kwaak')
904
transport = transport.clone('kwaak')
905
branch = self.make_remote_branch(transport, client)
906
result = branch.get_parent()
907
self.assertEqual(transport.clone('../foo').base, result)
909
def test_parent_absolute(self):
910
transport = MemoryTransport()
911
client = FakeClient(transport.base)
912
client.add_expected_call(
913
'Branch.get_stacked_on_url', ('kwaak/',),
914
'error', ('NotStacked',))
915
client.add_expected_call(
916
'Branch.get_parent', ('kwaak/',),
917
'success', ('http://foo/',))
918
transport.mkdir('kwaak')
919
transport = transport.clone('kwaak')
920
branch = self.make_remote_branch(transport, client)
921
result = branch.get_parent()
922
self.assertEqual('http://foo/', result)
923
self.assertFinished(client)
926
class TestBranchSetParentLocation(RemoteBranchTestCase):
928
def test_no_parent(self):
929
# We call the verb when setting parent to None
930
transport = MemoryTransport()
931
client = FakeClient(transport.base)
932
client.add_expected_call(
933
'Branch.get_stacked_on_url', ('quack/',),
934
'error', ('NotStacked',))
935
client.add_expected_call(
936
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
938
transport.mkdir('quack')
939
transport = transport.clone('quack')
940
branch = self.make_remote_branch(transport, client)
941
branch._lock_token = 'b'
942
branch._repo_lock_token = 'r'
943
branch._set_parent_location(None)
944
self.assertFinished(client)
946
def test_parent(self):
947
transport = MemoryTransport()
948
client = FakeClient(transport.base)
949
client.add_expected_call(
950
'Branch.get_stacked_on_url', ('kwaak/',),
951
'error', ('NotStacked',))
952
client.add_expected_call(
953
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
955
transport.mkdir('kwaak')
956
transport = transport.clone('kwaak')
957
branch = self.make_remote_branch(transport, client)
958
branch._lock_token = 'b'
959
branch._repo_lock_token = 'r'
960
branch._set_parent_location('foo')
961
self.assertFinished(client)
963
def test_backwards_compat(self):
964
self.setup_smart_server_with_call_log()
965
branch = self.make_branch('.')
966
self.reset_smart_call_log()
967
verb = 'Branch.set_parent_location'
968
self.disable_verb(verb)
969
branch.set_parent('http://foo/')
970
self.assertLength(12, self.hpss_calls)
973
class TestBranchGetTagsBytes(RemoteBranchTestCase):
975
def test_backwards_compat(self):
976
self.setup_smart_server_with_call_log()
977
branch = self.make_branch('.')
978
self.reset_smart_call_log()
979
verb = 'Branch.get_tags_bytes'
980
self.disable_verb(verb)
981
branch.tags.get_tag_dict()
982
call_count = len([call for call in self.hpss_calls if
983
call.call.method == verb])
984
self.assertEqual(1, call_count)
986
def test_trivial(self):
987
transport = MemoryTransport()
988
client = FakeClient(transport.base)
989
client.add_expected_call(
990
'Branch.get_stacked_on_url', ('quack/',),
991
'error', ('NotStacked',))
992
client.add_expected_call(
993
'Branch.get_tags_bytes', ('quack/',),
995
transport.mkdir('quack')
996
transport = transport.clone('quack')
997
branch = self.make_remote_branch(transport, client)
998
result = branch.tags.get_tag_dict()
999
self.assertFinished(client)
1000
self.assertEqual({}, result)
1003
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1005
def test_empty_branch(self):
1006
# in an empty branch we decode the response properly
1007
transport = MemoryTransport()
1008
client = FakeClient(transport.base)
1009
client.add_expected_call(
1010
'Branch.get_stacked_on_url', ('quack/',),
1011
'error', ('NotStacked',))
1012
client.add_expected_call(
1013
'Branch.last_revision_info', ('quack/',),
1014
'success', ('ok', '0', 'null:'))
1015
transport.mkdir('quack')
1016
transport = transport.clone('quack')
1017
branch = self.make_remote_branch(transport, client)
1018
result = branch.last_revision_info()
1019
self.assertFinished(client)
1020
self.assertEqual((0, NULL_REVISION), result)
1022
def test_non_empty_branch(self):
1023
# in a non-empty branch we also decode the response properly
1024
revid = u'\xc8'.encode('utf8')
1025
transport = MemoryTransport()
1026
client = FakeClient(transport.base)
1027
client.add_expected_call(
1028
'Branch.get_stacked_on_url', ('kwaak/',),
1029
'error', ('NotStacked',))
1030
client.add_expected_call(
1031
'Branch.last_revision_info', ('kwaak/',),
1032
'success', ('ok', '2', revid))
1033
transport.mkdir('kwaak')
1034
transport = transport.clone('kwaak')
1035
branch = self.make_remote_branch(transport, client)
1036
result = branch.last_revision_info()
1037
self.assertEqual((2, revid), result)
1040
class TestBranch_get_stacked_on_url(TestRemote):
1041
"""Test Branch._get_stacked_on_url rpc"""
1043
def test_get_stacked_on_invalid_url(self):
1044
# test that asking for a stacked on url the server can't access works.
1045
# This isn't perfect, but then as we're in the same process there
1046
# really isn't anything we can do to be 100% sure that the server
1047
# doesn't just open in - this test probably needs to be rewritten using
1048
# a spawn()ed server.
1049
stacked_branch = self.make_branch('stacked', format='1.9')
1050
memory_branch = self.make_branch('base', format='1.9')
1051
vfs_url = self.get_vfs_only_url('base')
1052
stacked_branch.set_stacked_on_url(vfs_url)
1053
transport = stacked_branch.bzrdir.root_transport
1054
client = FakeClient(transport.base)
1055
client.add_expected_call(
1056
'Branch.get_stacked_on_url', ('stacked/',),
1057
'success', ('ok', vfs_url))
1058
# XXX: Multiple calls are bad, this second call documents what is
1060
client.add_expected_call(
1061
'Branch.get_stacked_on_url', ('stacked/',),
1062
'success', ('ok', vfs_url))
1063
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1065
repo_fmt = remote.RemoteRepositoryFormat()
1066
repo_fmt._custom_format = stacked_branch.repository._format
1067
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1069
result = branch.get_stacked_on_url()
1070
self.assertEqual(vfs_url, result)
1072
def test_backwards_compatible(self):
1073
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1074
base_branch = self.make_branch('base', format='1.6')
1075
stacked_branch = self.make_branch('stacked', format='1.6')
1076
stacked_branch.set_stacked_on_url('../base')
1077
client = FakeClient(self.get_url())
1078
branch_network_name = self.get_branch_format().network_name()
1079
client.add_expected_call(
1080
'BzrDir.open_branchV2', ('stacked/',),
1081
'success', ('branch', branch_network_name))
1082
client.add_expected_call(
1083
'BzrDir.find_repositoryV3', ('stacked/',),
1084
'success', ('ok', '', 'no', 'no', 'yes',
1085
stacked_branch.repository._format.network_name()))
1086
# called twice, once from constructor and then again by us
1087
client.add_expected_call(
1088
'Branch.get_stacked_on_url', ('stacked/',),
1089
'unknown', ('Branch.get_stacked_on_url',))
1090
client.add_expected_call(
1091
'Branch.get_stacked_on_url', ('stacked/',),
1092
'unknown', ('Branch.get_stacked_on_url',))
1093
# this will also do vfs access, but that goes direct to the transport
1094
# and isn't seen by the FakeClient.
1095
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1096
remote.RemoteBzrDirFormat(), _client=client)
1097
branch = bzrdir.open_branch()
1098
result = branch.get_stacked_on_url()
1099
self.assertEqual('../base', result)
1100
self.assertFinished(client)
1101
# it's in the fallback list both for the RemoteRepository and its vfs
1103
self.assertEqual(1, len(branch.repository._fallback_repositories))
1105
len(branch.repository._real_repository._fallback_repositories))
1107
def test_get_stacked_on_real_branch(self):
1108
base_branch = self.make_branch('base', format='1.6')
1109
stacked_branch = self.make_branch('stacked', format='1.6')
1110
stacked_branch.set_stacked_on_url('../base')
1111
reference_format = self.get_repo_format()
1112
network_name = reference_format.network_name()
1113
client = FakeClient(self.get_url())
1114
branch_network_name = self.get_branch_format().network_name()
1115
client.add_expected_call(
1116
'BzrDir.open_branchV2', ('stacked/',),
1117
'success', ('branch', branch_network_name))
1118
client.add_expected_call(
1119
'BzrDir.find_repositoryV3', ('stacked/',),
1120
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1121
# called twice, once from constructor and then again by us
1122
client.add_expected_call(
1123
'Branch.get_stacked_on_url', ('stacked/',),
1124
'success', ('ok', '../base'))
1125
client.add_expected_call(
1126
'Branch.get_stacked_on_url', ('stacked/',),
1127
'success', ('ok', '../base'))
1128
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1129
remote.RemoteBzrDirFormat(), _client=client)
1130
branch = bzrdir.open_branch()
1131
result = branch.get_stacked_on_url()
1132
self.assertEqual('../base', result)
1133
self.assertFinished(client)
1134
# it's in the fallback list both for the RemoteRepository.
1135
self.assertEqual(1, len(branch.repository._fallback_repositories))
1136
# And we haven't had to construct a real repository.
1137
self.assertEqual(None, branch.repository._real_repository)
1140
class TestBranchSetLastRevision(RemoteBranchTestCase):
1142
def test_set_empty(self):
1143
# set_revision_history([]) is translated to calling
1144
# Branch.set_last_revision(path, '') on the wire.
1145
transport = MemoryTransport()
1146
transport.mkdir('branch')
1147
transport = transport.clone('branch')
1149
client = FakeClient(transport.base)
1150
client.add_expected_call(
1151
'Branch.get_stacked_on_url', ('branch/',),
1152
'error', ('NotStacked',))
1153
client.add_expected_call(
1154
'Branch.lock_write', ('branch/', '', ''),
1155
'success', ('ok', 'branch token', 'repo token'))
1156
client.add_expected_call(
1157
'Branch.last_revision_info',
1159
'success', ('ok', '0', 'null:'))
1160
client.add_expected_call(
1161
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1163
client.add_expected_call(
1164
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1166
branch = self.make_remote_branch(transport, client)
1167
# This is a hack to work around the problem that RemoteBranch currently
1168
# unnecessarily invokes _ensure_real upon a call to lock_write.
1169
branch._ensure_real = lambda: None
1171
result = branch.set_revision_history([])
1173
self.assertEqual(None, result)
1174
self.assertFinished(client)
1176
def test_set_nonempty(self):
1177
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1178
# Branch.set_last_revision(path, rev-idN) on the wire.
1179
transport = MemoryTransport()
1180
transport.mkdir('branch')
1181
transport = transport.clone('branch')
1183
client = FakeClient(transport.base)
1184
client.add_expected_call(
1185
'Branch.get_stacked_on_url', ('branch/',),
1186
'error', ('NotStacked',))
1187
client.add_expected_call(
1188
'Branch.lock_write', ('branch/', '', ''),
1189
'success', ('ok', 'branch token', 'repo token'))
1190
client.add_expected_call(
1191
'Branch.last_revision_info',
1193
'success', ('ok', '0', 'null:'))
1195
encoded_body = bz2.compress('\n'.join(lines))
1196
client.add_success_response_with_body(encoded_body, 'ok')
1197
client.add_expected_call(
1198
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1200
client.add_expected_call(
1201
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1203
branch = self.make_remote_branch(transport, client)
1204
# This is a hack to work around the problem that RemoteBranch currently
1205
# unnecessarily invokes _ensure_real upon a call to lock_write.
1206
branch._ensure_real = lambda: None
1207
# Lock the branch, reset the record of remote calls.
1209
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1211
self.assertEqual(None, result)
1212
self.assertFinished(client)
1214
def test_no_such_revision(self):
1215
transport = MemoryTransport()
1216
transport.mkdir('branch')
1217
transport = transport.clone('branch')
1218
# A response of 'NoSuchRevision' is translated into an exception.
1219
client = FakeClient(transport.base)
1220
client.add_expected_call(
1221
'Branch.get_stacked_on_url', ('branch/',),
1222
'error', ('NotStacked',))
1223
client.add_expected_call(
1224
'Branch.lock_write', ('branch/', '', ''),
1225
'success', ('ok', 'branch token', 'repo token'))
1226
client.add_expected_call(
1227
'Branch.last_revision_info',
1229
'success', ('ok', '0', 'null:'))
1230
# get_graph calls to construct the revision history, for the set_rh
1233
encoded_body = bz2.compress('\n'.join(lines))
1234
client.add_success_response_with_body(encoded_body, 'ok')
1235
client.add_expected_call(
1236
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1237
'error', ('NoSuchRevision', 'rev-id'))
1238
client.add_expected_call(
1239
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1242
branch = self.make_remote_branch(transport, client)
1245
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1247
self.assertFinished(client)
1249
def test_tip_change_rejected(self):
1250
"""TipChangeRejected responses cause a TipChangeRejected exception to
1253
transport = MemoryTransport()
1254
transport.mkdir('branch')
1255
transport = transport.clone('branch')
1256
client = FakeClient(transport.base)
1257
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1258
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1259
client.add_expected_call(
1260
'Branch.get_stacked_on_url', ('branch/',),
1261
'error', ('NotStacked',))
1262
client.add_expected_call(
1263
'Branch.lock_write', ('branch/', '', ''),
1264
'success', ('ok', 'branch token', 'repo token'))
1265
client.add_expected_call(
1266
'Branch.last_revision_info',
1268
'success', ('ok', '0', 'null:'))
1270
encoded_body = bz2.compress('\n'.join(lines))
1271
client.add_success_response_with_body(encoded_body, 'ok')
1272
client.add_expected_call(
1273
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1274
'error', ('TipChangeRejected', rejection_msg_utf8))
1275
client.add_expected_call(
1276
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1278
branch = self.make_remote_branch(transport, client)
1279
branch._ensure_real = lambda: None
1281
# The 'TipChangeRejected' error response triggered by calling
1282
# set_revision_history causes a TipChangeRejected exception.
1283
err = self.assertRaises(
1284
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1285
# The UTF-8 message from the response has been decoded into a unicode
1287
self.assertIsInstance(err.msg, unicode)
1288
self.assertEqual(rejection_msg_unicode, err.msg)
1290
self.assertFinished(client)
1293
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1295
def test_set_last_revision_info(self):
1296
# set_last_revision_info(num, 'rev-id') is translated to calling
1297
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1298
transport = MemoryTransport()
1299
transport.mkdir('branch')
1300
transport = transport.clone('branch')
1301
client = FakeClient(transport.base)
1302
# get_stacked_on_url
1303
client.add_error_response('NotStacked')
1305
client.add_success_response('ok', 'branch token', 'repo token')
1306
# query the current revision
1307
client.add_success_response('ok', '0', 'null:')
1309
client.add_success_response('ok')
1311
client.add_success_response('ok')
1313
branch = self.make_remote_branch(transport, client)
1314
# Lock the branch, reset the record of remote calls.
1317
result = branch.set_last_revision_info(1234, 'a-revision-id')
1319
[('call', 'Branch.last_revision_info', ('branch/',)),
1320
('call', 'Branch.set_last_revision_info',
1321
('branch/', 'branch token', 'repo token',
1322
'1234', 'a-revision-id'))],
1324
self.assertEqual(None, result)
1326
def test_no_such_revision(self):
1327
# A response of 'NoSuchRevision' is translated into an exception.
1328
transport = MemoryTransport()
1329
transport.mkdir('branch')
1330
transport = transport.clone('branch')
1331
client = FakeClient(transport.base)
1332
# get_stacked_on_url
1333
client.add_error_response('NotStacked')
1335
client.add_success_response('ok', 'branch token', 'repo token')
1337
client.add_error_response('NoSuchRevision', 'revid')
1339
client.add_success_response('ok')
1341
branch = self.make_remote_branch(transport, client)
1342
# Lock the branch, reset the record of remote calls.
1347
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1350
def lock_remote_branch(self, branch):
1351
"""Trick a RemoteBranch into thinking it is locked."""
1352
branch._lock_mode = 'w'
1353
branch._lock_count = 2
1354
branch._lock_token = 'branch token'
1355
branch._repo_lock_token = 'repo token'
1356
branch.repository._lock_mode = 'w'
1357
branch.repository._lock_count = 2
1358
branch.repository._lock_token = 'repo token'
1360
def test_backwards_compatibility(self):
1361
"""If the server does not support the Branch.set_last_revision_info
1362
verb (which is new in 1.4), then the client falls back to VFS methods.
1364
# This test is a little messy. Unlike most tests in this file, it
1365
# doesn't purely test what a Remote* object sends over the wire, and
1366
# how it reacts to responses from the wire. It instead relies partly
1367
# on asserting that the RemoteBranch will call
1368
# self._real_branch.set_last_revision_info(...).
1370
# First, set up our RemoteBranch with a FakeClient that raises
1371
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1372
transport = MemoryTransport()
1373
transport.mkdir('branch')
1374
transport = transport.clone('branch')
1375
client = FakeClient(transport.base)
1376
client.add_expected_call(
1377
'Branch.get_stacked_on_url', ('branch/',),
1378
'error', ('NotStacked',))
1379
client.add_expected_call(
1380
'Branch.last_revision_info',
1382
'success', ('ok', '0', 'null:'))
1383
client.add_expected_call(
1384
'Branch.set_last_revision_info',
1385
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1386
'unknown', 'Branch.set_last_revision_info')
1388
branch = self.make_remote_branch(transport, client)
1389
class StubRealBranch(object):
1392
def set_last_revision_info(self, revno, revision_id):
1394
('set_last_revision_info', revno, revision_id))
1395
def _clear_cached_state(self):
1397
real_branch = StubRealBranch()
1398
branch._real_branch = real_branch
1399
self.lock_remote_branch(branch)
1401
# Call set_last_revision_info, and verify it behaved as expected.
1402
result = branch.set_last_revision_info(1234, 'a-revision-id')
1404
[('set_last_revision_info', 1234, 'a-revision-id')],
1406
self.assertFinished(client)
1408
def test_unexpected_error(self):
1409
# If the server sends an error the client doesn't understand, it gets
1410
# turned into an UnknownErrorFromSmartServer, which is presented as a
1411
# non-internal error to the user.
1412
transport = MemoryTransport()
1413
transport.mkdir('branch')
1414
transport = transport.clone('branch')
1415
client = FakeClient(transport.base)
1416
# get_stacked_on_url
1417
client.add_error_response('NotStacked')
1419
client.add_success_response('ok', 'branch token', 'repo token')
1421
client.add_error_response('UnexpectedError')
1423
client.add_success_response('ok')
1425
branch = self.make_remote_branch(transport, client)
1426
# Lock the branch, reset the record of remote calls.
1430
err = self.assertRaises(
1431
errors.UnknownErrorFromSmartServer,
1432
branch.set_last_revision_info, 123, 'revid')
1433
self.assertEqual(('UnexpectedError',), err.error_tuple)
1436
def test_tip_change_rejected(self):
1437
"""TipChangeRejected responses cause a TipChangeRejected exception to
1440
transport = MemoryTransport()
1441
transport.mkdir('branch')
1442
transport = transport.clone('branch')
1443
client = FakeClient(transport.base)
1444
# get_stacked_on_url
1445
client.add_error_response('NotStacked')
1447
client.add_success_response('ok', 'branch token', 'repo token')
1449
client.add_error_response('TipChangeRejected', 'rejection message')
1451
client.add_success_response('ok')
1453
branch = self.make_remote_branch(transport, client)
1454
# Lock the branch, reset the record of remote calls.
1456
self.addCleanup(branch.unlock)
1459
# The 'TipChangeRejected' error response triggered by calling
1460
# set_last_revision_info causes a TipChangeRejected exception.
1461
err = self.assertRaises(
1462
errors.TipChangeRejected,
1463
branch.set_last_revision_info, 123, 'revid')
1464
self.assertEqual('rejection message', err.msg)
1467
class TestBranchGetSetConfig(RemoteBranchTestCase):
1469
def test_get_branch_conf(self):
1470
# in an empty branch we decode the response properly
1471
client = FakeClient()
1472
client.add_expected_call(
1473
'Branch.get_stacked_on_url', ('memory:///',),
1474
'error', ('NotStacked',),)
1475
client.add_success_response_with_body('# config file body', 'ok')
1476
transport = MemoryTransport()
1477
branch = self.make_remote_branch(transport, client)
1478
config = branch.get_config()
1479
config.has_explicit_nickname()
1481
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1482
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1485
def test_get_multi_line_branch_conf(self):
1486
# Make sure that multiple-line branch.conf files are supported
1488
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1489
client = FakeClient()
1490
client.add_expected_call(
1491
'Branch.get_stacked_on_url', ('memory:///',),
1492
'error', ('NotStacked',),)
1493
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1494
transport = MemoryTransport()
1495
branch = self.make_remote_branch(transport, client)
1496
config = branch.get_config()
1497
self.assertEqual(u'2', config.get_user_option('b'))
1499
def test_set_option(self):
1500
client = FakeClient()
1501
client.add_expected_call(
1502
'Branch.get_stacked_on_url', ('memory:///',),
1503
'error', ('NotStacked',),)
1504
client.add_expected_call(
1505
'Branch.lock_write', ('memory:///', '', ''),
1506
'success', ('ok', 'branch token', 'repo token'))
1507
client.add_expected_call(
1508
'Branch.set_config_option', ('memory:///', 'branch token',
1509
'repo token', 'foo', 'bar', ''),
1511
client.add_expected_call(
1512
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1514
transport = MemoryTransport()
1515
branch = self.make_remote_branch(transport, client)
1517
config = branch._get_config()
1518
config.set_option('foo', 'bar')
1520
self.assertFinished(client)
1522
def test_backwards_compat_set_option(self):
1523
self.setup_smart_server_with_call_log()
1524
branch = self.make_branch('.')
1525
verb = 'Branch.set_config_option'
1526
self.disable_verb(verb)
1528
self.addCleanup(branch.unlock)
1529
self.reset_smart_call_log()
1530
branch._get_config().set_option('value', 'name')
1531
self.assertLength(10, self.hpss_calls)
1532
self.assertEqual('value', branch._get_config().get_option('name'))
1535
class TestBranchLockWrite(RemoteBranchTestCase):
1537
def test_lock_write_unlockable(self):
1538
transport = MemoryTransport()
1539
client = FakeClient(transport.base)
1540
client.add_expected_call(
1541
'Branch.get_stacked_on_url', ('quack/',),
1542
'error', ('NotStacked',),)
1543
client.add_expected_call(
1544
'Branch.lock_write', ('quack/', '', ''),
1545
'error', ('UnlockableTransport',))
1546
transport.mkdir('quack')
1547
transport = transport.clone('quack')
1548
branch = self.make_remote_branch(transport, client)
1549
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1550
self.assertFinished(client)
1553
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1555
def test__get_config(self):
1556
client = FakeClient()
1557
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1558
transport = MemoryTransport()
1559
bzrdir = self.make_remote_bzrdir(transport, client)
1560
config = bzrdir.get_config()
1561
self.assertEqual('/', config.get_default_stack_on())
1563
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1566
def test_set_option_uses_vfs(self):
1567
self.setup_smart_server_with_call_log()
1568
bzrdir = self.make_bzrdir('.')
1569
self.reset_smart_call_log()
1570
config = bzrdir.get_config()
1571
config.set_default_stack_on('/')
1572
self.assertLength(3, self.hpss_calls)
1574
def test_backwards_compat_get_option(self):
1575
self.setup_smart_server_with_call_log()
1576
bzrdir = self.make_bzrdir('.')
1577
verb = 'BzrDir.get_config_file'
1578
self.disable_verb(verb)
1579
self.reset_smart_call_log()
1580
self.assertEqual(None,
1581
bzrdir._get_config().get_option('default_stack_on'))
1582
self.assertLength(3, self.hpss_calls)
1585
class TestTransportIsReadonly(tests.TestCase):
1587
def test_true(self):
1588
client = FakeClient()
1589
client.add_success_response('yes')
1590
transport = RemoteTransport('bzr://example.com/', medium=False,
1592
self.assertEqual(True, transport.is_readonly())
1594
[('call', 'Transport.is_readonly', ())],
1597
def test_false(self):
1598
client = FakeClient()
1599
client.add_success_response('no')
1600
transport = RemoteTransport('bzr://example.com/', medium=False,
1602
self.assertEqual(False, transport.is_readonly())
1604
[('call', 'Transport.is_readonly', ())],
1607
def test_error_from_old_server(self):
1608
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1610
Clients should treat it as a "no" response, because is_readonly is only
1611
advisory anyway (a transport could be read-write, but then the
1612
underlying filesystem could be readonly anyway).
1614
client = FakeClient()
1615
client.add_unknown_method_response('Transport.is_readonly')
1616
transport = RemoteTransport('bzr://example.com/', medium=False,
1618
self.assertEqual(False, transport.is_readonly())
1620
[('call', 'Transport.is_readonly', ())],
1624
class TestTransportMkdir(tests.TestCase):
1626
def test_permissiondenied(self):
1627
client = FakeClient()
1628
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1629
transport = RemoteTransport('bzr://example.com/', medium=False,
1631
exc = self.assertRaises(
1632
errors.PermissionDenied, transport.mkdir, 'client path')
1633
expected_error = errors.PermissionDenied('/client path', 'extra')
1634
self.assertEqual(expected_error, exc)
1637
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1639
def test_defaults_to_none(self):
1640
t = RemoteSSHTransport('bzr+ssh://example.com')
1641
self.assertIs(None, t._get_credentials()[0])
1643
def test_uses_authentication_config(self):
1644
conf = config.AuthenticationConfig()
1645
conf._get_config().update(
1646
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1649
t = RemoteSSHTransport('bzr+ssh://example.com')
1650
self.assertEqual('bar', t._get_credentials()[0])
1653
class TestRemoteRepository(TestRemote):
1654
"""Base for testing RemoteRepository protocol usage.
1656
These tests contain frozen requests and responses. We want any changes to
1657
what is sent or expected to be require a thoughtful update to these tests
1658
because they might break compatibility with different-versioned servers.
1661
def setup_fake_client_and_repository(self, transport_path):
1662
"""Create the fake client and repository for testing with.
1664
There's no real server here; we just have canned responses sent
1667
:param transport_path: Path below the root of the MemoryTransport
1668
where the repository will be created.
1670
transport = MemoryTransport()
1671
transport.mkdir(transport_path)
1672
client = FakeClient(transport.base)
1673
transport = transport.clone(transport_path)
1674
# we do not want bzrdir to make any remote calls
1675
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1677
repo = RemoteRepository(bzrdir, None, _client=client)
1681
class TestRepositoryFormat(TestRemoteRepository):
1683
def test_fast_delta(self):
1684
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1685
true_format = RemoteRepositoryFormat()
1686
true_format._network_name = true_name
1687
self.assertEqual(True, true_format.fast_deltas)
1688
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1689
false_format = RemoteRepositoryFormat()
1690
false_format._network_name = false_name
1691
self.assertEqual(False, false_format.fast_deltas)
1694
class TestRepositoryGatherStats(TestRemoteRepository):
1696
def test_revid_none(self):
1697
# ('ok',), body with revisions and size
1698
transport_path = 'quack'
1699
repo, client = self.setup_fake_client_and_repository(transport_path)
1700
client.add_success_response_with_body(
1701
'revisions: 2\nsize: 18\n', 'ok')
1702
result = repo.gather_stats(None)
1704
[('call_expecting_body', 'Repository.gather_stats',
1705
('quack/','','no'))],
1707
self.assertEqual({'revisions': 2, 'size': 18}, result)
1709
def test_revid_no_committers(self):
1710
# ('ok',), body without committers
1711
body = ('firstrev: 123456.300 3600\n'
1712
'latestrev: 654231.400 0\n'
1715
transport_path = 'quick'
1716
revid = u'\xc8'.encode('utf8')
1717
repo, client = self.setup_fake_client_and_repository(transport_path)
1718
client.add_success_response_with_body(body, 'ok')
1719
result = repo.gather_stats(revid)
1721
[('call_expecting_body', 'Repository.gather_stats',
1722
('quick/', revid, 'no'))],
1724
self.assertEqual({'revisions': 2, 'size': 18,
1725
'firstrev': (123456.300, 3600),
1726
'latestrev': (654231.400, 0),},
1729
def test_revid_with_committers(self):
1730
# ('ok',), body with committers
1731
body = ('committers: 128\n'
1732
'firstrev: 123456.300 3600\n'
1733
'latestrev: 654231.400 0\n'
1736
transport_path = 'buick'
1737
revid = u'\xc8'.encode('utf8')
1738
repo, client = self.setup_fake_client_and_repository(transport_path)
1739
client.add_success_response_with_body(body, 'ok')
1740
result = repo.gather_stats(revid, True)
1742
[('call_expecting_body', 'Repository.gather_stats',
1743
('buick/', revid, 'yes'))],
1745
self.assertEqual({'revisions': 2, 'size': 18,
1747
'firstrev': (123456.300, 3600),
1748
'latestrev': (654231.400, 0),},
1752
class TestRepositoryGetGraph(TestRemoteRepository):
1754
def test_get_graph(self):
1755
# get_graph returns a graph with a custom parents provider.
1756
transport_path = 'quack'
1757
repo, client = self.setup_fake_client_and_repository(transport_path)
1758
graph = repo.get_graph()
1759
self.assertNotEqual(graph._parents_provider, repo)
1762
class TestRepositoryGetParentMap(TestRemoteRepository):
1764
def test_get_parent_map_caching(self):
1765
# get_parent_map returns from cache until unlock()
1766
# setup a reponse with two revisions
1767
r1 = u'\u0e33'.encode('utf8')
1768
r2 = u'\u0dab'.encode('utf8')
1769
lines = [' '.join([r2, r1]), r1]
1770
encoded_body = bz2.compress('\n'.join(lines))
1772
transport_path = 'quack'
1773
repo, client = self.setup_fake_client_and_repository(transport_path)
1774
client.add_success_response_with_body(encoded_body, 'ok')
1775
client.add_success_response_with_body(encoded_body, 'ok')
1777
graph = repo.get_graph()
1778
parents = graph.get_parent_map([r2])
1779
self.assertEqual({r2: (r1,)}, parents)
1780
# locking and unlocking deeper should not reset
1783
parents = graph.get_parent_map([r1])
1784
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1786
[('call_with_body_bytes_expecting_body',
1787
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1791
# now we call again, and it should use the second response.
1793
graph = repo.get_graph()
1794
parents = graph.get_parent_map([r1])
1795
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1797
[('call_with_body_bytes_expecting_body',
1798
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1800
('call_with_body_bytes_expecting_body',
1801
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1807
def test_get_parent_map_reconnects_if_unknown_method(self):
1808
transport_path = 'quack'
1809
rev_id = 'revision-id'
1810
repo, client = self.setup_fake_client_and_repository(transport_path)
1811
client.add_unknown_method_response('Repository.get_parent_map')
1812
client.add_success_response_with_body(rev_id, 'ok')
1813
self.assertFalse(client._medium._is_remote_before((1, 2)))
1814
parents = repo.get_parent_map([rev_id])
1816
[('call_with_body_bytes_expecting_body',
1817
'Repository.get_parent_map', ('quack/', 'include-missing:',
1819
('disconnect medium',),
1820
('call_expecting_body', 'Repository.get_revision_graph',
1823
# The medium is now marked as being connected to an older server
1824
self.assertTrue(client._medium._is_remote_before((1, 2)))
1825
self.assertEqual({rev_id: ('null:',)}, parents)
1827
def test_get_parent_map_fallback_parentless_node(self):
1828
"""get_parent_map falls back to get_revision_graph on old servers. The
1829
results from get_revision_graph are tweaked to match the get_parent_map
1832
Specifically, a {key: ()} result from get_revision_graph means "no
1833
parents" for that key, which in get_parent_map results should be
1834
represented as {key: ('null:',)}.
1836
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1838
rev_id = 'revision-id'
1839
transport_path = 'quack'
1840
repo, client = self.setup_fake_client_and_repository(transport_path)
1841
client.add_success_response_with_body(rev_id, 'ok')
1842
client._medium._remember_remote_is_before((1, 2))
1843
parents = repo.get_parent_map([rev_id])
1845
[('call_expecting_body', 'Repository.get_revision_graph',
1848
self.assertEqual({rev_id: ('null:',)}, parents)
1850
def test_get_parent_map_unexpected_response(self):
1851
repo, client = self.setup_fake_client_and_repository('path')
1852
client.add_success_response('something unexpected!')
1854
errors.UnexpectedSmartServerResponse,
1855
repo.get_parent_map, ['a-revision-id'])
1857
def test_get_parent_map_negative_caches_missing_keys(self):
1858
self.setup_smart_server_with_call_log()
1859
repo = self.make_repository('foo')
1860
self.assertIsInstance(repo, RemoteRepository)
1862
self.addCleanup(repo.unlock)
1863
self.reset_smart_call_log()
1864
graph = repo.get_graph()
1865
self.assertEqual({},
1866
graph.get_parent_map(['some-missing', 'other-missing']))
1867
self.assertLength(1, self.hpss_calls)
1868
# No call if we repeat this
1869
self.reset_smart_call_log()
1870
graph = repo.get_graph()
1871
self.assertEqual({},
1872
graph.get_parent_map(['some-missing', 'other-missing']))
1873
self.assertLength(0, self.hpss_calls)
1874
# Asking for more unknown keys makes a request.
1875
self.reset_smart_call_log()
1876
graph = repo.get_graph()
1877
self.assertEqual({},
1878
graph.get_parent_map(['some-missing', 'other-missing',
1880
self.assertLength(1, self.hpss_calls)
1882
def disableExtraResults(self):
1883
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1884
SmartServerRepositoryGetParentMap.no_extra_results = True
1886
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1887
self.addCleanup(reset_values)
1889
def test_null_cached_missing_and_stop_key(self):
1890
self.setup_smart_server_with_call_log()
1891
# Make a branch with a single revision.
1892
builder = self.make_branch_builder('foo')
1893
builder.start_series()
1894
builder.build_snapshot('first', None, [
1895
('add', ('', 'root-id', 'directory', ''))])
1896
builder.finish_series()
1897
branch = builder.get_branch()
1898
repo = branch.repository
1899
self.assertIsInstance(repo, RemoteRepository)
1900
# Stop the server from sending extra results.
1901
self.disableExtraResults()
1903
self.addCleanup(repo.unlock)
1904
self.reset_smart_call_log()
1905
graph = repo.get_graph()
1906
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1907
# 'first' it will be a candidate for the stop_keys of subsequent
1908
# requests, and because 'null:' was queried but not returned it will be
1909
# cached as missing.
1910
self.assertEqual({'first': ('null:',)},
1911
graph.get_parent_map(['first', 'null:']))
1912
# Now query for another key. This request will pass along a recipe of
1913
# start and stop keys describing the already cached results, and this
1914
# recipe's revision count must be correct (or else it will trigger an
1915
# error from the server).
1916
self.assertEqual({}, graph.get_parent_map(['another-key']))
1917
# This assertion guards against disableExtraResults silently failing to
1918
# work, thus invalidating the test.
1919
self.assertLength(2, self.hpss_calls)
1921
def test_get_parent_map_gets_ghosts_from_result(self):
1922
# asking for a revision should negatively cache close ghosts in its
1924
self.setup_smart_server_with_call_log()
1925
tree = self.make_branch_and_memory_tree('foo')
1928
builder = treebuilder.TreeBuilder()
1929
builder.start_tree(tree)
1931
builder.finish_tree()
1932
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1933
rev_id = tree.commit('')
1937
self.addCleanup(tree.unlock)
1938
repo = tree.branch.repository
1939
self.assertIsInstance(repo, RemoteRepository)
1941
repo.get_parent_map([rev_id])
1942
self.reset_smart_call_log()
1943
# Now asking for rev_id's ghost parent should not make calls
1944
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1945
self.assertLength(0, self.hpss_calls)
1948
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1950
def test_allows_new_revisions(self):
1951
"""get_parent_map's results can be updated by commit."""
1952
smart_server = server.SmartTCPServer_for_testing()
1953
smart_server.setUp()
1954
self.addCleanup(smart_server.tearDown)
1955
self.make_branch('branch')
1956
branch = Branch.open(smart_server.get_url() + '/branch')
1957
tree = branch.create_checkout('tree', lightweight=True)
1959
self.addCleanup(tree.unlock)
1960
graph = tree.branch.repository.get_graph()
1961
# This provides an opportunity for the missing rev-id to be cached.
1962
self.assertEqual({}, graph.get_parent_map(['rev1']))
1963
tree.commit('message', rev_id='rev1')
1964
graph = tree.branch.repository.get_graph()
1965
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1968
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1970
def test_null_revision(self):
1971
# a null revision has the predictable result {}, we should have no wire
1972
# traffic when calling it with this argument
1973
transport_path = 'empty'
1974
repo, client = self.setup_fake_client_and_repository(transport_path)
1975
client.add_success_response('notused')
1976
# actual RemoteRepository.get_revision_graph is gone, but there's an
1977
# equivalent private method for testing
1978
result = repo._get_revision_graph(NULL_REVISION)
1979
self.assertEqual([], client._calls)
1980
self.assertEqual({}, result)
1982
def test_none_revision(self):
1983
# with none we want the entire graph
1984
r1 = u'\u0e33'.encode('utf8')
1985
r2 = u'\u0dab'.encode('utf8')
1986
lines = [' '.join([r2, r1]), r1]
1987
encoded_body = '\n'.join(lines)
1989
transport_path = 'sinhala'
1990
repo, client = self.setup_fake_client_and_repository(transport_path)
1991
client.add_success_response_with_body(encoded_body, 'ok')
1992
# actual RemoteRepository.get_revision_graph is gone, but there's an
1993
# equivalent private method for testing
1994
result = repo._get_revision_graph(None)
1996
[('call_expecting_body', 'Repository.get_revision_graph',
1999
self.assertEqual({r1: (), r2: (r1, )}, result)
2001
def test_specific_revision(self):
2002
# with a specific revision we want the graph for that
2003
# with none we want the entire graph
2004
r11 = u'\u0e33'.encode('utf8')
2005
r12 = u'\xc9'.encode('utf8')
2006
r2 = u'\u0dab'.encode('utf8')
2007
lines = [' '.join([r2, r11, r12]), r11, r12]
2008
encoded_body = '\n'.join(lines)
2010
transport_path = 'sinhala'
2011
repo, client = self.setup_fake_client_and_repository(transport_path)
2012
client.add_success_response_with_body(encoded_body, 'ok')
2013
result = repo._get_revision_graph(r2)
2015
[('call_expecting_body', 'Repository.get_revision_graph',
2018
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2020
def test_no_such_revision(self):
2022
transport_path = 'sinhala'
2023
repo, client = self.setup_fake_client_and_repository(transport_path)
2024
client.add_error_response('nosuchrevision', revid)
2025
# also check that the right revision is reported in the error
2026
self.assertRaises(errors.NoSuchRevision,
2027
repo._get_revision_graph, revid)
2029
[('call_expecting_body', 'Repository.get_revision_graph',
2030
('sinhala/', revid))],
2033
def test_unexpected_error(self):
2035
transport_path = 'sinhala'
2036
repo, client = self.setup_fake_client_and_repository(transport_path)
2037
client.add_error_response('AnUnexpectedError')
2038
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2039
repo._get_revision_graph, revid)
2040
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2043
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2046
repo, client = self.setup_fake_client_and_repository('quack')
2047
client.add_expected_call(
2048
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2049
'success', ('ok', 'rev-five'))
2050
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2051
self.assertEqual((True, 'rev-five'), result)
2052
self.assertFinished(client)
2054
def test_history_incomplete(self):
2055
repo, client = self.setup_fake_client_and_repository('quack')
2056
client.add_expected_call(
2057
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2058
'success', ('history-incomplete', 10, 'rev-ten'))
2059
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2060
self.assertEqual((False, (10, 'rev-ten')), result)
2061
self.assertFinished(client)
2063
def test_history_incomplete_with_fallback(self):
2064
"""A 'history-incomplete' response causes the fallback repository to be
2065
queried too, if one is set.
2067
# Make a repo with a fallback repo, both using a FakeClient.
2068
format = remote.response_tuple_to_repo_format(
2069
('yes', 'no', 'yes', 'fake-network-name'))
2070
repo, client = self.setup_fake_client_and_repository('quack')
2071
repo._format = format
2072
fallback_repo, ignored = self.setup_fake_client_and_repository(
2074
fallback_repo._client = client
2075
repo.add_fallback_repository(fallback_repo)
2076
# First the client should ask the primary repo
2077
client.add_expected_call(
2078
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2079
'success', ('history-incomplete', 2, 'rev-two'))
2080
# Then it should ask the fallback, using revno/revid from the
2081
# history-incomplete response as the known revno/revid.
2082
client.add_expected_call(
2083
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2084
'success', ('ok', 'rev-one'))
2085
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2086
self.assertEqual((True, 'rev-one'), result)
2087
self.assertFinished(client)
2089
def test_nosuchrevision(self):
2090
# 'nosuchrevision' is returned when the known-revid is not found in the
2091
# remote repo. The client translates that response to NoSuchRevision.
2092
repo, client = self.setup_fake_client_and_repository('quack')
2093
client.add_expected_call(
2094
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2095
'error', ('nosuchrevision', 'rev-foo'))
2097
errors.NoSuchRevision,
2098
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2099
self.assertFinished(client)
2102
class TestRepositoryIsShared(TestRemoteRepository):
2104
def test_is_shared(self):
2105
# ('yes', ) for Repository.is_shared -> 'True'.
2106
transport_path = 'quack'
2107
repo, client = self.setup_fake_client_and_repository(transport_path)
2108
client.add_success_response('yes')
2109
result = repo.is_shared()
2111
[('call', 'Repository.is_shared', ('quack/',))],
2113
self.assertEqual(True, result)
2115
def test_is_not_shared(self):
2116
# ('no', ) for Repository.is_shared -> 'False'.
2117
transport_path = 'qwack'
2118
repo, client = self.setup_fake_client_and_repository(transport_path)
2119
client.add_success_response('no')
2120
result = repo.is_shared()
2122
[('call', 'Repository.is_shared', ('qwack/',))],
2124
self.assertEqual(False, result)
2127
class TestRepositoryLockWrite(TestRemoteRepository):
2129
def test_lock_write(self):
2130
transport_path = 'quack'
2131
repo, client = self.setup_fake_client_and_repository(transport_path)
2132
client.add_success_response('ok', 'a token')
2133
result = repo.lock_write()
2135
[('call', 'Repository.lock_write', ('quack/', ''))],
2137
self.assertEqual('a token', result)
2139
def test_lock_write_already_locked(self):
2140
transport_path = 'quack'
2141
repo, client = self.setup_fake_client_and_repository(transport_path)
2142
client.add_error_response('LockContention')
2143
self.assertRaises(errors.LockContention, repo.lock_write)
2145
[('call', 'Repository.lock_write', ('quack/', ''))],
2148
def test_lock_write_unlockable(self):
2149
transport_path = 'quack'
2150
repo, client = self.setup_fake_client_and_repository(transport_path)
2151
client.add_error_response('UnlockableTransport')
2152
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2154
[('call', 'Repository.lock_write', ('quack/', ''))],
2158
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2160
def test_backwards_compat(self):
2161
self.setup_smart_server_with_call_log()
2162
repo = self.make_repository('.')
2163
self.reset_smart_call_log()
2164
verb = 'Repository.set_make_working_trees'
2165
self.disable_verb(verb)
2166
repo.set_make_working_trees(True)
2167
call_count = len([call for call in self.hpss_calls if
2168
call.call.method == verb])
2169
self.assertEqual(1, call_count)
2171
def test_current(self):
2172
transport_path = 'quack'
2173
repo, client = self.setup_fake_client_and_repository(transport_path)
2174
client.add_expected_call(
2175
'Repository.set_make_working_trees', ('quack/', 'True'),
2177
client.add_expected_call(
2178
'Repository.set_make_working_trees', ('quack/', 'False'),
2180
repo.set_make_working_trees(True)
2181
repo.set_make_working_trees(False)
2184
class TestRepositoryUnlock(TestRemoteRepository):
2186
def test_unlock(self):
2187
transport_path = 'quack'
2188
repo, client = self.setup_fake_client_and_repository(transport_path)
2189
client.add_success_response('ok', 'a token')
2190
client.add_success_response('ok')
2194
[('call', 'Repository.lock_write', ('quack/', '')),
2195
('call', 'Repository.unlock', ('quack/', 'a token'))],
2198
def test_unlock_wrong_token(self):
2199
# If somehow the token is wrong, unlock will raise TokenMismatch.
2200
transport_path = 'quack'
2201
repo, client = self.setup_fake_client_and_repository(transport_path)
2202
client.add_success_response('ok', 'a token')
2203
client.add_error_response('TokenMismatch')
2205
self.assertRaises(errors.TokenMismatch, repo.unlock)
2208
class TestRepositoryHasRevision(TestRemoteRepository):
2210
def test_none(self):
2211
# repo.has_revision(None) should not cause any traffic.
2212
transport_path = 'quack'
2213
repo, client = self.setup_fake_client_and_repository(transport_path)
2215
# The null revision is always there, so has_revision(None) == True.
2216
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2218
# The remote repo shouldn't be accessed.
2219
self.assertEqual([], client._calls)
2222
class TestRepositoryInsertStream(TestRemoteRepository):
2224
def test_unlocked_repo(self):
2225
transport_path = 'quack'
2226
repo, client = self.setup_fake_client_and_repository(transport_path)
2227
client.add_expected_call(
2228
'Repository.insert_stream', ('quack/', ''),
2230
client.add_expected_call(
2231
'Repository.insert_stream', ('quack/', ''),
2233
sink = repo._get_sink()
2234
fmt = repository.RepositoryFormat.get_default_format()
2235
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2236
self.assertEqual([], resume_tokens)
2237
self.assertEqual(set(), missing_keys)
2238
self.assertFinished(client)
2240
def test_locked_repo_with_no_lock_token(self):
2241
transport_path = 'quack'
2242
repo, client = self.setup_fake_client_and_repository(transport_path)
2243
client.add_expected_call(
2244
'Repository.lock_write', ('quack/', ''),
2245
'success', ('ok', ''))
2246
client.add_expected_call(
2247
'Repository.insert_stream', ('quack/', ''),
2249
client.add_expected_call(
2250
'Repository.insert_stream', ('quack/', ''),
2253
sink = repo._get_sink()
2254
fmt = repository.RepositoryFormat.get_default_format()
2255
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2256
self.assertEqual([], resume_tokens)
2257
self.assertEqual(set(), missing_keys)
2258
self.assertFinished(client)
2260
def test_locked_repo_with_lock_token(self):
2261
transport_path = 'quack'
2262
repo, client = self.setup_fake_client_and_repository(transport_path)
2263
client.add_expected_call(
2264
'Repository.lock_write', ('quack/', ''),
2265
'success', ('ok', 'a token'))
2266
client.add_expected_call(
2267
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2269
client.add_expected_call(
2270
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2273
sink = repo._get_sink()
2274
fmt = repository.RepositoryFormat.get_default_format()
2275
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2276
self.assertEqual([], resume_tokens)
2277
self.assertEqual(set(), missing_keys)
2278
self.assertFinished(client)
2281
class TestRepositoryTarball(TestRemoteRepository):
2283
# This is a canned tarball reponse we can validate against
2285
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2286
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2287
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2288
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2289
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2290
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2291
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2292
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2293
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2294
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2295
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2296
'nWQ7QH/F3JFOFCQ0aSPfA='
2299
def test_repository_tarball(self):
2300
# Test that Repository.tarball generates the right operations
2301
transport_path = 'repo'
2302
expected_calls = [('call_expecting_body', 'Repository.tarball',
2303
('repo/', 'bz2',),),
2305
repo, client = self.setup_fake_client_and_repository(transport_path)
2306
client.add_success_response_with_body(self.tarball_content, 'ok')
2307
# Now actually ask for the tarball
2308
tarball_file = repo._get_tarball('bz2')
2310
self.assertEqual(expected_calls, client._calls)
2311
self.assertEqual(self.tarball_content, tarball_file.read())
2313
tarball_file.close()
2316
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2317
"""RemoteRepository.copy_content_into optimizations"""
2319
def test_copy_content_remote_to_local(self):
2320
self.transport_server = server.SmartTCPServer_for_testing
2321
src_repo = self.make_repository('repo1')
2322
src_repo = repository.Repository.open(self.get_url('repo1'))
2323
# At the moment the tarball-based copy_content_into can't write back
2324
# into a smart server. It would be good if it could upload the
2325
# tarball; once that works we'd have to create repositories of
2326
# different formats. -- mbp 20070410
2327
dest_url = self.get_vfs_only_url('repo2')
2328
dest_bzrdir = BzrDir.create(dest_url)
2329
dest_repo = dest_bzrdir.create_repository()
2330
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2331
self.assertTrue(isinstance(src_repo, RemoteRepository))
2332
src_repo.copy_content_into(dest_repo)
2335
class _StubRealPackRepository(object):
2337
def __init__(self, calls):
2339
self._pack_collection = _StubPackCollection(calls)
2341
def is_in_write_group(self):
2344
def refresh_data(self):
2345
self.calls.append(('pack collection reload_pack_names',))
2348
class _StubPackCollection(object):
2350
def __init__(self, calls):
2354
self.calls.append(('pack collection autopack',))
2357
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2358
"""Tests for RemoteRepository.autopack implementation."""
2361
"""When the server returns 'ok' and there's no _real_repository, then
2362
nothing else happens: the autopack method is done.
2364
transport_path = 'quack'
2365
repo, client = self.setup_fake_client_and_repository(transport_path)
2366
client.add_expected_call(
2367
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2369
self.assertFinished(client)
2371
def test_ok_with_real_repo(self):
2372
"""When the server returns 'ok' and there is a _real_repository, then
2373
the _real_repository's reload_pack_name's method will be called.
2375
transport_path = 'quack'
2376
repo, client = self.setup_fake_client_and_repository(transport_path)
2377
client.add_expected_call(
2378
'PackRepository.autopack', ('quack/',),
2380
repo._real_repository = _StubRealPackRepository(client._calls)
2383
[('call', 'PackRepository.autopack', ('quack/',)),
2384
('pack collection reload_pack_names',)],
2387
def test_backwards_compatibility(self):
2388
"""If the server does not recognise the PackRepository.autopack verb,
2389
fallback to the real_repository's implementation.
2391
transport_path = 'quack'
2392
repo, client = self.setup_fake_client_and_repository(transport_path)
2393
client.add_unknown_method_response('PackRepository.autopack')
2394
def stub_ensure_real():
2395
client._calls.append(('_ensure_real',))
2396
repo._real_repository = _StubRealPackRepository(client._calls)
2397
repo._ensure_real = stub_ensure_real
2400
[('call', 'PackRepository.autopack', ('quack/',)),
2402
('pack collection autopack',)],
2406
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2407
"""Base class for unit tests for bzrlib.remote._translate_error."""
2409
def translateTuple(self, error_tuple, **context):
2410
"""Call _translate_error with an ErrorFromSmartServer built from the
2413
:param error_tuple: A tuple of a smart server response, as would be
2414
passed to an ErrorFromSmartServer.
2415
:kwargs context: context items to call _translate_error with.
2417
:returns: The error raised by _translate_error.
2419
# Raise the ErrorFromSmartServer before passing it as an argument,
2420
# because _translate_error may need to re-raise it with a bare 'raise'
2422
server_error = errors.ErrorFromSmartServer(error_tuple)
2423
translated_error = self.translateErrorFromSmartServer(
2424
server_error, **context)
2425
return translated_error
2427
def translateErrorFromSmartServer(self, error_object, **context):
2428
"""Like translateTuple, but takes an already constructed
2429
ErrorFromSmartServer rather than a tuple.
2433
except errors.ErrorFromSmartServer, server_error:
2434
translated_error = self.assertRaises(
2435
errors.BzrError, remote._translate_error, server_error,
2437
return translated_error
2440
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2441
"""Unit tests for bzrlib.remote._translate_error.
2443
Given an ErrorFromSmartServer (which has an error tuple from a smart
2444
server) and some context, _translate_error raises more specific errors from
2447
This test case covers the cases where _translate_error succeeds in
2448
translating an ErrorFromSmartServer to something better. See
2449
TestErrorTranslationRobustness for other cases.
2452
def test_NoSuchRevision(self):
2453
branch = self.make_branch('')
2455
translated_error = self.translateTuple(
2456
('NoSuchRevision', revid), branch=branch)
2457
expected_error = errors.NoSuchRevision(branch, revid)
2458
self.assertEqual(expected_error, translated_error)
2460
def test_nosuchrevision(self):
2461
repository = self.make_repository('')
2463
translated_error = self.translateTuple(
2464
('nosuchrevision', revid), repository=repository)
2465
expected_error = errors.NoSuchRevision(repository, revid)
2466
self.assertEqual(expected_error, translated_error)
2468
def test_nobranch(self):
2469
bzrdir = self.make_bzrdir('')
2470
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2471
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2472
self.assertEqual(expected_error, translated_error)
2474
def test_LockContention(self):
2475
translated_error = self.translateTuple(('LockContention',))
2476
expected_error = errors.LockContention('(remote lock)')
2477
self.assertEqual(expected_error, translated_error)
2479
def test_UnlockableTransport(self):
2480
bzrdir = self.make_bzrdir('')
2481
translated_error = self.translateTuple(
2482
('UnlockableTransport',), bzrdir=bzrdir)
2483
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2484
self.assertEqual(expected_error, translated_error)
2486
def test_LockFailed(self):
2487
lock = 'str() of a server lock'
2488
why = 'str() of why'
2489
translated_error = self.translateTuple(('LockFailed', lock, why))
2490
expected_error = errors.LockFailed(lock, why)
2491
self.assertEqual(expected_error, translated_error)
2493
def test_TokenMismatch(self):
2494
token = 'a lock token'
2495
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2496
expected_error = errors.TokenMismatch(token, '(remote token)')
2497
self.assertEqual(expected_error, translated_error)
2499
def test_Diverged(self):
2500
branch = self.make_branch('a')
2501
other_branch = self.make_branch('b')
2502
translated_error = self.translateTuple(
2503
('Diverged',), branch=branch, other_branch=other_branch)
2504
expected_error = errors.DivergedBranches(branch, other_branch)
2505
self.assertEqual(expected_error, translated_error)
2507
def test_ReadError_no_args(self):
2509
translated_error = self.translateTuple(('ReadError',), path=path)
2510
expected_error = errors.ReadError(path)
2511
self.assertEqual(expected_error, translated_error)
2513
def test_ReadError(self):
2515
translated_error = self.translateTuple(('ReadError', path))
2516
expected_error = errors.ReadError(path)
2517
self.assertEqual(expected_error, translated_error)
2519
def test_PermissionDenied_no_args(self):
2521
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2522
expected_error = errors.PermissionDenied(path)
2523
self.assertEqual(expected_error, translated_error)
2525
def test_PermissionDenied_one_arg(self):
2527
translated_error = self.translateTuple(('PermissionDenied', path))
2528
expected_error = errors.PermissionDenied(path)
2529
self.assertEqual(expected_error, translated_error)
2531
def test_PermissionDenied_one_arg_and_context(self):
2532
"""Given a choice between a path from the local context and a path on
2533
the wire, _translate_error prefers the path from the local context.
2535
local_path = 'local path'
2536
remote_path = 'remote path'
2537
translated_error = self.translateTuple(
2538
('PermissionDenied', remote_path), path=local_path)
2539
expected_error = errors.PermissionDenied(local_path)
2540
self.assertEqual(expected_error, translated_error)
2542
def test_PermissionDenied_two_args(self):
2544
extra = 'a string with extra info'
2545
translated_error = self.translateTuple(
2546
('PermissionDenied', path, extra))
2547
expected_error = errors.PermissionDenied(path, extra)
2548
self.assertEqual(expected_error, translated_error)
2551
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2552
"""Unit tests for bzrlib.remote._translate_error's robustness.
2554
TestErrorTranslationSuccess is for cases where _translate_error can
2555
translate successfully. This class about how _translate_err behaves when
2556
it fails to translate: it re-raises the original error.
2559
def test_unrecognised_server_error(self):
2560
"""If the error code from the server is not recognised, the original
2561
ErrorFromSmartServer is propagated unmodified.
2563
error_tuple = ('An unknown error tuple',)
2564
server_error = errors.ErrorFromSmartServer(error_tuple)
2565
translated_error = self.translateErrorFromSmartServer(server_error)
2566
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2567
self.assertEqual(expected_error, translated_error)
2569
def test_context_missing_a_key(self):
2570
"""In case of a bug in the client, or perhaps an unexpected response
2571
from a server, _translate_error returns the original error tuple from
2572
the server and mutters a warning.
2574
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2575
# in the context dict. So let's give it an empty context dict instead
2576
# to exercise its error recovery.
2578
error_tuple = ('NoSuchRevision', 'revid')
2579
server_error = errors.ErrorFromSmartServer(error_tuple)
2580
translated_error = self.translateErrorFromSmartServer(server_error)
2581
self.assertEqual(server_error, translated_error)
2582
# In addition to re-raising ErrorFromSmartServer, some debug info has
2583
# been muttered to the log file for developer to look at.
2584
self.assertContainsRe(
2585
self._get_log(keep_log_file=True),
2586
"Missing key 'branch' in context")
2588
def test_path_missing(self):
2589
"""Some translations (PermissionDenied, ReadError) can determine the
2590
'path' variable from either the wire or the local context. If neither
2591
has it, then an error is raised.
2593
error_tuple = ('ReadError',)
2594
server_error = errors.ErrorFromSmartServer(error_tuple)
2595
translated_error = self.translateErrorFromSmartServer(server_error)
2596
self.assertEqual(server_error, translated_error)
2597
# In addition to re-raising ErrorFromSmartServer, some debug info has
2598
# been muttered to the log file for developer to look at.
2599
self.assertContainsRe(
2600
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2603
class TestStacking(tests.TestCaseWithTransport):
2604
"""Tests for operations on stacked remote repositories.
2606
The underlying format type must support stacking.
2609
def test_access_stacked_remote(self):
2610
# based on <http://launchpad.net/bugs/261315>
2611
# make a branch stacked on another repository containing an empty
2612
# revision, then open it over hpss - we should be able to see that
2614
base_transport = self.get_transport()
2615
base_builder = self.make_branch_builder('base', format='1.9')
2616
base_builder.start_series()
2617
base_revid = base_builder.build_snapshot('rev-id', None,
2618
[('add', ('', None, 'directory', None))],
2620
base_builder.finish_series()
2621
stacked_branch = self.make_branch('stacked', format='1.9')
2622
stacked_branch.set_stacked_on_url('../base')
2623
# start a server looking at this
2624
smart_server = server.SmartTCPServer_for_testing()
2625
smart_server.setUp()
2626
self.addCleanup(smart_server.tearDown)
2627
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2628
# can get its branch and repository
2629
remote_branch = remote_bzrdir.open_branch()
2630
remote_repo = remote_branch.repository
2631
remote_repo.lock_read()
2633
# it should have an appropriate fallback repository, which should also
2634
# be a RemoteRepository
2635
self.assertLength(1, remote_repo._fallback_repositories)
2636
self.assertIsInstance(remote_repo._fallback_repositories[0],
2638
# and it has the revision committed to the underlying repository;
2639
# these have varying implementations so we try several of them
2640
self.assertTrue(remote_repo.has_revisions([base_revid]))
2641
self.assertTrue(remote_repo.has_revision(base_revid))
2642
self.assertEqual(remote_repo.get_revision(base_revid).message,
2645
remote_repo.unlock()
2647
def prepare_stacked_remote_branch(self):
2648
"""Get stacked_upon and stacked branches with content in each."""
2649
self.setup_smart_server_with_call_log()
2650
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2651
tree1.commit('rev1', rev_id='rev1')
2652
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2653
).open_workingtree()
2654
tree2.commit('local changes make me feel good.')
2655
branch2 = Branch.open(self.get_url('tree2'))
2657
self.addCleanup(branch2.unlock)
2658
return tree1.branch, branch2
2660
def test_stacked_get_parent_map(self):
2661
# the public implementation of get_parent_map obeys stacking
2662
_, branch = self.prepare_stacked_remote_branch()
2663
repo = branch.repository
2664
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2666
def test_unstacked_get_parent_map(self):
2667
# _unstacked_provider.get_parent_map ignores stacking
2668
_, branch = self.prepare_stacked_remote_branch()
2669
provider = branch.repository._unstacked_provider
2670
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2672
def fetch_stream_to_rev_order(self, stream):
2674
for kind, substream in stream:
2675
if not kind == 'revisions':
2678
for content in substream:
2679
result.append(content.key[-1])
2682
def get_ordered_revs(self, format, order, branch_factory=None):
2683
"""Get a list of the revisions in a stream to format format.
2685
:param format: The format of the target.
2686
:param order: the order that target should have requested.
2687
:param branch_factory: A callable to create a trunk and stacked branch
2688
to fetch from. If none, self.prepare_stacked_remote_branch is used.
2689
:result: The revision ids in the stream, in the order seen,
2690
the topological order of revisions in the source.
2692
unordered_format = bzrdir.format_registry.get(format)()
2693
target_repository_format = unordered_format.repository_format
2695
self.assertEqual(order, target_repository_format._fetch_order)
2696
if branch_factory is None:
2697
branch_factory = self.prepare_stacked_remote_branch
2698
_, stacked = branch_factory()
2699
source = stacked.repository._get_source(target_repository_format)
2700
tip = stacked.last_revision()
2701
revs = stacked.repository.get_ancestry(tip)
2702
search = graph.PendingAncestryResult([tip], stacked.repository)
2703
self.reset_smart_call_log()
2704
stream = source.get_stream(search)
2707
# We trust that if a revision is in the stream the rest of the new
2708
# content for it is too, as per our main fetch tests; here we are
2709
# checking that the revisions are actually included at all, and their
2711
return self.fetch_stream_to_rev_order(stream), revs
2713
def test_stacked_get_stream_unordered(self):
2714
# Repository._get_source.get_stream() from a stacked repository with
2715
# unordered yields the full data from both stacked and stacked upon
2717
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2718
self.assertEqual(set(expected_revs), set(rev_ord))
2719
# Getting unordered results should have made a streaming data request
2720
# from the server, then one from the backing branch.
2721
self.assertLength(2, self.hpss_calls)
2723
def test_stacked_on_stacked_get_stream_unordered(self):
2724
# Repository._get_source.get_stream() from a stacked repository which
2725
# is itself stacked yields the full data from all three sources.
2726
def make_stacked_stacked():
2727
_, stacked = self.prepare_stacked_remote_branch()
2728
tree = stacked.bzrdir.sprout('tree3', stacked=True
2729
).open_workingtree()
2730
tree.commit('more local changes are better')
2731
branch = Branch.open(self.get_url('tree3'))
2734
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
2735
branch_factory=make_stacked_stacked)
2736
self.assertEqual(set(expected_revs), set(rev_ord))
2737
# Getting unordered results should have made a streaming data request
2738
# from the server, and one from each backing repo
2739
self.assertLength(3, self.hpss_calls)
2741
def test_stacked_get_stream_topological(self):
2742
# Repository._get_source.get_stream() from a stacked repository with
2743
# topological sorting yields the full data from both stacked and
2744
# stacked upon sources in topological order.
2745
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2746
self.assertEqual(expected_revs, rev_ord)
2747
# Getting topological sort requires VFS calls still
2748
self.assertLength(12, self.hpss_calls)
2750
def test_stacked_get_stream_groupcompress(self):
2751
# Repository._get_source.get_stream() from a stacked repository with
2752
# groupcompress sorting yields the full data from both stacked and
2753
# stacked upon sources in groupcompress order.
2754
raise tests.TestSkipped('No groupcompress ordered format available')
2755
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2756
self.assertEqual(expected_revs, reversed(rev_ord))
2757
# Getting unordered results should have made a streaming data request
2758
# from the backing branch, and one from the stacked on branch.
2759
self.assertLength(2, self.hpss_calls)
2761
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
2762
# When pulling some fixed amount of content that is more than the
2763
# source has (because some is coming from a fallback branch, no error
2764
# should be received. This was reported as bug 360791.
2765
# Need three branches: a trunk, a stacked branch, and a preexisting
2766
# branch pulling content from stacked and trunk.
2767
self.setup_smart_server_with_call_log()
2768
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
2769
r1 = trunk.commit('start')
2770
stacked_branch = trunk.branch.create_clone_on_transport(
2771
self.get_transport('stacked'), stacked_on=trunk.branch.base)
2772
local = self.make_branch('local', format='1.9-rich-root')
2773
local.repository.fetch(stacked_branch.repository,
2774
stacked_branch.last_revision())
2777
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2780
super(TestRemoteBranchEffort, self).setUp()
2781
# Create a smart server that publishes whatever the backing VFS server
2783
self.smart_server = server.SmartTCPServer_for_testing()
2784
self.smart_server.setUp(self.get_server())
2785
self.addCleanup(self.smart_server.tearDown)
2786
# Log all HPSS calls into self.hpss_calls.
2787
_SmartClient.hooks.install_named_hook(
2788
'call', self.capture_hpss_call, None)
2789
self.hpss_calls = []
2791
def capture_hpss_call(self, params):
2792
self.hpss_calls.append(params.method)
2794
def test_copy_content_into_avoids_revision_history(self):
2795
local = self.make_branch('local')
2796
remote_backing_tree = self.make_branch_and_tree('remote')
2797
remote_backing_tree.commit("Commit.")
2798
remote_branch_url = self.smart_server.get_url() + 'remote'
2799
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2800
local.repository.fetch(remote_branch.repository)
2801
self.hpss_calls = []
2802
remote_branch.copy_content_into(local)
2803
self.assertFalse('Branch.revision_history' in self.hpss_calls)