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,
65
from bzrlib.transport import get_transport
66
from bzrlib.transport.memory import MemoryTransport
67
from bzrlib.transport.remote import (
73
def load_tests(standard_tests, module, loader):
74
to_adapt, result = split_suite_by_condition(
75
standard_tests, condition_isinstance(BasicRemoteObjectTests))
76
smart_server_version_scenarios = [
78
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
80
{'transport_server': server.SmartTCPServer_for_testing})]
81
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
84
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
87
super(BasicRemoteObjectTests, self).setUp()
88
self.transport = self.get_transport()
89
# make a branch that can be opened over the smart transport
90
self.local_wt = BzrDir.create_standalone_workingtree('.')
93
self.transport.disconnect()
94
tests.TestCaseWithTransport.tearDown(self)
96
def test_create_remote_bzrdir(self):
97
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
98
self.assertIsInstance(b, BzrDir)
100
def test_open_remote_branch(self):
101
# open a standalone branch in the working directory
102
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
103
branch = b.open_branch()
104
self.assertIsInstance(branch, Branch)
106
def test_remote_repository(self):
107
b = BzrDir.open_from_transport(self.transport)
108
repo = b.open_repository()
109
revid = u'\xc823123123'.encode('utf8')
110
self.assertFalse(repo.has_revision(revid))
111
self.local_wt.commit(message='test commit', rev_id=revid)
112
self.assertTrue(repo.has_revision(revid))
114
def test_remote_branch_revision_history(self):
115
b = BzrDir.open_from_transport(self.transport).open_branch()
116
self.assertEqual([], b.revision_history())
117
r1 = self.local_wt.commit('1st commit')
118
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
119
self.assertEqual([r1, r2], b.revision_history())
121
def test_find_correct_format(self):
122
"""Should open a RemoteBzrDir over a RemoteTransport"""
123
fmt = BzrDirFormat.find_format(self.transport)
124
self.assertTrue(RemoteBzrDirFormat
125
in BzrDirFormat._control_server_formats)
126
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
128
def test_open_detected_smart_format(self):
129
fmt = BzrDirFormat.find_format(self.transport)
130
d = fmt.open(self.transport)
131
self.assertIsInstance(d, BzrDir)
133
def test_remote_branch_repr(self):
134
b = BzrDir.open_from_transport(self.transport).open_branch()
135
self.assertStartsWith(str(b), 'RemoteBranch(')
137
def test_remote_bzrdir_repr(self):
138
b = BzrDir.open_from_transport(self.transport)
139
self.assertStartsWith(str(b), 'RemoteBzrDir(')
141
def test_remote_branch_format_supports_stacking(self):
143
self.make_branch('unstackable', format='pack-0.92')
144
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
145
self.assertFalse(b._format.supports_stacking())
146
self.make_branch('stackable', format='1.9')
147
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
148
self.assertTrue(b._format.supports_stacking())
150
def test_remote_repo_format_supports_external_references(self):
152
bd = self.make_bzrdir('unstackable', format='pack-0.92')
153
r = bd.create_repository()
154
self.assertFalse(r._format.supports_external_lookups)
155
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
156
self.assertFalse(r._format.supports_external_lookups)
157
bd = self.make_bzrdir('stackable', format='1.9')
158
r = bd.create_repository()
159
self.assertTrue(r._format.supports_external_lookups)
160
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
161
self.assertTrue(r._format.supports_external_lookups)
163
def test_remote_branch_set_append_revisions_only(self):
164
# Make a format 1.9 branch, which supports append_revisions_only
165
branch = self.make_branch('branch', format='1.9')
166
config = branch.get_config()
167
branch.set_append_revisions_only(True)
169
'True', config.get_user_option('append_revisions_only'))
170
branch.set_append_revisions_only(False)
172
'False', config.get_user_option('append_revisions_only'))
174
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
175
branch = self.make_branch('branch', format='knit')
176
config = branch.get_config()
178
errors.UpgradeRequired, branch.set_append_revisions_only, True)
181
class FakeProtocol(object):
182
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
184
def __init__(self, body, fake_client):
186
self._body_buffer = None
187
self._fake_client = fake_client
189
def read_body_bytes(self, count=-1):
190
if self._body_buffer is None:
191
self._body_buffer = StringIO(self.body)
192
bytes = self._body_buffer.read(count)
193
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
194
self._fake_client.expecting_body = False
197
def cancel_read_body(self):
198
self._fake_client.expecting_body = False
200
def read_streamed_body(self):
204
class FakeClient(_SmartClient):
205
"""Lookalike for _SmartClient allowing testing."""
207
def __init__(self, fake_medium_base='fake base'):
208
"""Create a FakeClient."""
211
self.expecting_body = False
212
# if non-None, this is the list of expected calls, with only the
213
# method name and arguments included. the body might be hard to
214
# compute so is not included. If a call is None, that call can
216
self._expected_calls = None
217
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
219
def add_expected_call(self, call_name, call_args, response_type,
220
response_args, response_body=None):
221
if self._expected_calls is None:
222
self._expected_calls = []
223
self._expected_calls.append((call_name, call_args))
224
self.responses.append((response_type, response_args, response_body))
226
def add_success_response(self, *args):
227
self.responses.append(('success', args, None))
229
def add_success_response_with_body(self, body, *args):
230
self.responses.append(('success', args, body))
231
if self._expected_calls is not None:
232
self._expected_calls.append(None)
234
def add_error_response(self, *args):
235
self.responses.append(('error', args))
237
def add_unknown_method_response(self, verb):
238
self.responses.append(('unknown', verb))
240
def finished_test(self):
241
if self._expected_calls:
242
raise AssertionError("%r finished but was still expecting %r"
243
% (self, self._expected_calls[0]))
245
def _get_next_response(self):
247
response_tuple = self.responses.pop(0)
248
except IndexError, e:
249
raise AssertionError("%r didn't expect any more calls"
251
if response_tuple[0] == 'unknown':
252
raise errors.UnknownSmartMethod(response_tuple[1])
253
elif response_tuple[0] == 'error':
254
raise errors.ErrorFromSmartServer(response_tuple[1])
255
return response_tuple
257
def _check_call(self, method, args):
258
if self._expected_calls is None:
259
# the test should be updated to say what it expects
262
next_call = self._expected_calls.pop(0)
264
raise AssertionError("%r didn't expect any more calls "
266
% (self, method, args,))
267
if next_call is None:
269
if method != next_call[0] or args != next_call[1]:
270
raise AssertionError("%r expected %r%r "
272
% (self, next_call[0], next_call[1], method, args,))
274
def call(self, method, *args):
275
self._check_call(method, args)
276
self._calls.append(('call', method, args))
277
return self._get_next_response()[1]
279
def call_expecting_body(self, method, *args):
280
self._check_call(method, args)
281
self._calls.append(('call_expecting_body', method, args))
282
result = self._get_next_response()
283
self.expecting_body = True
284
return result[1], FakeProtocol(result[2], self)
286
def call_with_body_bytes(self, method, args, body):
287
self._check_call(method, args)
288
self._calls.append(('call_with_body_bytes', method, args, body))
289
result = self._get_next_response()
290
return result[1], FakeProtocol(result[2], self)
292
def call_with_body_bytes_expecting_body(self, method, args, body):
293
self._check_call(method, args)
294
self._calls.append(('call_with_body_bytes_expecting_body', method,
296
result = self._get_next_response()
297
self.expecting_body = True
298
return result[1], FakeProtocol(result[2], self)
300
def call_with_body_stream(self, args, stream):
301
# Explicitly consume the stream before checking for an error, because
302
# that's what happens a real medium.
303
stream = list(stream)
304
self._check_call(args[0], args[1:])
305
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
306
result = self._get_next_response()
307
# The second value returned from call_with_body_stream is supposed to
308
# be a response_handler object, but so far no tests depend on that.
309
response_handler = None
310
return result[1], response_handler
313
class FakeMedium(medium.SmartClientMedium):
315
def __init__(self, client_calls, base):
316
medium.SmartClientMedium.__init__(self, base)
317
self._client_calls = client_calls
319
def disconnect(self):
320
self._client_calls.append(('disconnect medium',))
323
class TestVfsHas(tests.TestCase):
325
def test_unicode_path(self):
326
client = FakeClient('/')
327
client.add_success_response('yes',)
328
transport = RemoteTransport('bzr://localhost/', _client=client)
329
filename = u'/hell\u00d8'.encode('utf8')
330
result = transport.has(filename)
332
[('call', 'has', (filename,))],
334
self.assertTrue(result)
337
class TestRemote(tests.TestCaseWithMemoryTransport):
339
def get_branch_format(self):
340
reference_bzrdir_format = bzrdir.format_registry.get('default')()
341
return reference_bzrdir_format.get_branch_format()
343
def get_repo_format(self):
344
reference_bzrdir_format = bzrdir.format_registry.get('default')()
345
return reference_bzrdir_format.repository_format
347
def assertFinished(self, fake_client):
348
"""Assert that all of a FakeClient's expected calls have occurred."""
349
fake_client.finished_test()
352
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
353
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
355
def assertRemotePath(self, expected, client_base, transport_base):
356
"""Assert that the result of
357
SmartClientMedium.remote_path_from_transport is the expected value for
358
a given client_base and transport_base.
360
client_medium = medium.SmartClientMedium(client_base)
361
transport = get_transport(transport_base)
362
result = client_medium.remote_path_from_transport(transport)
363
self.assertEqual(expected, result)
365
def test_remote_path_from_transport(self):
366
"""SmartClientMedium.remote_path_from_transport calculates a URL for
367
the given transport relative to the root of the client base URL.
369
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
370
self.assertRemotePath(
371
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
373
def assertRemotePathHTTP(self, expected, transport_base, relpath):
374
"""Assert that the result of
375
HttpTransportBase.remote_path_from_transport is the expected value for
376
a given transport_base and relpath of that transport. (Note that
377
HttpTransportBase is a subclass of SmartClientMedium)
379
base_transport = get_transport(transport_base)
380
client_medium = base_transport.get_smart_medium()
381
cloned_transport = base_transport.clone(relpath)
382
result = client_medium.remote_path_from_transport(cloned_transport)
383
self.assertEqual(expected, result)
385
def test_remote_path_from_transport_http(self):
386
"""Remote paths for HTTP transports are calculated differently to other
387
transports. They are just relative to the client base, not the root
388
directory of the host.
390
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
391
self.assertRemotePathHTTP(
392
'../xyz/', scheme + '//host/path', '../xyz/')
393
self.assertRemotePathHTTP(
394
'xyz/', scheme + '//host/path', 'xyz/')
397
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
398
"""Tests for the behaviour of client_medium.remote_is_at_least."""
400
def test_initially_unlimited(self):
401
"""A fresh medium assumes that the remote side supports all
404
client_medium = medium.SmartClientMedium('dummy base')
405
self.assertFalse(client_medium._is_remote_before((99, 99)))
407
def test__remember_remote_is_before(self):
408
"""Calling _remember_remote_is_before ratchets down the known remote
411
client_medium = medium.SmartClientMedium('dummy base')
412
# Mark the remote side as being less than 1.6. The remote side may
414
client_medium._remember_remote_is_before((1, 6))
415
self.assertTrue(client_medium._is_remote_before((1, 6)))
416
self.assertFalse(client_medium._is_remote_before((1, 5)))
417
# Calling _remember_remote_is_before again with a lower value works.
418
client_medium._remember_remote_is_before((1, 5))
419
self.assertTrue(client_medium._is_remote_before((1, 5)))
420
# You cannot call _remember_remote_is_before with a larger value.
422
AssertionError, client_medium._remember_remote_is_before, (1, 9))
425
class TestBzrDirCloningMetaDir(TestRemote):
427
def test_backwards_compat(self):
428
self.setup_smart_server_with_call_log()
429
a_dir = self.make_bzrdir('.')
430
self.reset_smart_call_log()
431
verb = 'BzrDir.cloning_metadir'
432
self.disable_verb(verb)
433
format = a_dir.cloning_metadir()
434
call_count = len([call for call in self.hpss_calls if
435
call.call.method == verb])
436
self.assertEqual(1, call_count)
438
def test_branch_reference(self):
439
transport = self.get_transport('quack')
440
referenced = self.make_branch('referenced')
441
expected = referenced.bzrdir.cloning_metadir()
442
client = FakeClient(transport.base)
443
client.add_expected_call(
444
'BzrDir.cloning_metadir', ('quack/', 'False'),
445
'error', ('BranchReference',)),
446
client.add_expected_call(
447
'BzrDir.open_branchV3', ('quack/',),
448
'success', ('ref', self.get_url('referenced'))),
449
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
451
result = a_bzrdir.cloning_metadir()
452
# We should have got a control dir matching the referenced branch.
453
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
454
self.assertEqual(expected._repository_format, result._repository_format)
455
self.assertEqual(expected._branch_format, result._branch_format)
456
self.assertFinished(client)
458
def test_current_server(self):
459
transport = self.get_transport('.')
460
transport = transport.clone('quack')
461
self.make_bzrdir('quack')
462
client = FakeClient(transport.base)
463
reference_bzrdir_format = bzrdir.format_registry.get('default')()
464
control_name = reference_bzrdir_format.network_name()
465
client.add_expected_call(
466
'BzrDir.cloning_metadir', ('quack/', 'False'),
467
'success', (control_name, '', ('branch', ''))),
468
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
470
result = a_bzrdir.cloning_metadir()
471
# We should have got a reference control dir with default branch and
472
# repository formats.
473
# This pokes a little, just to be sure.
474
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
475
self.assertEqual(None, result._repository_format)
476
self.assertEqual(None, result._branch_format)
477
self.assertFinished(client)
480
class TestBzrDirOpen(TestRemote):
482
def make_fake_client_and_transport(self, path='quack'):
483
transport = MemoryTransport()
484
transport.mkdir(path)
485
transport = transport.clone(path)
486
client = FakeClient(transport.base)
487
return client, transport
489
def test_absent(self):
490
client, transport = self.make_fake_client_and_transport()
491
client.add_expected_call(
492
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
493
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
494
remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
495
self.assertFinished(client)
497
def test_present_without_workingtree(self):
498
client, transport = self.make_fake_client_and_transport()
499
client.add_expected_call(
500
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
501
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
502
_client=client, _force_probe=True)
503
self.assertIsInstance(bd, RemoteBzrDir)
504
self.assertFalse(bd.has_workingtree())
505
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
506
self.assertFinished(client)
508
def test_present_with_workingtree(self):
509
client, transport = self.make_fake_client_and_transport()
510
client.add_expected_call(
511
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
512
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
513
_client=client, _force_probe=True)
514
self.assertIsInstance(bd, RemoteBzrDir)
515
self.assertTrue(bd.has_workingtree())
516
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
517
self.assertFinished(client)
519
def test_backwards_compat(self):
520
client, transport = self.make_fake_client_and_transport()
521
client.add_expected_call(
522
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
523
client.add_expected_call(
524
'BzrDir.open', ('quack/',), 'success', ('yes',))
525
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
526
_client=client, _force_probe=True)
527
self.assertIsInstance(bd, RemoteBzrDir)
528
self.assertFinished(client)
531
class TestBzrDirOpenBranch(TestRemote):
533
def test_backwards_compat(self):
534
self.setup_smart_server_with_call_log()
535
self.make_branch('.')
536
a_dir = BzrDir.open(self.get_url('.'))
537
self.reset_smart_call_log()
538
verb = 'BzrDir.open_branchV3'
539
self.disable_verb(verb)
540
format = a_dir.open_branch()
541
call_count = len([call for call in self.hpss_calls if
542
call.call.method == verb])
543
self.assertEqual(1, call_count)
545
def test_branch_present(self):
546
reference_format = self.get_repo_format()
547
network_name = reference_format.network_name()
548
branch_network_name = self.get_branch_format().network_name()
549
transport = MemoryTransport()
550
transport.mkdir('quack')
551
transport = transport.clone('quack')
552
client = FakeClient(transport.base)
553
client.add_expected_call(
554
'BzrDir.open_branchV3', ('quack/',),
555
'success', ('branch', branch_network_name))
556
client.add_expected_call(
557
'BzrDir.find_repositoryV3', ('quack/',),
558
'success', ('ok', '', 'no', 'no', 'no', network_name))
559
client.add_expected_call(
560
'Branch.get_stacked_on_url', ('quack/',),
561
'error', ('NotStacked',))
562
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
564
result = bzrdir.open_branch()
565
self.assertIsInstance(result, RemoteBranch)
566
self.assertEqual(bzrdir, result.bzrdir)
567
self.assertFinished(client)
569
def test_branch_missing(self):
570
transport = MemoryTransport()
571
transport.mkdir('quack')
572
transport = transport.clone('quack')
573
client = FakeClient(transport.base)
574
client.add_error_response('nobranch')
575
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
577
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
579
[('call', 'BzrDir.open_branchV3', ('quack/',))],
582
def test__get_tree_branch(self):
583
# _get_tree_branch is a form of open_branch, but it should only ask for
584
# branch opening, not any other network requests.
587
calls.append("Called")
589
transport = MemoryTransport()
590
# no requests on the network - catches other api calls being made.
591
client = FakeClient(transport.base)
592
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
594
# patch the open_branch call to record that it was called.
595
bzrdir.open_branch = open_branch
596
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
597
self.assertEqual(["Called"], calls)
598
self.assertEqual([], client._calls)
600
def test_url_quoting_of_path(self):
601
# Relpaths on the wire should not be URL-escaped. So "~" should be
602
# transmitted as "~", not "%7E".
603
transport = RemoteTCPTransport('bzr://localhost/~hello/')
604
client = FakeClient(transport.base)
605
reference_format = self.get_repo_format()
606
network_name = reference_format.network_name()
607
branch_network_name = self.get_branch_format().network_name()
608
client.add_expected_call(
609
'BzrDir.open_branchV3', ('~hello/',),
610
'success', ('branch', branch_network_name))
611
client.add_expected_call(
612
'BzrDir.find_repositoryV3', ('~hello/',),
613
'success', ('ok', '', 'no', 'no', 'no', network_name))
614
client.add_expected_call(
615
'Branch.get_stacked_on_url', ('~hello/',),
616
'error', ('NotStacked',))
617
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
619
result = bzrdir.open_branch()
620
self.assertFinished(client)
622
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
623
reference_format = self.get_repo_format()
624
network_name = reference_format.network_name()
625
transport = MemoryTransport()
626
transport.mkdir('quack')
627
transport = transport.clone('quack')
629
rich_response = 'yes'
633
subtree_response = 'yes'
635
subtree_response = 'no'
636
client = FakeClient(transport.base)
637
client.add_success_response(
638
'ok', '', rich_response, subtree_response, external_lookup,
640
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
642
result = bzrdir.open_repository()
644
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
646
self.assertIsInstance(result, RemoteRepository)
647
self.assertEqual(bzrdir, result.bzrdir)
648
self.assertEqual(rich_root, result._format.rich_root_data)
649
self.assertEqual(subtrees, result._format.supports_tree_reference)
651
def test_open_repository_sets_format_attributes(self):
652
self.check_open_repository(True, True)
653
self.check_open_repository(False, True)
654
self.check_open_repository(True, False)
655
self.check_open_repository(False, False)
656
self.check_open_repository(False, False, 'yes')
658
def test_old_server(self):
659
"""RemoteBzrDirFormat should fail to probe if the server version is too
662
self.assertRaises(errors.NotBranchError,
663
RemoteBzrDirFormat.probe_transport, OldServerTransport())
666
class TestBzrDirCreateBranch(TestRemote):
668
def test_backwards_compat(self):
669
self.setup_smart_server_with_call_log()
670
repo = self.make_repository('.')
671
self.reset_smart_call_log()
672
self.disable_verb('BzrDir.create_branch')
673
branch = repo.bzrdir.create_branch()
674
create_branch_call_count = len([call for call in self.hpss_calls if
675
call.call.method == 'BzrDir.create_branch'])
676
self.assertEqual(1, create_branch_call_count)
678
def test_current_server(self):
679
transport = self.get_transport('.')
680
transport = transport.clone('quack')
681
self.make_repository('quack')
682
client = FakeClient(transport.base)
683
reference_bzrdir_format = bzrdir.format_registry.get('default')()
684
reference_format = reference_bzrdir_format.get_branch_format()
685
network_name = reference_format.network_name()
686
reference_repo_fmt = reference_bzrdir_format.repository_format
687
reference_repo_name = reference_repo_fmt.network_name()
688
client.add_expected_call(
689
'BzrDir.create_branch', ('quack/', network_name),
690
'success', ('ok', network_name, '', 'no', 'no', 'yes',
691
reference_repo_name))
692
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
694
branch = a_bzrdir.create_branch()
695
# We should have got a remote branch
696
self.assertIsInstance(branch, remote.RemoteBranch)
697
# its format should have the settings from the response
698
format = branch._format
699
self.assertEqual(network_name, format.network_name())
702
class TestBzrDirCreateRepository(TestRemote):
704
def test_backwards_compat(self):
705
self.setup_smart_server_with_call_log()
706
bzrdir = self.make_bzrdir('.')
707
self.reset_smart_call_log()
708
self.disable_verb('BzrDir.create_repository')
709
repo = bzrdir.create_repository()
710
create_repo_call_count = len([call for call in self.hpss_calls if
711
call.call.method == 'BzrDir.create_repository'])
712
self.assertEqual(1, create_repo_call_count)
714
def test_current_server(self):
715
transport = self.get_transport('.')
716
transport = transport.clone('quack')
717
self.make_bzrdir('quack')
718
client = FakeClient(transport.base)
719
reference_bzrdir_format = bzrdir.format_registry.get('default')()
720
reference_format = reference_bzrdir_format.repository_format
721
network_name = reference_format.network_name()
722
client.add_expected_call(
723
'BzrDir.create_repository', ('quack/',
724
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
726
'success', ('ok', 'yes', 'yes', 'yes', network_name))
727
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
729
repo = a_bzrdir.create_repository()
730
# We should have got a remote repository
731
self.assertIsInstance(repo, remote.RemoteRepository)
732
# its format should have the settings from the response
733
format = repo._format
734
self.assertTrue(format.rich_root_data)
735
self.assertTrue(format.supports_tree_reference)
736
self.assertTrue(format.supports_external_lookups)
737
self.assertEqual(network_name, format.network_name())
740
class TestBzrDirOpenRepository(TestRemote):
742
def test_backwards_compat_1_2_3(self):
743
# fallback all the way to the first version.
744
reference_format = self.get_repo_format()
745
network_name = reference_format.network_name()
746
server_url = 'bzr://example.com/'
747
self.permit_url(server_url)
748
client = FakeClient(server_url)
749
client.add_unknown_method_response('BzrDir.find_repositoryV3')
750
client.add_unknown_method_response('BzrDir.find_repositoryV2')
751
client.add_success_response('ok', '', 'no', 'no')
752
# A real repository instance will be created to determine the network
754
client.add_success_response_with_body(
755
"Bazaar-NG meta directory, format 1\n", 'ok')
756
client.add_success_response_with_body(
757
reference_format.get_format_string(), 'ok')
758
# PackRepository wants to do a stat
759
client.add_success_response('stat', '0', '65535')
760
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
762
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
764
repo = bzrdir.open_repository()
766
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
767
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
768
('call', 'BzrDir.find_repository', ('quack/',)),
769
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
770
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
771
('call', 'stat', ('/quack/.bzr/repository',)),
774
self.assertEqual(network_name, repo._format.network_name())
776
def test_backwards_compat_2(self):
777
# fallback to find_repositoryV2
778
reference_format = self.get_repo_format()
779
network_name = reference_format.network_name()
780
server_url = 'bzr://example.com/'
781
self.permit_url(server_url)
782
client = FakeClient(server_url)
783
client.add_unknown_method_response('BzrDir.find_repositoryV3')
784
client.add_success_response('ok', '', 'no', 'no', 'no')
785
# A real repository instance will be created to determine the network
787
client.add_success_response_with_body(
788
"Bazaar-NG meta directory, format 1\n", 'ok')
789
client.add_success_response_with_body(
790
reference_format.get_format_string(), 'ok')
791
# PackRepository wants to do a stat
792
client.add_success_response('stat', '0', '65535')
793
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
795
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
797
repo = bzrdir.open_repository()
799
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
800
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
801
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
802
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
803
('call', 'stat', ('/quack/.bzr/repository',)),
806
self.assertEqual(network_name, repo._format.network_name())
808
def test_current_server(self):
809
reference_format = self.get_repo_format()
810
network_name = reference_format.network_name()
811
transport = MemoryTransport()
812
transport.mkdir('quack')
813
transport = transport.clone('quack')
814
client = FakeClient(transport.base)
815
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
816
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
818
repo = bzrdir.open_repository()
820
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
822
self.assertEqual(network_name, repo._format.network_name())
825
class TestBzrDirFormatInitializeEx(TestRemote):
827
def test_success(self):
828
"""Simple test for typical successful call."""
829
fmt = bzrdir.RemoteBzrDirFormat()
830
default_format_name = BzrDirFormat.get_default_format().network_name()
831
transport = self.get_transport()
832
client = FakeClient(transport.base)
833
client.add_expected_call(
834
'BzrDirFormat.initialize_ex_1.16',
835
(default_format_name, 'path', 'False', 'False', 'False', '',
836
'', '', '', 'False'),
838
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
839
'bzrdir fmt', 'False', '', '', 'repo lock token'))
840
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
841
# it's currently hard to test that without supplying a real remote
842
# transport connected to a real server.
843
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
844
transport, False, False, False, None, None, None, None, False)
845
self.assertFinished(client)
847
def test_error(self):
848
"""Error responses are translated, e.g. 'PermissionDenied' raises the
849
corresponding error from the client.
851
fmt = bzrdir.RemoteBzrDirFormat()
852
default_format_name = BzrDirFormat.get_default_format().network_name()
853
transport = self.get_transport()
854
client = FakeClient(transport.base)
855
client.add_expected_call(
856
'BzrDirFormat.initialize_ex_1.16',
857
(default_format_name, 'path', 'False', 'False', 'False', '',
858
'', '', '', 'False'),
860
('PermissionDenied', 'path', 'extra info'))
861
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
862
# it's currently hard to test that without supplying a real remote
863
# transport connected to a real server.
864
err = self.assertRaises(errors.PermissionDenied,
865
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
866
False, False, False, None, None, None, None, False)
867
self.assertEqual('path', err.path)
868
self.assertEqual(': extra info', err.extra)
869
self.assertFinished(client)
871
def test_error_from_real_server(self):
872
"""Integration test for error translation."""
873
transport = self.make_smart_server('foo')
874
transport = transport.clone('no-such-path')
875
fmt = bzrdir.RemoteBzrDirFormat()
876
err = self.assertRaises(errors.NoSuchFile,
877
fmt.initialize_on_transport_ex, transport, create_prefix=False)
880
class OldSmartClient(object):
881
"""A fake smart client for test_old_version that just returns a version one
882
response to the 'hello' (query version) command.
885
def get_request(self):
886
input_file = StringIO('ok\x011\n')
887
output_file = StringIO()
888
client_medium = medium.SmartSimplePipesClientMedium(
889
input_file, output_file)
890
return medium.SmartClientStreamMediumRequest(client_medium)
892
def protocol_version(self):
896
class OldServerTransport(object):
897
"""A fake transport for test_old_server that reports it's smart server
898
protocol version as version one.
904
def get_smart_client(self):
905
return OldSmartClient()
908
class RemoteBzrDirTestCase(TestRemote):
910
def make_remote_bzrdir(self, transport, client):
911
"""Make a RemotebzrDir using 'client' as the _client."""
912
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
916
class RemoteBranchTestCase(RemoteBzrDirTestCase):
918
def lock_remote_branch(self, branch):
919
"""Trick a RemoteBranch into thinking it is locked."""
920
branch._lock_mode = 'w'
921
branch._lock_count = 2
922
branch._lock_token = 'branch token'
923
branch._repo_lock_token = 'repo token'
924
branch.repository._lock_mode = 'w'
925
branch.repository._lock_count = 2
926
branch.repository._lock_token = 'repo token'
928
def make_remote_branch(self, transport, client):
929
"""Make a RemoteBranch using 'client' as its _SmartClient.
931
A RemoteBzrDir and RemoteRepository will also be created to fill out
932
the RemoteBranch, albeit with stub values for some of their attributes.
934
# we do not want bzrdir to make any remote calls, so use False as its
935
# _client. If it tries to make a remote call, this will fail
937
bzrdir = self.make_remote_bzrdir(transport, False)
938
repo = RemoteRepository(bzrdir, None, _client=client)
939
branch_format = self.get_branch_format()
940
format = RemoteBranchFormat(network_name=branch_format.network_name())
941
return RemoteBranch(bzrdir, repo, _client=client, format=format)
944
class TestBranchGetParent(RemoteBranchTestCase):
946
def test_no_parent(self):
947
# in an empty branch we decode the response properly
948
transport = MemoryTransport()
949
client = FakeClient(transport.base)
950
client.add_expected_call(
951
'Branch.get_stacked_on_url', ('quack/',),
952
'error', ('NotStacked',))
953
client.add_expected_call(
954
'Branch.get_parent', ('quack/',),
956
transport.mkdir('quack')
957
transport = transport.clone('quack')
958
branch = self.make_remote_branch(transport, client)
959
result = branch.get_parent()
960
self.assertFinished(client)
961
self.assertEqual(None, result)
963
def test_parent_relative(self):
964
transport = MemoryTransport()
965
client = FakeClient(transport.base)
966
client.add_expected_call(
967
'Branch.get_stacked_on_url', ('kwaak/',),
968
'error', ('NotStacked',))
969
client.add_expected_call(
970
'Branch.get_parent', ('kwaak/',),
971
'success', ('../foo/',))
972
transport.mkdir('kwaak')
973
transport = transport.clone('kwaak')
974
branch = self.make_remote_branch(transport, client)
975
result = branch.get_parent()
976
self.assertEqual(transport.clone('../foo').base, result)
978
def test_parent_absolute(self):
979
transport = MemoryTransport()
980
client = FakeClient(transport.base)
981
client.add_expected_call(
982
'Branch.get_stacked_on_url', ('kwaak/',),
983
'error', ('NotStacked',))
984
client.add_expected_call(
985
'Branch.get_parent', ('kwaak/',),
986
'success', ('http://foo/',))
987
transport.mkdir('kwaak')
988
transport = transport.clone('kwaak')
989
branch = self.make_remote_branch(transport, client)
990
result = branch.get_parent()
991
self.assertEqual('http://foo/', result)
992
self.assertFinished(client)
995
class TestBranchSetParentLocation(RemoteBranchTestCase):
997
def test_no_parent(self):
998
# We call the verb when setting parent to None
999
transport = MemoryTransport()
1000
client = FakeClient(transport.base)
1001
client.add_expected_call(
1002
'Branch.get_stacked_on_url', ('quack/',),
1003
'error', ('NotStacked',))
1004
client.add_expected_call(
1005
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1007
transport.mkdir('quack')
1008
transport = transport.clone('quack')
1009
branch = self.make_remote_branch(transport, client)
1010
branch._lock_token = 'b'
1011
branch._repo_lock_token = 'r'
1012
branch._set_parent_location(None)
1013
self.assertFinished(client)
1015
def test_parent(self):
1016
transport = MemoryTransport()
1017
client = FakeClient(transport.base)
1018
client.add_expected_call(
1019
'Branch.get_stacked_on_url', ('kwaak/',),
1020
'error', ('NotStacked',))
1021
client.add_expected_call(
1022
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1024
transport.mkdir('kwaak')
1025
transport = transport.clone('kwaak')
1026
branch = self.make_remote_branch(transport, client)
1027
branch._lock_token = 'b'
1028
branch._repo_lock_token = 'r'
1029
branch._set_parent_location('foo')
1030
self.assertFinished(client)
1032
def test_backwards_compat(self):
1033
self.setup_smart_server_with_call_log()
1034
branch = self.make_branch('.')
1035
self.reset_smart_call_log()
1036
verb = 'Branch.set_parent_location'
1037
self.disable_verb(verb)
1038
branch.set_parent('http://foo/')
1039
self.assertLength(12, self.hpss_calls)
1042
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1044
def test_backwards_compat(self):
1045
self.setup_smart_server_with_call_log()
1046
branch = self.make_branch('.')
1047
self.reset_smart_call_log()
1048
verb = 'Branch.get_tags_bytes'
1049
self.disable_verb(verb)
1050
branch.tags.get_tag_dict()
1051
call_count = len([call for call in self.hpss_calls if
1052
call.call.method == verb])
1053
self.assertEqual(1, call_count)
1055
def test_trivial(self):
1056
transport = MemoryTransport()
1057
client = FakeClient(transport.base)
1058
client.add_expected_call(
1059
'Branch.get_stacked_on_url', ('quack/',),
1060
'error', ('NotStacked',))
1061
client.add_expected_call(
1062
'Branch.get_tags_bytes', ('quack/',),
1064
transport.mkdir('quack')
1065
transport = transport.clone('quack')
1066
branch = self.make_remote_branch(transport, client)
1067
result = branch.tags.get_tag_dict()
1068
self.assertFinished(client)
1069
self.assertEqual({}, result)
1072
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1074
def test_trivial(self):
1075
transport = MemoryTransport()
1076
client = FakeClient(transport.base)
1077
client.add_expected_call(
1078
'Branch.get_stacked_on_url', ('quack/',),
1079
'error', ('NotStacked',))
1080
client.add_expected_call(
1081
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1083
transport.mkdir('quack')
1084
transport = transport.clone('quack')
1085
branch = self.make_remote_branch(transport, client)
1086
self.lock_remote_branch(branch)
1087
branch._set_tags_bytes('tags bytes')
1088
self.assertFinished(client)
1089
self.assertEqual('tags bytes', client._calls[-1][-1])
1091
def test_backwards_compatible(self):
1092
transport = MemoryTransport()
1093
client = FakeClient(transport.base)
1094
client.add_expected_call(
1095
'Branch.get_stacked_on_url', ('quack/',),
1096
'error', ('NotStacked',))
1097
client.add_expected_call(
1098
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1099
'unknown', ('Branch.set_tags_bytes',))
1100
transport.mkdir('quack')
1101
transport = transport.clone('quack')
1102
branch = self.make_remote_branch(transport, client)
1103
self.lock_remote_branch(branch)
1104
class StubRealBranch(object):
1107
def _set_tags_bytes(self, bytes):
1108
self.calls.append(('set_tags_bytes', bytes))
1109
real_branch = StubRealBranch()
1110
branch._real_branch = real_branch
1111
branch._set_tags_bytes('tags bytes')
1112
# Call a second time, to exercise the 'remote version already inferred'
1114
branch._set_tags_bytes('tags bytes')
1115
self.assertFinished(client)
1117
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1120
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1122
def test_empty_branch(self):
1123
# in an empty branch we decode the response properly
1124
transport = MemoryTransport()
1125
client = FakeClient(transport.base)
1126
client.add_expected_call(
1127
'Branch.get_stacked_on_url', ('quack/',),
1128
'error', ('NotStacked',))
1129
client.add_expected_call(
1130
'Branch.last_revision_info', ('quack/',),
1131
'success', ('ok', '0', 'null:'))
1132
transport.mkdir('quack')
1133
transport = transport.clone('quack')
1134
branch = self.make_remote_branch(transport, client)
1135
result = branch.last_revision_info()
1136
self.assertFinished(client)
1137
self.assertEqual((0, NULL_REVISION), result)
1139
def test_non_empty_branch(self):
1140
# in a non-empty branch we also decode the response properly
1141
revid = u'\xc8'.encode('utf8')
1142
transport = MemoryTransport()
1143
client = FakeClient(transport.base)
1144
client.add_expected_call(
1145
'Branch.get_stacked_on_url', ('kwaak/',),
1146
'error', ('NotStacked',))
1147
client.add_expected_call(
1148
'Branch.last_revision_info', ('kwaak/',),
1149
'success', ('ok', '2', revid))
1150
transport.mkdir('kwaak')
1151
transport = transport.clone('kwaak')
1152
branch = self.make_remote_branch(transport, client)
1153
result = branch.last_revision_info()
1154
self.assertEqual((2, revid), result)
1157
class TestBranch_get_stacked_on_url(TestRemote):
1158
"""Test Branch._get_stacked_on_url rpc"""
1160
def test_get_stacked_on_invalid_url(self):
1161
# test that asking for a stacked on url the server can't access works.
1162
# This isn't perfect, but then as we're in the same process there
1163
# really isn't anything we can do to be 100% sure that the server
1164
# doesn't just open in - this test probably needs to be rewritten using
1165
# a spawn()ed server.
1166
stacked_branch = self.make_branch('stacked', format='1.9')
1167
memory_branch = self.make_branch('base', format='1.9')
1168
vfs_url = self.get_vfs_only_url('base')
1169
stacked_branch.set_stacked_on_url(vfs_url)
1170
transport = stacked_branch.bzrdir.root_transport
1171
client = FakeClient(transport.base)
1172
client.add_expected_call(
1173
'Branch.get_stacked_on_url', ('stacked/',),
1174
'success', ('ok', vfs_url))
1175
# XXX: Multiple calls are bad, this second call documents what is
1177
client.add_expected_call(
1178
'Branch.get_stacked_on_url', ('stacked/',),
1179
'success', ('ok', vfs_url))
1180
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1182
repo_fmt = remote.RemoteRepositoryFormat()
1183
repo_fmt._custom_format = stacked_branch.repository._format
1184
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1186
result = branch.get_stacked_on_url()
1187
self.assertEqual(vfs_url, result)
1189
def test_backwards_compatible(self):
1190
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1191
base_branch = self.make_branch('base', format='1.6')
1192
stacked_branch = self.make_branch('stacked', format='1.6')
1193
stacked_branch.set_stacked_on_url('../base')
1194
client = FakeClient(self.get_url())
1195
branch_network_name = self.get_branch_format().network_name()
1196
client.add_expected_call(
1197
'BzrDir.open_branchV3', ('stacked/',),
1198
'success', ('branch', branch_network_name))
1199
client.add_expected_call(
1200
'BzrDir.find_repositoryV3', ('stacked/',),
1201
'success', ('ok', '', 'no', 'no', 'yes',
1202
stacked_branch.repository._format.network_name()))
1203
# called twice, once from constructor and then again by us
1204
client.add_expected_call(
1205
'Branch.get_stacked_on_url', ('stacked/',),
1206
'unknown', ('Branch.get_stacked_on_url',))
1207
client.add_expected_call(
1208
'Branch.get_stacked_on_url', ('stacked/',),
1209
'unknown', ('Branch.get_stacked_on_url',))
1210
# this will also do vfs access, but that goes direct to the transport
1211
# and isn't seen by the FakeClient.
1212
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1213
remote.RemoteBzrDirFormat(), _client=client)
1214
branch = bzrdir.open_branch()
1215
result = branch.get_stacked_on_url()
1216
self.assertEqual('../base', result)
1217
self.assertFinished(client)
1218
# it's in the fallback list both for the RemoteRepository and its vfs
1220
self.assertEqual(1, len(branch.repository._fallback_repositories))
1222
len(branch.repository._real_repository._fallback_repositories))
1224
def test_get_stacked_on_real_branch(self):
1225
base_branch = self.make_branch('base', format='1.6')
1226
stacked_branch = self.make_branch('stacked', format='1.6')
1227
stacked_branch.set_stacked_on_url('../base')
1228
reference_format = self.get_repo_format()
1229
network_name = reference_format.network_name()
1230
client = FakeClient(self.get_url())
1231
branch_network_name = self.get_branch_format().network_name()
1232
client.add_expected_call(
1233
'BzrDir.open_branchV3', ('stacked/',),
1234
'success', ('branch', branch_network_name))
1235
client.add_expected_call(
1236
'BzrDir.find_repositoryV3', ('stacked/',),
1237
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1238
# called twice, once from constructor and then again by us
1239
client.add_expected_call(
1240
'Branch.get_stacked_on_url', ('stacked/',),
1241
'success', ('ok', '../base'))
1242
client.add_expected_call(
1243
'Branch.get_stacked_on_url', ('stacked/',),
1244
'success', ('ok', '../base'))
1245
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1246
remote.RemoteBzrDirFormat(), _client=client)
1247
branch = bzrdir.open_branch()
1248
result = branch.get_stacked_on_url()
1249
self.assertEqual('../base', result)
1250
self.assertFinished(client)
1251
# it's in the fallback list both for the RemoteRepository.
1252
self.assertEqual(1, len(branch.repository._fallback_repositories))
1253
# And we haven't had to construct a real repository.
1254
self.assertEqual(None, branch.repository._real_repository)
1257
class TestBranchSetLastRevision(RemoteBranchTestCase):
1259
def test_set_empty(self):
1260
# set_revision_history([]) is translated to calling
1261
# Branch.set_last_revision(path, '') on the wire.
1262
transport = MemoryTransport()
1263
transport.mkdir('branch')
1264
transport = transport.clone('branch')
1266
client = FakeClient(transport.base)
1267
client.add_expected_call(
1268
'Branch.get_stacked_on_url', ('branch/',),
1269
'error', ('NotStacked',))
1270
client.add_expected_call(
1271
'Branch.lock_write', ('branch/', '', ''),
1272
'success', ('ok', 'branch token', 'repo token'))
1273
client.add_expected_call(
1274
'Branch.last_revision_info',
1276
'success', ('ok', '0', 'null:'))
1277
client.add_expected_call(
1278
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1280
client.add_expected_call(
1281
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1283
branch = self.make_remote_branch(transport, client)
1284
# This is a hack to work around the problem that RemoteBranch currently
1285
# unnecessarily invokes _ensure_real upon a call to lock_write.
1286
branch._ensure_real = lambda: None
1288
result = branch.set_revision_history([])
1290
self.assertEqual(None, result)
1291
self.assertFinished(client)
1293
def test_set_nonempty(self):
1294
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1295
# Branch.set_last_revision(path, rev-idN) on the wire.
1296
transport = MemoryTransport()
1297
transport.mkdir('branch')
1298
transport = transport.clone('branch')
1300
client = FakeClient(transport.base)
1301
client.add_expected_call(
1302
'Branch.get_stacked_on_url', ('branch/',),
1303
'error', ('NotStacked',))
1304
client.add_expected_call(
1305
'Branch.lock_write', ('branch/', '', ''),
1306
'success', ('ok', 'branch token', 'repo token'))
1307
client.add_expected_call(
1308
'Branch.last_revision_info',
1310
'success', ('ok', '0', 'null:'))
1312
encoded_body = bz2.compress('\n'.join(lines))
1313
client.add_success_response_with_body(encoded_body, 'ok')
1314
client.add_expected_call(
1315
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1317
client.add_expected_call(
1318
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1320
branch = self.make_remote_branch(transport, client)
1321
# This is a hack to work around the problem that RemoteBranch currently
1322
# unnecessarily invokes _ensure_real upon a call to lock_write.
1323
branch._ensure_real = lambda: None
1324
# Lock the branch, reset the record of remote calls.
1326
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1328
self.assertEqual(None, result)
1329
self.assertFinished(client)
1331
def test_no_such_revision(self):
1332
transport = MemoryTransport()
1333
transport.mkdir('branch')
1334
transport = transport.clone('branch')
1335
# A response of 'NoSuchRevision' is translated into an exception.
1336
client = FakeClient(transport.base)
1337
client.add_expected_call(
1338
'Branch.get_stacked_on_url', ('branch/',),
1339
'error', ('NotStacked',))
1340
client.add_expected_call(
1341
'Branch.lock_write', ('branch/', '', ''),
1342
'success', ('ok', 'branch token', 'repo token'))
1343
client.add_expected_call(
1344
'Branch.last_revision_info',
1346
'success', ('ok', '0', 'null:'))
1347
# get_graph calls to construct the revision history, for the set_rh
1350
encoded_body = bz2.compress('\n'.join(lines))
1351
client.add_success_response_with_body(encoded_body, 'ok')
1352
client.add_expected_call(
1353
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1354
'error', ('NoSuchRevision', 'rev-id'))
1355
client.add_expected_call(
1356
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1359
branch = self.make_remote_branch(transport, client)
1362
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1364
self.assertFinished(client)
1366
def test_tip_change_rejected(self):
1367
"""TipChangeRejected responses cause a TipChangeRejected exception to
1370
transport = MemoryTransport()
1371
transport.mkdir('branch')
1372
transport = transport.clone('branch')
1373
client = FakeClient(transport.base)
1374
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1375
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1376
client.add_expected_call(
1377
'Branch.get_stacked_on_url', ('branch/',),
1378
'error', ('NotStacked',))
1379
client.add_expected_call(
1380
'Branch.lock_write', ('branch/', '', ''),
1381
'success', ('ok', 'branch token', 'repo token'))
1382
client.add_expected_call(
1383
'Branch.last_revision_info',
1385
'success', ('ok', '0', 'null:'))
1387
encoded_body = bz2.compress('\n'.join(lines))
1388
client.add_success_response_with_body(encoded_body, 'ok')
1389
client.add_expected_call(
1390
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1391
'error', ('TipChangeRejected', rejection_msg_utf8))
1392
client.add_expected_call(
1393
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1395
branch = self.make_remote_branch(transport, client)
1396
branch._ensure_real = lambda: None
1398
# The 'TipChangeRejected' error response triggered by calling
1399
# set_revision_history causes a TipChangeRejected exception.
1400
err = self.assertRaises(
1401
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1402
# The UTF-8 message from the response has been decoded into a unicode
1404
self.assertIsInstance(err.msg, unicode)
1405
self.assertEqual(rejection_msg_unicode, err.msg)
1407
self.assertFinished(client)
1410
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1412
def test_set_last_revision_info(self):
1413
# set_last_revision_info(num, 'rev-id') is translated to calling
1414
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1415
transport = MemoryTransport()
1416
transport.mkdir('branch')
1417
transport = transport.clone('branch')
1418
client = FakeClient(transport.base)
1419
# get_stacked_on_url
1420
client.add_error_response('NotStacked')
1422
client.add_success_response('ok', 'branch token', 'repo token')
1423
# query the current revision
1424
client.add_success_response('ok', '0', 'null:')
1426
client.add_success_response('ok')
1428
client.add_success_response('ok')
1430
branch = self.make_remote_branch(transport, client)
1431
# Lock the branch, reset the record of remote calls.
1434
result = branch.set_last_revision_info(1234, 'a-revision-id')
1436
[('call', 'Branch.last_revision_info', ('branch/',)),
1437
('call', 'Branch.set_last_revision_info',
1438
('branch/', 'branch token', 'repo token',
1439
'1234', 'a-revision-id'))],
1441
self.assertEqual(None, result)
1443
def test_no_such_revision(self):
1444
# A response of 'NoSuchRevision' is translated into an exception.
1445
transport = MemoryTransport()
1446
transport.mkdir('branch')
1447
transport = transport.clone('branch')
1448
client = FakeClient(transport.base)
1449
# get_stacked_on_url
1450
client.add_error_response('NotStacked')
1452
client.add_success_response('ok', 'branch token', 'repo token')
1454
client.add_error_response('NoSuchRevision', 'revid')
1456
client.add_success_response('ok')
1458
branch = self.make_remote_branch(transport, client)
1459
# Lock the branch, reset the record of remote calls.
1464
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1467
def test_backwards_compatibility(self):
1468
"""If the server does not support the Branch.set_last_revision_info
1469
verb (which is new in 1.4), then the client falls back to VFS methods.
1471
# This test is a little messy. Unlike most tests in this file, it
1472
# doesn't purely test what a Remote* object sends over the wire, and
1473
# how it reacts to responses from the wire. It instead relies partly
1474
# on asserting that the RemoteBranch will call
1475
# self._real_branch.set_last_revision_info(...).
1477
# First, set up our RemoteBranch with a FakeClient that raises
1478
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1479
transport = MemoryTransport()
1480
transport.mkdir('branch')
1481
transport = transport.clone('branch')
1482
client = FakeClient(transport.base)
1483
client.add_expected_call(
1484
'Branch.get_stacked_on_url', ('branch/',),
1485
'error', ('NotStacked',))
1486
client.add_expected_call(
1487
'Branch.last_revision_info',
1489
'success', ('ok', '0', 'null:'))
1490
client.add_expected_call(
1491
'Branch.set_last_revision_info',
1492
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1493
'unknown', 'Branch.set_last_revision_info')
1495
branch = self.make_remote_branch(transport, client)
1496
class StubRealBranch(object):
1499
def set_last_revision_info(self, revno, revision_id):
1501
('set_last_revision_info', revno, revision_id))
1502
def _clear_cached_state(self):
1504
real_branch = StubRealBranch()
1505
branch._real_branch = real_branch
1506
self.lock_remote_branch(branch)
1508
# Call set_last_revision_info, and verify it behaved as expected.
1509
result = branch.set_last_revision_info(1234, 'a-revision-id')
1511
[('set_last_revision_info', 1234, 'a-revision-id')],
1513
self.assertFinished(client)
1515
def test_unexpected_error(self):
1516
# If the server sends an error the client doesn't understand, it gets
1517
# turned into an UnknownErrorFromSmartServer, which is presented as a
1518
# non-internal error to the user.
1519
transport = MemoryTransport()
1520
transport.mkdir('branch')
1521
transport = transport.clone('branch')
1522
client = FakeClient(transport.base)
1523
# get_stacked_on_url
1524
client.add_error_response('NotStacked')
1526
client.add_success_response('ok', 'branch token', 'repo token')
1528
client.add_error_response('UnexpectedError')
1530
client.add_success_response('ok')
1532
branch = self.make_remote_branch(transport, client)
1533
# Lock the branch, reset the record of remote calls.
1537
err = self.assertRaises(
1538
errors.UnknownErrorFromSmartServer,
1539
branch.set_last_revision_info, 123, 'revid')
1540
self.assertEqual(('UnexpectedError',), err.error_tuple)
1543
def test_tip_change_rejected(self):
1544
"""TipChangeRejected responses cause a TipChangeRejected exception to
1547
transport = MemoryTransport()
1548
transport.mkdir('branch')
1549
transport = transport.clone('branch')
1550
client = FakeClient(transport.base)
1551
# get_stacked_on_url
1552
client.add_error_response('NotStacked')
1554
client.add_success_response('ok', 'branch token', 'repo token')
1556
client.add_error_response('TipChangeRejected', 'rejection message')
1558
client.add_success_response('ok')
1560
branch = self.make_remote_branch(transport, client)
1561
# Lock the branch, reset the record of remote calls.
1563
self.addCleanup(branch.unlock)
1566
# The 'TipChangeRejected' error response triggered by calling
1567
# set_last_revision_info causes a TipChangeRejected exception.
1568
err = self.assertRaises(
1569
errors.TipChangeRejected,
1570
branch.set_last_revision_info, 123, 'revid')
1571
self.assertEqual('rejection message', err.msg)
1574
class TestBranchGetSetConfig(RemoteBranchTestCase):
1576
def test_get_branch_conf(self):
1577
# in an empty branch we decode the response properly
1578
client = FakeClient()
1579
client.add_expected_call(
1580
'Branch.get_stacked_on_url', ('memory:///',),
1581
'error', ('NotStacked',),)
1582
client.add_success_response_with_body('# config file body', 'ok')
1583
transport = MemoryTransport()
1584
branch = self.make_remote_branch(transport, client)
1585
config = branch.get_config()
1586
config.has_explicit_nickname()
1588
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1589
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1592
def test_get_multi_line_branch_conf(self):
1593
# Make sure that multiple-line branch.conf files are supported
1595
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1596
client = FakeClient()
1597
client.add_expected_call(
1598
'Branch.get_stacked_on_url', ('memory:///',),
1599
'error', ('NotStacked',),)
1600
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1601
transport = MemoryTransport()
1602
branch = self.make_remote_branch(transport, client)
1603
config = branch.get_config()
1604
self.assertEqual(u'2', config.get_user_option('b'))
1606
def test_set_option(self):
1607
client = FakeClient()
1608
client.add_expected_call(
1609
'Branch.get_stacked_on_url', ('memory:///',),
1610
'error', ('NotStacked',),)
1611
client.add_expected_call(
1612
'Branch.lock_write', ('memory:///', '', ''),
1613
'success', ('ok', 'branch token', 'repo token'))
1614
client.add_expected_call(
1615
'Branch.set_config_option', ('memory:///', 'branch token',
1616
'repo token', 'foo', 'bar', ''),
1618
client.add_expected_call(
1619
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1621
transport = MemoryTransport()
1622
branch = self.make_remote_branch(transport, client)
1624
config = branch._get_config()
1625
config.set_option('foo', 'bar')
1627
self.assertFinished(client)
1629
def test_backwards_compat_set_option(self):
1630
self.setup_smart_server_with_call_log()
1631
branch = self.make_branch('.')
1632
verb = 'Branch.set_config_option'
1633
self.disable_verb(verb)
1635
self.addCleanup(branch.unlock)
1636
self.reset_smart_call_log()
1637
branch._get_config().set_option('value', 'name')
1638
self.assertLength(10, self.hpss_calls)
1639
self.assertEqual('value', branch._get_config().get_option('name'))
1642
class TestBranchLockWrite(RemoteBranchTestCase):
1644
def test_lock_write_unlockable(self):
1645
transport = MemoryTransport()
1646
client = FakeClient(transport.base)
1647
client.add_expected_call(
1648
'Branch.get_stacked_on_url', ('quack/',),
1649
'error', ('NotStacked',),)
1650
client.add_expected_call(
1651
'Branch.lock_write', ('quack/', '', ''),
1652
'error', ('UnlockableTransport',))
1653
transport.mkdir('quack')
1654
transport = transport.clone('quack')
1655
branch = self.make_remote_branch(transport, client)
1656
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1657
self.assertFinished(client)
1660
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1662
def test__get_config(self):
1663
client = FakeClient()
1664
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1665
transport = MemoryTransport()
1666
bzrdir = self.make_remote_bzrdir(transport, client)
1667
config = bzrdir.get_config()
1668
self.assertEqual('/', config.get_default_stack_on())
1670
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1673
def test_set_option_uses_vfs(self):
1674
self.setup_smart_server_with_call_log()
1675
bzrdir = self.make_bzrdir('.')
1676
self.reset_smart_call_log()
1677
config = bzrdir.get_config()
1678
config.set_default_stack_on('/')
1679
self.assertLength(3, self.hpss_calls)
1681
def test_backwards_compat_get_option(self):
1682
self.setup_smart_server_with_call_log()
1683
bzrdir = self.make_bzrdir('.')
1684
verb = 'BzrDir.get_config_file'
1685
self.disable_verb(verb)
1686
self.reset_smart_call_log()
1687
self.assertEqual(None,
1688
bzrdir._get_config().get_option('default_stack_on'))
1689
self.assertLength(3, self.hpss_calls)
1692
class TestTransportIsReadonly(tests.TestCase):
1694
def test_true(self):
1695
client = FakeClient()
1696
client.add_success_response('yes')
1697
transport = RemoteTransport('bzr://example.com/', medium=False,
1699
self.assertEqual(True, transport.is_readonly())
1701
[('call', 'Transport.is_readonly', ())],
1704
def test_false(self):
1705
client = FakeClient()
1706
client.add_success_response('no')
1707
transport = RemoteTransport('bzr://example.com/', medium=False,
1709
self.assertEqual(False, transport.is_readonly())
1711
[('call', 'Transport.is_readonly', ())],
1714
def test_error_from_old_server(self):
1715
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1717
Clients should treat it as a "no" response, because is_readonly is only
1718
advisory anyway (a transport could be read-write, but then the
1719
underlying filesystem could be readonly anyway).
1721
client = FakeClient()
1722
client.add_unknown_method_response('Transport.is_readonly')
1723
transport = RemoteTransport('bzr://example.com/', medium=False,
1725
self.assertEqual(False, transport.is_readonly())
1727
[('call', 'Transport.is_readonly', ())],
1731
class TestTransportMkdir(tests.TestCase):
1733
def test_permissiondenied(self):
1734
client = FakeClient()
1735
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1736
transport = RemoteTransport('bzr://example.com/', medium=False,
1738
exc = self.assertRaises(
1739
errors.PermissionDenied, transport.mkdir, 'client path')
1740
expected_error = errors.PermissionDenied('/client path', 'extra')
1741
self.assertEqual(expected_error, exc)
1744
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1746
def test_defaults_to_none(self):
1747
t = RemoteSSHTransport('bzr+ssh://example.com')
1748
self.assertIs(None, t._get_credentials()[0])
1750
def test_uses_authentication_config(self):
1751
conf = config.AuthenticationConfig()
1752
conf._get_config().update(
1753
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1756
t = RemoteSSHTransport('bzr+ssh://example.com')
1757
self.assertEqual('bar', t._get_credentials()[0])
1760
class TestRemoteRepository(TestRemote):
1761
"""Base for testing RemoteRepository protocol usage.
1763
These tests contain frozen requests and responses. We want any changes to
1764
what is sent or expected to be require a thoughtful update to these tests
1765
because they might break compatibility with different-versioned servers.
1768
def setup_fake_client_and_repository(self, transport_path):
1769
"""Create the fake client and repository for testing with.
1771
There's no real server here; we just have canned responses sent
1774
:param transport_path: Path below the root of the MemoryTransport
1775
where the repository will be created.
1777
transport = MemoryTransport()
1778
transport.mkdir(transport_path)
1779
client = FakeClient(transport.base)
1780
transport = transport.clone(transport_path)
1781
# we do not want bzrdir to make any remote calls
1782
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1784
repo = RemoteRepository(bzrdir, None, _client=client)
1788
def remoted_description(format):
1789
return 'Remote: ' + format.get_format_description()
1792
class TestBranchFormat(tests.TestCase):
1794
def test_get_format_description(self):
1795
remote_format = RemoteBranchFormat()
1796
real_format = branch.BranchFormat.get_default_format()
1797
remote_format._network_name = real_format.network_name()
1798
self.assertEqual(remoted_description(real_format),
1799
remote_format.get_format_description())
1802
class TestRepositoryFormat(TestRemoteRepository):
1804
def test_fast_delta(self):
1805
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1806
true_format = RemoteRepositoryFormat()
1807
true_format._network_name = true_name
1808
self.assertEqual(True, true_format.fast_deltas)
1809
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1810
false_format = RemoteRepositoryFormat()
1811
false_format._network_name = false_name
1812
self.assertEqual(False, false_format.fast_deltas)
1814
def test_get_format_description(self):
1815
remote_repo_format = RemoteRepositoryFormat()
1816
real_format = repository.RepositoryFormat.get_default_format()
1817
remote_repo_format._network_name = real_format.network_name()
1818
self.assertEqual(remoted_description(real_format),
1819
remote_repo_format.get_format_description())
1822
class TestRepositoryGatherStats(TestRemoteRepository):
1824
def test_revid_none(self):
1825
# ('ok',), body with revisions and size
1826
transport_path = 'quack'
1827
repo, client = self.setup_fake_client_and_repository(transport_path)
1828
client.add_success_response_with_body(
1829
'revisions: 2\nsize: 18\n', 'ok')
1830
result = repo.gather_stats(None)
1832
[('call_expecting_body', 'Repository.gather_stats',
1833
('quack/','','no'))],
1835
self.assertEqual({'revisions': 2, 'size': 18}, result)
1837
def test_revid_no_committers(self):
1838
# ('ok',), body without committers
1839
body = ('firstrev: 123456.300 3600\n'
1840
'latestrev: 654231.400 0\n'
1843
transport_path = 'quick'
1844
revid = u'\xc8'.encode('utf8')
1845
repo, client = self.setup_fake_client_and_repository(transport_path)
1846
client.add_success_response_with_body(body, 'ok')
1847
result = repo.gather_stats(revid)
1849
[('call_expecting_body', 'Repository.gather_stats',
1850
('quick/', revid, 'no'))],
1852
self.assertEqual({'revisions': 2, 'size': 18,
1853
'firstrev': (123456.300, 3600),
1854
'latestrev': (654231.400, 0),},
1857
def test_revid_with_committers(self):
1858
# ('ok',), body with committers
1859
body = ('committers: 128\n'
1860
'firstrev: 123456.300 3600\n'
1861
'latestrev: 654231.400 0\n'
1864
transport_path = 'buick'
1865
revid = u'\xc8'.encode('utf8')
1866
repo, client = self.setup_fake_client_and_repository(transport_path)
1867
client.add_success_response_with_body(body, 'ok')
1868
result = repo.gather_stats(revid, True)
1870
[('call_expecting_body', 'Repository.gather_stats',
1871
('buick/', revid, 'yes'))],
1873
self.assertEqual({'revisions': 2, 'size': 18,
1875
'firstrev': (123456.300, 3600),
1876
'latestrev': (654231.400, 0),},
1880
class TestRepositoryGetGraph(TestRemoteRepository):
1882
def test_get_graph(self):
1883
# get_graph returns a graph with a custom parents provider.
1884
transport_path = 'quack'
1885
repo, client = self.setup_fake_client_and_repository(transport_path)
1886
graph = repo.get_graph()
1887
self.assertNotEqual(graph._parents_provider, repo)
1890
class TestRepositoryGetParentMap(TestRemoteRepository):
1892
def test_get_parent_map_caching(self):
1893
# get_parent_map returns from cache until unlock()
1894
# setup a reponse with two revisions
1895
r1 = u'\u0e33'.encode('utf8')
1896
r2 = u'\u0dab'.encode('utf8')
1897
lines = [' '.join([r2, r1]), r1]
1898
encoded_body = bz2.compress('\n'.join(lines))
1900
transport_path = 'quack'
1901
repo, client = self.setup_fake_client_and_repository(transport_path)
1902
client.add_success_response_with_body(encoded_body, 'ok')
1903
client.add_success_response_with_body(encoded_body, 'ok')
1905
graph = repo.get_graph()
1906
parents = graph.get_parent_map([r2])
1907
self.assertEqual({r2: (r1,)}, parents)
1908
# locking and unlocking deeper should not reset
1911
parents = graph.get_parent_map([r1])
1912
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1914
[('call_with_body_bytes_expecting_body',
1915
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1919
# now we call again, and it should use the second response.
1921
graph = repo.get_graph()
1922
parents = graph.get_parent_map([r1])
1923
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1925
[('call_with_body_bytes_expecting_body',
1926
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1928
('call_with_body_bytes_expecting_body',
1929
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1935
def test_get_parent_map_reconnects_if_unknown_method(self):
1936
transport_path = 'quack'
1937
rev_id = 'revision-id'
1938
repo, client = self.setup_fake_client_and_repository(transport_path)
1939
client.add_unknown_method_response('Repository.get_parent_map')
1940
client.add_success_response_with_body(rev_id, 'ok')
1941
self.assertFalse(client._medium._is_remote_before((1, 2)))
1942
parents = repo.get_parent_map([rev_id])
1944
[('call_with_body_bytes_expecting_body',
1945
'Repository.get_parent_map', ('quack/', 'include-missing:',
1947
('disconnect medium',),
1948
('call_expecting_body', 'Repository.get_revision_graph',
1951
# The medium is now marked as being connected to an older server
1952
self.assertTrue(client._medium._is_remote_before((1, 2)))
1953
self.assertEqual({rev_id: ('null:',)}, parents)
1955
def test_get_parent_map_fallback_parentless_node(self):
1956
"""get_parent_map falls back to get_revision_graph on old servers. The
1957
results from get_revision_graph are tweaked to match the get_parent_map
1960
Specifically, a {key: ()} result from get_revision_graph means "no
1961
parents" for that key, which in get_parent_map results should be
1962
represented as {key: ('null:',)}.
1964
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1966
rev_id = 'revision-id'
1967
transport_path = 'quack'
1968
repo, client = self.setup_fake_client_and_repository(transport_path)
1969
client.add_success_response_with_body(rev_id, 'ok')
1970
client._medium._remember_remote_is_before((1, 2))
1971
parents = repo.get_parent_map([rev_id])
1973
[('call_expecting_body', 'Repository.get_revision_graph',
1976
self.assertEqual({rev_id: ('null:',)}, parents)
1978
def test_get_parent_map_unexpected_response(self):
1979
repo, client = self.setup_fake_client_and_repository('path')
1980
client.add_success_response('something unexpected!')
1982
errors.UnexpectedSmartServerResponse,
1983
repo.get_parent_map, ['a-revision-id'])
1985
def test_get_parent_map_negative_caches_missing_keys(self):
1986
self.setup_smart_server_with_call_log()
1987
repo = self.make_repository('foo')
1988
self.assertIsInstance(repo, RemoteRepository)
1990
self.addCleanup(repo.unlock)
1991
self.reset_smart_call_log()
1992
graph = repo.get_graph()
1993
self.assertEqual({},
1994
graph.get_parent_map(['some-missing', 'other-missing']))
1995
self.assertLength(1, self.hpss_calls)
1996
# No call if we repeat this
1997
self.reset_smart_call_log()
1998
graph = repo.get_graph()
1999
self.assertEqual({},
2000
graph.get_parent_map(['some-missing', 'other-missing']))
2001
self.assertLength(0, self.hpss_calls)
2002
# Asking for more unknown keys makes a request.
2003
self.reset_smart_call_log()
2004
graph = repo.get_graph()
2005
self.assertEqual({},
2006
graph.get_parent_map(['some-missing', 'other-missing',
2008
self.assertLength(1, self.hpss_calls)
2010
def disableExtraResults(self):
2011
self.overrideAttr(SmartServerRepositoryGetParentMap,
2012
'no_extra_results', True)
2014
def test_null_cached_missing_and_stop_key(self):
2015
self.setup_smart_server_with_call_log()
2016
# Make a branch with a single revision.
2017
builder = self.make_branch_builder('foo')
2018
builder.start_series()
2019
builder.build_snapshot('first', None, [
2020
('add', ('', 'root-id', 'directory', ''))])
2021
builder.finish_series()
2022
branch = builder.get_branch()
2023
repo = branch.repository
2024
self.assertIsInstance(repo, RemoteRepository)
2025
# Stop the server from sending extra results.
2026
self.disableExtraResults()
2028
self.addCleanup(repo.unlock)
2029
self.reset_smart_call_log()
2030
graph = repo.get_graph()
2031
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2032
# 'first' it will be a candidate for the stop_keys of subsequent
2033
# requests, and because 'null:' was queried but not returned it will be
2034
# cached as missing.
2035
self.assertEqual({'first': ('null:',)},
2036
graph.get_parent_map(['first', 'null:']))
2037
# Now query for another key. This request will pass along a recipe of
2038
# start and stop keys describing the already cached results, and this
2039
# recipe's revision count must be correct (or else it will trigger an
2040
# error from the server).
2041
self.assertEqual({}, graph.get_parent_map(['another-key']))
2042
# This assertion guards against disableExtraResults silently failing to
2043
# work, thus invalidating the test.
2044
self.assertLength(2, self.hpss_calls)
2046
def test_get_parent_map_gets_ghosts_from_result(self):
2047
# asking for a revision should negatively cache close ghosts in its
2049
self.setup_smart_server_with_call_log()
2050
tree = self.make_branch_and_memory_tree('foo')
2053
builder = treebuilder.TreeBuilder()
2054
builder.start_tree(tree)
2056
builder.finish_tree()
2057
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2058
rev_id = tree.commit('')
2062
self.addCleanup(tree.unlock)
2063
repo = tree.branch.repository
2064
self.assertIsInstance(repo, RemoteRepository)
2066
repo.get_parent_map([rev_id])
2067
self.reset_smart_call_log()
2068
# Now asking for rev_id's ghost parent should not make calls
2069
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2070
self.assertLength(0, self.hpss_calls)
2073
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2075
def test_allows_new_revisions(self):
2076
"""get_parent_map's results can be updated by commit."""
2077
smart_server = server.SmartTCPServer_for_testing()
2078
self.start_server(smart_server)
2079
self.make_branch('branch')
2080
branch = Branch.open(smart_server.get_url() + '/branch')
2081
tree = branch.create_checkout('tree', lightweight=True)
2083
self.addCleanup(tree.unlock)
2084
graph = tree.branch.repository.get_graph()
2085
# This provides an opportunity for the missing rev-id to be cached.
2086
self.assertEqual({}, graph.get_parent_map(['rev1']))
2087
tree.commit('message', rev_id='rev1')
2088
graph = tree.branch.repository.get_graph()
2089
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2092
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2094
def test_null_revision(self):
2095
# a null revision has the predictable result {}, we should have no wire
2096
# traffic when calling it with this argument
2097
transport_path = 'empty'
2098
repo, client = self.setup_fake_client_and_repository(transport_path)
2099
client.add_success_response('notused')
2100
# actual RemoteRepository.get_revision_graph is gone, but there's an
2101
# equivalent private method for testing
2102
result = repo._get_revision_graph(NULL_REVISION)
2103
self.assertEqual([], client._calls)
2104
self.assertEqual({}, result)
2106
def test_none_revision(self):
2107
# with none we want the entire graph
2108
r1 = u'\u0e33'.encode('utf8')
2109
r2 = u'\u0dab'.encode('utf8')
2110
lines = [' '.join([r2, r1]), r1]
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
# actual RemoteRepository.get_revision_graph is gone, but there's an
2117
# equivalent private method for testing
2118
result = repo._get_revision_graph(None)
2120
[('call_expecting_body', 'Repository.get_revision_graph',
2123
self.assertEqual({r1: (), r2: (r1, )}, result)
2125
def test_specific_revision(self):
2126
# with a specific revision we want the graph for that
2127
# with none we want the entire graph
2128
r11 = u'\u0e33'.encode('utf8')
2129
r12 = u'\xc9'.encode('utf8')
2130
r2 = u'\u0dab'.encode('utf8')
2131
lines = [' '.join([r2, r11, r12]), r11, r12]
2132
encoded_body = '\n'.join(lines)
2134
transport_path = 'sinhala'
2135
repo, client = self.setup_fake_client_and_repository(transport_path)
2136
client.add_success_response_with_body(encoded_body, 'ok')
2137
result = repo._get_revision_graph(r2)
2139
[('call_expecting_body', 'Repository.get_revision_graph',
2142
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2144
def test_no_such_revision(self):
2146
transport_path = 'sinhala'
2147
repo, client = self.setup_fake_client_and_repository(transport_path)
2148
client.add_error_response('nosuchrevision', revid)
2149
# also check that the right revision is reported in the error
2150
self.assertRaises(errors.NoSuchRevision,
2151
repo._get_revision_graph, revid)
2153
[('call_expecting_body', 'Repository.get_revision_graph',
2154
('sinhala/', revid))],
2157
def test_unexpected_error(self):
2159
transport_path = 'sinhala'
2160
repo, client = self.setup_fake_client_and_repository(transport_path)
2161
client.add_error_response('AnUnexpectedError')
2162
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2163
repo._get_revision_graph, revid)
2164
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2167
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2170
repo, client = self.setup_fake_client_and_repository('quack')
2171
client.add_expected_call(
2172
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2173
'success', ('ok', 'rev-five'))
2174
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2175
self.assertEqual((True, 'rev-five'), result)
2176
self.assertFinished(client)
2178
def test_history_incomplete(self):
2179
repo, client = self.setup_fake_client_and_repository('quack')
2180
client.add_expected_call(
2181
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2182
'success', ('history-incomplete', 10, 'rev-ten'))
2183
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2184
self.assertEqual((False, (10, 'rev-ten')), result)
2185
self.assertFinished(client)
2187
def test_history_incomplete_with_fallback(self):
2188
"""A 'history-incomplete' response causes the fallback repository to be
2189
queried too, if one is set.
2191
# Make a repo with a fallback repo, both using a FakeClient.
2192
format = remote.response_tuple_to_repo_format(
2193
('yes', 'no', 'yes', 'fake-network-name'))
2194
repo, client = self.setup_fake_client_and_repository('quack')
2195
repo._format = format
2196
fallback_repo, ignored = self.setup_fake_client_and_repository(
2198
fallback_repo._client = client
2199
repo.add_fallback_repository(fallback_repo)
2200
# First the client should ask the primary repo
2201
client.add_expected_call(
2202
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2203
'success', ('history-incomplete', 2, 'rev-two'))
2204
# Then it should ask the fallback, using revno/revid from the
2205
# history-incomplete response as the known revno/revid.
2206
client.add_expected_call(
2207
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2208
'success', ('ok', 'rev-one'))
2209
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2210
self.assertEqual((True, 'rev-one'), result)
2211
self.assertFinished(client)
2213
def test_nosuchrevision(self):
2214
# 'nosuchrevision' is returned when the known-revid is not found in the
2215
# remote repo. The client translates that response to NoSuchRevision.
2216
repo, client = self.setup_fake_client_and_repository('quack')
2217
client.add_expected_call(
2218
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2219
'error', ('nosuchrevision', 'rev-foo'))
2221
errors.NoSuchRevision,
2222
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2223
self.assertFinished(client)
2225
def test_branch_fallback_locking(self):
2226
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2227
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2228
will be invoked, which will fail if the repo is unlocked.
2230
self.setup_smart_server_with_call_log()
2231
tree = self.make_branch_and_memory_tree('.')
2233
rev1 = tree.commit('First')
2234
rev2 = tree.commit('Second')
2236
branch = tree.branch
2237
self.assertFalse(branch.is_locked())
2238
self.reset_smart_call_log()
2239
verb = 'Repository.get_rev_id_for_revno'
2240
self.disable_verb(verb)
2241
self.assertEqual(rev1, branch.get_rev_id(1))
2242
self.assertLength(1, [call for call in self.hpss_calls if
2243
call.call.method == verb])
2246
class TestRepositoryIsShared(TestRemoteRepository):
2248
def test_is_shared(self):
2249
# ('yes', ) for Repository.is_shared -> 'True'.
2250
transport_path = 'quack'
2251
repo, client = self.setup_fake_client_and_repository(transport_path)
2252
client.add_success_response('yes')
2253
result = repo.is_shared()
2255
[('call', 'Repository.is_shared', ('quack/',))],
2257
self.assertEqual(True, result)
2259
def test_is_not_shared(self):
2260
# ('no', ) for Repository.is_shared -> 'False'.
2261
transport_path = 'qwack'
2262
repo, client = self.setup_fake_client_and_repository(transport_path)
2263
client.add_success_response('no')
2264
result = repo.is_shared()
2266
[('call', 'Repository.is_shared', ('qwack/',))],
2268
self.assertEqual(False, result)
2271
class TestRepositoryLockWrite(TestRemoteRepository):
2273
def test_lock_write(self):
2274
transport_path = 'quack'
2275
repo, client = self.setup_fake_client_and_repository(transport_path)
2276
client.add_success_response('ok', 'a token')
2277
result = repo.lock_write()
2279
[('call', 'Repository.lock_write', ('quack/', ''))],
2281
self.assertEqual('a token', result)
2283
def test_lock_write_already_locked(self):
2284
transport_path = 'quack'
2285
repo, client = self.setup_fake_client_and_repository(transport_path)
2286
client.add_error_response('LockContention')
2287
self.assertRaises(errors.LockContention, repo.lock_write)
2289
[('call', 'Repository.lock_write', ('quack/', ''))],
2292
def test_lock_write_unlockable(self):
2293
transport_path = 'quack'
2294
repo, client = self.setup_fake_client_and_repository(transport_path)
2295
client.add_error_response('UnlockableTransport')
2296
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2298
[('call', 'Repository.lock_write', ('quack/', ''))],
2302
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2304
def test_backwards_compat(self):
2305
self.setup_smart_server_with_call_log()
2306
repo = self.make_repository('.')
2307
self.reset_smart_call_log()
2308
verb = 'Repository.set_make_working_trees'
2309
self.disable_verb(verb)
2310
repo.set_make_working_trees(True)
2311
call_count = len([call for call in self.hpss_calls if
2312
call.call.method == verb])
2313
self.assertEqual(1, call_count)
2315
def test_current(self):
2316
transport_path = 'quack'
2317
repo, client = self.setup_fake_client_and_repository(transport_path)
2318
client.add_expected_call(
2319
'Repository.set_make_working_trees', ('quack/', 'True'),
2321
client.add_expected_call(
2322
'Repository.set_make_working_trees', ('quack/', 'False'),
2324
repo.set_make_working_trees(True)
2325
repo.set_make_working_trees(False)
2328
class TestRepositoryUnlock(TestRemoteRepository):
2330
def test_unlock(self):
2331
transport_path = 'quack'
2332
repo, client = self.setup_fake_client_and_repository(transport_path)
2333
client.add_success_response('ok', 'a token')
2334
client.add_success_response('ok')
2338
[('call', 'Repository.lock_write', ('quack/', '')),
2339
('call', 'Repository.unlock', ('quack/', 'a token'))],
2342
def test_unlock_wrong_token(self):
2343
# If somehow the token is wrong, unlock will raise TokenMismatch.
2344
transport_path = 'quack'
2345
repo, client = self.setup_fake_client_and_repository(transport_path)
2346
client.add_success_response('ok', 'a token')
2347
client.add_error_response('TokenMismatch')
2349
self.assertRaises(errors.TokenMismatch, repo.unlock)
2352
class TestRepositoryHasRevision(TestRemoteRepository):
2354
def test_none(self):
2355
# repo.has_revision(None) should not cause any traffic.
2356
transport_path = 'quack'
2357
repo, client = self.setup_fake_client_and_repository(transport_path)
2359
# The null revision is always there, so has_revision(None) == True.
2360
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2362
# The remote repo shouldn't be accessed.
2363
self.assertEqual([], client._calls)
2366
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2367
"""Base class for Repository.insert_stream and .insert_stream_1.19
2371
def checkInsertEmptyStream(self, repo, client):
2372
"""Insert an empty stream, checking the result.
2374
This checks that there are no resume_tokens or missing_keys, and that
2375
the client is finished.
2377
sink = repo._get_sink()
2378
fmt = repository.RepositoryFormat.get_default_format()
2379
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2380
self.assertEqual([], resume_tokens)
2381
self.assertEqual(set(), missing_keys)
2382
self.assertFinished(client)
2385
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2386
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2389
This test case is very similar to TestRepositoryInsertStream_1_19.
2393
TestRemoteRepository.setUp(self)
2394
self.disable_verb('Repository.insert_stream_1.19')
2396
def test_unlocked_repo(self):
2397
transport_path = 'quack'
2398
repo, client = self.setup_fake_client_and_repository(transport_path)
2399
client.add_expected_call(
2400
'Repository.insert_stream_1.19', ('quack/', ''),
2401
'unknown', ('Repository.insert_stream_1.19',))
2402
client.add_expected_call(
2403
'Repository.insert_stream', ('quack/', ''),
2405
client.add_expected_call(
2406
'Repository.insert_stream', ('quack/', ''),
2408
self.checkInsertEmptyStream(repo, client)
2410
def test_locked_repo_with_no_lock_token(self):
2411
transport_path = 'quack'
2412
repo, client = self.setup_fake_client_and_repository(transport_path)
2413
client.add_expected_call(
2414
'Repository.lock_write', ('quack/', ''),
2415
'success', ('ok', ''))
2416
client.add_expected_call(
2417
'Repository.insert_stream_1.19', ('quack/', ''),
2418
'unknown', ('Repository.insert_stream_1.19',))
2419
client.add_expected_call(
2420
'Repository.insert_stream', ('quack/', ''),
2422
client.add_expected_call(
2423
'Repository.insert_stream', ('quack/', ''),
2426
self.checkInsertEmptyStream(repo, client)
2428
def test_locked_repo_with_lock_token(self):
2429
transport_path = 'quack'
2430
repo, client = self.setup_fake_client_and_repository(transport_path)
2431
client.add_expected_call(
2432
'Repository.lock_write', ('quack/', ''),
2433
'success', ('ok', 'a token'))
2434
client.add_expected_call(
2435
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2436
'unknown', ('Repository.insert_stream_1.19',))
2437
client.add_expected_call(
2438
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2440
client.add_expected_call(
2441
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2444
self.checkInsertEmptyStream(repo, client)
2446
def test_stream_with_inventory_deltas(self):
2447
"""'inventory-deltas' substreams cannot be sent to the
2448
Repository.insert_stream verb, because not all servers that implement
2449
that verb will accept them. So when one is encountered the RemoteSink
2450
immediately stops using that verb and falls back to VFS insert_stream.
2452
transport_path = 'quack'
2453
repo, client = self.setup_fake_client_and_repository(transport_path)
2454
client.add_expected_call(
2455
'Repository.insert_stream_1.19', ('quack/', ''),
2456
'unknown', ('Repository.insert_stream_1.19',))
2457
client.add_expected_call(
2458
'Repository.insert_stream', ('quack/', ''),
2460
client.add_expected_call(
2461
'Repository.insert_stream', ('quack/', ''),
2463
# Create a fake real repository for insert_stream to fall back on, so
2464
# that we can directly see the records the RemoteSink passes to the
2469
def insert_stream(self, stream, src_format, resume_tokens):
2470
for substream_kind, substream in stream:
2471
self.records.append(
2472
(substream_kind, [record.key for record in substream]))
2473
return ['fake tokens'], ['fake missing keys']
2474
fake_real_sink = FakeRealSink()
2475
class FakeRealRepository:
2476
def _get_sink(self):
2477
return fake_real_sink
2478
def is_in_write_group(self):
2480
def refresh_data(self):
2482
repo._real_repository = FakeRealRepository()
2483
sink = repo._get_sink()
2484
fmt = repository.RepositoryFormat.get_default_format()
2485
stream = self.make_stream_with_inv_deltas(fmt)
2486
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2487
# Every record from the first inventory delta should have been sent to
2489
expected_records = [
2490
('inventory-deltas', [('rev2',), ('rev3',)]),
2491
('texts', [('some-rev', 'some-file')])]
2492
self.assertEqual(expected_records, fake_real_sink.records)
2493
# The return values from the real sink's insert_stream are propagated
2494
# back to the original caller.
2495
self.assertEqual(['fake tokens'], resume_tokens)
2496
self.assertEqual(['fake missing keys'], missing_keys)
2497
self.assertFinished(client)
2499
def make_stream_with_inv_deltas(self, fmt):
2500
"""Make a simple stream with an inventory delta followed by more
2501
records and more substreams to test that all records and substreams
2502
from that point on are used.
2504
This sends, in order:
2505
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2507
* texts substream: (some-rev, some-file)
2509
# Define a stream using generators so that it isn't rewindable.
2510
inv = inventory.Inventory(revision_id='rev1')
2511
inv.root.revision = 'rev1'
2512
def stream_with_inv_delta():
2513
yield ('inventories', inventories_substream())
2514
yield ('inventory-deltas', inventory_delta_substream())
2516
versionedfile.FulltextContentFactory(
2517
('some-rev', 'some-file'), (), None, 'content')])
2518
def inventories_substream():
2519
# An empty inventory fulltext. This will be streamed normally.
2520
text = fmt._serializer.write_inventory_to_string(inv)
2521
yield versionedfile.FulltextContentFactory(
2522
('rev1',), (), None, text)
2523
def inventory_delta_substream():
2524
# An inventory delta. This can't be streamed via this verb, so it
2525
# will trigger a fallback to VFS insert_stream.
2526
entry = inv.make_entry(
2527
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2528
entry.revision = 'ghost'
2529
delta = [(None, 'newdir', 'newdir-id', entry)]
2530
serializer = inventory_delta.InventoryDeltaSerializer(
2531
versioned_root=True, tree_references=False)
2532
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2533
yield versionedfile.ChunkedContentFactory(
2534
('rev2',), (('rev1',)), None, lines)
2536
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2537
yield versionedfile.ChunkedContentFactory(
2538
('rev3',), (('rev1',)), None, lines)
2539
return stream_with_inv_delta()
2542
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2544
def test_unlocked_repo(self):
2545
transport_path = 'quack'
2546
repo, client = self.setup_fake_client_and_repository(transport_path)
2547
client.add_expected_call(
2548
'Repository.insert_stream_1.19', ('quack/', ''),
2550
client.add_expected_call(
2551
'Repository.insert_stream_1.19', ('quack/', ''),
2553
self.checkInsertEmptyStream(repo, client)
2555
def test_locked_repo_with_no_lock_token(self):
2556
transport_path = 'quack'
2557
repo, client = self.setup_fake_client_and_repository(transport_path)
2558
client.add_expected_call(
2559
'Repository.lock_write', ('quack/', ''),
2560
'success', ('ok', ''))
2561
client.add_expected_call(
2562
'Repository.insert_stream_1.19', ('quack/', ''),
2564
client.add_expected_call(
2565
'Repository.insert_stream_1.19', ('quack/', ''),
2568
self.checkInsertEmptyStream(repo, client)
2570
def test_locked_repo_with_lock_token(self):
2571
transport_path = 'quack'
2572
repo, client = self.setup_fake_client_and_repository(transport_path)
2573
client.add_expected_call(
2574
'Repository.lock_write', ('quack/', ''),
2575
'success', ('ok', 'a token'))
2576
client.add_expected_call(
2577
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2579
client.add_expected_call(
2580
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2583
self.checkInsertEmptyStream(repo, client)
2586
class TestRepositoryTarball(TestRemoteRepository):
2588
# This is a canned tarball reponse we can validate against
2590
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2591
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2592
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2593
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2594
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2595
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2596
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2597
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2598
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2599
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2600
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2601
'nWQ7QH/F3JFOFCQ0aSPfA='
2604
def test_repository_tarball(self):
2605
# Test that Repository.tarball generates the right operations
2606
transport_path = 'repo'
2607
expected_calls = [('call_expecting_body', 'Repository.tarball',
2608
('repo/', 'bz2',),),
2610
repo, client = self.setup_fake_client_and_repository(transport_path)
2611
client.add_success_response_with_body(self.tarball_content, 'ok')
2612
# Now actually ask for the tarball
2613
tarball_file = repo._get_tarball('bz2')
2615
self.assertEqual(expected_calls, client._calls)
2616
self.assertEqual(self.tarball_content, tarball_file.read())
2618
tarball_file.close()
2621
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2622
"""RemoteRepository.copy_content_into optimizations"""
2624
def test_copy_content_remote_to_local(self):
2625
self.transport_server = server.SmartTCPServer_for_testing
2626
src_repo = self.make_repository('repo1')
2627
src_repo = repository.Repository.open(self.get_url('repo1'))
2628
# At the moment the tarball-based copy_content_into can't write back
2629
# into a smart server. It would be good if it could upload the
2630
# tarball; once that works we'd have to create repositories of
2631
# different formats. -- mbp 20070410
2632
dest_url = self.get_vfs_only_url('repo2')
2633
dest_bzrdir = BzrDir.create(dest_url)
2634
dest_repo = dest_bzrdir.create_repository()
2635
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2636
self.assertTrue(isinstance(src_repo, RemoteRepository))
2637
src_repo.copy_content_into(dest_repo)
2640
class _StubRealPackRepository(object):
2642
def __init__(self, calls):
2644
self._pack_collection = _StubPackCollection(calls)
2646
def is_in_write_group(self):
2649
def refresh_data(self):
2650
self.calls.append(('pack collection reload_pack_names',))
2653
class _StubPackCollection(object):
2655
def __init__(self, calls):
2659
self.calls.append(('pack collection autopack',))
2662
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2663
"""Tests for RemoteRepository.autopack implementation."""
2666
"""When the server returns 'ok' and there's no _real_repository, then
2667
nothing else happens: the autopack method is done.
2669
transport_path = 'quack'
2670
repo, client = self.setup_fake_client_and_repository(transport_path)
2671
client.add_expected_call(
2672
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2674
self.assertFinished(client)
2676
def test_ok_with_real_repo(self):
2677
"""When the server returns 'ok' and there is a _real_repository, then
2678
the _real_repository's reload_pack_name's method will be called.
2680
transport_path = 'quack'
2681
repo, client = self.setup_fake_client_and_repository(transport_path)
2682
client.add_expected_call(
2683
'PackRepository.autopack', ('quack/',),
2685
repo._real_repository = _StubRealPackRepository(client._calls)
2688
[('call', 'PackRepository.autopack', ('quack/',)),
2689
('pack collection reload_pack_names',)],
2692
def test_backwards_compatibility(self):
2693
"""If the server does not recognise the PackRepository.autopack verb,
2694
fallback to the real_repository's implementation.
2696
transport_path = 'quack'
2697
repo, client = self.setup_fake_client_and_repository(transport_path)
2698
client.add_unknown_method_response('PackRepository.autopack')
2699
def stub_ensure_real():
2700
client._calls.append(('_ensure_real',))
2701
repo._real_repository = _StubRealPackRepository(client._calls)
2702
repo._ensure_real = stub_ensure_real
2705
[('call', 'PackRepository.autopack', ('quack/',)),
2707
('pack collection autopack',)],
2711
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2712
"""Base class for unit tests for bzrlib.remote._translate_error."""
2714
def translateTuple(self, error_tuple, **context):
2715
"""Call _translate_error with an ErrorFromSmartServer built from the
2718
:param error_tuple: A tuple of a smart server response, as would be
2719
passed to an ErrorFromSmartServer.
2720
:kwargs context: context items to call _translate_error with.
2722
:returns: The error raised by _translate_error.
2724
# Raise the ErrorFromSmartServer before passing it as an argument,
2725
# because _translate_error may need to re-raise it with a bare 'raise'
2727
server_error = errors.ErrorFromSmartServer(error_tuple)
2728
translated_error = self.translateErrorFromSmartServer(
2729
server_error, **context)
2730
return translated_error
2732
def translateErrorFromSmartServer(self, error_object, **context):
2733
"""Like translateTuple, but takes an already constructed
2734
ErrorFromSmartServer rather than a tuple.
2738
except errors.ErrorFromSmartServer, server_error:
2739
translated_error = self.assertRaises(
2740
errors.BzrError, remote._translate_error, server_error,
2742
return translated_error
2745
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2746
"""Unit tests for bzrlib.remote._translate_error.
2748
Given an ErrorFromSmartServer (which has an error tuple from a smart
2749
server) and some context, _translate_error raises more specific errors from
2752
This test case covers the cases where _translate_error succeeds in
2753
translating an ErrorFromSmartServer to something better. See
2754
TestErrorTranslationRobustness for other cases.
2757
def test_NoSuchRevision(self):
2758
branch = self.make_branch('')
2760
translated_error = self.translateTuple(
2761
('NoSuchRevision', revid), branch=branch)
2762
expected_error = errors.NoSuchRevision(branch, revid)
2763
self.assertEqual(expected_error, translated_error)
2765
def test_nosuchrevision(self):
2766
repository = self.make_repository('')
2768
translated_error = self.translateTuple(
2769
('nosuchrevision', revid), repository=repository)
2770
expected_error = errors.NoSuchRevision(repository, revid)
2771
self.assertEqual(expected_error, translated_error)
2773
def test_nobranch(self):
2774
bzrdir = self.make_bzrdir('')
2775
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2776
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2777
self.assertEqual(expected_error, translated_error)
2779
def test_nobranch_one_arg(self):
2780
bzrdir = self.make_bzrdir('')
2781
translated_error = self.translateTuple(
2782
('nobranch', 'extra detail'), bzrdir=bzrdir)
2783
expected_error = errors.NotBranchError(
2784
path=bzrdir.root_transport.base,
2785
detail='extra detail')
2786
self.assertEqual(expected_error, translated_error)
2788
def test_LockContention(self):
2789
translated_error = self.translateTuple(('LockContention',))
2790
expected_error = errors.LockContention('(remote lock)')
2791
self.assertEqual(expected_error, translated_error)
2793
def test_UnlockableTransport(self):
2794
bzrdir = self.make_bzrdir('')
2795
translated_error = self.translateTuple(
2796
('UnlockableTransport',), bzrdir=bzrdir)
2797
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2798
self.assertEqual(expected_error, translated_error)
2800
def test_LockFailed(self):
2801
lock = 'str() of a server lock'
2802
why = 'str() of why'
2803
translated_error = self.translateTuple(('LockFailed', lock, why))
2804
expected_error = errors.LockFailed(lock, why)
2805
self.assertEqual(expected_error, translated_error)
2807
def test_TokenMismatch(self):
2808
token = 'a lock token'
2809
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2810
expected_error = errors.TokenMismatch(token, '(remote token)')
2811
self.assertEqual(expected_error, translated_error)
2813
def test_Diverged(self):
2814
branch = self.make_branch('a')
2815
other_branch = self.make_branch('b')
2816
translated_error = self.translateTuple(
2817
('Diverged',), branch=branch, other_branch=other_branch)
2818
expected_error = errors.DivergedBranches(branch, other_branch)
2819
self.assertEqual(expected_error, translated_error)
2821
def test_ReadError_no_args(self):
2823
translated_error = self.translateTuple(('ReadError',), path=path)
2824
expected_error = errors.ReadError(path)
2825
self.assertEqual(expected_error, translated_error)
2827
def test_ReadError(self):
2829
translated_error = self.translateTuple(('ReadError', path))
2830
expected_error = errors.ReadError(path)
2831
self.assertEqual(expected_error, translated_error)
2833
def test_IncompatibleRepositories(self):
2834
translated_error = self.translateTuple(('IncompatibleRepositories',
2835
"repo1", "repo2", "details here"))
2836
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2838
self.assertEqual(expected_error, translated_error)
2840
def test_PermissionDenied_no_args(self):
2842
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2843
expected_error = errors.PermissionDenied(path)
2844
self.assertEqual(expected_error, translated_error)
2846
def test_PermissionDenied_one_arg(self):
2848
translated_error = self.translateTuple(('PermissionDenied', path))
2849
expected_error = errors.PermissionDenied(path)
2850
self.assertEqual(expected_error, translated_error)
2852
def test_PermissionDenied_one_arg_and_context(self):
2853
"""Given a choice between a path from the local context and a path on
2854
the wire, _translate_error prefers the path from the local context.
2856
local_path = 'local path'
2857
remote_path = 'remote path'
2858
translated_error = self.translateTuple(
2859
('PermissionDenied', remote_path), path=local_path)
2860
expected_error = errors.PermissionDenied(local_path)
2861
self.assertEqual(expected_error, translated_error)
2863
def test_PermissionDenied_two_args(self):
2865
extra = 'a string with extra info'
2866
translated_error = self.translateTuple(
2867
('PermissionDenied', path, extra))
2868
expected_error = errors.PermissionDenied(path, extra)
2869
self.assertEqual(expected_error, translated_error)
2872
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2873
"""Unit tests for bzrlib.remote._translate_error's robustness.
2875
TestErrorTranslationSuccess is for cases where _translate_error can
2876
translate successfully. This class about how _translate_err behaves when
2877
it fails to translate: it re-raises the original error.
2880
def test_unrecognised_server_error(self):
2881
"""If the error code from the server is not recognised, the original
2882
ErrorFromSmartServer is propagated unmodified.
2884
error_tuple = ('An unknown error tuple',)
2885
server_error = errors.ErrorFromSmartServer(error_tuple)
2886
translated_error = self.translateErrorFromSmartServer(server_error)
2887
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2888
self.assertEqual(expected_error, translated_error)
2890
def test_context_missing_a_key(self):
2891
"""In case of a bug in the client, or perhaps an unexpected response
2892
from a server, _translate_error returns the original error tuple from
2893
the server and mutters a warning.
2895
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2896
# in the context dict. So let's give it an empty context dict instead
2897
# to exercise its error recovery.
2899
error_tuple = ('NoSuchRevision', 'revid')
2900
server_error = errors.ErrorFromSmartServer(error_tuple)
2901
translated_error = self.translateErrorFromSmartServer(server_error)
2902
self.assertEqual(server_error, translated_error)
2903
# In addition to re-raising ErrorFromSmartServer, some debug info has
2904
# been muttered to the log file for developer to look at.
2905
self.assertContainsRe(
2907
"Missing key 'branch' in context")
2909
def test_path_missing(self):
2910
"""Some translations (PermissionDenied, ReadError) can determine the
2911
'path' variable from either the wire or the local context. If neither
2912
has it, then an error is raised.
2914
error_tuple = ('ReadError',)
2915
server_error = errors.ErrorFromSmartServer(error_tuple)
2916
translated_error = self.translateErrorFromSmartServer(server_error)
2917
self.assertEqual(server_error, translated_error)
2918
# In addition to re-raising ErrorFromSmartServer, some debug info has
2919
# been muttered to the log file for developer to look at.
2920
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
2923
class TestStacking(tests.TestCaseWithTransport):
2924
"""Tests for operations on stacked remote repositories.
2926
The underlying format type must support stacking.
2929
def test_access_stacked_remote(self):
2930
# based on <http://launchpad.net/bugs/261315>
2931
# make a branch stacked on another repository containing an empty
2932
# revision, then open it over hpss - we should be able to see that
2934
base_transport = self.get_transport()
2935
base_builder = self.make_branch_builder('base', format='1.9')
2936
base_builder.start_series()
2937
base_revid = base_builder.build_snapshot('rev-id', None,
2938
[('add', ('', None, 'directory', None))],
2940
base_builder.finish_series()
2941
stacked_branch = self.make_branch('stacked', format='1.9')
2942
stacked_branch.set_stacked_on_url('../base')
2943
# start a server looking at this
2944
smart_server = server.SmartTCPServer_for_testing()
2945
self.start_server(smart_server)
2946
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2947
# can get its branch and repository
2948
remote_branch = remote_bzrdir.open_branch()
2949
remote_repo = remote_branch.repository
2950
remote_repo.lock_read()
2952
# it should have an appropriate fallback repository, which should also
2953
# be a RemoteRepository
2954
self.assertLength(1, remote_repo._fallback_repositories)
2955
self.assertIsInstance(remote_repo._fallback_repositories[0],
2957
# and it has the revision committed to the underlying repository;
2958
# these have varying implementations so we try several of them
2959
self.assertTrue(remote_repo.has_revisions([base_revid]))
2960
self.assertTrue(remote_repo.has_revision(base_revid))
2961
self.assertEqual(remote_repo.get_revision(base_revid).message,
2964
remote_repo.unlock()
2966
def prepare_stacked_remote_branch(self):
2967
"""Get stacked_upon and stacked branches with content in each."""
2968
self.setup_smart_server_with_call_log()
2969
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2970
tree1.commit('rev1', rev_id='rev1')
2971
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2972
).open_workingtree()
2973
local_tree = tree2.branch.create_checkout('local')
2974
local_tree.commit('local changes make me feel good.')
2975
branch2 = Branch.open(self.get_url('tree2'))
2977
self.addCleanup(branch2.unlock)
2978
return tree1.branch, branch2
2980
def test_stacked_get_parent_map(self):
2981
# the public implementation of get_parent_map obeys stacking
2982
_, branch = self.prepare_stacked_remote_branch()
2983
repo = branch.repository
2984
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2986
def test_unstacked_get_parent_map(self):
2987
# _unstacked_provider.get_parent_map ignores stacking
2988
_, branch = self.prepare_stacked_remote_branch()
2989
provider = branch.repository._unstacked_provider
2990
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2992
def fetch_stream_to_rev_order(self, stream):
2994
for kind, substream in stream:
2995
if not kind == 'revisions':
2998
for content in substream:
2999
result.append(content.key[-1])
3002
def get_ordered_revs(self, format, order, branch_factory=None):
3003
"""Get a list of the revisions in a stream to format format.
3005
:param format: The format of the target.
3006
:param order: the order that target should have requested.
3007
:param branch_factory: A callable to create a trunk and stacked branch
3008
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3009
:result: The revision ids in the stream, in the order seen,
3010
the topological order of revisions in the source.
3012
unordered_format = bzrdir.format_registry.get(format)()
3013
target_repository_format = unordered_format.repository_format
3015
self.assertEqual(order, target_repository_format._fetch_order)
3016
if branch_factory is None:
3017
branch_factory = self.prepare_stacked_remote_branch
3018
_, stacked = branch_factory()
3019
source = stacked.repository._get_source(target_repository_format)
3020
tip = stacked.last_revision()
3021
revs = stacked.repository.get_ancestry(tip)
3022
search = graph.PendingAncestryResult([tip], stacked.repository)
3023
self.reset_smart_call_log()
3024
stream = source.get_stream(search)
3027
# We trust that if a revision is in the stream the rest of the new
3028
# content for it is too, as per our main fetch tests; here we are
3029
# checking that the revisions are actually included at all, and their
3031
return self.fetch_stream_to_rev_order(stream), revs
3033
def test_stacked_get_stream_unordered(self):
3034
# Repository._get_source.get_stream() from a stacked repository with
3035
# unordered yields the full data from both stacked and stacked upon
3037
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3038
self.assertEqual(set(expected_revs), set(rev_ord))
3039
# Getting unordered results should have made a streaming data request
3040
# from the server, then one from the backing branch.
3041
self.assertLength(2, self.hpss_calls)
3043
def test_stacked_on_stacked_get_stream_unordered(self):
3044
# Repository._get_source.get_stream() from a stacked repository which
3045
# is itself stacked yields the full data from all three sources.
3046
def make_stacked_stacked():
3047
_, stacked = self.prepare_stacked_remote_branch()
3048
tree = stacked.bzrdir.sprout('tree3', stacked=True
3049
).open_workingtree()
3050
local_tree = tree.branch.create_checkout('local-tree3')
3051
local_tree.commit('more local changes are better')
3052
branch = Branch.open(self.get_url('tree3'))
3054
self.addCleanup(branch.unlock)
3056
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3057
branch_factory=make_stacked_stacked)
3058
self.assertEqual(set(expected_revs), set(rev_ord))
3059
# Getting unordered results should have made a streaming data request
3060
# from the server, and one from each backing repo
3061
self.assertLength(3, self.hpss_calls)
3063
def test_stacked_get_stream_topological(self):
3064
# Repository._get_source.get_stream() from a stacked repository with
3065
# topological sorting yields the full data from both stacked and
3066
# stacked upon sources in topological order.
3067
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3068
self.assertEqual(expected_revs, rev_ord)
3069
# Getting topological sort requires VFS calls still - one of which is
3070
# pushing up from the bound branch.
3071
self.assertLength(13, self.hpss_calls)
3073
def test_stacked_get_stream_groupcompress(self):
3074
# Repository._get_source.get_stream() from a stacked repository with
3075
# groupcompress sorting yields the full data from both stacked and
3076
# stacked upon sources in groupcompress order.
3077
raise tests.TestSkipped('No groupcompress ordered format available')
3078
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3079
self.assertEqual(expected_revs, reversed(rev_ord))
3080
# Getting unordered results should have made a streaming data request
3081
# from the backing branch, and one from the stacked on branch.
3082
self.assertLength(2, self.hpss_calls)
3084
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3085
# When pulling some fixed amount of content that is more than the
3086
# source has (because some is coming from a fallback branch, no error
3087
# should be received. This was reported as bug 360791.
3088
# Need three branches: a trunk, a stacked branch, and a preexisting
3089
# branch pulling content from stacked and trunk.
3090
self.setup_smart_server_with_call_log()
3091
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3092
r1 = trunk.commit('start')
3093
stacked_branch = trunk.branch.create_clone_on_transport(
3094
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3095
local = self.make_branch('local', format='1.9-rich-root')
3096
local.repository.fetch(stacked_branch.repository,
3097
stacked_branch.last_revision())
3100
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3103
super(TestRemoteBranchEffort, self).setUp()
3104
# Create a smart server that publishes whatever the backing VFS server
3106
self.smart_server = server.SmartTCPServer_for_testing()
3107
self.start_server(self.smart_server, self.get_server())
3108
# Log all HPSS calls into self.hpss_calls.
3109
_SmartClient.hooks.install_named_hook(
3110
'call', self.capture_hpss_call, None)
3111
self.hpss_calls = []
3113
def capture_hpss_call(self, params):
3114
self.hpss_calls.append(params.method)
3116
def test_copy_content_into_avoids_revision_history(self):
3117
local = self.make_branch('local')
3118
remote_backing_tree = self.make_branch_and_tree('remote')
3119
remote_backing_tree.commit("Commit.")
3120
remote_branch_url = self.smart_server.get_url() + 'remote'
3121
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3122
local.repository.fetch(remote_branch.repository)
3123
self.hpss_calls = []
3124
remote_branch.copy_content_into(local)
3125
self.assertFalse('Branch.revision_history' in self.hpss_calls)