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
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
2012
SmartServerRepositoryGetParentMap.no_extra_results = True
2014
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
2015
self.addCleanup(reset_values)
2017
def test_null_cached_missing_and_stop_key(self):
2018
self.setup_smart_server_with_call_log()
2019
# Make a branch with a single revision.
2020
builder = self.make_branch_builder('foo')
2021
builder.start_series()
2022
builder.build_snapshot('first', None, [
2023
('add', ('', 'root-id', 'directory', ''))])
2024
builder.finish_series()
2025
branch = builder.get_branch()
2026
repo = branch.repository
2027
self.assertIsInstance(repo, RemoteRepository)
2028
# Stop the server from sending extra results.
2029
self.disableExtraResults()
2031
self.addCleanup(repo.unlock)
2032
self.reset_smart_call_log()
2033
graph = repo.get_graph()
2034
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2035
# 'first' it will be a candidate for the stop_keys of subsequent
2036
# requests, and because 'null:' was queried but not returned it will be
2037
# cached as missing.
2038
self.assertEqual({'first': ('null:',)},
2039
graph.get_parent_map(['first', 'null:']))
2040
# Now query for another key. This request will pass along a recipe of
2041
# start and stop keys describing the already cached results, and this
2042
# recipe's revision count must be correct (or else it will trigger an
2043
# error from the server).
2044
self.assertEqual({}, graph.get_parent_map(['another-key']))
2045
# This assertion guards against disableExtraResults silently failing to
2046
# work, thus invalidating the test.
2047
self.assertLength(2, self.hpss_calls)
2049
def test_get_parent_map_gets_ghosts_from_result(self):
2050
# asking for a revision should negatively cache close ghosts in its
2052
self.setup_smart_server_with_call_log()
2053
tree = self.make_branch_and_memory_tree('foo')
2056
builder = treebuilder.TreeBuilder()
2057
builder.start_tree(tree)
2059
builder.finish_tree()
2060
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2061
rev_id = tree.commit('')
2065
self.addCleanup(tree.unlock)
2066
repo = tree.branch.repository
2067
self.assertIsInstance(repo, RemoteRepository)
2069
repo.get_parent_map([rev_id])
2070
self.reset_smart_call_log()
2071
# Now asking for rev_id's ghost parent should not make calls
2072
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2073
self.assertLength(0, self.hpss_calls)
2076
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2078
def test_allows_new_revisions(self):
2079
"""get_parent_map's results can be updated by commit."""
2080
smart_server = server.SmartTCPServer_for_testing()
2081
self.start_server(smart_server)
2082
self.make_branch('branch')
2083
branch = Branch.open(smart_server.get_url() + '/branch')
2084
tree = branch.create_checkout('tree', lightweight=True)
2086
self.addCleanup(tree.unlock)
2087
graph = tree.branch.repository.get_graph()
2088
# This provides an opportunity for the missing rev-id to be cached.
2089
self.assertEqual({}, graph.get_parent_map(['rev1']))
2090
tree.commit('message', rev_id='rev1')
2091
graph = tree.branch.repository.get_graph()
2092
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2095
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2097
def test_null_revision(self):
2098
# a null revision has the predictable result {}, we should have no wire
2099
# traffic when calling it with this argument
2100
transport_path = 'empty'
2101
repo, client = self.setup_fake_client_and_repository(transport_path)
2102
client.add_success_response('notused')
2103
# actual RemoteRepository.get_revision_graph is gone, but there's an
2104
# equivalent private method for testing
2105
result = repo._get_revision_graph(NULL_REVISION)
2106
self.assertEqual([], client._calls)
2107
self.assertEqual({}, result)
2109
def test_none_revision(self):
2110
# with none we want the entire graph
2111
r1 = u'\u0e33'.encode('utf8')
2112
r2 = u'\u0dab'.encode('utf8')
2113
lines = [' '.join([r2, r1]), r1]
2114
encoded_body = '\n'.join(lines)
2116
transport_path = 'sinhala'
2117
repo, client = self.setup_fake_client_and_repository(transport_path)
2118
client.add_success_response_with_body(encoded_body, 'ok')
2119
# actual RemoteRepository.get_revision_graph is gone, but there's an
2120
# equivalent private method for testing
2121
result = repo._get_revision_graph(None)
2123
[('call_expecting_body', 'Repository.get_revision_graph',
2126
self.assertEqual({r1: (), r2: (r1, )}, result)
2128
def test_specific_revision(self):
2129
# with a specific revision we want the graph for that
2130
# with none we want the entire graph
2131
r11 = u'\u0e33'.encode('utf8')
2132
r12 = u'\xc9'.encode('utf8')
2133
r2 = u'\u0dab'.encode('utf8')
2134
lines = [' '.join([r2, r11, r12]), r11, r12]
2135
encoded_body = '\n'.join(lines)
2137
transport_path = 'sinhala'
2138
repo, client = self.setup_fake_client_and_repository(transport_path)
2139
client.add_success_response_with_body(encoded_body, 'ok')
2140
result = repo._get_revision_graph(r2)
2142
[('call_expecting_body', 'Repository.get_revision_graph',
2145
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2147
def test_no_such_revision(self):
2149
transport_path = 'sinhala'
2150
repo, client = self.setup_fake_client_and_repository(transport_path)
2151
client.add_error_response('nosuchrevision', revid)
2152
# also check that the right revision is reported in the error
2153
self.assertRaises(errors.NoSuchRevision,
2154
repo._get_revision_graph, revid)
2156
[('call_expecting_body', 'Repository.get_revision_graph',
2157
('sinhala/', revid))],
2160
def test_unexpected_error(self):
2162
transport_path = 'sinhala'
2163
repo, client = self.setup_fake_client_and_repository(transport_path)
2164
client.add_error_response('AnUnexpectedError')
2165
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2166
repo._get_revision_graph, revid)
2167
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2170
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2173
repo, client = self.setup_fake_client_and_repository('quack')
2174
client.add_expected_call(
2175
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2176
'success', ('ok', 'rev-five'))
2177
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2178
self.assertEqual((True, 'rev-five'), result)
2179
self.assertFinished(client)
2181
def test_history_incomplete(self):
2182
repo, client = self.setup_fake_client_and_repository('quack')
2183
client.add_expected_call(
2184
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2185
'success', ('history-incomplete', 10, 'rev-ten'))
2186
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2187
self.assertEqual((False, (10, 'rev-ten')), result)
2188
self.assertFinished(client)
2190
def test_history_incomplete_with_fallback(self):
2191
"""A 'history-incomplete' response causes the fallback repository to be
2192
queried too, if one is set.
2194
# Make a repo with a fallback repo, both using a FakeClient.
2195
format = remote.response_tuple_to_repo_format(
2196
('yes', 'no', 'yes', 'fake-network-name'))
2197
repo, client = self.setup_fake_client_and_repository('quack')
2198
repo._format = format
2199
fallback_repo, ignored = self.setup_fake_client_and_repository(
2201
fallback_repo._client = client
2202
repo.add_fallback_repository(fallback_repo)
2203
# First the client should ask the primary repo
2204
client.add_expected_call(
2205
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2206
'success', ('history-incomplete', 2, 'rev-two'))
2207
# Then it should ask the fallback, using revno/revid from the
2208
# history-incomplete response as the known revno/revid.
2209
client.add_expected_call(
2210
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2211
'success', ('ok', 'rev-one'))
2212
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2213
self.assertEqual((True, 'rev-one'), result)
2214
self.assertFinished(client)
2216
def test_nosuchrevision(self):
2217
# 'nosuchrevision' is returned when the known-revid is not found in the
2218
# remote repo. The client translates that response to NoSuchRevision.
2219
repo, client = self.setup_fake_client_and_repository('quack')
2220
client.add_expected_call(
2221
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2222
'error', ('nosuchrevision', 'rev-foo'))
2224
errors.NoSuchRevision,
2225
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2226
self.assertFinished(client)
2228
def test_branch_fallback_locking(self):
2229
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2230
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2231
will be invoked, which will fail if the repo is unlocked.
2233
self.setup_smart_server_with_call_log()
2234
tree = self.make_branch_and_memory_tree('.')
2236
rev1 = tree.commit('First')
2237
rev2 = tree.commit('Second')
2239
branch = tree.branch
2240
self.assertFalse(branch.is_locked())
2241
self.reset_smart_call_log()
2242
verb = 'Repository.get_rev_id_for_revno'
2243
self.disable_verb(verb)
2244
self.assertEqual(rev1, branch.get_rev_id(1))
2245
self.assertLength(1, [call for call in self.hpss_calls if
2246
call.call.method == verb])
2249
class TestRepositoryIsShared(TestRemoteRepository):
2251
def test_is_shared(self):
2252
# ('yes', ) for Repository.is_shared -> 'True'.
2253
transport_path = 'quack'
2254
repo, client = self.setup_fake_client_and_repository(transport_path)
2255
client.add_success_response('yes')
2256
result = repo.is_shared()
2258
[('call', 'Repository.is_shared', ('quack/',))],
2260
self.assertEqual(True, result)
2262
def test_is_not_shared(self):
2263
# ('no', ) for Repository.is_shared -> 'False'.
2264
transport_path = 'qwack'
2265
repo, client = self.setup_fake_client_and_repository(transport_path)
2266
client.add_success_response('no')
2267
result = repo.is_shared()
2269
[('call', 'Repository.is_shared', ('qwack/',))],
2271
self.assertEqual(False, result)
2274
class TestRepositoryLockWrite(TestRemoteRepository):
2276
def test_lock_write(self):
2277
transport_path = 'quack'
2278
repo, client = self.setup_fake_client_and_repository(transport_path)
2279
client.add_success_response('ok', 'a token')
2280
result = repo.lock_write()
2282
[('call', 'Repository.lock_write', ('quack/', ''))],
2284
self.assertEqual('a token', result)
2286
def test_lock_write_already_locked(self):
2287
transport_path = 'quack'
2288
repo, client = self.setup_fake_client_and_repository(transport_path)
2289
client.add_error_response('LockContention')
2290
self.assertRaises(errors.LockContention, repo.lock_write)
2292
[('call', 'Repository.lock_write', ('quack/', ''))],
2295
def test_lock_write_unlockable(self):
2296
transport_path = 'quack'
2297
repo, client = self.setup_fake_client_and_repository(transport_path)
2298
client.add_error_response('UnlockableTransport')
2299
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2301
[('call', 'Repository.lock_write', ('quack/', ''))],
2305
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2307
def test_backwards_compat(self):
2308
self.setup_smart_server_with_call_log()
2309
repo = self.make_repository('.')
2310
self.reset_smart_call_log()
2311
verb = 'Repository.set_make_working_trees'
2312
self.disable_verb(verb)
2313
repo.set_make_working_trees(True)
2314
call_count = len([call for call in self.hpss_calls if
2315
call.call.method == verb])
2316
self.assertEqual(1, call_count)
2318
def test_current(self):
2319
transport_path = 'quack'
2320
repo, client = self.setup_fake_client_and_repository(transport_path)
2321
client.add_expected_call(
2322
'Repository.set_make_working_trees', ('quack/', 'True'),
2324
client.add_expected_call(
2325
'Repository.set_make_working_trees', ('quack/', 'False'),
2327
repo.set_make_working_trees(True)
2328
repo.set_make_working_trees(False)
2331
class TestRepositoryUnlock(TestRemoteRepository):
2333
def test_unlock(self):
2334
transport_path = 'quack'
2335
repo, client = self.setup_fake_client_and_repository(transport_path)
2336
client.add_success_response('ok', 'a token')
2337
client.add_success_response('ok')
2341
[('call', 'Repository.lock_write', ('quack/', '')),
2342
('call', 'Repository.unlock', ('quack/', 'a token'))],
2345
def test_unlock_wrong_token(self):
2346
# If somehow the token is wrong, unlock will raise TokenMismatch.
2347
transport_path = 'quack'
2348
repo, client = self.setup_fake_client_and_repository(transport_path)
2349
client.add_success_response('ok', 'a token')
2350
client.add_error_response('TokenMismatch')
2352
self.assertRaises(errors.TokenMismatch, repo.unlock)
2355
class TestRepositoryHasRevision(TestRemoteRepository):
2357
def test_none(self):
2358
# repo.has_revision(None) should not cause any traffic.
2359
transport_path = 'quack'
2360
repo, client = self.setup_fake_client_and_repository(transport_path)
2362
# The null revision is always there, so has_revision(None) == True.
2363
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2365
# The remote repo shouldn't be accessed.
2366
self.assertEqual([], client._calls)
2369
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2370
"""Base class for Repository.insert_stream and .insert_stream_1.19
2374
def checkInsertEmptyStream(self, repo, client):
2375
"""Insert an empty stream, checking the result.
2377
This checks that there are no resume_tokens or missing_keys, and that
2378
the client is finished.
2380
sink = repo._get_sink()
2381
fmt = repository.RepositoryFormat.get_default_format()
2382
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2383
self.assertEqual([], resume_tokens)
2384
self.assertEqual(set(), missing_keys)
2385
self.assertFinished(client)
2388
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2389
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2392
This test case is very similar to TestRepositoryInsertStream_1_19.
2396
TestRemoteRepository.setUp(self)
2397
self.disable_verb('Repository.insert_stream_1.19')
2399
def test_unlocked_repo(self):
2400
transport_path = 'quack'
2401
repo, client = self.setup_fake_client_and_repository(transport_path)
2402
client.add_expected_call(
2403
'Repository.insert_stream_1.19', ('quack/', ''),
2404
'unknown', ('Repository.insert_stream_1.19',))
2405
client.add_expected_call(
2406
'Repository.insert_stream', ('quack/', ''),
2408
client.add_expected_call(
2409
'Repository.insert_stream', ('quack/', ''),
2411
self.checkInsertEmptyStream(repo, client)
2413
def test_locked_repo_with_no_lock_token(self):
2414
transport_path = 'quack'
2415
repo, client = self.setup_fake_client_and_repository(transport_path)
2416
client.add_expected_call(
2417
'Repository.lock_write', ('quack/', ''),
2418
'success', ('ok', ''))
2419
client.add_expected_call(
2420
'Repository.insert_stream_1.19', ('quack/', ''),
2421
'unknown', ('Repository.insert_stream_1.19',))
2422
client.add_expected_call(
2423
'Repository.insert_stream', ('quack/', ''),
2425
client.add_expected_call(
2426
'Repository.insert_stream', ('quack/', ''),
2429
self.checkInsertEmptyStream(repo, client)
2431
def test_locked_repo_with_lock_token(self):
2432
transport_path = 'quack'
2433
repo, client = self.setup_fake_client_and_repository(transport_path)
2434
client.add_expected_call(
2435
'Repository.lock_write', ('quack/', ''),
2436
'success', ('ok', 'a token'))
2437
client.add_expected_call(
2438
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2439
'unknown', ('Repository.insert_stream_1.19',))
2440
client.add_expected_call(
2441
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2443
client.add_expected_call(
2444
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2447
self.checkInsertEmptyStream(repo, client)
2449
def test_stream_with_inventory_deltas(self):
2450
"""'inventory-deltas' substreams cannot be sent to the
2451
Repository.insert_stream verb, because not all servers that implement
2452
that verb will accept them. So when one is encountered the RemoteSink
2453
immediately stops using that verb and falls back to VFS insert_stream.
2455
transport_path = 'quack'
2456
repo, client = self.setup_fake_client_and_repository(transport_path)
2457
client.add_expected_call(
2458
'Repository.insert_stream_1.19', ('quack/', ''),
2459
'unknown', ('Repository.insert_stream_1.19',))
2460
client.add_expected_call(
2461
'Repository.insert_stream', ('quack/', ''),
2463
client.add_expected_call(
2464
'Repository.insert_stream', ('quack/', ''),
2466
# Create a fake real repository for insert_stream to fall back on, so
2467
# that we can directly see the records the RemoteSink passes to the
2472
def insert_stream(self, stream, src_format, resume_tokens):
2473
for substream_kind, substream in stream:
2474
self.records.append(
2475
(substream_kind, [record.key for record in substream]))
2476
return ['fake tokens'], ['fake missing keys']
2477
fake_real_sink = FakeRealSink()
2478
class FakeRealRepository:
2479
def _get_sink(self):
2480
return fake_real_sink
2481
def is_in_write_group(self):
2483
def refresh_data(self):
2485
repo._real_repository = FakeRealRepository()
2486
sink = repo._get_sink()
2487
fmt = repository.RepositoryFormat.get_default_format()
2488
stream = self.make_stream_with_inv_deltas(fmt)
2489
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2490
# Every record from the first inventory delta should have been sent to
2492
expected_records = [
2493
('inventory-deltas', [('rev2',), ('rev3',)]),
2494
('texts', [('some-rev', 'some-file')])]
2495
self.assertEqual(expected_records, fake_real_sink.records)
2496
# The return values from the real sink's insert_stream are propagated
2497
# back to the original caller.
2498
self.assertEqual(['fake tokens'], resume_tokens)
2499
self.assertEqual(['fake missing keys'], missing_keys)
2500
self.assertFinished(client)
2502
def make_stream_with_inv_deltas(self, fmt):
2503
"""Make a simple stream with an inventory delta followed by more
2504
records and more substreams to test that all records and substreams
2505
from that point on are used.
2507
This sends, in order:
2508
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2510
* texts substream: (some-rev, some-file)
2512
# Define a stream using generators so that it isn't rewindable.
2513
inv = inventory.Inventory(revision_id='rev1')
2514
inv.root.revision = 'rev1'
2515
def stream_with_inv_delta():
2516
yield ('inventories', inventories_substream())
2517
yield ('inventory-deltas', inventory_delta_substream())
2519
versionedfile.FulltextContentFactory(
2520
('some-rev', 'some-file'), (), None, 'content')])
2521
def inventories_substream():
2522
# An empty inventory fulltext. This will be streamed normally.
2523
text = fmt._serializer.write_inventory_to_string(inv)
2524
yield versionedfile.FulltextContentFactory(
2525
('rev1',), (), None, text)
2526
def inventory_delta_substream():
2527
# An inventory delta. This can't be streamed via this verb, so it
2528
# will trigger a fallback to VFS insert_stream.
2529
entry = inv.make_entry(
2530
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2531
entry.revision = 'ghost'
2532
delta = [(None, 'newdir', 'newdir-id', entry)]
2533
serializer = inventory_delta.InventoryDeltaSerializer(
2534
versioned_root=True, tree_references=False)
2535
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2536
yield versionedfile.ChunkedContentFactory(
2537
('rev2',), (('rev1',)), None, lines)
2539
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2540
yield versionedfile.ChunkedContentFactory(
2541
('rev3',), (('rev1',)), None, lines)
2542
return stream_with_inv_delta()
2545
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2547
def test_unlocked_repo(self):
2548
transport_path = 'quack'
2549
repo, client = self.setup_fake_client_and_repository(transport_path)
2550
client.add_expected_call(
2551
'Repository.insert_stream_1.19', ('quack/', ''),
2553
client.add_expected_call(
2554
'Repository.insert_stream_1.19', ('quack/', ''),
2556
self.checkInsertEmptyStream(repo, client)
2558
def test_locked_repo_with_no_lock_token(self):
2559
transport_path = 'quack'
2560
repo, client = self.setup_fake_client_and_repository(transport_path)
2561
client.add_expected_call(
2562
'Repository.lock_write', ('quack/', ''),
2563
'success', ('ok', ''))
2564
client.add_expected_call(
2565
'Repository.insert_stream_1.19', ('quack/', ''),
2567
client.add_expected_call(
2568
'Repository.insert_stream_1.19', ('quack/', ''),
2571
self.checkInsertEmptyStream(repo, client)
2573
def test_locked_repo_with_lock_token(self):
2574
transport_path = 'quack'
2575
repo, client = self.setup_fake_client_and_repository(transport_path)
2576
client.add_expected_call(
2577
'Repository.lock_write', ('quack/', ''),
2578
'success', ('ok', 'a token'))
2579
client.add_expected_call(
2580
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2582
client.add_expected_call(
2583
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2586
self.checkInsertEmptyStream(repo, client)
2589
class TestRepositoryTarball(TestRemoteRepository):
2591
# This is a canned tarball reponse we can validate against
2593
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2594
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2595
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2596
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2597
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2598
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2599
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2600
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2601
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2602
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2603
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2604
'nWQ7QH/F3JFOFCQ0aSPfA='
2607
def test_repository_tarball(self):
2608
# Test that Repository.tarball generates the right operations
2609
transport_path = 'repo'
2610
expected_calls = [('call_expecting_body', 'Repository.tarball',
2611
('repo/', 'bz2',),),
2613
repo, client = self.setup_fake_client_and_repository(transport_path)
2614
client.add_success_response_with_body(self.tarball_content, 'ok')
2615
# Now actually ask for the tarball
2616
tarball_file = repo._get_tarball('bz2')
2618
self.assertEqual(expected_calls, client._calls)
2619
self.assertEqual(self.tarball_content, tarball_file.read())
2621
tarball_file.close()
2624
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2625
"""RemoteRepository.copy_content_into optimizations"""
2627
def test_copy_content_remote_to_local(self):
2628
self.transport_server = server.SmartTCPServer_for_testing
2629
src_repo = self.make_repository('repo1')
2630
src_repo = repository.Repository.open(self.get_url('repo1'))
2631
# At the moment the tarball-based copy_content_into can't write back
2632
# into a smart server. It would be good if it could upload the
2633
# tarball; once that works we'd have to create repositories of
2634
# different formats. -- mbp 20070410
2635
dest_url = self.get_vfs_only_url('repo2')
2636
dest_bzrdir = BzrDir.create(dest_url)
2637
dest_repo = dest_bzrdir.create_repository()
2638
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2639
self.assertTrue(isinstance(src_repo, RemoteRepository))
2640
src_repo.copy_content_into(dest_repo)
2643
class _StubRealPackRepository(object):
2645
def __init__(self, calls):
2647
self._pack_collection = _StubPackCollection(calls)
2649
def is_in_write_group(self):
2652
def refresh_data(self):
2653
self.calls.append(('pack collection reload_pack_names',))
2656
class _StubPackCollection(object):
2658
def __init__(self, calls):
2662
self.calls.append(('pack collection autopack',))
2665
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2666
"""Tests for RemoteRepository.autopack implementation."""
2669
"""When the server returns 'ok' and there's no _real_repository, then
2670
nothing else happens: the autopack method is done.
2672
transport_path = 'quack'
2673
repo, client = self.setup_fake_client_and_repository(transport_path)
2674
client.add_expected_call(
2675
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2677
self.assertFinished(client)
2679
def test_ok_with_real_repo(self):
2680
"""When the server returns 'ok' and there is a _real_repository, then
2681
the _real_repository's reload_pack_name's method will be called.
2683
transport_path = 'quack'
2684
repo, client = self.setup_fake_client_and_repository(transport_path)
2685
client.add_expected_call(
2686
'PackRepository.autopack', ('quack/',),
2688
repo._real_repository = _StubRealPackRepository(client._calls)
2691
[('call', 'PackRepository.autopack', ('quack/',)),
2692
('pack collection reload_pack_names',)],
2695
def test_backwards_compatibility(self):
2696
"""If the server does not recognise the PackRepository.autopack verb,
2697
fallback to the real_repository's implementation.
2699
transport_path = 'quack'
2700
repo, client = self.setup_fake_client_and_repository(transport_path)
2701
client.add_unknown_method_response('PackRepository.autopack')
2702
def stub_ensure_real():
2703
client._calls.append(('_ensure_real',))
2704
repo._real_repository = _StubRealPackRepository(client._calls)
2705
repo._ensure_real = stub_ensure_real
2708
[('call', 'PackRepository.autopack', ('quack/',)),
2710
('pack collection autopack',)],
2714
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2715
"""Base class for unit tests for bzrlib.remote._translate_error."""
2717
def translateTuple(self, error_tuple, **context):
2718
"""Call _translate_error with an ErrorFromSmartServer built from the
2721
:param error_tuple: A tuple of a smart server response, as would be
2722
passed to an ErrorFromSmartServer.
2723
:kwargs context: context items to call _translate_error with.
2725
:returns: The error raised by _translate_error.
2727
# Raise the ErrorFromSmartServer before passing it as an argument,
2728
# because _translate_error may need to re-raise it with a bare 'raise'
2730
server_error = errors.ErrorFromSmartServer(error_tuple)
2731
translated_error = self.translateErrorFromSmartServer(
2732
server_error, **context)
2733
return translated_error
2735
def translateErrorFromSmartServer(self, error_object, **context):
2736
"""Like translateTuple, but takes an already constructed
2737
ErrorFromSmartServer rather than a tuple.
2741
except errors.ErrorFromSmartServer, server_error:
2742
translated_error = self.assertRaises(
2743
errors.BzrError, remote._translate_error, server_error,
2745
return translated_error
2748
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2749
"""Unit tests for bzrlib.remote._translate_error.
2751
Given an ErrorFromSmartServer (which has an error tuple from a smart
2752
server) and some context, _translate_error raises more specific errors from
2755
This test case covers the cases where _translate_error succeeds in
2756
translating an ErrorFromSmartServer to something better. See
2757
TestErrorTranslationRobustness for other cases.
2760
def test_NoSuchRevision(self):
2761
branch = self.make_branch('')
2763
translated_error = self.translateTuple(
2764
('NoSuchRevision', revid), branch=branch)
2765
expected_error = errors.NoSuchRevision(branch, revid)
2766
self.assertEqual(expected_error, translated_error)
2768
def test_nosuchrevision(self):
2769
repository = self.make_repository('')
2771
translated_error = self.translateTuple(
2772
('nosuchrevision', revid), repository=repository)
2773
expected_error = errors.NoSuchRevision(repository, revid)
2774
self.assertEqual(expected_error, translated_error)
2776
def test_nobranch(self):
2777
bzrdir = self.make_bzrdir('')
2778
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2779
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2780
self.assertEqual(expected_error, translated_error)
2782
def test_nobranch_one_arg(self):
2783
bzrdir = self.make_bzrdir('')
2784
translated_error = self.translateTuple(
2785
('nobranch', 'extra detail'), bzrdir=bzrdir)
2786
expected_error = errors.NotBranchError(
2787
path=bzrdir.root_transport.base,
2788
detail='extra detail')
2789
self.assertEqual(expected_error, translated_error)
2791
def test_LockContention(self):
2792
translated_error = self.translateTuple(('LockContention',))
2793
expected_error = errors.LockContention('(remote lock)')
2794
self.assertEqual(expected_error, translated_error)
2796
def test_UnlockableTransport(self):
2797
bzrdir = self.make_bzrdir('')
2798
translated_error = self.translateTuple(
2799
('UnlockableTransport',), bzrdir=bzrdir)
2800
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2801
self.assertEqual(expected_error, translated_error)
2803
def test_LockFailed(self):
2804
lock = 'str() of a server lock'
2805
why = 'str() of why'
2806
translated_error = self.translateTuple(('LockFailed', lock, why))
2807
expected_error = errors.LockFailed(lock, why)
2808
self.assertEqual(expected_error, translated_error)
2810
def test_TokenMismatch(self):
2811
token = 'a lock token'
2812
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2813
expected_error = errors.TokenMismatch(token, '(remote token)')
2814
self.assertEqual(expected_error, translated_error)
2816
def test_Diverged(self):
2817
branch = self.make_branch('a')
2818
other_branch = self.make_branch('b')
2819
translated_error = self.translateTuple(
2820
('Diverged',), branch=branch, other_branch=other_branch)
2821
expected_error = errors.DivergedBranches(branch, other_branch)
2822
self.assertEqual(expected_error, translated_error)
2824
def test_ReadError_no_args(self):
2826
translated_error = self.translateTuple(('ReadError',), path=path)
2827
expected_error = errors.ReadError(path)
2828
self.assertEqual(expected_error, translated_error)
2830
def test_ReadError(self):
2832
translated_error = self.translateTuple(('ReadError', path))
2833
expected_error = errors.ReadError(path)
2834
self.assertEqual(expected_error, translated_error)
2836
def test_IncompatibleRepositories(self):
2837
translated_error = self.translateTuple(('IncompatibleRepositories',
2838
"repo1", "repo2", "details here"))
2839
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2841
self.assertEqual(expected_error, translated_error)
2843
def test_PermissionDenied_no_args(self):
2845
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2846
expected_error = errors.PermissionDenied(path)
2847
self.assertEqual(expected_error, translated_error)
2849
def test_PermissionDenied_one_arg(self):
2851
translated_error = self.translateTuple(('PermissionDenied', path))
2852
expected_error = errors.PermissionDenied(path)
2853
self.assertEqual(expected_error, translated_error)
2855
def test_PermissionDenied_one_arg_and_context(self):
2856
"""Given a choice between a path from the local context and a path on
2857
the wire, _translate_error prefers the path from the local context.
2859
local_path = 'local path'
2860
remote_path = 'remote path'
2861
translated_error = self.translateTuple(
2862
('PermissionDenied', remote_path), path=local_path)
2863
expected_error = errors.PermissionDenied(local_path)
2864
self.assertEqual(expected_error, translated_error)
2866
def test_PermissionDenied_two_args(self):
2868
extra = 'a string with extra info'
2869
translated_error = self.translateTuple(
2870
('PermissionDenied', path, extra))
2871
expected_error = errors.PermissionDenied(path, extra)
2872
self.assertEqual(expected_error, translated_error)
2875
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2876
"""Unit tests for bzrlib.remote._translate_error's robustness.
2878
TestErrorTranslationSuccess is for cases where _translate_error can
2879
translate successfully. This class about how _translate_err behaves when
2880
it fails to translate: it re-raises the original error.
2883
def test_unrecognised_server_error(self):
2884
"""If the error code from the server is not recognised, the original
2885
ErrorFromSmartServer is propagated unmodified.
2887
error_tuple = ('An unknown error tuple',)
2888
server_error = errors.ErrorFromSmartServer(error_tuple)
2889
translated_error = self.translateErrorFromSmartServer(server_error)
2890
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2891
self.assertEqual(expected_error, translated_error)
2893
def test_context_missing_a_key(self):
2894
"""In case of a bug in the client, or perhaps an unexpected response
2895
from a server, _translate_error returns the original error tuple from
2896
the server and mutters a warning.
2898
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2899
# in the context dict. So let's give it an empty context dict instead
2900
# to exercise its error recovery.
2902
error_tuple = ('NoSuchRevision', 'revid')
2903
server_error = errors.ErrorFromSmartServer(error_tuple)
2904
translated_error = self.translateErrorFromSmartServer(server_error)
2905
self.assertEqual(server_error, translated_error)
2906
# In addition to re-raising ErrorFromSmartServer, some debug info has
2907
# been muttered to the log file for developer to look at.
2908
self.assertContainsRe(
2910
"Missing key 'branch' in context")
2912
def test_path_missing(self):
2913
"""Some translations (PermissionDenied, ReadError) can determine the
2914
'path' variable from either the wire or the local context. If neither
2915
has it, then an error is raised.
2917
error_tuple = ('ReadError',)
2918
server_error = errors.ErrorFromSmartServer(error_tuple)
2919
translated_error = self.translateErrorFromSmartServer(server_error)
2920
self.assertEqual(server_error, translated_error)
2921
# In addition to re-raising ErrorFromSmartServer, some debug info has
2922
# been muttered to the log file for developer to look at.
2923
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
2926
class TestStacking(tests.TestCaseWithTransport):
2927
"""Tests for operations on stacked remote repositories.
2929
The underlying format type must support stacking.
2932
def test_access_stacked_remote(self):
2933
# based on <http://launchpad.net/bugs/261315>
2934
# make a branch stacked on another repository containing an empty
2935
# revision, then open it over hpss - we should be able to see that
2937
base_transport = self.get_transport()
2938
base_builder = self.make_branch_builder('base', format='1.9')
2939
base_builder.start_series()
2940
base_revid = base_builder.build_snapshot('rev-id', None,
2941
[('add', ('', None, 'directory', None))],
2943
base_builder.finish_series()
2944
stacked_branch = self.make_branch('stacked', format='1.9')
2945
stacked_branch.set_stacked_on_url('../base')
2946
# start a server looking at this
2947
smart_server = server.SmartTCPServer_for_testing()
2948
self.start_server(smart_server)
2949
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2950
# can get its branch and repository
2951
remote_branch = remote_bzrdir.open_branch()
2952
remote_repo = remote_branch.repository
2953
remote_repo.lock_read()
2955
# it should have an appropriate fallback repository, which should also
2956
# be a RemoteRepository
2957
self.assertLength(1, remote_repo._fallback_repositories)
2958
self.assertIsInstance(remote_repo._fallback_repositories[0],
2960
# and it has the revision committed to the underlying repository;
2961
# these have varying implementations so we try several of them
2962
self.assertTrue(remote_repo.has_revisions([base_revid]))
2963
self.assertTrue(remote_repo.has_revision(base_revid))
2964
self.assertEqual(remote_repo.get_revision(base_revid).message,
2967
remote_repo.unlock()
2969
def prepare_stacked_remote_branch(self):
2970
"""Get stacked_upon and stacked branches with content in each."""
2971
self.setup_smart_server_with_call_log()
2972
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2973
tree1.commit('rev1', rev_id='rev1')
2974
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2975
).open_workingtree()
2976
local_tree = tree2.branch.create_checkout('local')
2977
local_tree.commit('local changes make me feel good.')
2978
branch2 = Branch.open(self.get_url('tree2'))
2980
self.addCleanup(branch2.unlock)
2981
return tree1.branch, branch2
2983
def test_stacked_get_parent_map(self):
2984
# the public implementation of get_parent_map obeys stacking
2985
_, branch = self.prepare_stacked_remote_branch()
2986
repo = branch.repository
2987
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2989
def test_unstacked_get_parent_map(self):
2990
# _unstacked_provider.get_parent_map ignores stacking
2991
_, branch = self.prepare_stacked_remote_branch()
2992
provider = branch.repository._unstacked_provider
2993
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2995
def fetch_stream_to_rev_order(self, stream):
2997
for kind, substream in stream:
2998
if not kind == 'revisions':
3001
for content in substream:
3002
result.append(content.key[-1])
3005
def get_ordered_revs(self, format, order, branch_factory=None):
3006
"""Get a list of the revisions in a stream to format format.
3008
:param format: The format of the target.
3009
:param order: the order that target should have requested.
3010
:param branch_factory: A callable to create a trunk and stacked branch
3011
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3012
:result: The revision ids in the stream, in the order seen,
3013
the topological order of revisions in the source.
3015
unordered_format = bzrdir.format_registry.get(format)()
3016
target_repository_format = unordered_format.repository_format
3018
self.assertEqual(order, target_repository_format._fetch_order)
3019
if branch_factory is None:
3020
branch_factory = self.prepare_stacked_remote_branch
3021
_, stacked = branch_factory()
3022
source = stacked.repository._get_source(target_repository_format)
3023
tip = stacked.last_revision()
3024
revs = stacked.repository.get_ancestry(tip)
3025
search = graph.PendingAncestryResult([tip], stacked.repository)
3026
self.reset_smart_call_log()
3027
stream = source.get_stream(search)
3030
# We trust that if a revision is in the stream the rest of the new
3031
# content for it is too, as per our main fetch tests; here we are
3032
# checking that the revisions are actually included at all, and their
3034
return self.fetch_stream_to_rev_order(stream), revs
3036
def test_stacked_get_stream_unordered(self):
3037
# Repository._get_source.get_stream() from a stacked repository with
3038
# unordered yields the full data from both stacked and stacked upon
3040
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3041
self.assertEqual(set(expected_revs), set(rev_ord))
3042
# Getting unordered results should have made a streaming data request
3043
# from the server, then one from the backing branch.
3044
self.assertLength(2, self.hpss_calls)
3046
def test_stacked_on_stacked_get_stream_unordered(self):
3047
# Repository._get_source.get_stream() from a stacked repository which
3048
# is itself stacked yields the full data from all three sources.
3049
def make_stacked_stacked():
3050
_, stacked = self.prepare_stacked_remote_branch()
3051
tree = stacked.bzrdir.sprout('tree3', stacked=True
3052
).open_workingtree()
3053
local_tree = tree.branch.create_checkout('local-tree3')
3054
local_tree.commit('more local changes are better')
3055
branch = Branch.open(self.get_url('tree3'))
3057
self.addCleanup(branch.unlock)
3059
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3060
branch_factory=make_stacked_stacked)
3061
self.assertEqual(set(expected_revs), set(rev_ord))
3062
# Getting unordered results should have made a streaming data request
3063
# from the server, and one from each backing repo
3064
self.assertLength(3, self.hpss_calls)
3066
def test_stacked_get_stream_topological(self):
3067
# Repository._get_source.get_stream() from a stacked repository with
3068
# topological sorting yields the full data from both stacked and
3069
# stacked upon sources in topological order.
3070
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3071
self.assertEqual(expected_revs, rev_ord)
3072
# Getting topological sort requires VFS calls still - one of which is
3073
# pushing up from the bound branch.
3074
self.assertLength(13, self.hpss_calls)
3076
def test_stacked_get_stream_groupcompress(self):
3077
# Repository._get_source.get_stream() from a stacked repository with
3078
# groupcompress sorting yields the full data from both stacked and
3079
# stacked upon sources in groupcompress order.
3080
raise tests.TestSkipped('No groupcompress ordered format available')
3081
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3082
self.assertEqual(expected_revs, reversed(rev_ord))
3083
# Getting unordered results should have made a streaming data request
3084
# from the backing branch, and one from the stacked on branch.
3085
self.assertLength(2, self.hpss_calls)
3087
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3088
# When pulling some fixed amount of content that is more than the
3089
# source has (because some is coming from a fallback branch, no error
3090
# should be received. This was reported as bug 360791.
3091
# Need three branches: a trunk, a stacked branch, and a preexisting
3092
# branch pulling content from stacked and trunk.
3093
self.setup_smart_server_with_call_log()
3094
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3095
r1 = trunk.commit('start')
3096
stacked_branch = trunk.branch.create_clone_on_transport(
3097
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3098
local = self.make_branch('local', format='1.9-rich-root')
3099
local.repository.fetch(stacked_branch.repository,
3100
stacked_branch.last_revision())
3103
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3106
super(TestRemoteBranchEffort, self).setUp()
3107
# Create a smart server that publishes whatever the backing VFS server
3109
self.smart_server = server.SmartTCPServer_for_testing()
3110
self.start_server(self.smart_server, self.get_server())
3111
# Log all HPSS calls into self.hpss_calls.
3112
_SmartClient.hooks.install_named_hook(
3113
'call', self.capture_hpss_call, None)
3114
self.hpss_calls = []
3116
def capture_hpss_call(self, params):
3117
self.hpss_calls.append(params.method)
3119
def test_copy_content_into_avoids_revision_history(self):
3120
local = self.make_branch('local')
3121
remote_backing_tree = self.make_branch_and_tree('remote')
3122
remote_backing_tree.commit("Commit.")
3123
remote_branch_url = self.smart_server.get_url() + 'remote'
3124
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3125
local.repository.fetch(remote_branch.repository)
3126
self.hpss_calls = []
3127
remote_branch.copy_content_into(local)
3128
self.assertFalse('Branch.revision_history' in self.hpss_calls)