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
45
from bzrlib.branch import Branch
46
from bzrlib.bzrdir import BzrDir, BzrDirFormat
47
from bzrlib.remote import (
53
RemoteRepositoryFormat,
55
from bzrlib.repofmt import groupcompress_repo, pack_repo
56
from bzrlib.revision import NULL_REVISION
57
from bzrlib.smart import server, medium
58
from bzrlib.smart.client import _SmartClient
59
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
60
from bzrlib.tests import (
62
split_suite_by_condition,
66
from bzrlib.transport import get_transport, http
67
from bzrlib.transport.memory import MemoryTransport
68
from bzrlib.transport.remote import (
74
def load_tests(standard_tests, module, loader):
75
to_adapt, result = split_suite_by_condition(
76
standard_tests, condition_isinstance(BasicRemoteObjectTests))
77
smart_server_version_scenarios = [
79
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
81
{'transport_server': server.SmartTCPServer_for_testing})]
82
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
85
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
88
super(BasicRemoteObjectTests, self).setUp()
89
self.transport = self.get_transport()
90
# make a branch that can be opened over the smart transport
91
self.local_wt = BzrDir.create_standalone_workingtree('.')
94
self.transport.disconnect()
95
tests.TestCaseWithTransport.tearDown(self)
97
def test_create_remote_bzrdir(self):
98
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
99
self.assertIsInstance(b, BzrDir)
101
def test_open_remote_branch(self):
102
# open a standalone branch in the working directory
103
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
104
branch = b.open_branch()
105
self.assertIsInstance(branch, Branch)
107
def test_remote_repository(self):
108
b = BzrDir.open_from_transport(self.transport)
109
repo = b.open_repository()
110
revid = u'\xc823123123'.encode('utf8')
111
self.assertFalse(repo.has_revision(revid))
112
self.local_wt.commit(message='test commit', rev_id=revid)
113
self.assertTrue(repo.has_revision(revid))
115
def test_remote_branch_revision_history(self):
116
b = BzrDir.open_from_transport(self.transport).open_branch()
117
self.assertEqual([], b.revision_history())
118
r1 = self.local_wt.commit('1st commit')
119
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
120
self.assertEqual([r1, r2], b.revision_history())
122
def test_find_correct_format(self):
123
"""Should open a RemoteBzrDir over a RemoteTransport"""
124
fmt = BzrDirFormat.find_format(self.transport)
125
self.assertTrue(RemoteBzrDirFormat
126
in BzrDirFormat._control_server_formats)
127
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
129
def test_open_detected_smart_format(self):
130
fmt = BzrDirFormat.find_format(self.transport)
131
d = fmt.open(self.transport)
132
self.assertIsInstance(d, BzrDir)
134
def test_remote_branch_repr(self):
135
b = BzrDir.open_from_transport(self.transport).open_branch()
136
self.assertStartsWith(str(b), 'RemoteBranch(')
138
def test_remote_branch_format_supports_stacking(self):
140
self.make_branch('unstackable', format='pack-0.92')
141
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
142
self.assertFalse(b._format.supports_stacking())
143
self.make_branch('stackable', format='1.9')
144
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
145
self.assertTrue(b._format.supports_stacking())
147
def test_remote_repo_format_supports_external_references(self):
149
bd = self.make_bzrdir('unstackable', format='pack-0.92')
150
r = bd.create_repository()
151
self.assertFalse(r._format.supports_external_lookups)
152
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
153
self.assertFalse(r._format.supports_external_lookups)
154
bd = self.make_bzrdir('stackable', format='1.9')
155
r = bd.create_repository()
156
self.assertTrue(r._format.supports_external_lookups)
157
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
158
self.assertTrue(r._format.supports_external_lookups)
160
def test_remote_branch_set_append_revisions_only(self):
161
# Make a format 1.9 branch, which supports append_revisions_only
162
branch = self.make_branch('branch', format='1.9')
163
config = branch.get_config()
164
branch.set_append_revisions_only(True)
166
'True', config.get_user_option('append_revisions_only'))
167
branch.set_append_revisions_only(False)
169
'False', config.get_user_option('append_revisions_only'))
171
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
172
branch = self.make_branch('branch', format='knit')
173
config = branch.get_config()
175
errors.UpgradeRequired, branch.set_append_revisions_only, True)
178
class FakeProtocol(object):
179
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
181
def __init__(self, body, fake_client):
183
self._body_buffer = None
184
self._fake_client = fake_client
186
def read_body_bytes(self, count=-1):
187
if self._body_buffer is None:
188
self._body_buffer = StringIO(self.body)
189
bytes = self._body_buffer.read(count)
190
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
191
self._fake_client.expecting_body = False
194
def cancel_read_body(self):
195
self._fake_client.expecting_body = False
197
def read_streamed_body(self):
201
class FakeClient(_SmartClient):
202
"""Lookalike for _SmartClient allowing testing."""
204
def __init__(self, fake_medium_base='fake base'):
205
"""Create a FakeClient."""
208
self.expecting_body = False
209
# if non-None, this is the list of expected calls, with only the
210
# method name and arguments included. the body might be hard to
211
# compute so is not included. If a call is None, that call can
213
self._expected_calls = None
214
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
216
def add_expected_call(self, call_name, call_args, response_type,
217
response_args, response_body=None):
218
if self._expected_calls is None:
219
self._expected_calls = []
220
self._expected_calls.append((call_name, call_args))
221
self.responses.append((response_type, response_args, response_body))
223
def add_success_response(self, *args):
224
self.responses.append(('success', args, None))
226
def add_success_response_with_body(self, body, *args):
227
self.responses.append(('success', args, body))
228
if self._expected_calls is not None:
229
self._expected_calls.append(None)
231
def add_error_response(self, *args):
232
self.responses.append(('error', args))
234
def add_unknown_method_response(self, verb):
235
self.responses.append(('unknown', verb))
237
def finished_test(self):
238
if self._expected_calls:
239
raise AssertionError("%r finished but was still expecting %r"
240
% (self, self._expected_calls[0]))
242
def _get_next_response(self):
244
response_tuple = self.responses.pop(0)
245
except IndexError, e:
246
raise AssertionError("%r didn't expect any more calls"
248
if response_tuple[0] == 'unknown':
249
raise errors.UnknownSmartMethod(response_tuple[1])
250
elif response_tuple[0] == 'error':
251
raise errors.ErrorFromSmartServer(response_tuple[1])
252
return response_tuple
254
def _check_call(self, method, args):
255
if self._expected_calls is None:
256
# the test should be updated to say what it expects
259
next_call = self._expected_calls.pop(0)
261
raise AssertionError("%r didn't expect any more calls "
263
% (self, method, args,))
264
if next_call is None:
266
if method != next_call[0] or args != next_call[1]:
267
raise AssertionError("%r expected %r%r "
269
% (self, next_call[0], next_call[1], method, args,))
271
def call(self, method, *args):
272
self._check_call(method, args)
273
self._calls.append(('call', method, args))
274
return self._get_next_response()[1]
276
def call_expecting_body(self, method, *args):
277
self._check_call(method, args)
278
self._calls.append(('call_expecting_body', method, args))
279
result = self._get_next_response()
280
self.expecting_body = True
281
return result[1], FakeProtocol(result[2], self)
283
def call_with_body_bytes(self, method, args, body):
284
self._check_call(method, args)
285
self._calls.append(('call_with_body_bytes', method, args, body))
286
result = self._get_next_response()
287
return result[1], FakeProtocol(result[2], self)
289
def call_with_body_bytes_expecting_body(self, method, args, body):
290
self._check_call(method, args)
291
self._calls.append(('call_with_body_bytes_expecting_body', method,
293
result = self._get_next_response()
294
self.expecting_body = True
295
return result[1], FakeProtocol(result[2], self)
297
def call_with_body_stream(self, args, stream):
298
# Explicitly consume the stream before checking for an error, because
299
# that's what happens a real medium.
300
stream = list(stream)
301
self._check_call(args[0], args[1:])
302
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
303
result = self._get_next_response()
304
# The second value returned from call_with_body_stream is supposed to
305
# be a response_handler object, but so far no tests depend on that.
306
response_handler = None
307
return result[1], response_handler
310
class FakeMedium(medium.SmartClientMedium):
312
def __init__(self, client_calls, base):
313
medium.SmartClientMedium.__init__(self, base)
314
self._client_calls = client_calls
316
def disconnect(self):
317
self._client_calls.append(('disconnect medium',))
320
class TestVfsHas(tests.TestCase):
322
def test_unicode_path(self):
323
client = FakeClient('/')
324
client.add_success_response('yes',)
325
transport = RemoteTransport('bzr://localhost/', _client=client)
326
filename = u'/hell\u00d8'.encode('utf8')
327
result = transport.has(filename)
329
[('call', 'has', (filename,))],
331
self.assertTrue(result)
334
class TestRemote(tests.TestCaseWithMemoryTransport):
336
def get_branch_format(self):
337
reference_bzrdir_format = bzrdir.format_registry.get('default')()
338
return reference_bzrdir_format.get_branch_format()
340
def get_repo_format(self):
341
reference_bzrdir_format = bzrdir.format_registry.get('default')()
342
return reference_bzrdir_format.repository_format
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 TestBzrDirOpen(TestRemote):
479
def make_fake_client_and_transport(self, path='quack'):
480
transport = MemoryTransport()
481
transport.mkdir(path)
482
transport = transport.clone(path)
483
client = FakeClient(transport.base)
484
return client, transport
486
def test_absent(self):
487
client, transport = self.make_fake_client_and_transport()
488
client.add_expected_call(
489
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
490
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
491
remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
492
self.assertFinished(client)
494
def test_present_without_workingtree(self):
495
client, transport = self.make_fake_client_and_transport()
496
client.add_expected_call(
497
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
498
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
499
_client=client, _force_probe=True)
500
self.assertIsInstance(bd, RemoteBzrDir)
501
self.assertFalse(bd.has_workingtree())
502
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
503
self.assertFinished(client)
505
def test_present_with_workingtree(self):
506
client, transport = self.make_fake_client_and_transport()
507
client.add_expected_call(
508
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
509
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
510
_client=client, _force_probe=True)
511
self.assertIsInstance(bd, RemoteBzrDir)
512
self.assertTrue(bd.has_workingtree())
513
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
514
self.assertFinished(client)
516
def test_backwards_compat(self):
517
client, transport = self.make_fake_client_and_transport()
518
client.add_expected_call(
519
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
520
client.add_expected_call(
521
'BzrDir.open', ('quack/',), 'success', ('yes',))
522
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
523
_client=client, _force_probe=True)
524
self.assertIsInstance(bd, RemoteBzrDir)
525
self.assertFinished(client)
528
class TestBzrDirOpenBranch(TestRemote):
530
def test_backwards_compat(self):
531
self.setup_smart_server_with_call_log()
532
self.make_branch('.')
533
a_dir = BzrDir.open(self.get_url('.'))
534
self.reset_smart_call_log()
535
verb = 'BzrDir.open_branchV2'
536
self.disable_verb(verb)
537
format = a_dir.open_branch()
538
call_count = len([call for call in self.hpss_calls if
539
call.call.method == verb])
540
self.assertEqual(1, call_count)
542
def test_branch_present(self):
543
reference_format = self.get_repo_format()
544
network_name = reference_format.network_name()
545
branch_network_name = self.get_branch_format().network_name()
546
transport = MemoryTransport()
547
transport.mkdir('quack')
548
transport = transport.clone('quack')
549
client = FakeClient(transport.base)
550
client.add_expected_call(
551
'BzrDir.open_branchV2', ('quack/',),
552
'success', ('branch', branch_network_name))
553
client.add_expected_call(
554
'BzrDir.find_repositoryV3', ('quack/',),
555
'success', ('ok', '', 'no', 'no', 'no', network_name))
556
client.add_expected_call(
557
'Branch.get_stacked_on_url', ('quack/',),
558
'error', ('NotStacked',))
559
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
561
result = bzrdir.open_branch()
562
self.assertIsInstance(result, RemoteBranch)
563
self.assertEqual(bzrdir, result.bzrdir)
564
self.assertFinished(client)
566
def test_branch_missing(self):
567
transport = MemoryTransport()
568
transport.mkdir('quack')
569
transport = transport.clone('quack')
570
client = FakeClient(transport.base)
571
client.add_error_response('nobranch')
572
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
574
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
576
[('call', 'BzrDir.open_branchV2', ('quack/',))],
579
def test__get_tree_branch(self):
580
# _get_tree_branch is a form of open_branch, but it should only ask for
581
# branch opening, not any other network requests.
584
calls.append("Called")
586
transport = MemoryTransport()
587
# no requests on the network - catches other api calls being made.
588
client = FakeClient(transport.base)
589
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
591
# patch the open_branch call to record that it was called.
592
bzrdir.open_branch = open_branch
593
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
594
self.assertEqual(["Called"], calls)
595
self.assertEqual([], client._calls)
597
def test_url_quoting_of_path(self):
598
# Relpaths on the wire should not be URL-escaped. So "~" should be
599
# transmitted as "~", not "%7E".
600
transport = RemoteTCPTransport('bzr://localhost/~hello/')
601
client = FakeClient(transport.base)
602
reference_format = self.get_repo_format()
603
network_name = reference_format.network_name()
604
branch_network_name = self.get_branch_format().network_name()
605
client.add_expected_call(
606
'BzrDir.open_branchV2', ('~hello/',),
607
'success', ('branch', branch_network_name))
608
client.add_expected_call(
609
'BzrDir.find_repositoryV3', ('~hello/',),
610
'success', ('ok', '', 'no', 'no', 'no', network_name))
611
client.add_expected_call(
612
'Branch.get_stacked_on_url', ('~hello/',),
613
'error', ('NotStacked',))
614
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
616
result = bzrdir.open_branch()
617
self.assertFinished(client)
619
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
620
reference_format = self.get_repo_format()
621
network_name = reference_format.network_name()
622
transport = MemoryTransport()
623
transport.mkdir('quack')
624
transport = transport.clone('quack')
626
rich_response = 'yes'
630
subtree_response = 'yes'
632
subtree_response = 'no'
633
client = FakeClient(transport.base)
634
client.add_success_response(
635
'ok', '', rich_response, subtree_response, external_lookup,
637
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
639
result = bzrdir.open_repository()
641
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
643
self.assertIsInstance(result, RemoteRepository)
644
self.assertEqual(bzrdir, result.bzrdir)
645
self.assertEqual(rich_root, result._format.rich_root_data)
646
self.assertEqual(subtrees, result._format.supports_tree_reference)
648
def test_open_repository_sets_format_attributes(self):
649
self.check_open_repository(True, True)
650
self.check_open_repository(False, True)
651
self.check_open_repository(True, False)
652
self.check_open_repository(False, False)
653
self.check_open_repository(False, False, 'yes')
655
def test_old_server(self):
656
"""RemoteBzrDirFormat should fail to probe if the server version is too
659
self.assertRaises(errors.NotBranchError,
660
RemoteBzrDirFormat.probe_transport, OldServerTransport())
663
class TestBzrDirCreateBranch(TestRemote):
665
def test_backwards_compat(self):
666
self.setup_smart_server_with_call_log()
667
repo = self.make_repository('.')
668
self.reset_smart_call_log()
669
self.disable_verb('BzrDir.create_branch')
670
branch = repo.bzrdir.create_branch()
671
create_branch_call_count = len([call for call in self.hpss_calls if
672
call.call.method == 'BzrDir.create_branch'])
673
self.assertEqual(1, create_branch_call_count)
675
def test_current_server(self):
676
transport = self.get_transport('.')
677
transport = transport.clone('quack')
678
self.make_repository('quack')
679
client = FakeClient(transport.base)
680
reference_bzrdir_format = bzrdir.format_registry.get('default')()
681
reference_format = reference_bzrdir_format.get_branch_format()
682
network_name = reference_format.network_name()
683
reference_repo_fmt = reference_bzrdir_format.repository_format
684
reference_repo_name = reference_repo_fmt.network_name()
685
client.add_expected_call(
686
'BzrDir.create_branch', ('quack/', network_name),
687
'success', ('ok', network_name, '', 'no', 'no', 'yes',
688
reference_repo_name))
689
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
691
branch = a_bzrdir.create_branch()
692
# We should have got a remote branch
693
self.assertIsInstance(branch, remote.RemoteBranch)
694
# its format should have the settings from the response
695
format = branch._format
696
self.assertEqual(network_name, format.network_name())
699
class TestBzrDirCreateRepository(TestRemote):
701
def test_backwards_compat(self):
702
self.setup_smart_server_with_call_log()
703
bzrdir = self.make_bzrdir('.')
704
self.reset_smart_call_log()
705
self.disable_verb('BzrDir.create_repository')
706
repo = bzrdir.create_repository()
707
create_repo_call_count = len([call for call in self.hpss_calls if
708
call.call.method == 'BzrDir.create_repository'])
709
self.assertEqual(1, create_repo_call_count)
711
def test_current_server(self):
712
transport = self.get_transport('.')
713
transport = transport.clone('quack')
714
self.make_bzrdir('quack')
715
client = FakeClient(transport.base)
716
reference_bzrdir_format = bzrdir.format_registry.get('default')()
717
reference_format = reference_bzrdir_format.repository_format
718
network_name = reference_format.network_name()
719
client.add_expected_call(
720
'BzrDir.create_repository', ('quack/',
721
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
723
'success', ('ok', 'yes', 'yes', 'yes', network_name))
724
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
726
repo = a_bzrdir.create_repository()
727
# We should have got a remote repository
728
self.assertIsInstance(repo, remote.RemoteRepository)
729
# its format should have the settings from the response
730
format = repo._format
731
self.assertTrue(format.rich_root_data)
732
self.assertTrue(format.supports_tree_reference)
733
self.assertTrue(format.supports_external_lookups)
734
self.assertEqual(network_name, format.network_name())
737
class TestBzrDirOpenRepository(TestRemote):
739
def test_backwards_compat_1_2_3(self):
740
# fallback all the way to the first version.
741
reference_format = self.get_repo_format()
742
network_name = reference_format.network_name()
743
server_url = 'bzr://example.com/'
744
self.permit_url(server_url)
745
client = FakeClient(server_url)
746
client.add_unknown_method_response('BzrDir.find_repositoryV3')
747
client.add_unknown_method_response('BzrDir.find_repositoryV2')
748
client.add_success_response('ok', '', 'no', 'no')
749
# A real repository instance will be created to determine the network
751
client.add_success_response_with_body(
752
"Bazaar-NG meta directory, format 1\n", 'ok')
753
client.add_success_response_with_body(
754
reference_format.get_format_string(), 'ok')
755
# PackRepository wants to do a stat
756
client.add_success_response('stat', '0', '65535')
757
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
759
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
761
repo = bzrdir.open_repository()
763
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
764
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
765
('call', 'BzrDir.find_repository', ('quack/',)),
766
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
767
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
768
('call', 'stat', ('/quack/.bzr/repository',)),
771
self.assertEqual(network_name, repo._format.network_name())
773
def test_backwards_compat_2(self):
774
# fallback to find_repositoryV2
775
reference_format = self.get_repo_format()
776
network_name = reference_format.network_name()
777
server_url = 'bzr://example.com/'
778
self.permit_url(server_url)
779
client = FakeClient(server_url)
780
client.add_unknown_method_response('BzrDir.find_repositoryV3')
781
client.add_success_response('ok', '', 'no', 'no', 'no')
782
# A real repository instance will be created to determine the network
784
client.add_success_response_with_body(
785
"Bazaar-NG meta directory, format 1\n", 'ok')
786
client.add_success_response_with_body(
787
reference_format.get_format_string(), 'ok')
788
# PackRepository wants to do a stat
789
client.add_success_response('stat', '0', '65535')
790
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
792
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
794
repo = bzrdir.open_repository()
796
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
797
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
798
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
799
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
800
('call', 'stat', ('/quack/.bzr/repository',)),
803
self.assertEqual(network_name, repo._format.network_name())
805
def test_current_server(self):
806
reference_format = self.get_repo_format()
807
network_name = reference_format.network_name()
808
transport = MemoryTransport()
809
transport.mkdir('quack')
810
transport = transport.clone('quack')
811
client = FakeClient(transport.base)
812
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
813
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
815
repo = bzrdir.open_repository()
817
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
819
self.assertEqual(network_name, repo._format.network_name())
822
class TestBzrDirFormatInitializeEx(TestRemote):
824
def test_success(self):
825
"""Simple test for typical successful call."""
826
fmt = bzrdir.RemoteBzrDirFormat()
827
default_format_name = BzrDirFormat.get_default_format().network_name()
828
transport = self.get_transport()
829
client = FakeClient(transport.base)
830
client.add_expected_call(
831
'BzrDirFormat.initialize_ex_1.16',
832
(default_format_name, 'path', 'False', 'False', 'False', '',
833
'', '', '', 'False'),
835
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
836
'bzrdir fmt', 'False', '', '', 'repo lock token'))
837
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
838
# it's currently hard to test that without supplying a real remote
839
# transport connected to a real server.
840
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
841
transport, False, False, False, None, None, None, None, False)
842
self.assertFinished(client)
844
def test_error(self):
845
"""Error responses are translated, e.g. 'PermissionDenied' raises the
846
corresponding error from the client.
848
fmt = bzrdir.RemoteBzrDirFormat()
849
default_format_name = BzrDirFormat.get_default_format().network_name()
850
transport = self.get_transport()
851
client = FakeClient(transport.base)
852
client.add_expected_call(
853
'BzrDirFormat.initialize_ex_1.16',
854
(default_format_name, 'path', 'False', 'False', 'False', '',
855
'', '', '', 'False'),
857
('PermissionDenied', 'path', 'extra info'))
858
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
859
# it's currently hard to test that without supplying a real remote
860
# transport connected to a real server.
861
err = self.assertRaises(errors.PermissionDenied,
862
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
863
False, False, False, None, None, None, None, False)
864
self.assertEqual('path', err.path)
865
self.assertEqual(': extra info', err.extra)
866
self.assertFinished(client)
868
def test_error_from_real_server(self):
869
"""Integration test for error translation."""
870
transport = self.make_smart_server('foo')
871
transport = transport.clone('no-such-path')
872
fmt = bzrdir.RemoteBzrDirFormat()
873
err = self.assertRaises(errors.NoSuchFile,
874
fmt.initialize_on_transport_ex, transport, create_prefix=False)
877
class OldSmartClient(object):
878
"""A fake smart client for test_old_version that just returns a version one
879
response to the 'hello' (query version) command.
882
def get_request(self):
883
input_file = StringIO('ok\x011\n')
884
output_file = StringIO()
885
client_medium = medium.SmartSimplePipesClientMedium(
886
input_file, output_file)
887
return medium.SmartClientStreamMediumRequest(client_medium)
889
def protocol_version(self):
893
class OldServerTransport(object):
894
"""A fake transport for test_old_server that reports it's smart server
895
protocol version as version one.
901
def get_smart_client(self):
902
return OldSmartClient()
905
class RemoteBzrDirTestCase(TestRemote):
907
def make_remote_bzrdir(self, transport, client):
908
"""Make a RemotebzrDir using 'client' as the _client."""
909
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
913
class RemoteBranchTestCase(RemoteBzrDirTestCase):
915
def lock_remote_branch(self, branch):
916
"""Trick a RemoteBranch into thinking it is locked."""
917
branch._lock_mode = 'w'
918
branch._lock_count = 2
919
branch._lock_token = 'branch token'
920
branch._repo_lock_token = 'repo token'
921
branch.repository._lock_mode = 'w'
922
branch.repository._lock_count = 2
923
branch.repository._lock_token = 'repo token'
925
def make_remote_branch(self, transport, client):
926
"""Make a RemoteBranch using 'client' as its _SmartClient.
928
A RemoteBzrDir and RemoteRepository will also be created to fill out
929
the RemoteBranch, albeit with stub values for some of their attributes.
931
# we do not want bzrdir to make any remote calls, so use False as its
932
# _client. If it tries to make a remote call, this will fail
934
bzrdir = self.make_remote_bzrdir(transport, False)
935
repo = RemoteRepository(bzrdir, None, _client=client)
936
branch_format = self.get_branch_format()
937
format = RemoteBranchFormat(network_name=branch_format.network_name())
938
return RemoteBranch(bzrdir, repo, _client=client, format=format)
941
class TestBranchGetParent(RemoteBranchTestCase):
943
def test_no_parent(self):
944
# in an empty branch we decode the response properly
945
transport = MemoryTransport()
946
client = FakeClient(transport.base)
947
client.add_expected_call(
948
'Branch.get_stacked_on_url', ('quack/',),
949
'error', ('NotStacked',))
950
client.add_expected_call(
951
'Branch.get_parent', ('quack/',),
953
transport.mkdir('quack')
954
transport = transport.clone('quack')
955
branch = self.make_remote_branch(transport, client)
956
result = branch.get_parent()
957
self.assertFinished(client)
958
self.assertEqual(None, result)
960
def test_parent_relative(self):
961
transport = MemoryTransport()
962
client = FakeClient(transport.base)
963
client.add_expected_call(
964
'Branch.get_stacked_on_url', ('kwaak/',),
965
'error', ('NotStacked',))
966
client.add_expected_call(
967
'Branch.get_parent', ('kwaak/',),
968
'success', ('../foo/',))
969
transport.mkdir('kwaak')
970
transport = transport.clone('kwaak')
971
branch = self.make_remote_branch(transport, client)
972
result = branch.get_parent()
973
self.assertEqual(transport.clone('../foo').base, result)
975
def test_parent_absolute(self):
976
transport = MemoryTransport()
977
client = FakeClient(transport.base)
978
client.add_expected_call(
979
'Branch.get_stacked_on_url', ('kwaak/',),
980
'error', ('NotStacked',))
981
client.add_expected_call(
982
'Branch.get_parent', ('kwaak/',),
983
'success', ('http://foo/',))
984
transport.mkdir('kwaak')
985
transport = transport.clone('kwaak')
986
branch = self.make_remote_branch(transport, client)
987
result = branch.get_parent()
988
self.assertEqual('http://foo/', result)
989
self.assertFinished(client)
992
class TestBranchSetParentLocation(RemoteBranchTestCase):
994
def test_no_parent(self):
995
# We call the verb when setting parent to None
996
transport = MemoryTransport()
997
client = FakeClient(transport.base)
998
client.add_expected_call(
999
'Branch.get_stacked_on_url', ('quack/',),
1000
'error', ('NotStacked',))
1001
client.add_expected_call(
1002
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1004
transport.mkdir('quack')
1005
transport = transport.clone('quack')
1006
branch = self.make_remote_branch(transport, client)
1007
branch._lock_token = 'b'
1008
branch._repo_lock_token = 'r'
1009
branch._set_parent_location(None)
1010
self.assertFinished(client)
1012
def test_parent(self):
1013
transport = MemoryTransport()
1014
client = FakeClient(transport.base)
1015
client.add_expected_call(
1016
'Branch.get_stacked_on_url', ('kwaak/',),
1017
'error', ('NotStacked',))
1018
client.add_expected_call(
1019
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1021
transport.mkdir('kwaak')
1022
transport = transport.clone('kwaak')
1023
branch = self.make_remote_branch(transport, client)
1024
branch._lock_token = 'b'
1025
branch._repo_lock_token = 'r'
1026
branch._set_parent_location('foo')
1027
self.assertFinished(client)
1029
def test_backwards_compat(self):
1030
self.setup_smart_server_with_call_log()
1031
branch = self.make_branch('.')
1032
self.reset_smart_call_log()
1033
verb = 'Branch.set_parent_location'
1034
self.disable_verb(verb)
1035
branch.set_parent('http://foo/')
1036
self.assertLength(12, self.hpss_calls)
1039
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1041
def test_backwards_compat(self):
1042
self.setup_smart_server_with_call_log()
1043
branch = self.make_branch('.')
1044
self.reset_smart_call_log()
1045
verb = 'Branch.get_tags_bytes'
1046
self.disable_verb(verb)
1047
branch.tags.get_tag_dict()
1048
call_count = len([call for call in self.hpss_calls if
1049
call.call.method == verb])
1050
self.assertEqual(1, call_count)
1052
def test_trivial(self):
1053
transport = MemoryTransport()
1054
client = FakeClient(transport.base)
1055
client.add_expected_call(
1056
'Branch.get_stacked_on_url', ('quack/',),
1057
'error', ('NotStacked',))
1058
client.add_expected_call(
1059
'Branch.get_tags_bytes', ('quack/',),
1061
transport.mkdir('quack')
1062
transport = transport.clone('quack')
1063
branch = self.make_remote_branch(transport, client)
1064
result = branch.tags.get_tag_dict()
1065
self.assertFinished(client)
1066
self.assertEqual({}, result)
1069
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1071
def test_trivial(self):
1072
transport = MemoryTransport()
1073
client = FakeClient(transport.base)
1074
client.add_expected_call(
1075
'Branch.get_stacked_on_url', ('quack/',),
1076
'error', ('NotStacked',))
1077
client.add_expected_call(
1078
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1080
transport.mkdir('quack')
1081
transport = transport.clone('quack')
1082
branch = self.make_remote_branch(transport, client)
1083
self.lock_remote_branch(branch)
1084
branch._set_tags_bytes('tags bytes')
1085
self.assertFinished(client)
1086
self.assertEqual('tags bytes', client._calls[-1][-1])
1088
def test_backwards_compatible(self):
1089
transport = MemoryTransport()
1090
client = FakeClient(transport.base)
1091
client.add_expected_call(
1092
'Branch.get_stacked_on_url', ('quack/',),
1093
'error', ('NotStacked',))
1094
client.add_expected_call(
1095
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1096
'unknown', ('Branch.set_tags_bytes',))
1097
transport.mkdir('quack')
1098
transport = transport.clone('quack')
1099
branch = self.make_remote_branch(transport, client)
1100
self.lock_remote_branch(branch)
1101
class StubRealBranch(object):
1104
def _set_tags_bytes(self, bytes):
1105
self.calls.append(('set_tags_bytes', bytes))
1106
real_branch = StubRealBranch()
1107
branch._real_branch = real_branch
1108
branch._set_tags_bytes('tags bytes')
1109
# Call a second time, to exercise the 'remote version already inferred'
1111
branch._set_tags_bytes('tags bytes')
1112
self.assertFinished(client)
1114
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1117
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1119
def test_empty_branch(self):
1120
# in an empty branch we decode the response properly
1121
transport = MemoryTransport()
1122
client = FakeClient(transport.base)
1123
client.add_expected_call(
1124
'Branch.get_stacked_on_url', ('quack/',),
1125
'error', ('NotStacked',))
1126
client.add_expected_call(
1127
'Branch.last_revision_info', ('quack/',),
1128
'success', ('ok', '0', 'null:'))
1129
transport.mkdir('quack')
1130
transport = transport.clone('quack')
1131
branch = self.make_remote_branch(transport, client)
1132
result = branch.last_revision_info()
1133
self.assertFinished(client)
1134
self.assertEqual((0, NULL_REVISION), result)
1136
def test_non_empty_branch(self):
1137
# in a non-empty branch we also decode the response properly
1138
revid = u'\xc8'.encode('utf8')
1139
transport = MemoryTransport()
1140
client = FakeClient(transport.base)
1141
client.add_expected_call(
1142
'Branch.get_stacked_on_url', ('kwaak/',),
1143
'error', ('NotStacked',))
1144
client.add_expected_call(
1145
'Branch.last_revision_info', ('kwaak/',),
1146
'success', ('ok', '2', revid))
1147
transport.mkdir('kwaak')
1148
transport = transport.clone('kwaak')
1149
branch = self.make_remote_branch(transport, client)
1150
result = branch.last_revision_info()
1151
self.assertEqual((2, revid), result)
1154
class TestBranch_get_stacked_on_url(TestRemote):
1155
"""Test Branch._get_stacked_on_url rpc"""
1157
def test_get_stacked_on_invalid_url(self):
1158
# test that asking for a stacked on url the server can't access works.
1159
# This isn't perfect, but then as we're in the same process there
1160
# really isn't anything we can do to be 100% sure that the server
1161
# doesn't just open in - this test probably needs to be rewritten using
1162
# a spawn()ed server.
1163
stacked_branch = self.make_branch('stacked', format='1.9')
1164
memory_branch = self.make_branch('base', format='1.9')
1165
vfs_url = self.get_vfs_only_url('base')
1166
stacked_branch.set_stacked_on_url(vfs_url)
1167
transport = stacked_branch.bzrdir.root_transport
1168
client = FakeClient(transport.base)
1169
client.add_expected_call(
1170
'Branch.get_stacked_on_url', ('stacked/',),
1171
'success', ('ok', vfs_url))
1172
# XXX: Multiple calls are bad, this second call documents what is
1174
client.add_expected_call(
1175
'Branch.get_stacked_on_url', ('stacked/',),
1176
'success', ('ok', vfs_url))
1177
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1179
repo_fmt = remote.RemoteRepositoryFormat()
1180
repo_fmt._custom_format = stacked_branch.repository._format
1181
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1183
result = branch.get_stacked_on_url()
1184
self.assertEqual(vfs_url, result)
1186
def test_backwards_compatible(self):
1187
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1188
base_branch = self.make_branch('base', format='1.6')
1189
stacked_branch = self.make_branch('stacked', format='1.6')
1190
stacked_branch.set_stacked_on_url('../base')
1191
client = FakeClient(self.get_url())
1192
branch_network_name = self.get_branch_format().network_name()
1193
client.add_expected_call(
1194
'BzrDir.open_branchV2', ('stacked/',),
1195
'success', ('branch', branch_network_name))
1196
client.add_expected_call(
1197
'BzrDir.find_repositoryV3', ('stacked/',),
1198
'success', ('ok', '', 'no', 'no', 'yes',
1199
stacked_branch.repository._format.network_name()))
1200
# called twice, once from constructor and then again by us
1201
client.add_expected_call(
1202
'Branch.get_stacked_on_url', ('stacked/',),
1203
'unknown', ('Branch.get_stacked_on_url',))
1204
client.add_expected_call(
1205
'Branch.get_stacked_on_url', ('stacked/',),
1206
'unknown', ('Branch.get_stacked_on_url',))
1207
# this will also do vfs access, but that goes direct to the transport
1208
# and isn't seen by the FakeClient.
1209
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1210
remote.RemoteBzrDirFormat(), _client=client)
1211
branch = bzrdir.open_branch()
1212
result = branch.get_stacked_on_url()
1213
self.assertEqual('../base', result)
1214
self.assertFinished(client)
1215
# it's in the fallback list both for the RemoteRepository and its vfs
1217
self.assertEqual(1, len(branch.repository._fallback_repositories))
1219
len(branch.repository._real_repository._fallback_repositories))
1221
def test_get_stacked_on_real_branch(self):
1222
base_branch = self.make_branch('base', format='1.6')
1223
stacked_branch = self.make_branch('stacked', format='1.6')
1224
stacked_branch.set_stacked_on_url('../base')
1225
reference_format = self.get_repo_format()
1226
network_name = reference_format.network_name()
1227
client = FakeClient(self.get_url())
1228
branch_network_name = self.get_branch_format().network_name()
1229
client.add_expected_call(
1230
'BzrDir.open_branchV2', ('stacked/',),
1231
'success', ('branch', branch_network_name))
1232
client.add_expected_call(
1233
'BzrDir.find_repositoryV3', ('stacked/',),
1234
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1235
# called twice, once from constructor and then again by us
1236
client.add_expected_call(
1237
'Branch.get_stacked_on_url', ('stacked/',),
1238
'success', ('ok', '../base'))
1239
client.add_expected_call(
1240
'Branch.get_stacked_on_url', ('stacked/',),
1241
'success', ('ok', '../base'))
1242
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1243
remote.RemoteBzrDirFormat(), _client=client)
1244
branch = bzrdir.open_branch()
1245
result = branch.get_stacked_on_url()
1246
self.assertEqual('../base', result)
1247
self.assertFinished(client)
1248
# it's in the fallback list both for the RemoteRepository.
1249
self.assertEqual(1, len(branch.repository._fallback_repositories))
1250
# And we haven't had to construct a real repository.
1251
self.assertEqual(None, branch.repository._real_repository)
1254
class TestBranchSetLastRevision(RemoteBranchTestCase):
1256
def test_set_empty(self):
1257
# set_revision_history([]) is translated to calling
1258
# Branch.set_last_revision(path, '') on the wire.
1259
transport = MemoryTransport()
1260
transport.mkdir('branch')
1261
transport = transport.clone('branch')
1263
client = FakeClient(transport.base)
1264
client.add_expected_call(
1265
'Branch.get_stacked_on_url', ('branch/',),
1266
'error', ('NotStacked',))
1267
client.add_expected_call(
1268
'Branch.lock_write', ('branch/', '', ''),
1269
'success', ('ok', 'branch token', 'repo token'))
1270
client.add_expected_call(
1271
'Branch.last_revision_info',
1273
'success', ('ok', '0', 'null:'))
1274
client.add_expected_call(
1275
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1277
client.add_expected_call(
1278
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1280
branch = self.make_remote_branch(transport, client)
1281
# This is a hack to work around the problem that RemoteBranch currently
1282
# unnecessarily invokes _ensure_real upon a call to lock_write.
1283
branch._ensure_real = lambda: None
1285
result = branch.set_revision_history([])
1287
self.assertEqual(None, result)
1288
self.assertFinished(client)
1290
def test_set_nonempty(self):
1291
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1292
# Branch.set_last_revision(path, rev-idN) on the wire.
1293
transport = MemoryTransport()
1294
transport.mkdir('branch')
1295
transport = transport.clone('branch')
1297
client = FakeClient(transport.base)
1298
client.add_expected_call(
1299
'Branch.get_stacked_on_url', ('branch/',),
1300
'error', ('NotStacked',))
1301
client.add_expected_call(
1302
'Branch.lock_write', ('branch/', '', ''),
1303
'success', ('ok', 'branch token', 'repo token'))
1304
client.add_expected_call(
1305
'Branch.last_revision_info',
1307
'success', ('ok', '0', 'null:'))
1309
encoded_body = bz2.compress('\n'.join(lines))
1310
client.add_success_response_with_body(encoded_body, 'ok')
1311
client.add_expected_call(
1312
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1314
client.add_expected_call(
1315
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1317
branch = self.make_remote_branch(transport, client)
1318
# This is a hack to work around the problem that RemoteBranch currently
1319
# unnecessarily invokes _ensure_real upon a call to lock_write.
1320
branch._ensure_real = lambda: None
1321
# Lock the branch, reset the record of remote calls.
1323
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1325
self.assertEqual(None, result)
1326
self.assertFinished(client)
1328
def test_no_such_revision(self):
1329
transport = MemoryTransport()
1330
transport.mkdir('branch')
1331
transport = transport.clone('branch')
1332
# A response of 'NoSuchRevision' is translated into an exception.
1333
client = FakeClient(transport.base)
1334
client.add_expected_call(
1335
'Branch.get_stacked_on_url', ('branch/',),
1336
'error', ('NotStacked',))
1337
client.add_expected_call(
1338
'Branch.lock_write', ('branch/', '', ''),
1339
'success', ('ok', 'branch token', 'repo token'))
1340
client.add_expected_call(
1341
'Branch.last_revision_info',
1343
'success', ('ok', '0', 'null:'))
1344
# get_graph calls to construct the revision history, for the set_rh
1347
encoded_body = bz2.compress('\n'.join(lines))
1348
client.add_success_response_with_body(encoded_body, 'ok')
1349
client.add_expected_call(
1350
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1351
'error', ('NoSuchRevision', 'rev-id'))
1352
client.add_expected_call(
1353
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1356
branch = self.make_remote_branch(transport, client)
1359
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1361
self.assertFinished(client)
1363
def test_tip_change_rejected(self):
1364
"""TipChangeRejected responses cause a TipChangeRejected exception to
1367
transport = MemoryTransport()
1368
transport.mkdir('branch')
1369
transport = transport.clone('branch')
1370
client = FakeClient(transport.base)
1371
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1372
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1373
client.add_expected_call(
1374
'Branch.get_stacked_on_url', ('branch/',),
1375
'error', ('NotStacked',))
1376
client.add_expected_call(
1377
'Branch.lock_write', ('branch/', '', ''),
1378
'success', ('ok', 'branch token', 'repo token'))
1379
client.add_expected_call(
1380
'Branch.last_revision_info',
1382
'success', ('ok', '0', 'null:'))
1384
encoded_body = bz2.compress('\n'.join(lines))
1385
client.add_success_response_with_body(encoded_body, 'ok')
1386
client.add_expected_call(
1387
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1388
'error', ('TipChangeRejected', rejection_msg_utf8))
1389
client.add_expected_call(
1390
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1392
branch = self.make_remote_branch(transport, client)
1393
branch._ensure_real = lambda: None
1395
# The 'TipChangeRejected' error response triggered by calling
1396
# set_revision_history causes a TipChangeRejected exception.
1397
err = self.assertRaises(
1398
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1399
# The UTF-8 message from the response has been decoded into a unicode
1401
self.assertIsInstance(err.msg, unicode)
1402
self.assertEqual(rejection_msg_unicode, err.msg)
1404
self.assertFinished(client)
1407
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1409
def test_set_last_revision_info(self):
1410
# set_last_revision_info(num, 'rev-id') is translated to calling
1411
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
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')
1420
# query the current revision
1421
client.add_success_response('ok', '0', 'null:')
1423
client.add_success_response('ok')
1425
client.add_success_response('ok')
1427
branch = self.make_remote_branch(transport, client)
1428
# Lock the branch, reset the record of remote calls.
1431
result = branch.set_last_revision_info(1234, 'a-revision-id')
1433
[('call', 'Branch.last_revision_info', ('branch/',)),
1434
('call', 'Branch.set_last_revision_info',
1435
('branch/', 'branch token', 'repo token',
1436
'1234', 'a-revision-id'))],
1438
self.assertEqual(None, result)
1440
def test_no_such_revision(self):
1441
# A response of 'NoSuchRevision' is translated into an exception.
1442
transport = MemoryTransport()
1443
transport.mkdir('branch')
1444
transport = transport.clone('branch')
1445
client = FakeClient(transport.base)
1446
# get_stacked_on_url
1447
client.add_error_response('NotStacked')
1449
client.add_success_response('ok', 'branch token', 'repo token')
1451
client.add_error_response('NoSuchRevision', 'revid')
1453
client.add_success_response('ok')
1455
branch = self.make_remote_branch(transport, client)
1456
# Lock the branch, reset the record of remote calls.
1461
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1464
def test_backwards_compatibility(self):
1465
"""If the server does not support the Branch.set_last_revision_info
1466
verb (which is new in 1.4), then the client falls back to VFS methods.
1468
# This test is a little messy. Unlike most tests in this file, it
1469
# doesn't purely test what a Remote* object sends over the wire, and
1470
# how it reacts to responses from the wire. It instead relies partly
1471
# on asserting that the RemoteBranch will call
1472
# self._real_branch.set_last_revision_info(...).
1474
# First, set up our RemoteBranch with a FakeClient that raises
1475
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1476
transport = MemoryTransport()
1477
transport.mkdir('branch')
1478
transport = transport.clone('branch')
1479
client = FakeClient(transport.base)
1480
client.add_expected_call(
1481
'Branch.get_stacked_on_url', ('branch/',),
1482
'error', ('NotStacked',))
1483
client.add_expected_call(
1484
'Branch.last_revision_info',
1486
'success', ('ok', '0', 'null:'))
1487
client.add_expected_call(
1488
'Branch.set_last_revision_info',
1489
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1490
'unknown', 'Branch.set_last_revision_info')
1492
branch = self.make_remote_branch(transport, client)
1493
class StubRealBranch(object):
1496
def set_last_revision_info(self, revno, revision_id):
1498
('set_last_revision_info', revno, revision_id))
1499
def _clear_cached_state(self):
1501
real_branch = StubRealBranch()
1502
branch._real_branch = real_branch
1503
self.lock_remote_branch(branch)
1505
# Call set_last_revision_info, and verify it behaved as expected.
1506
result = branch.set_last_revision_info(1234, 'a-revision-id')
1508
[('set_last_revision_info', 1234, 'a-revision-id')],
1510
self.assertFinished(client)
1512
def test_unexpected_error(self):
1513
# If the server sends an error the client doesn't understand, it gets
1514
# turned into an UnknownErrorFromSmartServer, which is presented as a
1515
# non-internal error to the user.
1516
transport = MemoryTransport()
1517
transport.mkdir('branch')
1518
transport = transport.clone('branch')
1519
client = FakeClient(transport.base)
1520
# get_stacked_on_url
1521
client.add_error_response('NotStacked')
1523
client.add_success_response('ok', 'branch token', 'repo token')
1525
client.add_error_response('UnexpectedError')
1527
client.add_success_response('ok')
1529
branch = self.make_remote_branch(transport, client)
1530
# Lock the branch, reset the record of remote calls.
1534
err = self.assertRaises(
1535
errors.UnknownErrorFromSmartServer,
1536
branch.set_last_revision_info, 123, 'revid')
1537
self.assertEqual(('UnexpectedError',), err.error_tuple)
1540
def test_tip_change_rejected(self):
1541
"""TipChangeRejected responses cause a TipChangeRejected exception to
1544
transport = MemoryTransport()
1545
transport.mkdir('branch')
1546
transport = transport.clone('branch')
1547
client = FakeClient(transport.base)
1548
# get_stacked_on_url
1549
client.add_error_response('NotStacked')
1551
client.add_success_response('ok', 'branch token', 'repo token')
1553
client.add_error_response('TipChangeRejected', 'rejection message')
1555
client.add_success_response('ok')
1557
branch = self.make_remote_branch(transport, client)
1558
# Lock the branch, reset the record of remote calls.
1560
self.addCleanup(branch.unlock)
1563
# The 'TipChangeRejected' error response triggered by calling
1564
# set_last_revision_info causes a TipChangeRejected exception.
1565
err = self.assertRaises(
1566
errors.TipChangeRejected,
1567
branch.set_last_revision_info, 123, 'revid')
1568
self.assertEqual('rejection message', err.msg)
1571
class TestBranchGetSetConfig(RemoteBranchTestCase):
1573
def test_get_branch_conf(self):
1574
# in an empty branch we decode the response properly
1575
client = FakeClient()
1576
client.add_expected_call(
1577
'Branch.get_stacked_on_url', ('memory:///',),
1578
'error', ('NotStacked',),)
1579
client.add_success_response_with_body('# config file body', 'ok')
1580
transport = MemoryTransport()
1581
branch = self.make_remote_branch(transport, client)
1582
config = branch.get_config()
1583
config.has_explicit_nickname()
1585
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1586
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1589
def test_get_multi_line_branch_conf(self):
1590
# Make sure that multiple-line branch.conf files are supported
1592
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1593
client = FakeClient()
1594
client.add_expected_call(
1595
'Branch.get_stacked_on_url', ('memory:///',),
1596
'error', ('NotStacked',),)
1597
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1598
transport = MemoryTransport()
1599
branch = self.make_remote_branch(transport, client)
1600
config = branch.get_config()
1601
self.assertEqual(u'2', config.get_user_option('b'))
1603
def test_set_option(self):
1604
client = FakeClient()
1605
client.add_expected_call(
1606
'Branch.get_stacked_on_url', ('memory:///',),
1607
'error', ('NotStacked',),)
1608
client.add_expected_call(
1609
'Branch.lock_write', ('memory:///', '', ''),
1610
'success', ('ok', 'branch token', 'repo token'))
1611
client.add_expected_call(
1612
'Branch.set_config_option', ('memory:///', 'branch token',
1613
'repo token', 'foo', 'bar', ''),
1615
client.add_expected_call(
1616
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1618
transport = MemoryTransport()
1619
branch = self.make_remote_branch(transport, client)
1621
config = branch._get_config()
1622
config.set_option('foo', 'bar')
1624
self.assertFinished(client)
1626
def test_backwards_compat_set_option(self):
1627
self.setup_smart_server_with_call_log()
1628
branch = self.make_branch('.')
1629
verb = 'Branch.set_config_option'
1630
self.disable_verb(verb)
1632
self.addCleanup(branch.unlock)
1633
self.reset_smart_call_log()
1634
branch._get_config().set_option('value', 'name')
1635
self.assertLength(10, self.hpss_calls)
1636
self.assertEqual('value', branch._get_config().get_option('name'))
1639
class TestBranchLockWrite(RemoteBranchTestCase):
1641
def test_lock_write_unlockable(self):
1642
transport = MemoryTransport()
1643
client = FakeClient(transport.base)
1644
client.add_expected_call(
1645
'Branch.get_stacked_on_url', ('quack/',),
1646
'error', ('NotStacked',),)
1647
client.add_expected_call(
1648
'Branch.lock_write', ('quack/', '', ''),
1649
'error', ('UnlockableTransport',))
1650
transport.mkdir('quack')
1651
transport = transport.clone('quack')
1652
branch = self.make_remote_branch(transport, client)
1653
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1654
self.assertFinished(client)
1657
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1659
def test__get_config(self):
1660
client = FakeClient()
1661
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1662
transport = MemoryTransport()
1663
bzrdir = self.make_remote_bzrdir(transport, client)
1664
config = bzrdir.get_config()
1665
self.assertEqual('/', config.get_default_stack_on())
1667
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1670
def test_set_option_uses_vfs(self):
1671
self.setup_smart_server_with_call_log()
1672
bzrdir = self.make_bzrdir('.')
1673
self.reset_smart_call_log()
1674
config = bzrdir.get_config()
1675
config.set_default_stack_on('/')
1676
self.assertLength(3, self.hpss_calls)
1678
def test_backwards_compat_get_option(self):
1679
self.setup_smart_server_with_call_log()
1680
bzrdir = self.make_bzrdir('.')
1681
verb = 'BzrDir.get_config_file'
1682
self.disable_verb(verb)
1683
self.reset_smart_call_log()
1684
self.assertEqual(None,
1685
bzrdir._get_config().get_option('default_stack_on'))
1686
self.assertLength(3, self.hpss_calls)
1689
class TestTransportIsReadonly(tests.TestCase):
1691
def test_true(self):
1692
client = FakeClient()
1693
client.add_success_response('yes')
1694
transport = RemoteTransport('bzr://example.com/', medium=False,
1696
self.assertEqual(True, transport.is_readonly())
1698
[('call', 'Transport.is_readonly', ())],
1701
def test_false(self):
1702
client = FakeClient()
1703
client.add_success_response('no')
1704
transport = RemoteTransport('bzr://example.com/', medium=False,
1706
self.assertEqual(False, transport.is_readonly())
1708
[('call', 'Transport.is_readonly', ())],
1711
def test_error_from_old_server(self):
1712
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1714
Clients should treat it as a "no" response, because is_readonly is only
1715
advisory anyway (a transport could be read-write, but then the
1716
underlying filesystem could be readonly anyway).
1718
client = FakeClient()
1719
client.add_unknown_method_response('Transport.is_readonly')
1720
transport = RemoteTransport('bzr://example.com/', medium=False,
1722
self.assertEqual(False, transport.is_readonly())
1724
[('call', 'Transport.is_readonly', ())],
1728
class TestTransportMkdir(tests.TestCase):
1730
def test_permissiondenied(self):
1731
client = FakeClient()
1732
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1733
transport = RemoteTransport('bzr://example.com/', medium=False,
1735
exc = self.assertRaises(
1736
errors.PermissionDenied, transport.mkdir, 'client path')
1737
expected_error = errors.PermissionDenied('/client path', 'extra')
1738
self.assertEqual(expected_error, exc)
1741
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1743
def test_defaults_to_none(self):
1744
t = RemoteSSHTransport('bzr+ssh://example.com')
1745
self.assertIs(None, t._get_credentials()[0])
1747
def test_uses_authentication_config(self):
1748
conf = config.AuthenticationConfig()
1749
conf._get_config().update(
1750
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1753
t = RemoteSSHTransport('bzr+ssh://example.com')
1754
self.assertEqual('bar', t._get_credentials()[0])
1757
class TestRemoteRepository(TestRemote):
1758
"""Base for testing RemoteRepository protocol usage.
1760
These tests contain frozen requests and responses. We want any changes to
1761
what is sent or expected to be require a thoughtful update to these tests
1762
because they might break compatibility with different-versioned servers.
1765
def setup_fake_client_and_repository(self, transport_path):
1766
"""Create the fake client and repository for testing with.
1768
There's no real server here; we just have canned responses sent
1771
:param transport_path: Path below the root of the MemoryTransport
1772
where the repository will be created.
1774
transport = MemoryTransport()
1775
transport.mkdir(transport_path)
1776
client = FakeClient(transport.base)
1777
transport = transport.clone(transport_path)
1778
# we do not want bzrdir to make any remote calls
1779
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1781
repo = RemoteRepository(bzrdir, None, _client=client)
1785
class TestRepositoryFormat(TestRemoteRepository):
1787
def test_fast_delta(self):
1788
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1789
true_format = RemoteRepositoryFormat()
1790
true_format._network_name = true_name
1791
self.assertEqual(True, true_format.fast_deltas)
1792
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1793
false_format = RemoteRepositoryFormat()
1794
false_format._network_name = false_name
1795
self.assertEqual(False, false_format.fast_deltas)
1798
class TestRepositoryGatherStats(TestRemoteRepository):
1800
def test_revid_none(self):
1801
# ('ok',), body with revisions and size
1802
transport_path = 'quack'
1803
repo, client = self.setup_fake_client_and_repository(transport_path)
1804
client.add_success_response_with_body(
1805
'revisions: 2\nsize: 18\n', 'ok')
1806
result = repo.gather_stats(None)
1808
[('call_expecting_body', 'Repository.gather_stats',
1809
('quack/','','no'))],
1811
self.assertEqual({'revisions': 2, 'size': 18}, result)
1813
def test_revid_no_committers(self):
1814
# ('ok',), body without committers
1815
body = ('firstrev: 123456.300 3600\n'
1816
'latestrev: 654231.400 0\n'
1819
transport_path = 'quick'
1820
revid = u'\xc8'.encode('utf8')
1821
repo, client = self.setup_fake_client_and_repository(transport_path)
1822
client.add_success_response_with_body(body, 'ok')
1823
result = repo.gather_stats(revid)
1825
[('call_expecting_body', 'Repository.gather_stats',
1826
('quick/', revid, 'no'))],
1828
self.assertEqual({'revisions': 2, 'size': 18,
1829
'firstrev': (123456.300, 3600),
1830
'latestrev': (654231.400, 0),},
1833
def test_revid_with_committers(self):
1834
# ('ok',), body with committers
1835
body = ('committers: 128\n'
1836
'firstrev: 123456.300 3600\n'
1837
'latestrev: 654231.400 0\n'
1840
transport_path = 'buick'
1841
revid = u'\xc8'.encode('utf8')
1842
repo, client = self.setup_fake_client_and_repository(transport_path)
1843
client.add_success_response_with_body(body, 'ok')
1844
result = repo.gather_stats(revid, True)
1846
[('call_expecting_body', 'Repository.gather_stats',
1847
('buick/', revid, 'yes'))],
1849
self.assertEqual({'revisions': 2, 'size': 18,
1851
'firstrev': (123456.300, 3600),
1852
'latestrev': (654231.400, 0),},
1856
class TestRepositoryGetGraph(TestRemoteRepository):
1858
def test_get_graph(self):
1859
# get_graph returns a graph with a custom parents provider.
1860
transport_path = 'quack'
1861
repo, client = self.setup_fake_client_and_repository(transport_path)
1862
graph = repo.get_graph()
1863
self.assertNotEqual(graph._parents_provider, repo)
1866
class TestRepositoryGetParentMap(TestRemoteRepository):
1868
def test_get_parent_map_caching(self):
1869
# get_parent_map returns from cache until unlock()
1870
# setup a reponse with two revisions
1871
r1 = u'\u0e33'.encode('utf8')
1872
r2 = u'\u0dab'.encode('utf8')
1873
lines = [' '.join([r2, r1]), r1]
1874
encoded_body = bz2.compress('\n'.join(lines))
1876
transport_path = 'quack'
1877
repo, client = self.setup_fake_client_and_repository(transport_path)
1878
client.add_success_response_with_body(encoded_body, 'ok')
1879
client.add_success_response_with_body(encoded_body, 'ok')
1881
graph = repo.get_graph()
1882
parents = graph.get_parent_map([r2])
1883
self.assertEqual({r2: (r1,)}, parents)
1884
# locking and unlocking deeper should not reset
1887
parents = graph.get_parent_map([r1])
1888
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1890
[('call_with_body_bytes_expecting_body',
1891
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1895
# now we call again, and it should use the second response.
1897
graph = repo.get_graph()
1898
parents = graph.get_parent_map([r1])
1899
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1901
[('call_with_body_bytes_expecting_body',
1902
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1904
('call_with_body_bytes_expecting_body',
1905
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1911
def test_get_parent_map_reconnects_if_unknown_method(self):
1912
transport_path = 'quack'
1913
rev_id = 'revision-id'
1914
repo, client = self.setup_fake_client_and_repository(transport_path)
1915
client.add_unknown_method_response('Repository.get_parent_map')
1916
client.add_success_response_with_body(rev_id, 'ok')
1917
self.assertFalse(client._medium._is_remote_before((1, 2)))
1918
parents = repo.get_parent_map([rev_id])
1920
[('call_with_body_bytes_expecting_body',
1921
'Repository.get_parent_map', ('quack/', 'include-missing:',
1923
('disconnect medium',),
1924
('call_expecting_body', 'Repository.get_revision_graph',
1927
# The medium is now marked as being connected to an older server
1928
self.assertTrue(client._medium._is_remote_before((1, 2)))
1929
self.assertEqual({rev_id: ('null:',)}, parents)
1931
def test_get_parent_map_fallback_parentless_node(self):
1932
"""get_parent_map falls back to get_revision_graph on old servers. The
1933
results from get_revision_graph are tweaked to match the get_parent_map
1936
Specifically, a {key: ()} result from get_revision_graph means "no
1937
parents" for that key, which in get_parent_map results should be
1938
represented as {key: ('null:',)}.
1940
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1942
rev_id = 'revision-id'
1943
transport_path = 'quack'
1944
repo, client = self.setup_fake_client_and_repository(transport_path)
1945
client.add_success_response_with_body(rev_id, 'ok')
1946
client._medium._remember_remote_is_before((1, 2))
1947
parents = repo.get_parent_map([rev_id])
1949
[('call_expecting_body', 'Repository.get_revision_graph',
1952
self.assertEqual({rev_id: ('null:',)}, parents)
1954
def test_get_parent_map_unexpected_response(self):
1955
repo, client = self.setup_fake_client_and_repository('path')
1956
client.add_success_response('something unexpected!')
1958
errors.UnexpectedSmartServerResponse,
1959
repo.get_parent_map, ['a-revision-id'])
1961
def test_get_parent_map_negative_caches_missing_keys(self):
1962
self.setup_smart_server_with_call_log()
1963
repo = self.make_repository('foo')
1964
self.assertIsInstance(repo, RemoteRepository)
1966
self.addCleanup(repo.unlock)
1967
self.reset_smart_call_log()
1968
graph = repo.get_graph()
1969
self.assertEqual({},
1970
graph.get_parent_map(['some-missing', 'other-missing']))
1971
self.assertLength(1, self.hpss_calls)
1972
# No call if we repeat this
1973
self.reset_smart_call_log()
1974
graph = repo.get_graph()
1975
self.assertEqual({},
1976
graph.get_parent_map(['some-missing', 'other-missing']))
1977
self.assertLength(0, self.hpss_calls)
1978
# Asking for more unknown keys makes a request.
1979
self.reset_smart_call_log()
1980
graph = repo.get_graph()
1981
self.assertEqual({},
1982
graph.get_parent_map(['some-missing', 'other-missing',
1984
self.assertLength(1, self.hpss_calls)
1986
def disableExtraResults(self):
1987
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1988
SmartServerRepositoryGetParentMap.no_extra_results = True
1990
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1991
self.addCleanup(reset_values)
1993
def test_null_cached_missing_and_stop_key(self):
1994
self.setup_smart_server_with_call_log()
1995
# Make a branch with a single revision.
1996
builder = self.make_branch_builder('foo')
1997
builder.start_series()
1998
builder.build_snapshot('first', None, [
1999
('add', ('', 'root-id', 'directory', ''))])
2000
builder.finish_series()
2001
branch = builder.get_branch()
2002
repo = branch.repository
2003
self.assertIsInstance(repo, RemoteRepository)
2004
# Stop the server from sending extra results.
2005
self.disableExtraResults()
2007
self.addCleanup(repo.unlock)
2008
self.reset_smart_call_log()
2009
graph = repo.get_graph()
2010
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2011
# 'first' it will be a candidate for the stop_keys of subsequent
2012
# requests, and because 'null:' was queried but not returned it will be
2013
# cached as missing.
2014
self.assertEqual({'first': ('null:',)},
2015
graph.get_parent_map(['first', 'null:']))
2016
# Now query for another key. This request will pass along a recipe of
2017
# start and stop keys describing the already cached results, and this
2018
# recipe's revision count must be correct (or else it will trigger an
2019
# error from the server).
2020
self.assertEqual({}, graph.get_parent_map(['another-key']))
2021
# This assertion guards against disableExtraResults silently failing to
2022
# work, thus invalidating the test.
2023
self.assertLength(2, self.hpss_calls)
2025
def test_get_parent_map_gets_ghosts_from_result(self):
2026
# asking for a revision should negatively cache close ghosts in its
2028
self.setup_smart_server_with_call_log()
2029
tree = self.make_branch_and_memory_tree('foo')
2032
builder = treebuilder.TreeBuilder()
2033
builder.start_tree(tree)
2035
builder.finish_tree()
2036
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2037
rev_id = tree.commit('')
2041
self.addCleanup(tree.unlock)
2042
repo = tree.branch.repository
2043
self.assertIsInstance(repo, RemoteRepository)
2045
repo.get_parent_map([rev_id])
2046
self.reset_smart_call_log()
2047
# Now asking for rev_id's ghost parent should not make calls
2048
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2049
self.assertLength(0, self.hpss_calls)
2052
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2054
def test_allows_new_revisions(self):
2055
"""get_parent_map's results can be updated by commit."""
2056
smart_server = server.SmartTCPServer_for_testing()
2057
self.start_server(smart_server)
2058
self.make_branch('branch')
2059
branch = Branch.open(smart_server.get_url() + '/branch')
2060
tree = branch.create_checkout('tree', lightweight=True)
2062
self.addCleanup(tree.unlock)
2063
graph = tree.branch.repository.get_graph()
2064
# This provides an opportunity for the missing rev-id to be cached.
2065
self.assertEqual({}, graph.get_parent_map(['rev1']))
2066
tree.commit('message', rev_id='rev1')
2067
graph = tree.branch.repository.get_graph()
2068
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2071
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2073
def test_null_revision(self):
2074
# a null revision has the predictable result {}, we should have no wire
2075
# traffic when calling it with this argument
2076
transport_path = 'empty'
2077
repo, client = self.setup_fake_client_and_repository(transport_path)
2078
client.add_success_response('notused')
2079
# actual RemoteRepository.get_revision_graph is gone, but there's an
2080
# equivalent private method for testing
2081
result = repo._get_revision_graph(NULL_REVISION)
2082
self.assertEqual([], client._calls)
2083
self.assertEqual({}, result)
2085
def test_none_revision(self):
2086
# with none we want the entire graph
2087
r1 = u'\u0e33'.encode('utf8')
2088
r2 = u'\u0dab'.encode('utf8')
2089
lines = [' '.join([r2, r1]), r1]
2090
encoded_body = '\n'.join(lines)
2092
transport_path = 'sinhala'
2093
repo, client = self.setup_fake_client_and_repository(transport_path)
2094
client.add_success_response_with_body(encoded_body, 'ok')
2095
# actual RemoteRepository.get_revision_graph is gone, but there's an
2096
# equivalent private method for testing
2097
result = repo._get_revision_graph(None)
2099
[('call_expecting_body', 'Repository.get_revision_graph',
2102
self.assertEqual({r1: (), r2: (r1, )}, result)
2104
def test_specific_revision(self):
2105
# with a specific revision we want the graph for that
2106
# with none we want the entire graph
2107
r11 = u'\u0e33'.encode('utf8')
2108
r12 = u'\xc9'.encode('utf8')
2109
r2 = u'\u0dab'.encode('utf8')
2110
lines = [' '.join([r2, r11, r12]), r11, r12]
2111
encoded_body = '\n'.join(lines)
2113
transport_path = 'sinhala'
2114
repo, client = self.setup_fake_client_and_repository(transport_path)
2115
client.add_success_response_with_body(encoded_body, 'ok')
2116
result = repo._get_revision_graph(r2)
2118
[('call_expecting_body', 'Repository.get_revision_graph',
2121
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2123
def test_no_such_revision(self):
2125
transport_path = 'sinhala'
2126
repo, client = self.setup_fake_client_and_repository(transport_path)
2127
client.add_error_response('nosuchrevision', revid)
2128
# also check that the right revision is reported in the error
2129
self.assertRaises(errors.NoSuchRevision,
2130
repo._get_revision_graph, revid)
2132
[('call_expecting_body', 'Repository.get_revision_graph',
2133
('sinhala/', revid))],
2136
def test_unexpected_error(self):
2138
transport_path = 'sinhala'
2139
repo, client = self.setup_fake_client_and_repository(transport_path)
2140
client.add_error_response('AnUnexpectedError')
2141
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2142
repo._get_revision_graph, revid)
2143
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2146
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2149
repo, client = self.setup_fake_client_and_repository('quack')
2150
client.add_expected_call(
2151
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2152
'success', ('ok', 'rev-five'))
2153
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2154
self.assertEqual((True, 'rev-five'), result)
2155
self.assertFinished(client)
2157
def test_history_incomplete(self):
2158
repo, client = self.setup_fake_client_and_repository('quack')
2159
client.add_expected_call(
2160
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2161
'success', ('history-incomplete', 10, 'rev-ten'))
2162
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2163
self.assertEqual((False, (10, 'rev-ten')), result)
2164
self.assertFinished(client)
2166
def test_history_incomplete_with_fallback(self):
2167
"""A 'history-incomplete' response causes the fallback repository to be
2168
queried too, if one is set.
2170
# Make a repo with a fallback repo, both using a FakeClient.
2171
format = remote.response_tuple_to_repo_format(
2172
('yes', 'no', 'yes', 'fake-network-name'))
2173
repo, client = self.setup_fake_client_and_repository('quack')
2174
repo._format = format
2175
fallback_repo, ignored = self.setup_fake_client_and_repository(
2177
fallback_repo._client = client
2178
repo.add_fallback_repository(fallback_repo)
2179
# First the client should ask the primary repo
2180
client.add_expected_call(
2181
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2182
'success', ('history-incomplete', 2, 'rev-two'))
2183
# Then it should ask the fallback, using revno/revid from the
2184
# history-incomplete response as the known revno/revid.
2185
client.add_expected_call(
2186
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2187
'success', ('ok', 'rev-one'))
2188
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2189
self.assertEqual((True, 'rev-one'), result)
2190
self.assertFinished(client)
2192
def test_nosuchrevision(self):
2193
# 'nosuchrevision' is returned when the known-revid is not found in the
2194
# remote repo. The client translates that response to NoSuchRevision.
2195
repo, client = self.setup_fake_client_and_repository('quack')
2196
client.add_expected_call(
2197
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2198
'error', ('nosuchrevision', 'rev-foo'))
2200
errors.NoSuchRevision,
2201
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2202
self.assertFinished(client)
2204
def test_branch_fallback_locking(self):
2205
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2206
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2207
will be invoked, which will fail if the repo is unlocked.
2209
self.setup_smart_server_with_call_log()
2210
tree = self.make_branch_and_memory_tree('.')
2212
rev1 = tree.commit('First')
2213
rev2 = tree.commit('Second')
2215
branch = tree.branch
2216
self.assertFalse(branch.is_locked())
2217
self.reset_smart_call_log()
2218
verb = 'Repository.get_rev_id_for_revno'
2219
self.disable_verb(verb)
2220
self.assertEqual(rev1, branch.get_rev_id(1))
2221
self.assertLength(1, [call for call in self.hpss_calls if
2222
call.call.method == verb])
2225
class TestRepositoryIsShared(TestRemoteRepository):
2227
def test_is_shared(self):
2228
# ('yes', ) for Repository.is_shared -> 'True'.
2229
transport_path = 'quack'
2230
repo, client = self.setup_fake_client_and_repository(transport_path)
2231
client.add_success_response('yes')
2232
result = repo.is_shared()
2234
[('call', 'Repository.is_shared', ('quack/',))],
2236
self.assertEqual(True, result)
2238
def test_is_not_shared(self):
2239
# ('no', ) for Repository.is_shared -> 'False'.
2240
transport_path = 'qwack'
2241
repo, client = self.setup_fake_client_and_repository(transport_path)
2242
client.add_success_response('no')
2243
result = repo.is_shared()
2245
[('call', 'Repository.is_shared', ('qwack/',))],
2247
self.assertEqual(False, result)
2250
class TestRepositoryLockWrite(TestRemoteRepository):
2252
def test_lock_write(self):
2253
transport_path = 'quack'
2254
repo, client = self.setup_fake_client_and_repository(transport_path)
2255
client.add_success_response('ok', 'a token')
2256
result = repo.lock_write()
2258
[('call', 'Repository.lock_write', ('quack/', ''))],
2260
self.assertEqual('a token', result)
2262
def test_lock_write_already_locked(self):
2263
transport_path = 'quack'
2264
repo, client = self.setup_fake_client_and_repository(transport_path)
2265
client.add_error_response('LockContention')
2266
self.assertRaises(errors.LockContention, repo.lock_write)
2268
[('call', 'Repository.lock_write', ('quack/', ''))],
2271
def test_lock_write_unlockable(self):
2272
transport_path = 'quack'
2273
repo, client = self.setup_fake_client_and_repository(transport_path)
2274
client.add_error_response('UnlockableTransport')
2275
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2277
[('call', 'Repository.lock_write', ('quack/', ''))],
2281
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2283
def test_backwards_compat(self):
2284
self.setup_smart_server_with_call_log()
2285
repo = self.make_repository('.')
2286
self.reset_smart_call_log()
2287
verb = 'Repository.set_make_working_trees'
2288
self.disable_verb(verb)
2289
repo.set_make_working_trees(True)
2290
call_count = len([call for call in self.hpss_calls if
2291
call.call.method == verb])
2292
self.assertEqual(1, call_count)
2294
def test_current(self):
2295
transport_path = 'quack'
2296
repo, client = self.setup_fake_client_and_repository(transport_path)
2297
client.add_expected_call(
2298
'Repository.set_make_working_trees', ('quack/', 'True'),
2300
client.add_expected_call(
2301
'Repository.set_make_working_trees', ('quack/', 'False'),
2303
repo.set_make_working_trees(True)
2304
repo.set_make_working_trees(False)
2307
class TestRepositoryUnlock(TestRemoteRepository):
2309
def test_unlock(self):
2310
transport_path = 'quack'
2311
repo, client = self.setup_fake_client_and_repository(transport_path)
2312
client.add_success_response('ok', 'a token')
2313
client.add_success_response('ok')
2317
[('call', 'Repository.lock_write', ('quack/', '')),
2318
('call', 'Repository.unlock', ('quack/', 'a token'))],
2321
def test_unlock_wrong_token(self):
2322
# If somehow the token is wrong, unlock will raise TokenMismatch.
2323
transport_path = 'quack'
2324
repo, client = self.setup_fake_client_and_repository(transport_path)
2325
client.add_success_response('ok', 'a token')
2326
client.add_error_response('TokenMismatch')
2328
self.assertRaises(errors.TokenMismatch, repo.unlock)
2331
class TestRepositoryHasRevision(TestRemoteRepository):
2333
def test_none(self):
2334
# repo.has_revision(None) should not cause any traffic.
2335
transport_path = 'quack'
2336
repo, client = self.setup_fake_client_and_repository(transport_path)
2338
# The null revision is always there, so has_revision(None) == True.
2339
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2341
# The remote repo shouldn't be accessed.
2342
self.assertEqual([], client._calls)
2345
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2346
"""Base class for Repository.insert_stream and .insert_stream_1.19
2350
def checkInsertEmptyStream(self, repo, client):
2351
"""Insert an empty stream, checking the result.
2353
This checks that there are no resume_tokens or missing_keys, and that
2354
the client is finished.
2356
sink = repo._get_sink()
2357
fmt = repository.RepositoryFormat.get_default_format()
2358
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2359
self.assertEqual([], resume_tokens)
2360
self.assertEqual(set(), missing_keys)
2361
self.assertFinished(client)
2364
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2365
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2368
This test case is very similar to TestRepositoryInsertStream_1_19.
2372
TestRemoteRepository.setUp(self)
2373
self.disable_verb('Repository.insert_stream_1.19')
2375
def test_unlocked_repo(self):
2376
transport_path = 'quack'
2377
repo, client = self.setup_fake_client_and_repository(transport_path)
2378
client.add_expected_call(
2379
'Repository.insert_stream_1.19', ('quack/', ''),
2380
'unknown', ('Repository.insert_stream_1.19',))
2381
client.add_expected_call(
2382
'Repository.insert_stream', ('quack/', ''),
2384
client.add_expected_call(
2385
'Repository.insert_stream', ('quack/', ''),
2387
self.checkInsertEmptyStream(repo, client)
2389
def test_locked_repo_with_no_lock_token(self):
2390
transport_path = 'quack'
2391
repo, client = self.setup_fake_client_and_repository(transport_path)
2392
client.add_expected_call(
2393
'Repository.lock_write', ('quack/', ''),
2394
'success', ('ok', ''))
2395
client.add_expected_call(
2396
'Repository.insert_stream_1.19', ('quack/', ''),
2397
'unknown', ('Repository.insert_stream_1.19',))
2398
client.add_expected_call(
2399
'Repository.insert_stream', ('quack/', ''),
2401
client.add_expected_call(
2402
'Repository.insert_stream', ('quack/', ''),
2405
self.checkInsertEmptyStream(repo, client)
2407
def test_locked_repo_with_lock_token(self):
2408
transport_path = 'quack'
2409
repo, client = self.setup_fake_client_and_repository(transport_path)
2410
client.add_expected_call(
2411
'Repository.lock_write', ('quack/', ''),
2412
'success', ('ok', 'a token'))
2413
client.add_expected_call(
2414
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2415
'unknown', ('Repository.insert_stream_1.19',))
2416
client.add_expected_call(
2417
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2419
client.add_expected_call(
2420
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2423
self.checkInsertEmptyStream(repo, client)
2425
def test_stream_with_inventory_deltas(self):
2426
"""'inventory-deltas' substreams cannot be sent to the
2427
Repository.insert_stream verb, because not all servers that implement
2428
that verb will accept them. So when one is encountered the RemoteSink
2429
immediately stops using that verb and falls back to VFS insert_stream.
2431
transport_path = 'quack'
2432
repo, client = self.setup_fake_client_and_repository(transport_path)
2433
client.add_expected_call(
2434
'Repository.insert_stream_1.19', ('quack/', ''),
2435
'unknown', ('Repository.insert_stream_1.19',))
2436
client.add_expected_call(
2437
'Repository.insert_stream', ('quack/', ''),
2439
client.add_expected_call(
2440
'Repository.insert_stream', ('quack/', ''),
2442
# Create a fake real repository for insert_stream to fall back on, so
2443
# that we can directly see the records the RemoteSink passes to the
2448
def insert_stream(self, stream, src_format, resume_tokens):
2449
for substream_kind, substream in stream:
2450
self.records.append(
2451
(substream_kind, [record.key for record in substream]))
2452
return ['fake tokens'], ['fake missing keys']
2453
fake_real_sink = FakeRealSink()
2454
class FakeRealRepository:
2455
def _get_sink(self):
2456
return fake_real_sink
2457
def is_in_write_group(self):
2459
def refresh_data(self):
2461
repo._real_repository = FakeRealRepository()
2462
sink = repo._get_sink()
2463
fmt = repository.RepositoryFormat.get_default_format()
2464
stream = self.make_stream_with_inv_deltas(fmt)
2465
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2466
# Every record from the first inventory delta should have been sent to
2468
expected_records = [
2469
('inventory-deltas', [('rev2',), ('rev3',)]),
2470
('texts', [('some-rev', 'some-file')])]
2471
self.assertEqual(expected_records, fake_real_sink.records)
2472
# The return values from the real sink's insert_stream are propagated
2473
# back to the original caller.
2474
self.assertEqual(['fake tokens'], resume_tokens)
2475
self.assertEqual(['fake missing keys'], missing_keys)
2476
self.assertFinished(client)
2478
def make_stream_with_inv_deltas(self, fmt):
2479
"""Make a simple stream with an inventory delta followed by more
2480
records and more substreams to test that all records and substreams
2481
from that point on are used.
2483
This sends, in order:
2484
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2486
* texts substream: (some-rev, some-file)
2488
# Define a stream using generators so that it isn't rewindable.
2489
inv = inventory.Inventory(revision_id='rev1')
2490
inv.root.revision = 'rev1'
2491
def stream_with_inv_delta():
2492
yield ('inventories', inventories_substream())
2493
yield ('inventory-deltas', inventory_delta_substream())
2495
versionedfile.FulltextContentFactory(
2496
('some-rev', 'some-file'), (), None, 'content')])
2497
def inventories_substream():
2498
# An empty inventory fulltext. This will be streamed normally.
2499
text = fmt._serializer.write_inventory_to_string(inv)
2500
yield versionedfile.FulltextContentFactory(
2501
('rev1',), (), None, text)
2502
def inventory_delta_substream():
2503
# An inventory delta. This can't be streamed via this verb, so it
2504
# will trigger a fallback to VFS insert_stream.
2505
entry = inv.make_entry(
2506
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2507
entry.revision = 'ghost'
2508
delta = [(None, 'newdir', 'newdir-id', entry)]
2509
serializer = inventory_delta.InventoryDeltaSerializer(
2510
versioned_root=True, tree_references=False)
2511
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2512
yield versionedfile.ChunkedContentFactory(
2513
('rev2',), (('rev1',)), None, lines)
2515
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2516
yield versionedfile.ChunkedContentFactory(
2517
('rev3',), (('rev1',)), None, lines)
2518
return stream_with_inv_delta()
2521
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2523
def test_unlocked_repo(self):
2524
transport_path = 'quack'
2525
repo, client = self.setup_fake_client_and_repository(transport_path)
2526
client.add_expected_call(
2527
'Repository.insert_stream_1.19', ('quack/', ''),
2529
client.add_expected_call(
2530
'Repository.insert_stream_1.19', ('quack/', ''),
2532
self.checkInsertEmptyStream(repo, client)
2534
def test_locked_repo_with_no_lock_token(self):
2535
transport_path = 'quack'
2536
repo, client = self.setup_fake_client_and_repository(transport_path)
2537
client.add_expected_call(
2538
'Repository.lock_write', ('quack/', ''),
2539
'success', ('ok', ''))
2540
client.add_expected_call(
2541
'Repository.insert_stream_1.19', ('quack/', ''),
2543
client.add_expected_call(
2544
'Repository.insert_stream_1.19', ('quack/', ''),
2547
self.checkInsertEmptyStream(repo, client)
2549
def test_locked_repo_with_lock_token(self):
2550
transport_path = 'quack'
2551
repo, client = self.setup_fake_client_and_repository(transport_path)
2552
client.add_expected_call(
2553
'Repository.lock_write', ('quack/', ''),
2554
'success', ('ok', 'a token'))
2555
client.add_expected_call(
2556
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2558
client.add_expected_call(
2559
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2562
self.checkInsertEmptyStream(repo, client)
2565
class TestRepositoryTarball(TestRemoteRepository):
2567
# This is a canned tarball reponse we can validate against
2569
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2570
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2571
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2572
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2573
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2574
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2575
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2576
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2577
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2578
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2579
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2580
'nWQ7QH/F3JFOFCQ0aSPfA='
2583
def test_repository_tarball(self):
2584
# Test that Repository.tarball generates the right operations
2585
transport_path = 'repo'
2586
expected_calls = [('call_expecting_body', 'Repository.tarball',
2587
('repo/', 'bz2',),),
2589
repo, client = self.setup_fake_client_and_repository(transport_path)
2590
client.add_success_response_with_body(self.tarball_content, 'ok')
2591
# Now actually ask for the tarball
2592
tarball_file = repo._get_tarball('bz2')
2594
self.assertEqual(expected_calls, client._calls)
2595
self.assertEqual(self.tarball_content, tarball_file.read())
2597
tarball_file.close()
2600
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2601
"""RemoteRepository.copy_content_into optimizations"""
2603
def test_copy_content_remote_to_local(self):
2604
self.transport_server = server.SmartTCPServer_for_testing
2605
src_repo = self.make_repository('repo1')
2606
src_repo = repository.Repository.open(self.get_url('repo1'))
2607
# At the moment the tarball-based copy_content_into can't write back
2608
# into a smart server. It would be good if it could upload the
2609
# tarball; once that works we'd have to create repositories of
2610
# different formats. -- mbp 20070410
2611
dest_url = self.get_vfs_only_url('repo2')
2612
dest_bzrdir = BzrDir.create(dest_url)
2613
dest_repo = dest_bzrdir.create_repository()
2614
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2615
self.assertTrue(isinstance(src_repo, RemoteRepository))
2616
src_repo.copy_content_into(dest_repo)
2619
class _StubRealPackRepository(object):
2621
def __init__(self, calls):
2623
self._pack_collection = _StubPackCollection(calls)
2625
def is_in_write_group(self):
2628
def refresh_data(self):
2629
self.calls.append(('pack collection reload_pack_names',))
2632
class _StubPackCollection(object):
2634
def __init__(self, calls):
2638
self.calls.append(('pack collection autopack',))
2641
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2642
"""Tests for RemoteRepository.autopack implementation."""
2645
"""When the server returns 'ok' and there's no _real_repository, then
2646
nothing else happens: the autopack method is done.
2648
transport_path = 'quack'
2649
repo, client = self.setup_fake_client_and_repository(transport_path)
2650
client.add_expected_call(
2651
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2653
self.assertFinished(client)
2655
def test_ok_with_real_repo(self):
2656
"""When the server returns 'ok' and there is a _real_repository, then
2657
the _real_repository's reload_pack_name's method will be called.
2659
transport_path = 'quack'
2660
repo, client = self.setup_fake_client_and_repository(transport_path)
2661
client.add_expected_call(
2662
'PackRepository.autopack', ('quack/',),
2664
repo._real_repository = _StubRealPackRepository(client._calls)
2667
[('call', 'PackRepository.autopack', ('quack/',)),
2668
('pack collection reload_pack_names',)],
2671
def test_backwards_compatibility(self):
2672
"""If the server does not recognise the PackRepository.autopack verb,
2673
fallback to the real_repository's implementation.
2675
transport_path = 'quack'
2676
repo, client = self.setup_fake_client_and_repository(transport_path)
2677
client.add_unknown_method_response('PackRepository.autopack')
2678
def stub_ensure_real():
2679
client._calls.append(('_ensure_real',))
2680
repo._real_repository = _StubRealPackRepository(client._calls)
2681
repo._ensure_real = stub_ensure_real
2684
[('call', 'PackRepository.autopack', ('quack/',)),
2686
('pack collection autopack',)],
2690
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2691
"""Base class for unit tests for bzrlib.remote._translate_error."""
2693
def translateTuple(self, error_tuple, **context):
2694
"""Call _translate_error with an ErrorFromSmartServer built from the
2697
:param error_tuple: A tuple of a smart server response, as would be
2698
passed to an ErrorFromSmartServer.
2699
:kwargs context: context items to call _translate_error with.
2701
:returns: The error raised by _translate_error.
2703
# Raise the ErrorFromSmartServer before passing it as an argument,
2704
# because _translate_error may need to re-raise it with a bare 'raise'
2706
server_error = errors.ErrorFromSmartServer(error_tuple)
2707
translated_error = self.translateErrorFromSmartServer(
2708
server_error, **context)
2709
return translated_error
2711
def translateErrorFromSmartServer(self, error_object, **context):
2712
"""Like translateTuple, but takes an already constructed
2713
ErrorFromSmartServer rather than a tuple.
2717
except errors.ErrorFromSmartServer, server_error:
2718
translated_error = self.assertRaises(
2719
errors.BzrError, remote._translate_error, server_error,
2721
return translated_error
2724
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2725
"""Unit tests for bzrlib.remote._translate_error.
2727
Given an ErrorFromSmartServer (which has an error tuple from a smart
2728
server) and some context, _translate_error raises more specific errors from
2731
This test case covers the cases where _translate_error succeeds in
2732
translating an ErrorFromSmartServer to something better. See
2733
TestErrorTranslationRobustness for other cases.
2736
def test_NoSuchRevision(self):
2737
branch = self.make_branch('')
2739
translated_error = self.translateTuple(
2740
('NoSuchRevision', revid), branch=branch)
2741
expected_error = errors.NoSuchRevision(branch, revid)
2742
self.assertEqual(expected_error, translated_error)
2744
def test_nosuchrevision(self):
2745
repository = self.make_repository('')
2747
translated_error = self.translateTuple(
2748
('nosuchrevision', revid), repository=repository)
2749
expected_error = errors.NoSuchRevision(repository, revid)
2750
self.assertEqual(expected_error, translated_error)
2752
def test_nobranch(self):
2753
bzrdir = self.make_bzrdir('')
2754
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2755
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2756
self.assertEqual(expected_error, translated_error)
2758
def test_LockContention(self):
2759
translated_error = self.translateTuple(('LockContention',))
2760
expected_error = errors.LockContention('(remote lock)')
2761
self.assertEqual(expected_error, translated_error)
2763
def test_UnlockableTransport(self):
2764
bzrdir = self.make_bzrdir('')
2765
translated_error = self.translateTuple(
2766
('UnlockableTransport',), bzrdir=bzrdir)
2767
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2768
self.assertEqual(expected_error, translated_error)
2770
def test_LockFailed(self):
2771
lock = 'str() of a server lock'
2772
why = 'str() of why'
2773
translated_error = self.translateTuple(('LockFailed', lock, why))
2774
expected_error = errors.LockFailed(lock, why)
2775
self.assertEqual(expected_error, translated_error)
2777
def test_TokenMismatch(self):
2778
token = 'a lock token'
2779
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2780
expected_error = errors.TokenMismatch(token, '(remote token)')
2781
self.assertEqual(expected_error, translated_error)
2783
def test_Diverged(self):
2784
branch = self.make_branch('a')
2785
other_branch = self.make_branch('b')
2786
translated_error = self.translateTuple(
2787
('Diverged',), branch=branch, other_branch=other_branch)
2788
expected_error = errors.DivergedBranches(branch, other_branch)
2789
self.assertEqual(expected_error, translated_error)
2791
def test_ReadError_no_args(self):
2793
translated_error = self.translateTuple(('ReadError',), path=path)
2794
expected_error = errors.ReadError(path)
2795
self.assertEqual(expected_error, translated_error)
2797
def test_ReadError(self):
2799
translated_error = self.translateTuple(('ReadError', path))
2800
expected_error = errors.ReadError(path)
2801
self.assertEqual(expected_error, translated_error)
2803
def test_IncompatibleRepositories(self):
2804
translated_error = self.translateTuple(('IncompatibleRepositories',
2805
"repo1", "repo2", "details here"))
2806
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2808
self.assertEqual(expected_error, translated_error)
2810
def test_PermissionDenied_no_args(self):
2812
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2813
expected_error = errors.PermissionDenied(path)
2814
self.assertEqual(expected_error, translated_error)
2816
def test_PermissionDenied_one_arg(self):
2818
translated_error = self.translateTuple(('PermissionDenied', path))
2819
expected_error = errors.PermissionDenied(path)
2820
self.assertEqual(expected_error, translated_error)
2822
def test_PermissionDenied_one_arg_and_context(self):
2823
"""Given a choice between a path from the local context and a path on
2824
the wire, _translate_error prefers the path from the local context.
2826
local_path = 'local path'
2827
remote_path = 'remote path'
2828
translated_error = self.translateTuple(
2829
('PermissionDenied', remote_path), path=local_path)
2830
expected_error = errors.PermissionDenied(local_path)
2831
self.assertEqual(expected_error, translated_error)
2833
def test_PermissionDenied_two_args(self):
2835
extra = 'a string with extra info'
2836
translated_error = self.translateTuple(
2837
('PermissionDenied', path, extra))
2838
expected_error = errors.PermissionDenied(path, extra)
2839
self.assertEqual(expected_error, translated_error)
2842
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2843
"""Unit tests for bzrlib.remote._translate_error's robustness.
2845
TestErrorTranslationSuccess is for cases where _translate_error can
2846
translate successfully. This class about how _translate_err behaves when
2847
it fails to translate: it re-raises the original error.
2850
def test_unrecognised_server_error(self):
2851
"""If the error code from the server is not recognised, the original
2852
ErrorFromSmartServer is propagated unmodified.
2854
error_tuple = ('An unknown error tuple',)
2855
server_error = errors.ErrorFromSmartServer(error_tuple)
2856
translated_error = self.translateErrorFromSmartServer(server_error)
2857
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2858
self.assertEqual(expected_error, translated_error)
2860
def test_context_missing_a_key(self):
2861
"""In case of a bug in the client, or perhaps an unexpected response
2862
from a server, _translate_error returns the original error tuple from
2863
the server and mutters a warning.
2865
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2866
# in the context dict. So let's give it an empty context dict instead
2867
# to exercise its error recovery.
2869
error_tuple = ('NoSuchRevision', 'revid')
2870
server_error = errors.ErrorFromSmartServer(error_tuple)
2871
translated_error = self.translateErrorFromSmartServer(server_error)
2872
self.assertEqual(server_error, translated_error)
2873
# In addition to re-raising ErrorFromSmartServer, some debug info has
2874
# been muttered to the log file for developer to look at.
2875
self.assertContainsRe(
2876
self._get_log(keep_log_file=True),
2877
"Missing key 'branch' in context")
2879
def test_path_missing(self):
2880
"""Some translations (PermissionDenied, ReadError) can determine the
2881
'path' variable from either the wire or the local context. If neither
2882
has it, then an error is raised.
2884
error_tuple = ('ReadError',)
2885
server_error = errors.ErrorFromSmartServer(error_tuple)
2886
translated_error = self.translateErrorFromSmartServer(server_error)
2887
self.assertEqual(server_error, translated_error)
2888
# In addition to re-raising ErrorFromSmartServer, some debug info has
2889
# been muttered to the log file for developer to look at.
2890
self.assertContainsRe(
2891
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2894
class TestStacking(tests.TestCaseWithTransport):
2895
"""Tests for operations on stacked remote repositories.
2897
The underlying format type must support stacking.
2900
def test_access_stacked_remote(self):
2901
# based on <http://launchpad.net/bugs/261315>
2902
# make a branch stacked on another repository containing an empty
2903
# revision, then open it over hpss - we should be able to see that
2905
base_transport = self.get_transport()
2906
base_builder = self.make_branch_builder('base', format='1.9')
2907
base_builder.start_series()
2908
base_revid = base_builder.build_snapshot('rev-id', None,
2909
[('add', ('', None, 'directory', None))],
2911
base_builder.finish_series()
2912
stacked_branch = self.make_branch('stacked', format='1.9')
2913
stacked_branch.set_stacked_on_url('../base')
2914
# start a server looking at this
2915
smart_server = server.SmartTCPServer_for_testing()
2916
self.start_server(smart_server)
2917
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2918
# can get its branch and repository
2919
remote_branch = remote_bzrdir.open_branch()
2920
remote_repo = remote_branch.repository
2921
remote_repo.lock_read()
2923
# it should have an appropriate fallback repository, which should also
2924
# be a RemoteRepository
2925
self.assertLength(1, remote_repo._fallback_repositories)
2926
self.assertIsInstance(remote_repo._fallback_repositories[0],
2928
# and it has the revision committed to the underlying repository;
2929
# these have varying implementations so we try several of them
2930
self.assertTrue(remote_repo.has_revisions([base_revid]))
2931
self.assertTrue(remote_repo.has_revision(base_revid))
2932
self.assertEqual(remote_repo.get_revision(base_revid).message,
2935
remote_repo.unlock()
2937
def prepare_stacked_remote_branch(self):
2938
"""Get stacked_upon and stacked branches with content in each."""
2939
self.setup_smart_server_with_call_log()
2940
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2941
tree1.commit('rev1', rev_id='rev1')
2942
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2943
).open_workingtree()
2944
local_tree = tree2.branch.create_checkout('local')
2945
local_tree.commit('local changes make me feel good.')
2946
branch2 = Branch.open(self.get_url('tree2'))
2948
self.addCleanup(branch2.unlock)
2949
return tree1.branch, branch2
2951
def test_stacked_get_parent_map(self):
2952
# the public implementation of get_parent_map obeys stacking
2953
_, branch = self.prepare_stacked_remote_branch()
2954
repo = branch.repository
2955
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2957
def test_unstacked_get_parent_map(self):
2958
# _unstacked_provider.get_parent_map ignores stacking
2959
_, branch = self.prepare_stacked_remote_branch()
2960
provider = branch.repository._unstacked_provider
2961
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2963
def fetch_stream_to_rev_order(self, stream):
2965
for kind, substream in stream:
2966
if not kind == 'revisions':
2969
for content in substream:
2970
result.append(content.key[-1])
2973
def get_ordered_revs(self, format, order, branch_factory=None):
2974
"""Get a list of the revisions in a stream to format format.
2976
:param format: The format of the target.
2977
:param order: the order that target should have requested.
2978
:param branch_factory: A callable to create a trunk and stacked branch
2979
to fetch from. If none, self.prepare_stacked_remote_branch is used.
2980
:result: The revision ids in the stream, in the order seen,
2981
the topological order of revisions in the source.
2983
unordered_format = bzrdir.format_registry.get(format)()
2984
target_repository_format = unordered_format.repository_format
2986
self.assertEqual(order, target_repository_format._fetch_order)
2987
if branch_factory is None:
2988
branch_factory = self.prepare_stacked_remote_branch
2989
_, stacked = branch_factory()
2990
source = stacked.repository._get_source(target_repository_format)
2991
tip = stacked.last_revision()
2992
revs = stacked.repository.get_ancestry(tip)
2993
search = graph.PendingAncestryResult([tip], stacked.repository)
2994
self.reset_smart_call_log()
2995
stream = source.get_stream(search)
2998
# We trust that if a revision is in the stream the rest of the new
2999
# content for it is too, as per our main fetch tests; here we are
3000
# checking that the revisions are actually included at all, and their
3002
return self.fetch_stream_to_rev_order(stream), revs
3004
def test_stacked_get_stream_unordered(self):
3005
# Repository._get_source.get_stream() from a stacked repository with
3006
# unordered yields the full data from both stacked and stacked upon
3008
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3009
self.assertEqual(set(expected_revs), set(rev_ord))
3010
# Getting unordered results should have made a streaming data request
3011
# from the server, then one from the backing branch.
3012
self.assertLength(2, self.hpss_calls)
3014
def test_stacked_on_stacked_get_stream_unordered(self):
3015
# Repository._get_source.get_stream() from a stacked repository which
3016
# is itself stacked yields the full data from all three sources.
3017
def make_stacked_stacked():
3018
_, stacked = self.prepare_stacked_remote_branch()
3019
tree = stacked.bzrdir.sprout('tree3', stacked=True
3020
).open_workingtree()
3021
local_tree = tree.branch.create_checkout('local-tree3')
3022
local_tree.commit('more local changes are better')
3023
branch = Branch.open(self.get_url('tree3'))
3026
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3027
branch_factory=make_stacked_stacked)
3028
self.assertEqual(set(expected_revs), set(rev_ord))
3029
# Getting unordered results should have made a streaming data request
3030
# from the server, and one from each backing repo
3031
self.assertLength(3, self.hpss_calls)
3033
def test_stacked_get_stream_topological(self):
3034
# Repository._get_source.get_stream() from a stacked repository with
3035
# topological sorting yields the full data from both stacked and
3036
# stacked upon sources in topological order.
3037
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3038
self.assertEqual(expected_revs, rev_ord)
3039
# Getting topological sort requires VFS calls still - one of which is
3040
# pushing up from the bound branch.
3041
self.assertLength(13, self.hpss_calls)
3043
def test_stacked_get_stream_groupcompress(self):
3044
# Repository._get_source.get_stream() from a stacked repository with
3045
# groupcompress sorting yields the full data from both stacked and
3046
# stacked upon sources in groupcompress order.
3047
raise tests.TestSkipped('No groupcompress ordered format available')
3048
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3049
self.assertEqual(expected_revs, reversed(rev_ord))
3050
# Getting unordered results should have made a streaming data request
3051
# from the backing branch, and one from the stacked on branch.
3052
self.assertLength(2, self.hpss_calls)
3054
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3055
# When pulling some fixed amount of content that is more than the
3056
# source has (because some is coming from a fallback branch, no error
3057
# should be received. This was reported as bug 360791.
3058
# Need three branches: a trunk, a stacked branch, and a preexisting
3059
# branch pulling content from stacked and trunk.
3060
self.setup_smart_server_with_call_log()
3061
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3062
r1 = trunk.commit('start')
3063
stacked_branch = trunk.branch.create_clone_on_transport(
3064
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3065
local = self.make_branch('local', format='1.9-rich-root')
3066
local.repository.fetch(stacked_branch.repository,
3067
stacked_branch.last_revision())
3070
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3073
super(TestRemoteBranchEffort, self).setUp()
3074
# Create a smart server that publishes whatever the backing VFS server
3076
self.smart_server = server.SmartTCPServer_for_testing()
3077
self.start_server(self.smart_server, self.get_server())
3078
# Log all HPSS calls into self.hpss_calls.
3079
_SmartClient.hooks.install_named_hook(
3080
'call', self.capture_hpss_call, None)
3081
self.hpss_calls = []
3083
def capture_hpss_call(self, params):
3084
self.hpss_calls.append(params.method)
3086
def test_copy_content_into_avoids_revision_history(self):
3087
local = self.make_branch('local')
3088
remote_backing_tree = self.make_branch_and_tree('remote')
3089
remote_backing_tree.commit("Commit.")
3090
remote_branch_url = self.smart_server.get_url() + 'remote'
3091
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3092
local.repository.fetch(remote_branch.repository)
3093
self.hpss_calls = []
3094
remote_branch.copy_content_into(local)
3095
self.assertFalse('Branch.revision_history' in self.hpss_calls)