1
# Copyright (C) 2006-2011 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 (
51
from bzrlib.remote import (
57
RemoteRepositoryFormat,
59
from bzrlib.repofmt import groupcompress_repo, knitpack_repo
60
from bzrlib.revision import NULL_REVISION
61
from bzrlib.smart import medium, request
62
from bzrlib.smart.client import _SmartClient
63
from bzrlib.smart.repository import (
64
SmartServerRepositoryGetParentMap,
65
SmartServerRepositoryGetStream_1_19,
67
from bzrlib.tests import (
70
from bzrlib.tests.scenarios import load_tests_apply_scenarios
71
from bzrlib.transport.memory import MemoryTransport
72
from bzrlib.transport.remote import (
79
load_tests = load_tests_apply_scenarios
82
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
86
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
88
{'transport_server': test_server.SmartTCPServer_for_testing})]
92
super(BasicRemoteObjectTests, self).setUp()
93
self.transport = self.get_transport()
94
# make a branch that can be opened over the smart transport
95
self.local_wt = BzrDir.create_standalone_workingtree('.')
96
self.addCleanup(self.transport.disconnect)
98
def test_create_remote_bzrdir(self):
99
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
100
self.assertIsInstance(b, BzrDir)
102
def test_open_remote_branch(self):
103
# open a standalone branch in the working directory
104
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
105
branch = b.open_branch()
106
self.assertIsInstance(branch, Branch)
108
def test_remote_repository(self):
109
b = BzrDir.open_from_transport(self.transport)
110
repo = b.open_repository()
111
revid = u'\xc823123123'.encode('utf8')
112
self.assertFalse(repo.has_revision(revid))
113
self.local_wt.commit(message='test commit', rev_id=revid)
114
self.assertTrue(repo.has_revision(revid))
116
def test_remote_branch_revision_history(self):
117
b = BzrDir.open_from_transport(self.transport).open_branch()
118
self.assertEqual([], b.revision_history())
119
r1 = self.local_wt.commit('1st commit')
120
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
121
self.assertEqual([r1, r2], b.revision_history())
123
def test_find_correct_format(self):
124
"""Should open a RemoteBzrDir over a RemoteTransport"""
125
fmt = BzrDirFormat.find_format(self.transport)
126
self.assertTrue(bzrdir.RemoteBzrProber
127
in controldir.ControlDirFormat._server_probers)
128
self.assertIsInstance(fmt, RemoteBzrDirFormat)
130
def test_open_detected_smart_format(self):
131
fmt = BzrDirFormat.find_format(self.transport)
132
d = fmt.open(self.transport)
133
self.assertIsInstance(d, BzrDir)
135
def test_remote_branch_repr(self):
136
b = BzrDir.open_from_transport(self.transport).open_branch()
137
self.assertStartsWith(str(b), 'RemoteBranch(')
139
def test_remote_bzrdir_repr(self):
140
b = BzrDir.open_from_transport(self.transport)
141
self.assertStartsWith(str(b), 'RemoteBzrDir(')
143
def test_remote_branch_format_supports_stacking(self):
145
self.make_branch('unstackable', format='pack-0.92')
146
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
147
self.assertFalse(b._format.supports_stacking())
148
self.make_branch('stackable', format='1.9')
149
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
150
self.assertTrue(b._format.supports_stacking())
152
def test_remote_repo_format_supports_external_references(self):
154
bd = self.make_bzrdir('unstackable', format='pack-0.92')
155
r = bd.create_repository()
156
self.assertFalse(r._format.supports_external_lookups)
157
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
158
self.assertFalse(r._format.supports_external_lookups)
159
bd = self.make_bzrdir('stackable', format='1.9')
160
r = bd.create_repository()
161
self.assertTrue(r._format.supports_external_lookups)
162
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
163
self.assertTrue(r._format.supports_external_lookups)
165
def test_remote_branch_set_append_revisions_only(self):
166
# Make a format 1.9 branch, which supports append_revisions_only
167
branch = self.make_branch('branch', format='1.9')
168
config = branch.get_config()
169
branch.set_append_revisions_only(True)
171
'True', config.get_user_option('append_revisions_only'))
172
branch.set_append_revisions_only(False)
174
'False', config.get_user_option('append_revisions_only'))
176
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
177
branch = self.make_branch('branch', format='knit')
178
config = branch.get_config()
180
errors.UpgradeRequired, branch.set_append_revisions_only, True)
183
class FakeProtocol(object):
184
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
186
def __init__(self, body, fake_client):
188
self._body_buffer = None
189
self._fake_client = fake_client
191
def read_body_bytes(self, count=-1):
192
if self._body_buffer is None:
193
self._body_buffer = StringIO(self.body)
194
bytes = self._body_buffer.read(count)
195
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
196
self._fake_client.expecting_body = False
199
def cancel_read_body(self):
200
self._fake_client.expecting_body = False
202
def read_streamed_body(self):
206
class FakeClient(_SmartClient):
207
"""Lookalike for _SmartClient allowing testing."""
209
def __init__(self, fake_medium_base='fake base'):
210
"""Create a FakeClient."""
213
self.expecting_body = False
214
# if non-None, this is the list of expected calls, with only the
215
# method name and arguments included. the body might be hard to
216
# compute so is not included. If a call is None, that call can
218
self._expected_calls = None
219
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
221
def add_expected_call(self, call_name, call_args, response_type,
222
response_args, response_body=None):
223
if self._expected_calls is None:
224
self._expected_calls = []
225
self._expected_calls.append((call_name, call_args))
226
self.responses.append((response_type, response_args, response_body))
228
def add_success_response(self, *args):
229
self.responses.append(('success', args, None))
231
def add_success_response_with_body(self, body, *args):
232
self.responses.append(('success', args, body))
233
if self._expected_calls is not None:
234
self._expected_calls.append(None)
236
def add_error_response(self, *args):
237
self.responses.append(('error', args))
239
def add_unknown_method_response(self, verb):
240
self.responses.append(('unknown', verb))
242
def finished_test(self):
243
if self._expected_calls:
244
raise AssertionError("%r finished but was still expecting %r"
245
% (self, self._expected_calls[0]))
247
def _get_next_response(self):
249
response_tuple = self.responses.pop(0)
250
except IndexError, e:
251
raise AssertionError("%r didn't expect any more calls"
253
if response_tuple[0] == 'unknown':
254
raise errors.UnknownSmartMethod(response_tuple[1])
255
elif response_tuple[0] == 'error':
256
raise errors.ErrorFromSmartServer(response_tuple[1])
257
return response_tuple
259
def _check_call(self, method, args):
260
if self._expected_calls is None:
261
# the test should be updated to say what it expects
264
next_call = self._expected_calls.pop(0)
266
raise AssertionError("%r didn't expect any more calls "
268
% (self, method, args,))
269
if next_call is None:
271
if method != next_call[0] or args != next_call[1]:
272
raise AssertionError("%r expected %r%r "
274
% (self, next_call[0], next_call[1], method, args,))
276
def call(self, method, *args):
277
self._check_call(method, args)
278
self._calls.append(('call', method, args))
279
return self._get_next_response()[1]
281
def call_expecting_body(self, method, *args):
282
self._check_call(method, args)
283
self._calls.append(('call_expecting_body', method, args))
284
result = self._get_next_response()
285
self.expecting_body = True
286
return result[1], FakeProtocol(result[2], self)
288
def call_with_body_bytes(self, method, args, body):
289
self._check_call(method, args)
290
self._calls.append(('call_with_body_bytes', method, args, body))
291
result = self._get_next_response()
292
return result[1], FakeProtocol(result[2], self)
294
def call_with_body_bytes_expecting_body(self, method, args, body):
295
self._check_call(method, args)
296
self._calls.append(('call_with_body_bytes_expecting_body', method,
298
result = self._get_next_response()
299
self.expecting_body = True
300
return result[1], FakeProtocol(result[2], self)
302
def call_with_body_stream(self, args, stream):
303
# Explicitly consume the stream before checking for an error, because
304
# that's what happens a real medium.
305
stream = list(stream)
306
self._check_call(args[0], args[1:])
307
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
308
result = self._get_next_response()
309
# The second value returned from call_with_body_stream is supposed to
310
# be a response_handler object, but so far no tests depend on that.
311
response_handler = None
312
return result[1], response_handler
315
class FakeMedium(medium.SmartClientMedium):
317
def __init__(self, client_calls, base):
318
medium.SmartClientMedium.__init__(self, base)
319
self._client_calls = client_calls
321
def disconnect(self):
322
self._client_calls.append(('disconnect medium',))
325
class TestVfsHas(tests.TestCase):
327
def test_unicode_path(self):
328
client = FakeClient('/')
329
client.add_success_response('yes',)
330
transport = RemoteTransport('bzr://localhost/', _client=client)
331
filename = u'/hell\u00d8'.encode('utf8')
332
result = transport.has(filename)
334
[('call', 'has', (filename,))],
336
self.assertTrue(result)
339
class TestRemote(tests.TestCaseWithMemoryTransport):
341
def get_branch_format(self):
342
reference_bzrdir_format = bzrdir.format_registry.get('default')()
343
return reference_bzrdir_format.get_branch_format()
345
def get_repo_format(self):
346
reference_bzrdir_format = bzrdir.format_registry.get('default')()
347
return reference_bzrdir_format.repository_format
349
def assertFinished(self, fake_client):
350
"""Assert that all of a FakeClient's expected calls have occurred."""
351
fake_client.finished_test()
354
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
355
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
357
def assertRemotePath(self, expected, client_base, transport_base):
358
"""Assert that the result of
359
SmartClientMedium.remote_path_from_transport is the expected value for
360
a given client_base and transport_base.
362
client_medium = medium.SmartClientMedium(client_base)
363
t = transport.get_transport(transport_base)
364
result = client_medium.remote_path_from_transport(t)
365
self.assertEqual(expected, result)
367
def test_remote_path_from_transport(self):
368
"""SmartClientMedium.remote_path_from_transport calculates a URL for
369
the given transport relative to the root of the client base URL.
371
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
372
self.assertRemotePath(
373
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
375
def assertRemotePathHTTP(self, expected, transport_base, relpath):
376
"""Assert that the result of
377
HttpTransportBase.remote_path_from_transport is the expected value for
378
a given transport_base and relpath of that transport. (Note that
379
HttpTransportBase is a subclass of SmartClientMedium)
381
base_transport = transport.get_transport(transport_base)
382
client_medium = base_transport.get_smart_medium()
383
cloned_transport = base_transport.clone(relpath)
384
result = client_medium.remote_path_from_transport(cloned_transport)
385
self.assertEqual(expected, result)
387
def test_remote_path_from_transport_http(self):
388
"""Remote paths for HTTP transports are calculated differently to other
389
transports. They are just relative to the client base, not the root
390
directory of the host.
392
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
393
self.assertRemotePathHTTP(
394
'../xyz/', scheme + '//host/path', '../xyz/')
395
self.assertRemotePathHTTP(
396
'xyz/', scheme + '//host/path', 'xyz/')
399
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
400
"""Tests for the behaviour of client_medium.remote_is_at_least."""
402
def test_initially_unlimited(self):
403
"""A fresh medium assumes that the remote side supports all
406
client_medium = medium.SmartClientMedium('dummy base')
407
self.assertFalse(client_medium._is_remote_before((99, 99)))
409
def test__remember_remote_is_before(self):
410
"""Calling _remember_remote_is_before ratchets down the known remote
413
client_medium = medium.SmartClientMedium('dummy base')
414
# Mark the remote side as being less than 1.6. The remote side may
416
client_medium._remember_remote_is_before((1, 6))
417
self.assertTrue(client_medium._is_remote_before((1, 6)))
418
self.assertFalse(client_medium._is_remote_before((1, 5)))
419
# Calling _remember_remote_is_before again with a lower value works.
420
client_medium._remember_remote_is_before((1, 5))
421
self.assertTrue(client_medium._is_remote_before((1, 5)))
422
# If you call _remember_remote_is_before with a higher value it logs a
423
# warning, and continues to remember the lower value.
424
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
425
client_medium._remember_remote_is_before((1, 9))
426
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
427
self.assertTrue(client_medium._is_remote_before((1, 5)))
430
class TestBzrDirCloningMetaDir(TestRemote):
432
def test_backwards_compat(self):
433
self.setup_smart_server_with_call_log()
434
a_dir = self.make_bzrdir('.')
435
self.reset_smart_call_log()
436
verb = 'BzrDir.cloning_metadir'
437
self.disable_verb(verb)
438
format = a_dir.cloning_metadir()
439
call_count = len([call for call in self.hpss_calls if
440
call.call.method == verb])
441
self.assertEqual(1, call_count)
443
def test_branch_reference(self):
444
transport = self.get_transport('quack')
445
referenced = self.make_branch('referenced')
446
expected = referenced.bzrdir.cloning_metadir()
447
client = FakeClient(transport.base)
448
client.add_expected_call(
449
'BzrDir.cloning_metadir', ('quack/', 'False'),
450
'error', ('BranchReference',)),
451
client.add_expected_call(
452
'BzrDir.open_branchV3', ('quack/',),
453
'success', ('ref', self.get_url('referenced'))),
454
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
456
result = a_bzrdir.cloning_metadir()
457
# We should have got a control dir matching the referenced branch.
458
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
459
self.assertEqual(expected._repository_format, result._repository_format)
460
self.assertEqual(expected._branch_format, result._branch_format)
461
self.assertFinished(client)
463
def test_current_server(self):
464
transport = self.get_transport('.')
465
transport = transport.clone('quack')
466
self.make_bzrdir('quack')
467
client = FakeClient(transport.base)
468
reference_bzrdir_format = bzrdir.format_registry.get('default')()
469
control_name = reference_bzrdir_format.network_name()
470
client.add_expected_call(
471
'BzrDir.cloning_metadir', ('quack/', 'False'),
472
'success', (control_name, '', ('branch', ''))),
473
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
475
result = a_bzrdir.cloning_metadir()
476
# We should have got a reference control dir with default branch and
477
# repository formats.
478
# This pokes a little, just to be sure.
479
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
480
self.assertEqual(None, result._repository_format)
481
self.assertEqual(None, result._branch_format)
482
self.assertFinished(client)
485
class TestBzrDirOpen(TestRemote):
487
def make_fake_client_and_transport(self, path='quack'):
488
transport = MemoryTransport()
489
transport.mkdir(path)
490
transport = transport.clone(path)
491
client = FakeClient(transport.base)
492
return client, transport
494
def test_absent(self):
495
client, transport = self.make_fake_client_and_transport()
496
client.add_expected_call(
497
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
498
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
499
RemoteBzrDirFormat(), _client=client, _force_probe=True)
500
self.assertFinished(client)
502
def test_present_without_workingtree(self):
503
client, transport = self.make_fake_client_and_transport()
504
client.add_expected_call(
505
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
506
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
507
_client=client, _force_probe=True)
508
self.assertIsInstance(bd, RemoteBzrDir)
509
self.assertFalse(bd.has_workingtree())
510
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
511
self.assertFinished(client)
513
def test_present_with_workingtree(self):
514
client, transport = self.make_fake_client_and_transport()
515
client.add_expected_call(
516
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
517
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
518
_client=client, _force_probe=True)
519
self.assertIsInstance(bd, RemoteBzrDir)
520
self.assertTrue(bd.has_workingtree())
521
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
522
self.assertFinished(client)
524
def test_backwards_compat(self):
525
client, transport = self.make_fake_client_and_transport()
526
client.add_expected_call(
527
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
528
client.add_expected_call(
529
'BzrDir.open', ('quack/',), 'success', ('yes',))
530
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
531
_client=client, _force_probe=True)
532
self.assertIsInstance(bd, RemoteBzrDir)
533
self.assertFinished(client)
535
def test_backwards_compat_hpss_v2(self):
536
client, transport = self.make_fake_client_and_transport()
537
# Monkey-patch fake client to simulate real-world behaviour with v2
538
# server: upon first RPC call detect the protocol version, and because
539
# the version is 2 also do _remember_remote_is_before((1, 6)) before
540
# continuing with the RPC.
541
orig_check_call = client._check_call
542
def check_call(method, args):
543
client._medium._protocol_version = 2
544
client._medium._remember_remote_is_before((1, 6))
545
client._check_call = orig_check_call
546
client._check_call(method, args)
547
client._check_call = check_call
548
client.add_expected_call(
549
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
550
client.add_expected_call(
551
'BzrDir.open', ('quack/',), 'success', ('yes',))
552
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
553
_client=client, _force_probe=True)
554
self.assertIsInstance(bd, RemoteBzrDir)
555
self.assertFinished(client)
558
class TestBzrDirOpenBranch(TestRemote):
560
def test_backwards_compat(self):
561
self.setup_smart_server_with_call_log()
562
self.make_branch('.')
563
a_dir = BzrDir.open(self.get_url('.'))
564
self.reset_smart_call_log()
565
verb = 'BzrDir.open_branchV3'
566
self.disable_verb(verb)
567
format = a_dir.open_branch()
568
call_count = len([call for call in self.hpss_calls if
569
call.call.method == verb])
570
self.assertEqual(1, call_count)
572
def test_branch_present(self):
573
reference_format = self.get_repo_format()
574
network_name = reference_format.network_name()
575
branch_network_name = self.get_branch_format().network_name()
576
transport = MemoryTransport()
577
transport.mkdir('quack')
578
transport = transport.clone('quack')
579
client = FakeClient(transport.base)
580
client.add_expected_call(
581
'BzrDir.open_branchV3', ('quack/',),
582
'success', ('branch', branch_network_name))
583
client.add_expected_call(
584
'BzrDir.find_repositoryV3', ('quack/',),
585
'success', ('ok', '', 'no', 'no', 'no', network_name))
586
client.add_expected_call(
587
'Branch.get_stacked_on_url', ('quack/',),
588
'error', ('NotStacked',))
589
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
591
result = bzrdir.open_branch()
592
self.assertIsInstance(result, RemoteBranch)
593
self.assertEqual(bzrdir, result.bzrdir)
594
self.assertFinished(client)
596
def test_branch_missing(self):
597
transport = MemoryTransport()
598
transport.mkdir('quack')
599
transport = transport.clone('quack')
600
client = FakeClient(transport.base)
601
client.add_error_response('nobranch')
602
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
604
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
606
[('call', 'BzrDir.open_branchV3', ('quack/',))],
609
def test__get_tree_branch(self):
610
# _get_tree_branch is a form of open_branch, but it should only ask for
611
# branch opening, not any other network requests.
613
def open_branch(name=None):
614
calls.append("Called")
616
transport = MemoryTransport()
617
# no requests on the network - catches other api calls being made.
618
client = FakeClient(transport.base)
619
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
621
# patch the open_branch call to record that it was called.
622
bzrdir.open_branch = open_branch
623
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
624
self.assertEqual(["Called"], calls)
625
self.assertEqual([], client._calls)
627
def test_url_quoting_of_path(self):
628
# Relpaths on the wire should not be URL-escaped. So "~" should be
629
# transmitted as "~", not "%7E".
630
transport = RemoteTCPTransport('bzr://localhost/~hello/')
631
client = FakeClient(transport.base)
632
reference_format = self.get_repo_format()
633
network_name = reference_format.network_name()
634
branch_network_name = self.get_branch_format().network_name()
635
client.add_expected_call(
636
'BzrDir.open_branchV3', ('~hello/',),
637
'success', ('branch', branch_network_name))
638
client.add_expected_call(
639
'BzrDir.find_repositoryV3', ('~hello/',),
640
'success', ('ok', '', 'no', 'no', 'no', network_name))
641
client.add_expected_call(
642
'Branch.get_stacked_on_url', ('~hello/',),
643
'error', ('NotStacked',))
644
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
646
result = bzrdir.open_branch()
647
self.assertFinished(client)
649
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
650
reference_format = self.get_repo_format()
651
network_name = reference_format.network_name()
652
transport = MemoryTransport()
653
transport.mkdir('quack')
654
transport = transport.clone('quack')
656
rich_response = 'yes'
660
subtree_response = 'yes'
662
subtree_response = 'no'
663
client = FakeClient(transport.base)
664
client.add_success_response(
665
'ok', '', rich_response, subtree_response, external_lookup,
667
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
669
result = bzrdir.open_repository()
671
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
673
self.assertIsInstance(result, RemoteRepository)
674
self.assertEqual(bzrdir, result.bzrdir)
675
self.assertEqual(rich_root, result._format.rich_root_data)
676
self.assertEqual(subtrees, result._format.supports_tree_reference)
678
def test_open_repository_sets_format_attributes(self):
679
self.check_open_repository(True, True)
680
self.check_open_repository(False, True)
681
self.check_open_repository(True, False)
682
self.check_open_repository(False, False)
683
self.check_open_repository(False, False, 'yes')
685
def test_old_server(self):
686
"""RemoteBzrDirFormat should fail to probe if the server version is too
689
self.assertRaises(errors.NotBranchError,
690
RemoteBzrProber.probe_transport, OldServerTransport())
693
class TestBzrDirCreateBranch(TestRemote):
695
def test_backwards_compat(self):
696
self.setup_smart_server_with_call_log()
697
repo = self.make_repository('.')
698
self.reset_smart_call_log()
699
self.disable_verb('BzrDir.create_branch')
700
branch = repo.bzrdir.create_branch()
701
create_branch_call_count = len([call for call in self.hpss_calls if
702
call.call.method == 'BzrDir.create_branch'])
703
self.assertEqual(1, create_branch_call_count)
705
def test_current_server(self):
706
transport = self.get_transport('.')
707
transport = transport.clone('quack')
708
self.make_repository('quack')
709
client = FakeClient(transport.base)
710
reference_bzrdir_format = bzrdir.format_registry.get('default')()
711
reference_format = reference_bzrdir_format.get_branch_format()
712
network_name = reference_format.network_name()
713
reference_repo_fmt = reference_bzrdir_format.repository_format
714
reference_repo_name = reference_repo_fmt.network_name()
715
client.add_expected_call(
716
'BzrDir.create_branch', ('quack/', network_name),
717
'success', ('ok', network_name, '', 'no', 'no', 'yes',
718
reference_repo_name))
719
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
721
branch = a_bzrdir.create_branch()
722
# We should have got a remote branch
723
self.assertIsInstance(branch, remote.RemoteBranch)
724
# its format should have the settings from the response
725
format = branch._format
726
self.assertEqual(network_name, format.network_name())
728
def test_already_open_repo_and_reused_medium(self):
729
"""Bug 726584: create_branch(..., repository=repo) should work
730
regardless of what the smart medium's base URL is.
732
self.transport_server = test_server.SmartTCPServer_for_testing
733
transport = self.get_transport('.')
734
repo = self.make_repository('quack')
735
# Client's medium rooted a transport root (not at the bzrdir)
736
client = FakeClient(transport.base)
737
transport = transport.clone('quack')
738
reference_bzrdir_format = bzrdir.format_registry.get('default')()
739
reference_format = reference_bzrdir_format.get_branch_format()
740
network_name = reference_format.network_name()
741
reference_repo_fmt = reference_bzrdir_format.repository_format
742
reference_repo_name = reference_repo_fmt.network_name()
743
client.add_expected_call(
744
'BzrDir.create_branch', ('extra/quack/', network_name),
745
'success', ('ok', network_name, '', 'no', 'no', 'yes',
746
reference_repo_name))
747
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
749
branch = a_bzrdir.create_branch(repository=repo)
750
# We should have got a remote branch
751
self.assertIsInstance(branch, remote.RemoteBranch)
752
# its format should have the settings from the response
753
format = branch._format
754
self.assertEqual(network_name, format.network_name())
757
class TestBzrDirCreateRepository(TestRemote):
759
def test_backwards_compat(self):
760
self.setup_smart_server_with_call_log()
761
bzrdir = self.make_bzrdir('.')
762
self.reset_smart_call_log()
763
self.disable_verb('BzrDir.create_repository')
764
repo = bzrdir.create_repository()
765
create_repo_call_count = len([call for call in self.hpss_calls if
766
call.call.method == 'BzrDir.create_repository'])
767
self.assertEqual(1, create_repo_call_count)
769
def test_current_server(self):
770
transport = self.get_transport('.')
771
transport = transport.clone('quack')
772
self.make_bzrdir('quack')
773
client = FakeClient(transport.base)
774
reference_bzrdir_format = bzrdir.format_registry.get('default')()
775
reference_format = reference_bzrdir_format.repository_format
776
network_name = reference_format.network_name()
777
client.add_expected_call(
778
'BzrDir.create_repository', ('quack/',
779
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
781
'success', ('ok', 'yes', 'yes', 'yes', network_name))
782
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
784
repo = a_bzrdir.create_repository()
785
# We should have got a remote repository
786
self.assertIsInstance(repo, remote.RemoteRepository)
787
# its format should have the settings from the response
788
format = repo._format
789
self.assertTrue(format.rich_root_data)
790
self.assertTrue(format.supports_tree_reference)
791
self.assertTrue(format.supports_external_lookups)
792
self.assertEqual(network_name, format.network_name())
795
class TestBzrDirOpenRepository(TestRemote):
797
def test_backwards_compat_1_2_3(self):
798
# fallback all the way to the first version.
799
reference_format = self.get_repo_format()
800
network_name = reference_format.network_name()
801
server_url = 'bzr://example.com/'
802
self.permit_url(server_url)
803
client = FakeClient(server_url)
804
client.add_unknown_method_response('BzrDir.find_repositoryV3')
805
client.add_unknown_method_response('BzrDir.find_repositoryV2')
806
client.add_success_response('ok', '', 'no', 'no')
807
# A real repository instance will be created to determine the network
809
client.add_success_response_with_body(
810
"Bazaar-NG meta directory, format 1\n", 'ok')
811
client.add_success_response_with_body(
812
reference_format.get_format_string(), 'ok')
813
# PackRepository wants to do a stat
814
client.add_success_response('stat', '0', '65535')
815
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
817
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
819
repo = bzrdir.open_repository()
821
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
822
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
823
('call', 'BzrDir.find_repository', ('quack/',)),
824
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
825
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
826
('call', 'stat', ('/quack/.bzr/repository',)),
829
self.assertEqual(network_name, repo._format.network_name())
831
def test_backwards_compat_2(self):
832
# fallback to find_repositoryV2
833
reference_format = self.get_repo_format()
834
network_name = reference_format.network_name()
835
server_url = 'bzr://example.com/'
836
self.permit_url(server_url)
837
client = FakeClient(server_url)
838
client.add_unknown_method_response('BzrDir.find_repositoryV3')
839
client.add_success_response('ok', '', 'no', 'no', 'no')
840
# A real repository instance will be created to determine the network
842
client.add_success_response_with_body(
843
"Bazaar-NG meta directory, format 1\n", 'ok')
844
client.add_success_response_with_body(
845
reference_format.get_format_string(), 'ok')
846
# PackRepository wants to do a stat
847
client.add_success_response('stat', '0', '65535')
848
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
850
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
852
repo = bzrdir.open_repository()
854
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
855
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
856
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
857
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
858
('call', 'stat', ('/quack/.bzr/repository',)),
861
self.assertEqual(network_name, repo._format.network_name())
863
def test_current_server(self):
864
reference_format = self.get_repo_format()
865
network_name = reference_format.network_name()
866
transport = MemoryTransport()
867
transport.mkdir('quack')
868
transport = transport.clone('quack')
869
client = FakeClient(transport.base)
870
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
871
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
873
repo = bzrdir.open_repository()
875
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
877
self.assertEqual(network_name, repo._format.network_name())
880
class TestBzrDirFormatInitializeEx(TestRemote):
882
def test_success(self):
883
"""Simple test for typical successful call."""
884
fmt = RemoteBzrDirFormat()
885
default_format_name = BzrDirFormat.get_default_format().network_name()
886
transport = self.get_transport()
887
client = FakeClient(transport.base)
888
client.add_expected_call(
889
'BzrDirFormat.initialize_ex_1.16',
890
(default_format_name, 'path', 'False', 'False', 'False', '',
891
'', '', '', 'False'),
893
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
894
'bzrdir fmt', 'False', '', '', 'repo lock token'))
895
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
896
# it's currently hard to test that without supplying a real remote
897
# transport connected to a real server.
898
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
899
transport, False, False, False, None, None, None, None, False)
900
self.assertFinished(client)
902
def test_error(self):
903
"""Error responses are translated, e.g. 'PermissionDenied' raises the
904
corresponding error from the client.
906
fmt = RemoteBzrDirFormat()
907
default_format_name = BzrDirFormat.get_default_format().network_name()
908
transport = self.get_transport()
909
client = FakeClient(transport.base)
910
client.add_expected_call(
911
'BzrDirFormat.initialize_ex_1.16',
912
(default_format_name, 'path', 'False', 'False', 'False', '',
913
'', '', '', 'False'),
915
('PermissionDenied', 'path', 'extra info'))
916
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
917
# it's currently hard to test that without supplying a real remote
918
# transport connected to a real server.
919
err = self.assertRaises(errors.PermissionDenied,
920
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
921
False, False, False, None, None, None, None, False)
922
self.assertEqual('path', err.path)
923
self.assertEqual(': extra info', err.extra)
924
self.assertFinished(client)
926
def test_error_from_real_server(self):
927
"""Integration test for error translation."""
928
transport = self.make_smart_server('foo')
929
transport = transport.clone('no-such-path')
930
fmt = RemoteBzrDirFormat()
931
err = self.assertRaises(errors.NoSuchFile,
932
fmt.initialize_on_transport_ex, transport, create_prefix=False)
935
class OldSmartClient(object):
936
"""A fake smart client for test_old_version that just returns a version one
937
response to the 'hello' (query version) command.
940
def get_request(self):
941
input_file = StringIO('ok\x011\n')
942
output_file = StringIO()
943
client_medium = medium.SmartSimplePipesClientMedium(
944
input_file, output_file)
945
return medium.SmartClientStreamMediumRequest(client_medium)
947
def protocol_version(self):
951
class OldServerTransport(object):
952
"""A fake transport for test_old_server that reports it's smart server
953
protocol version as version one.
959
def get_smart_client(self):
960
return OldSmartClient()
963
class RemoteBzrDirTestCase(TestRemote):
965
def make_remote_bzrdir(self, transport, client):
966
"""Make a RemotebzrDir using 'client' as the _client."""
967
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
971
class RemoteBranchTestCase(RemoteBzrDirTestCase):
973
def lock_remote_branch(self, branch):
974
"""Trick a RemoteBranch into thinking it is locked."""
975
branch._lock_mode = 'w'
976
branch._lock_count = 2
977
branch._lock_token = 'branch token'
978
branch._repo_lock_token = 'repo token'
979
branch.repository._lock_mode = 'w'
980
branch.repository._lock_count = 2
981
branch.repository._lock_token = 'repo token'
983
def make_remote_branch(self, transport, client):
984
"""Make a RemoteBranch using 'client' as its _SmartClient.
986
A RemoteBzrDir and RemoteRepository will also be created to fill out
987
the RemoteBranch, albeit with stub values for some of their attributes.
989
# we do not want bzrdir to make any remote calls, so use False as its
990
# _client. If it tries to make a remote call, this will fail
992
bzrdir = self.make_remote_bzrdir(transport, False)
993
repo = RemoteRepository(bzrdir, None, _client=client)
994
branch_format = self.get_branch_format()
995
format = RemoteBranchFormat(network_name=branch_format.network_name())
996
return RemoteBranch(bzrdir, repo, _client=client, format=format)
999
class TestBranchGetParent(RemoteBranchTestCase):
1001
def test_no_parent(self):
1002
# in an empty branch we decode the response properly
1003
transport = MemoryTransport()
1004
client = FakeClient(transport.base)
1005
client.add_expected_call(
1006
'Branch.get_stacked_on_url', ('quack/',),
1007
'error', ('NotStacked',))
1008
client.add_expected_call(
1009
'Branch.get_parent', ('quack/',),
1011
transport.mkdir('quack')
1012
transport = transport.clone('quack')
1013
branch = self.make_remote_branch(transport, client)
1014
result = branch.get_parent()
1015
self.assertFinished(client)
1016
self.assertEqual(None, result)
1018
def test_parent_relative(self):
1019
transport = MemoryTransport()
1020
client = FakeClient(transport.base)
1021
client.add_expected_call(
1022
'Branch.get_stacked_on_url', ('kwaak/',),
1023
'error', ('NotStacked',))
1024
client.add_expected_call(
1025
'Branch.get_parent', ('kwaak/',),
1026
'success', ('../foo/',))
1027
transport.mkdir('kwaak')
1028
transport = transport.clone('kwaak')
1029
branch = self.make_remote_branch(transport, client)
1030
result = branch.get_parent()
1031
self.assertEqual(transport.clone('../foo').base, result)
1033
def test_parent_absolute(self):
1034
transport = MemoryTransport()
1035
client = FakeClient(transport.base)
1036
client.add_expected_call(
1037
'Branch.get_stacked_on_url', ('kwaak/',),
1038
'error', ('NotStacked',))
1039
client.add_expected_call(
1040
'Branch.get_parent', ('kwaak/',),
1041
'success', ('http://foo/',))
1042
transport.mkdir('kwaak')
1043
transport = transport.clone('kwaak')
1044
branch = self.make_remote_branch(transport, client)
1045
result = branch.get_parent()
1046
self.assertEqual('http://foo/', result)
1047
self.assertFinished(client)
1050
class TestBranchSetParentLocation(RemoteBranchTestCase):
1052
def test_no_parent(self):
1053
# We call the verb when setting parent to None
1054
transport = MemoryTransport()
1055
client = FakeClient(transport.base)
1056
client.add_expected_call(
1057
'Branch.get_stacked_on_url', ('quack/',),
1058
'error', ('NotStacked',))
1059
client.add_expected_call(
1060
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1062
transport.mkdir('quack')
1063
transport = transport.clone('quack')
1064
branch = self.make_remote_branch(transport, client)
1065
branch._lock_token = 'b'
1066
branch._repo_lock_token = 'r'
1067
branch._set_parent_location(None)
1068
self.assertFinished(client)
1070
def test_parent(self):
1071
transport = MemoryTransport()
1072
client = FakeClient(transport.base)
1073
client.add_expected_call(
1074
'Branch.get_stacked_on_url', ('kwaak/',),
1075
'error', ('NotStacked',))
1076
client.add_expected_call(
1077
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1079
transport.mkdir('kwaak')
1080
transport = transport.clone('kwaak')
1081
branch = self.make_remote_branch(transport, client)
1082
branch._lock_token = 'b'
1083
branch._repo_lock_token = 'r'
1084
branch._set_parent_location('foo')
1085
self.assertFinished(client)
1087
def test_backwards_compat(self):
1088
self.setup_smart_server_with_call_log()
1089
branch = self.make_branch('.')
1090
self.reset_smart_call_log()
1091
verb = 'Branch.set_parent_location'
1092
self.disable_verb(verb)
1093
branch.set_parent('http://foo/')
1094
self.assertLength(12, self.hpss_calls)
1097
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1099
def test_backwards_compat(self):
1100
self.setup_smart_server_with_call_log()
1101
branch = self.make_branch('.')
1102
self.reset_smart_call_log()
1103
verb = 'Branch.get_tags_bytes'
1104
self.disable_verb(verb)
1105
branch.tags.get_tag_dict()
1106
call_count = len([call for call in self.hpss_calls if
1107
call.call.method == verb])
1108
self.assertEqual(1, call_count)
1110
def test_trivial(self):
1111
transport = MemoryTransport()
1112
client = FakeClient(transport.base)
1113
client.add_expected_call(
1114
'Branch.get_stacked_on_url', ('quack/',),
1115
'error', ('NotStacked',))
1116
client.add_expected_call(
1117
'Branch.get_tags_bytes', ('quack/',),
1119
transport.mkdir('quack')
1120
transport = transport.clone('quack')
1121
branch = self.make_remote_branch(transport, client)
1122
result = branch.tags.get_tag_dict()
1123
self.assertFinished(client)
1124
self.assertEqual({}, result)
1127
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1129
def test_trivial(self):
1130
transport = MemoryTransport()
1131
client = FakeClient(transport.base)
1132
client.add_expected_call(
1133
'Branch.get_stacked_on_url', ('quack/',),
1134
'error', ('NotStacked',))
1135
client.add_expected_call(
1136
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1138
transport.mkdir('quack')
1139
transport = transport.clone('quack')
1140
branch = self.make_remote_branch(transport, client)
1141
self.lock_remote_branch(branch)
1142
branch._set_tags_bytes('tags bytes')
1143
self.assertFinished(client)
1144
self.assertEqual('tags bytes', client._calls[-1][-1])
1146
def test_backwards_compatible(self):
1147
transport = MemoryTransport()
1148
client = FakeClient(transport.base)
1149
client.add_expected_call(
1150
'Branch.get_stacked_on_url', ('quack/',),
1151
'error', ('NotStacked',))
1152
client.add_expected_call(
1153
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1154
'unknown', ('Branch.set_tags_bytes',))
1155
transport.mkdir('quack')
1156
transport = transport.clone('quack')
1157
branch = self.make_remote_branch(transport, client)
1158
self.lock_remote_branch(branch)
1159
class StubRealBranch(object):
1162
def _set_tags_bytes(self, bytes):
1163
self.calls.append(('set_tags_bytes', bytes))
1164
real_branch = StubRealBranch()
1165
branch._real_branch = real_branch
1166
branch._set_tags_bytes('tags bytes')
1167
# Call a second time, to exercise the 'remote version already inferred'
1169
branch._set_tags_bytes('tags bytes')
1170
self.assertFinished(client)
1172
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1175
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1177
def test_uses_last_revision_info_and_tags_by_default(self):
1178
transport = MemoryTransport()
1179
client = FakeClient(transport.base)
1180
client.add_expected_call(
1181
'Branch.get_stacked_on_url', ('quack/',),
1182
'error', ('NotStacked',))
1183
client.add_expected_call(
1184
'Branch.last_revision_info', ('quack/',),
1185
'success', ('ok', '1', 'rev-tip'))
1186
# XXX: this will break if the default format's serialization of tags
1187
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1188
client.add_expected_call(
1189
'Branch.get_tags_bytes', ('quack/',),
1190
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1191
transport.mkdir('quack')
1192
transport = transport.clone('quack')
1193
branch = self.make_remote_branch(transport, client)
1194
result = branch.heads_to_fetch()
1195
self.assertFinished(client)
1197
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1199
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1200
transport = MemoryTransport()
1201
client = FakeClient(transport.base)
1202
client.add_expected_call(
1203
'Branch.get_stacked_on_url', ('quack/',),
1204
'error', ('NotStacked',))
1205
client.add_expected_call(
1206
'Branch.heads_to_fetch', ('quack/',),
1207
'success', (['tip'], ['tagged-1', 'tagged-2']))
1208
transport.mkdir('quack')
1209
transport = transport.clone('quack')
1210
branch = self.make_remote_branch(transport, client)
1211
branch._format._use_default_local_heads_to_fetch = lambda: False
1212
result = branch.heads_to_fetch()
1213
self.assertFinished(client)
1214
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1216
def test_backwards_compatible(self):
1217
self.setup_smart_server_with_call_log()
1218
# Make a branch with a single revision.
1219
builder = self.make_branch_builder('foo')
1220
builder.start_series()
1221
builder.build_snapshot('tip', None, [
1222
('add', ('', 'root-id', 'directory', ''))])
1223
builder.finish_series()
1224
branch = builder.get_branch()
1225
# Add two tags to that branch
1226
branch.tags.set_tag('tag-1', 'rev-1')
1227
branch.tags.set_tag('tag-2', 'rev-2')
1228
self.addCleanup(branch.lock_read().unlock)
1229
# Disable the heads_to_fetch verb
1230
verb = 'Branch.heads_to_fetch'
1231
self.disable_verb(verb)
1232
self.reset_smart_call_log()
1233
result = branch.heads_to_fetch()
1234
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1236
['Branch.last_revision_info', 'Branch.get_tags_bytes'],
1237
[call.call.method for call in self.hpss_calls])
1240
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1242
def test_empty_branch(self):
1243
# in an empty branch we decode the response properly
1244
transport = MemoryTransport()
1245
client = FakeClient(transport.base)
1246
client.add_expected_call(
1247
'Branch.get_stacked_on_url', ('quack/',),
1248
'error', ('NotStacked',))
1249
client.add_expected_call(
1250
'Branch.last_revision_info', ('quack/',),
1251
'success', ('ok', '0', 'null:'))
1252
transport.mkdir('quack')
1253
transport = transport.clone('quack')
1254
branch = self.make_remote_branch(transport, client)
1255
result = branch.last_revision_info()
1256
self.assertFinished(client)
1257
self.assertEqual((0, NULL_REVISION), result)
1259
def test_non_empty_branch(self):
1260
# in a non-empty branch we also decode the response properly
1261
revid = u'\xc8'.encode('utf8')
1262
transport = MemoryTransport()
1263
client = FakeClient(transport.base)
1264
client.add_expected_call(
1265
'Branch.get_stacked_on_url', ('kwaak/',),
1266
'error', ('NotStacked',))
1267
client.add_expected_call(
1268
'Branch.last_revision_info', ('kwaak/',),
1269
'success', ('ok', '2', revid))
1270
transport.mkdir('kwaak')
1271
transport = transport.clone('kwaak')
1272
branch = self.make_remote_branch(transport, client)
1273
result = branch.last_revision_info()
1274
self.assertEqual((2, revid), result)
1277
class TestBranch_get_stacked_on_url(TestRemote):
1278
"""Test Branch._get_stacked_on_url rpc"""
1280
def test_get_stacked_on_invalid_url(self):
1281
# test that asking for a stacked on url the server can't access works.
1282
# This isn't perfect, but then as we're in the same process there
1283
# really isn't anything we can do to be 100% sure that the server
1284
# doesn't just open in - this test probably needs to be rewritten using
1285
# a spawn()ed server.
1286
stacked_branch = self.make_branch('stacked', format='1.9')
1287
memory_branch = self.make_branch('base', format='1.9')
1288
vfs_url = self.get_vfs_only_url('base')
1289
stacked_branch.set_stacked_on_url(vfs_url)
1290
transport = stacked_branch.bzrdir.root_transport
1291
client = FakeClient(transport.base)
1292
client.add_expected_call(
1293
'Branch.get_stacked_on_url', ('stacked/',),
1294
'success', ('ok', vfs_url))
1295
# XXX: Multiple calls are bad, this second call documents what is
1297
client.add_expected_call(
1298
'Branch.get_stacked_on_url', ('stacked/',),
1299
'success', ('ok', vfs_url))
1300
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1302
repo_fmt = remote.RemoteRepositoryFormat()
1303
repo_fmt._custom_format = stacked_branch.repository._format
1304
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1306
result = branch.get_stacked_on_url()
1307
self.assertEqual(vfs_url, result)
1309
def test_backwards_compatible(self):
1310
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1311
base_branch = self.make_branch('base', format='1.6')
1312
stacked_branch = self.make_branch('stacked', format='1.6')
1313
stacked_branch.set_stacked_on_url('../base')
1314
client = FakeClient(self.get_url())
1315
branch_network_name = self.get_branch_format().network_name()
1316
client.add_expected_call(
1317
'BzrDir.open_branchV3', ('stacked/',),
1318
'success', ('branch', branch_network_name))
1319
client.add_expected_call(
1320
'BzrDir.find_repositoryV3', ('stacked/',),
1321
'success', ('ok', '', 'no', 'no', 'yes',
1322
stacked_branch.repository._format.network_name()))
1323
# called twice, once from constructor and then again by us
1324
client.add_expected_call(
1325
'Branch.get_stacked_on_url', ('stacked/',),
1326
'unknown', ('Branch.get_stacked_on_url',))
1327
client.add_expected_call(
1328
'Branch.get_stacked_on_url', ('stacked/',),
1329
'unknown', ('Branch.get_stacked_on_url',))
1330
# this will also do vfs access, but that goes direct to the transport
1331
# and isn't seen by the FakeClient.
1332
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1333
RemoteBzrDirFormat(), _client=client)
1334
branch = bzrdir.open_branch()
1335
result = branch.get_stacked_on_url()
1336
self.assertEqual('../base', result)
1337
self.assertFinished(client)
1338
# it's in the fallback list both for the RemoteRepository and its vfs
1340
self.assertEqual(1, len(branch.repository._fallback_repositories))
1342
len(branch.repository._real_repository._fallback_repositories))
1344
def test_get_stacked_on_real_branch(self):
1345
base_branch = self.make_branch('base')
1346
stacked_branch = self.make_branch('stacked')
1347
stacked_branch.set_stacked_on_url('../base')
1348
reference_format = self.get_repo_format()
1349
network_name = reference_format.network_name()
1350
client = FakeClient(self.get_url())
1351
branch_network_name = self.get_branch_format().network_name()
1352
client.add_expected_call(
1353
'BzrDir.open_branchV3', ('stacked/',),
1354
'success', ('branch', branch_network_name))
1355
client.add_expected_call(
1356
'BzrDir.find_repositoryV3', ('stacked/',),
1357
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1358
# called twice, once from constructor and then again by us
1359
client.add_expected_call(
1360
'Branch.get_stacked_on_url', ('stacked/',),
1361
'success', ('ok', '../base'))
1362
client.add_expected_call(
1363
'Branch.get_stacked_on_url', ('stacked/',),
1364
'success', ('ok', '../base'))
1365
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1366
RemoteBzrDirFormat(), _client=client)
1367
branch = bzrdir.open_branch()
1368
result = branch.get_stacked_on_url()
1369
self.assertEqual('../base', result)
1370
self.assertFinished(client)
1371
# it's in the fallback list both for the RemoteRepository.
1372
self.assertEqual(1, len(branch.repository._fallback_repositories))
1373
# And we haven't had to construct a real repository.
1374
self.assertEqual(None, branch.repository._real_repository)
1377
class TestBranchSetLastRevision(RemoteBranchTestCase):
1379
def test_set_empty(self):
1380
# _set_last_revision_info('null:') is translated to calling
1381
# Branch.set_last_revision(path, '') on the wire.
1382
transport = MemoryTransport()
1383
transport.mkdir('branch')
1384
transport = transport.clone('branch')
1386
client = FakeClient(transport.base)
1387
client.add_expected_call(
1388
'Branch.get_stacked_on_url', ('branch/',),
1389
'error', ('NotStacked',))
1390
client.add_expected_call(
1391
'Branch.lock_write', ('branch/', '', ''),
1392
'success', ('ok', 'branch token', 'repo token'))
1393
client.add_expected_call(
1394
'Branch.last_revision_info',
1396
'success', ('ok', '0', 'null:'))
1397
client.add_expected_call(
1398
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1400
client.add_expected_call(
1401
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1403
branch = self.make_remote_branch(transport, client)
1404
# This is a hack to work around the problem that RemoteBranch currently
1405
# unnecessarily invokes _ensure_real upon a call to lock_write.
1406
branch._ensure_real = lambda: None
1408
result = branch._set_last_revision(NULL_REVISION)
1410
self.assertEqual(None, result)
1411
self.assertFinished(client)
1413
def test_set_nonempty(self):
1414
# set_last_revision_info(N, rev-idN) is translated to calling
1415
# Branch.set_last_revision(path, rev-idN) on the wire.
1416
transport = MemoryTransport()
1417
transport.mkdir('branch')
1418
transport = transport.clone('branch')
1420
client = FakeClient(transport.base)
1421
client.add_expected_call(
1422
'Branch.get_stacked_on_url', ('branch/',),
1423
'error', ('NotStacked',))
1424
client.add_expected_call(
1425
'Branch.lock_write', ('branch/', '', ''),
1426
'success', ('ok', 'branch token', 'repo token'))
1427
client.add_expected_call(
1428
'Branch.last_revision_info',
1430
'success', ('ok', '0', 'null:'))
1432
encoded_body = bz2.compress('\n'.join(lines))
1433
client.add_success_response_with_body(encoded_body, 'ok')
1434
client.add_expected_call(
1435
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1437
client.add_expected_call(
1438
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1440
branch = self.make_remote_branch(transport, client)
1441
# This is a hack to work around the problem that RemoteBranch currently
1442
# unnecessarily invokes _ensure_real upon a call to lock_write.
1443
branch._ensure_real = lambda: None
1444
# Lock the branch, reset the record of remote calls.
1446
result = branch._set_last_revision('rev-id2')
1448
self.assertEqual(None, result)
1449
self.assertFinished(client)
1451
def test_no_such_revision(self):
1452
transport = MemoryTransport()
1453
transport.mkdir('branch')
1454
transport = transport.clone('branch')
1455
# A response of 'NoSuchRevision' is translated into an exception.
1456
client = FakeClient(transport.base)
1457
client.add_expected_call(
1458
'Branch.get_stacked_on_url', ('branch/',),
1459
'error', ('NotStacked',))
1460
client.add_expected_call(
1461
'Branch.lock_write', ('branch/', '', ''),
1462
'success', ('ok', 'branch token', 'repo token'))
1463
client.add_expected_call(
1464
'Branch.last_revision_info',
1466
'success', ('ok', '0', 'null:'))
1467
# get_graph calls to construct the revision history, for the set_rh
1470
encoded_body = bz2.compress('\n'.join(lines))
1471
client.add_success_response_with_body(encoded_body, 'ok')
1472
client.add_expected_call(
1473
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1474
'error', ('NoSuchRevision', 'rev-id'))
1475
client.add_expected_call(
1476
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1479
branch = self.make_remote_branch(transport, client)
1482
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1484
self.assertFinished(client)
1486
def test_tip_change_rejected(self):
1487
"""TipChangeRejected responses cause a TipChangeRejected exception to
1490
transport = MemoryTransport()
1491
transport.mkdir('branch')
1492
transport = transport.clone('branch')
1493
client = FakeClient(transport.base)
1494
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1495
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1496
client.add_expected_call(
1497
'Branch.get_stacked_on_url', ('branch/',),
1498
'error', ('NotStacked',))
1499
client.add_expected_call(
1500
'Branch.lock_write', ('branch/', '', ''),
1501
'success', ('ok', 'branch token', 'repo token'))
1502
client.add_expected_call(
1503
'Branch.last_revision_info',
1505
'success', ('ok', '0', 'null:'))
1507
encoded_body = bz2.compress('\n'.join(lines))
1508
client.add_success_response_with_body(encoded_body, 'ok')
1509
client.add_expected_call(
1510
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1511
'error', ('TipChangeRejected', rejection_msg_utf8))
1512
client.add_expected_call(
1513
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1515
branch = self.make_remote_branch(transport, client)
1516
branch._ensure_real = lambda: None
1518
# The 'TipChangeRejected' error response triggered by calling
1519
# set_last_revision_info causes a TipChangeRejected exception.
1520
err = self.assertRaises(
1521
errors.TipChangeRejected,
1522
branch._set_last_revision, 'rev-id')
1523
# The UTF-8 message from the response has been decoded into a unicode
1525
self.assertIsInstance(err.msg, unicode)
1526
self.assertEqual(rejection_msg_unicode, err.msg)
1528
self.assertFinished(client)
1531
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1533
def test_set_last_revision_info(self):
1534
# set_last_revision_info(num, 'rev-id') is translated to calling
1535
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1536
transport = MemoryTransport()
1537
transport.mkdir('branch')
1538
transport = transport.clone('branch')
1539
client = FakeClient(transport.base)
1540
# get_stacked_on_url
1541
client.add_error_response('NotStacked')
1543
client.add_success_response('ok', 'branch token', 'repo token')
1544
# query the current revision
1545
client.add_success_response('ok', '0', 'null:')
1547
client.add_success_response('ok')
1549
client.add_success_response('ok')
1551
branch = self.make_remote_branch(transport, client)
1552
# Lock the branch, reset the record of remote calls.
1555
result = branch.set_last_revision_info(1234, 'a-revision-id')
1557
[('call', 'Branch.last_revision_info', ('branch/',)),
1558
('call', 'Branch.set_last_revision_info',
1559
('branch/', 'branch token', 'repo token',
1560
'1234', 'a-revision-id'))],
1562
self.assertEqual(None, result)
1564
def test_no_such_revision(self):
1565
# A response of 'NoSuchRevision' is translated into an exception.
1566
transport = MemoryTransport()
1567
transport.mkdir('branch')
1568
transport = transport.clone('branch')
1569
client = FakeClient(transport.base)
1570
# get_stacked_on_url
1571
client.add_error_response('NotStacked')
1573
client.add_success_response('ok', 'branch token', 'repo token')
1575
client.add_error_response('NoSuchRevision', 'revid')
1577
client.add_success_response('ok')
1579
branch = self.make_remote_branch(transport, client)
1580
# Lock the branch, reset the record of remote calls.
1585
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1588
def test_backwards_compatibility(self):
1589
"""If the server does not support the Branch.set_last_revision_info
1590
verb (which is new in 1.4), then the client falls back to VFS methods.
1592
# This test is a little messy. Unlike most tests in this file, it
1593
# doesn't purely test what a Remote* object sends over the wire, and
1594
# how it reacts to responses from the wire. It instead relies partly
1595
# on asserting that the RemoteBranch will call
1596
# self._real_branch.set_last_revision_info(...).
1598
# First, set up our RemoteBranch with a FakeClient that raises
1599
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1600
transport = MemoryTransport()
1601
transport.mkdir('branch')
1602
transport = transport.clone('branch')
1603
client = FakeClient(transport.base)
1604
client.add_expected_call(
1605
'Branch.get_stacked_on_url', ('branch/',),
1606
'error', ('NotStacked',))
1607
client.add_expected_call(
1608
'Branch.last_revision_info',
1610
'success', ('ok', '0', 'null:'))
1611
client.add_expected_call(
1612
'Branch.set_last_revision_info',
1613
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1614
'unknown', 'Branch.set_last_revision_info')
1616
branch = self.make_remote_branch(transport, client)
1617
class StubRealBranch(object):
1620
def set_last_revision_info(self, revno, revision_id):
1622
('set_last_revision_info', revno, revision_id))
1623
def _clear_cached_state(self):
1625
real_branch = StubRealBranch()
1626
branch._real_branch = real_branch
1627
self.lock_remote_branch(branch)
1629
# Call set_last_revision_info, and verify it behaved as expected.
1630
result = branch.set_last_revision_info(1234, 'a-revision-id')
1632
[('set_last_revision_info', 1234, 'a-revision-id')],
1634
self.assertFinished(client)
1636
def test_unexpected_error(self):
1637
# If the server sends an error the client doesn't understand, it gets
1638
# turned into an UnknownErrorFromSmartServer, which is presented as a
1639
# non-internal error to the user.
1640
transport = MemoryTransport()
1641
transport.mkdir('branch')
1642
transport = transport.clone('branch')
1643
client = FakeClient(transport.base)
1644
# get_stacked_on_url
1645
client.add_error_response('NotStacked')
1647
client.add_success_response('ok', 'branch token', 'repo token')
1649
client.add_error_response('UnexpectedError')
1651
client.add_success_response('ok')
1653
branch = self.make_remote_branch(transport, client)
1654
# Lock the branch, reset the record of remote calls.
1658
err = self.assertRaises(
1659
errors.UnknownErrorFromSmartServer,
1660
branch.set_last_revision_info, 123, 'revid')
1661
self.assertEqual(('UnexpectedError',), err.error_tuple)
1664
def test_tip_change_rejected(self):
1665
"""TipChangeRejected responses cause a TipChangeRejected exception to
1668
transport = MemoryTransport()
1669
transport.mkdir('branch')
1670
transport = transport.clone('branch')
1671
client = FakeClient(transport.base)
1672
# get_stacked_on_url
1673
client.add_error_response('NotStacked')
1675
client.add_success_response('ok', 'branch token', 'repo token')
1677
client.add_error_response('TipChangeRejected', 'rejection message')
1679
client.add_success_response('ok')
1681
branch = self.make_remote_branch(transport, client)
1682
# Lock the branch, reset the record of remote calls.
1684
self.addCleanup(branch.unlock)
1687
# The 'TipChangeRejected' error response triggered by calling
1688
# set_last_revision_info causes a TipChangeRejected exception.
1689
err = self.assertRaises(
1690
errors.TipChangeRejected,
1691
branch.set_last_revision_info, 123, 'revid')
1692
self.assertEqual('rejection message', err.msg)
1695
class TestBranchGetSetConfig(RemoteBranchTestCase):
1697
def test_get_branch_conf(self):
1698
# in an empty branch we decode the response properly
1699
client = FakeClient()
1700
client.add_expected_call(
1701
'Branch.get_stacked_on_url', ('memory:///',),
1702
'error', ('NotStacked',),)
1703
client.add_success_response_with_body('# config file body', 'ok')
1704
transport = MemoryTransport()
1705
branch = self.make_remote_branch(transport, client)
1706
config = branch.get_config()
1707
config.has_explicit_nickname()
1709
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1710
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1713
def test_get_multi_line_branch_conf(self):
1714
# Make sure that multiple-line branch.conf files are supported
1716
# https://bugs.launchpad.net/bzr/+bug/354075
1717
client = FakeClient()
1718
client.add_expected_call(
1719
'Branch.get_stacked_on_url', ('memory:///',),
1720
'error', ('NotStacked',),)
1721
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1722
transport = MemoryTransport()
1723
branch = self.make_remote_branch(transport, client)
1724
config = branch.get_config()
1725
self.assertEqual(u'2', config.get_user_option('b'))
1727
def test_set_option(self):
1728
client = FakeClient()
1729
client.add_expected_call(
1730
'Branch.get_stacked_on_url', ('memory:///',),
1731
'error', ('NotStacked',),)
1732
client.add_expected_call(
1733
'Branch.lock_write', ('memory:///', '', ''),
1734
'success', ('ok', 'branch token', 'repo token'))
1735
client.add_expected_call(
1736
'Branch.set_config_option', ('memory:///', 'branch token',
1737
'repo token', 'foo', 'bar', ''),
1739
client.add_expected_call(
1740
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1742
transport = MemoryTransport()
1743
branch = self.make_remote_branch(transport, client)
1745
config = branch._get_config()
1746
config.set_option('foo', 'bar')
1748
self.assertFinished(client)
1750
def test_set_option_with_dict(self):
1751
client = FakeClient()
1752
client.add_expected_call(
1753
'Branch.get_stacked_on_url', ('memory:///',),
1754
'error', ('NotStacked',),)
1755
client.add_expected_call(
1756
'Branch.lock_write', ('memory:///', '', ''),
1757
'success', ('ok', 'branch token', 'repo token'))
1758
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1759
client.add_expected_call(
1760
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1761
'repo token', encoded_dict_value, 'foo', ''),
1763
client.add_expected_call(
1764
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1766
transport = MemoryTransport()
1767
branch = self.make_remote_branch(transport, client)
1769
config = branch._get_config()
1771
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1774
self.assertFinished(client)
1776
def test_backwards_compat_set_option(self):
1777
self.setup_smart_server_with_call_log()
1778
branch = self.make_branch('.')
1779
verb = 'Branch.set_config_option'
1780
self.disable_verb(verb)
1782
self.addCleanup(branch.unlock)
1783
self.reset_smart_call_log()
1784
branch._get_config().set_option('value', 'name')
1785
self.assertLength(10, self.hpss_calls)
1786
self.assertEqual('value', branch._get_config().get_option('name'))
1788
def test_backwards_compat_set_option_with_dict(self):
1789
self.setup_smart_server_with_call_log()
1790
branch = self.make_branch('.')
1791
verb = 'Branch.set_config_option_dict'
1792
self.disable_verb(verb)
1794
self.addCleanup(branch.unlock)
1795
self.reset_smart_call_log()
1796
config = branch._get_config()
1797
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
1798
config.set_option(value_dict, 'name')
1799
self.assertLength(10, self.hpss_calls)
1800
self.assertEqual(value_dict, branch._get_config().get_option('name'))
1803
class TestBranchLockWrite(RemoteBranchTestCase):
1805
def test_lock_write_unlockable(self):
1806
transport = MemoryTransport()
1807
client = FakeClient(transport.base)
1808
client.add_expected_call(
1809
'Branch.get_stacked_on_url', ('quack/',),
1810
'error', ('NotStacked',),)
1811
client.add_expected_call(
1812
'Branch.lock_write', ('quack/', '', ''),
1813
'error', ('UnlockableTransport',))
1814
transport.mkdir('quack')
1815
transport = transport.clone('quack')
1816
branch = self.make_remote_branch(transport, client)
1817
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1818
self.assertFinished(client)
1821
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1823
def test__get_config(self):
1824
client = FakeClient()
1825
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1826
transport = MemoryTransport()
1827
bzrdir = self.make_remote_bzrdir(transport, client)
1828
config = bzrdir.get_config()
1829
self.assertEqual('/', config.get_default_stack_on())
1831
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1834
def test_set_option_uses_vfs(self):
1835
self.setup_smart_server_with_call_log()
1836
bzrdir = self.make_bzrdir('.')
1837
self.reset_smart_call_log()
1838
config = bzrdir.get_config()
1839
config.set_default_stack_on('/')
1840
self.assertLength(3, self.hpss_calls)
1842
def test_backwards_compat_get_option(self):
1843
self.setup_smart_server_with_call_log()
1844
bzrdir = self.make_bzrdir('.')
1845
verb = 'BzrDir.get_config_file'
1846
self.disable_verb(verb)
1847
self.reset_smart_call_log()
1848
self.assertEqual(None,
1849
bzrdir._get_config().get_option('default_stack_on'))
1850
self.assertLength(3, self.hpss_calls)
1853
class TestTransportIsReadonly(tests.TestCase):
1855
def test_true(self):
1856
client = FakeClient()
1857
client.add_success_response('yes')
1858
transport = RemoteTransport('bzr://example.com/', medium=False,
1860
self.assertEqual(True, transport.is_readonly())
1862
[('call', 'Transport.is_readonly', ())],
1865
def test_false(self):
1866
client = FakeClient()
1867
client.add_success_response('no')
1868
transport = RemoteTransport('bzr://example.com/', medium=False,
1870
self.assertEqual(False, transport.is_readonly())
1872
[('call', 'Transport.is_readonly', ())],
1875
def test_error_from_old_server(self):
1876
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1878
Clients should treat it as a "no" response, because is_readonly is only
1879
advisory anyway (a transport could be read-write, but then the
1880
underlying filesystem could be readonly anyway).
1882
client = FakeClient()
1883
client.add_unknown_method_response('Transport.is_readonly')
1884
transport = RemoteTransport('bzr://example.com/', medium=False,
1886
self.assertEqual(False, transport.is_readonly())
1888
[('call', 'Transport.is_readonly', ())],
1892
class TestTransportMkdir(tests.TestCase):
1894
def test_permissiondenied(self):
1895
client = FakeClient()
1896
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1897
transport = RemoteTransport('bzr://example.com/', medium=False,
1899
exc = self.assertRaises(
1900
errors.PermissionDenied, transport.mkdir, 'client path')
1901
expected_error = errors.PermissionDenied('/client path', 'extra')
1902
self.assertEqual(expected_error, exc)
1905
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1907
def test_defaults_to_none(self):
1908
t = RemoteSSHTransport('bzr+ssh://example.com')
1909
self.assertIs(None, t._get_credentials()[0])
1911
def test_uses_authentication_config(self):
1912
conf = config.AuthenticationConfig()
1913
conf._get_config().update(
1914
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1917
t = RemoteSSHTransport('bzr+ssh://example.com')
1918
self.assertEqual('bar', t._get_credentials()[0])
1921
class TestRemoteRepository(TestRemote):
1922
"""Base for testing RemoteRepository protocol usage.
1924
These tests contain frozen requests and responses. We want any changes to
1925
what is sent or expected to be require a thoughtful update to these tests
1926
because they might break compatibility with different-versioned servers.
1929
def setup_fake_client_and_repository(self, transport_path):
1930
"""Create the fake client and repository for testing with.
1932
There's no real server here; we just have canned responses sent
1935
:param transport_path: Path below the root of the MemoryTransport
1936
where the repository will be created.
1938
transport = MemoryTransport()
1939
transport.mkdir(transport_path)
1940
client = FakeClient(transport.base)
1941
transport = transport.clone(transport_path)
1942
# we do not want bzrdir to make any remote calls
1943
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1945
repo = RemoteRepository(bzrdir, None, _client=client)
1949
def remoted_description(format):
1950
return 'Remote: ' + format.get_format_description()
1953
class TestBranchFormat(tests.TestCase):
1955
def test_get_format_description(self):
1956
remote_format = RemoteBranchFormat()
1957
real_format = branch.format_registry.get_default()
1958
remote_format._network_name = real_format.network_name()
1959
self.assertEqual(remoted_description(real_format),
1960
remote_format.get_format_description())
1963
class TestRepositoryFormat(TestRemoteRepository):
1965
def test_fast_delta(self):
1966
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
1967
true_format = RemoteRepositoryFormat()
1968
true_format._network_name = true_name
1969
self.assertEqual(True, true_format.fast_deltas)
1970
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
1971
false_format = RemoteRepositoryFormat()
1972
false_format._network_name = false_name
1973
self.assertEqual(False, false_format.fast_deltas)
1975
def test_get_format_description(self):
1976
remote_repo_format = RemoteRepositoryFormat()
1977
real_format = repository.format_registry.get_default()
1978
remote_repo_format._network_name = real_format.network_name()
1979
self.assertEqual(remoted_description(real_format),
1980
remote_repo_format.get_format_description())
1983
class TestRepositoryGatherStats(TestRemoteRepository):
1985
def test_revid_none(self):
1986
# ('ok',), body with revisions and size
1987
transport_path = 'quack'
1988
repo, client = self.setup_fake_client_and_repository(transport_path)
1989
client.add_success_response_with_body(
1990
'revisions: 2\nsize: 18\n', 'ok')
1991
result = repo.gather_stats(None)
1993
[('call_expecting_body', 'Repository.gather_stats',
1994
('quack/','','no'))],
1996
self.assertEqual({'revisions': 2, 'size': 18}, result)
1998
def test_revid_no_committers(self):
1999
# ('ok',), body without committers
2000
body = ('firstrev: 123456.300 3600\n'
2001
'latestrev: 654231.400 0\n'
2004
transport_path = 'quick'
2005
revid = u'\xc8'.encode('utf8')
2006
repo, client = self.setup_fake_client_and_repository(transport_path)
2007
client.add_success_response_with_body(body, 'ok')
2008
result = repo.gather_stats(revid)
2010
[('call_expecting_body', 'Repository.gather_stats',
2011
('quick/', revid, 'no'))],
2013
self.assertEqual({'revisions': 2, 'size': 18,
2014
'firstrev': (123456.300, 3600),
2015
'latestrev': (654231.400, 0),},
2018
def test_revid_with_committers(self):
2019
# ('ok',), body with committers
2020
body = ('committers: 128\n'
2021
'firstrev: 123456.300 3600\n'
2022
'latestrev: 654231.400 0\n'
2025
transport_path = 'buick'
2026
revid = u'\xc8'.encode('utf8')
2027
repo, client = self.setup_fake_client_and_repository(transport_path)
2028
client.add_success_response_with_body(body, 'ok')
2029
result = repo.gather_stats(revid, True)
2031
[('call_expecting_body', 'Repository.gather_stats',
2032
('buick/', revid, 'yes'))],
2034
self.assertEqual({'revisions': 2, 'size': 18,
2036
'firstrev': (123456.300, 3600),
2037
'latestrev': (654231.400, 0),},
2041
class TestRepositoryGetGraph(TestRemoteRepository):
2043
def test_get_graph(self):
2044
# get_graph returns a graph with a custom parents provider.
2045
transport_path = 'quack'
2046
repo, client = self.setup_fake_client_and_repository(transport_path)
2047
graph = repo.get_graph()
2048
self.assertNotEqual(graph._parents_provider, repo)
2051
class TestRepositoryGetParentMap(TestRemoteRepository):
2053
def test_get_parent_map_caching(self):
2054
# get_parent_map returns from cache until unlock()
2055
# setup a reponse with two revisions
2056
r1 = u'\u0e33'.encode('utf8')
2057
r2 = u'\u0dab'.encode('utf8')
2058
lines = [' '.join([r2, r1]), r1]
2059
encoded_body = bz2.compress('\n'.join(lines))
2061
transport_path = 'quack'
2062
repo, client = self.setup_fake_client_and_repository(transport_path)
2063
client.add_success_response_with_body(encoded_body, 'ok')
2064
client.add_success_response_with_body(encoded_body, 'ok')
2066
graph = repo.get_graph()
2067
parents = graph.get_parent_map([r2])
2068
self.assertEqual({r2: (r1,)}, parents)
2069
# locking and unlocking deeper should not reset
2072
parents = graph.get_parent_map([r1])
2073
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2075
[('call_with_body_bytes_expecting_body',
2076
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2080
# now we call again, and it should use the second response.
2082
graph = repo.get_graph()
2083
parents = graph.get_parent_map([r1])
2084
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2086
[('call_with_body_bytes_expecting_body',
2087
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2089
('call_with_body_bytes_expecting_body',
2090
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2096
def test_get_parent_map_reconnects_if_unknown_method(self):
2097
transport_path = 'quack'
2098
rev_id = 'revision-id'
2099
repo, client = self.setup_fake_client_and_repository(transport_path)
2100
client.add_unknown_method_response('Repository.get_parent_map')
2101
client.add_success_response_with_body(rev_id, 'ok')
2102
self.assertFalse(client._medium._is_remote_before((1, 2)))
2103
parents = repo.get_parent_map([rev_id])
2105
[('call_with_body_bytes_expecting_body',
2106
'Repository.get_parent_map', ('quack/', 'include-missing:',
2108
('disconnect medium',),
2109
('call_expecting_body', 'Repository.get_revision_graph',
2112
# The medium is now marked as being connected to an older server
2113
self.assertTrue(client._medium._is_remote_before((1, 2)))
2114
self.assertEqual({rev_id: ('null:',)}, parents)
2116
def test_get_parent_map_fallback_parentless_node(self):
2117
"""get_parent_map falls back to get_revision_graph on old servers. The
2118
results from get_revision_graph are tweaked to match the get_parent_map
2121
Specifically, a {key: ()} result from get_revision_graph means "no
2122
parents" for that key, which in get_parent_map results should be
2123
represented as {key: ('null:',)}.
2125
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2127
rev_id = 'revision-id'
2128
transport_path = 'quack'
2129
repo, client = self.setup_fake_client_and_repository(transport_path)
2130
client.add_success_response_with_body(rev_id, 'ok')
2131
client._medium._remember_remote_is_before((1, 2))
2132
parents = repo.get_parent_map([rev_id])
2134
[('call_expecting_body', 'Repository.get_revision_graph',
2137
self.assertEqual({rev_id: ('null:',)}, parents)
2139
def test_get_parent_map_unexpected_response(self):
2140
repo, client = self.setup_fake_client_and_repository('path')
2141
client.add_success_response('something unexpected!')
2143
errors.UnexpectedSmartServerResponse,
2144
repo.get_parent_map, ['a-revision-id'])
2146
def test_get_parent_map_negative_caches_missing_keys(self):
2147
self.setup_smart_server_with_call_log()
2148
repo = self.make_repository('foo')
2149
self.assertIsInstance(repo, RemoteRepository)
2151
self.addCleanup(repo.unlock)
2152
self.reset_smart_call_log()
2153
graph = repo.get_graph()
2154
self.assertEqual({},
2155
graph.get_parent_map(['some-missing', 'other-missing']))
2156
self.assertLength(1, self.hpss_calls)
2157
# No call if we repeat this
2158
self.reset_smart_call_log()
2159
graph = repo.get_graph()
2160
self.assertEqual({},
2161
graph.get_parent_map(['some-missing', 'other-missing']))
2162
self.assertLength(0, self.hpss_calls)
2163
# Asking for more unknown keys makes a request.
2164
self.reset_smart_call_log()
2165
graph = repo.get_graph()
2166
self.assertEqual({},
2167
graph.get_parent_map(['some-missing', 'other-missing',
2169
self.assertLength(1, self.hpss_calls)
2171
def disableExtraResults(self):
2172
self.overrideAttr(SmartServerRepositoryGetParentMap,
2173
'no_extra_results', True)
2175
def test_null_cached_missing_and_stop_key(self):
2176
self.setup_smart_server_with_call_log()
2177
# Make a branch with a single revision.
2178
builder = self.make_branch_builder('foo')
2179
builder.start_series()
2180
builder.build_snapshot('first', None, [
2181
('add', ('', 'root-id', 'directory', ''))])
2182
builder.finish_series()
2183
branch = builder.get_branch()
2184
repo = branch.repository
2185
self.assertIsInstance(repo, RemoteRepository)
2186
# Stop the server from sending extra results.
2187
self.disableExtraResults()
2189
self.addCleanup(repo.unlock)
2190
self.reset_smart_call_log()
2191
graph = repo.get_graph()
2192
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2193
# 'first' it will be a candidate for the stop_keys of subsequent
2194
# requests, and because 'null:' was queried but not returned it will be
2195
# cached as missing.
2196
self.assertEqual({'first': ('null:',)},
2197
graph.get_parent_map(['first', 'null:']))
2198
# Now query for another key. This request will pass along a recipe of
2199
# start and stop keys describing the already cached results, and this
2200
# recipe's revision count must be correct (or else it will trigger an
2201
# error from the server).
2202
self.assertEqual({}, graph.get_parent_map(['another-key']))
2203
# This assertion guards against disableExtraResults silently failing to
2204
# work, thus invalidating the test.
2205
self.assertLength(2, self.hpss_calls)
2207
def test_get_parent_map_gets_ghosts_from_result(self):
2208
# asking for a revision should negatively cache close ghosts in its
2210
self.setup_smart_server_with_call_log()
2211
tree = self.make_branch_and_memory_tree('foo')
2214
builder = treebuilder.TreeBuilder()
2215
builder.start_tree(tree)
2217
builder.finish_tree()
2218
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2219
rev_id = tree.commit('')
2223
self.addCleanup(tree.unlock)
2224
repo = tree.branch.repository
2225
self.assertIsInstance(repo, RemoteRepository)
2227
repo.get_parent_map([rev_id])
2228
self.reset_smart_call_log()
2229
# Now asking for rev_id's ghost parent should not make calls
2230
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2231
self.assertLength(0, self.hpss_calls)
2234
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2236
def test_allows_new_revisions(self):
2237
"""get_parent_map's results can be updated by commit."""
2238
smart_server = test_server.SmartTCPServer_for_testing()
2239
self.start_server(smart_server)
2240
self.make_branch('branch')
2241
branch = Branch.open(smart_server.get_url() + '/branch')
2242
tree = branch.create_checkout('tree', lightweight=True)
2244
self.addCleanup(tree.unlock)
2245
graph = tree.branch.repository.get_graph()
2246
# This provides an opportunity for the missing rev-id to be cached.
2247
self.assertEqual({}, graph.get_parent_map(['rev1']))
2248
tree.commit('message', rev_id='rev1')
2249
graph = tree.branch.repository.get_graph()
2250
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2253
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2255
def test_null_revision(self):
2256
# a null revision has the predictable result {}, we should have no wire
2257
# traffic when calling it with this argument
2258
transport_path = 'empty'
2259
repo, client = self.setup_fake_client_and_repository(transport_path)
2260
client.add_success_response('notused')
2261
# actual RemoteRepository.get_revision_graph is gone, but there's an
2262
# equivalent private method for testing
2263
result = repo._get_revision_graph(NULL_REVISION)
2264
self.assertEqual([], client._calls)
2265
self.assertEqual({}, result)
2267
def test_none_revision(self):
2268
# with none we want the entire graph
2269
r1 = u'\u0e33'.encode('utf8')
2270
r2 = u'\u0dab'.encode('utf8')
2271
lines = [' '.join([r2, r1]), r1]
2272
encoded_body = '\n'.join(lines)
2274
transport_path = 'sinhala'
2275
repo, client = self.setup_fake_client_and_repository(transport_path)
2276
client.add_success_response_with_body(encoded_body, 'ok')
2277
# actual RemoteRepository.get_revision_graph is gone, but there's an
2278
# equivalent private method for testing
2279
result = repo._get_revision_graph(None)
2281
[('call_expecting_body', 'Repository.get_revision_graph',
2284
self.assertEqual({r1: (), r2: (r1, )}, result)
2286
def test_specific_revision(self):
2287
# with a specific revision we want the graph for that
2288
# with none we want the entire graph
2289
r11 = u'\u0e33'.encode('utf8')
2290
r12 = u'\xc9'.encode('utf8')
2291
r2 = u'\u0dab'.encode('utf8')
2292
lines = [' '.join([r2, r11, r12]), r11, r12]
2293
encoded_body = '\n'.join(lines)
2295
transport_path = 'sinhala'
2296
repo, client = self.setup_fake_client_and_repository(transport_path)
2297
client.add_success_response_with_body(encoded_body, 'ok')
2298
result = repo._get_revision_graph(r2)
2300
[('call_expecting_body', 'Repository.get_revision_graph',
2303
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2305
def test_no_such_revision(self):
2307
transport_path = 'sinhala'
2308
repo, client = self.setup_fake_client_and_repository(transport_path)
2309
client.add_error_response('nosuchrevision', revid)
2310
# also check that the right revision is reported in the error
2311
self.assertRaises(errors.NoSuchRevision,
2312
repo._get_revision_graph, revid)
2314
[('call_expecting_body', 'Repository.get_revision_graph',
2315
('sinhala/', revid))],
2318
def test_unexpected_error(self):
2320
transport_path = 'sinhala'
2321
repo, client = self.setup_fake_client_and_repository(transport_path)
2322
client.add_error_response('AnUnexpectedError')
2323
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2324
repo._get_revision_graph, revid)
2325
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2328
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2331
repo, client = self.setup_fake_client_and_repository('quack')
2332
client.add_expected_call(
2333
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2334
'success', ('ok', 'rev-five'))
2335
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2336
self.assertEqual((True, 'rev-five'), result)
2337
self.assertFinished(client)
2339
def test_history_incomplete(self):
2340
repo, client = self.setup_fake_client_and_repository('quack')
2341
client.add_expected_call(
2342
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2343
'success', ('history-incomplete', 10, 'rev-ten'))
2344
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2345
self.assertEqual((False, (10, 'rev-ten')), result)
2346
self.assertFinished(client)
2348
def test_history_incomplete_with_fallback(self):
2349
"""A 'history-incomplete' response causes the fallback repository to be
2350
queried too, if one is set.
2352
# Make a repo with a fallback repo, both using a FakeClient.
2353
format = remote.response_tuple_to_repo_format(
2354
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2355
repo, client = self.setup_fake_client_and_repository('quack')
2356
repo._format = format
2357
fallback_repo, ignored = self.setup_fake_client_and_repository(
2359
fallback_repo._client = client
2360
fallback_repo._format = format
2361
repo.add_fallback_repository(fallback_repo)
2362
# First the client should ask the primary repo
2363
client.add_expected_call(
2364
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2365
'success', ('history-incomplete', 2, 'rev-two'))
2366
# Then it should ask the fallback, using revno/revid from the
2367
# history-incomplete response as the known revno/revid.
2368
client.add_expected_call(
2369
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2370
'success', ('ok', 'rev-one'))
2371
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2372
self.assertEqual((True, 'rev-one'), result)
2373
self.assertFinished(client)
2375
def test_nosuchrevision(self):
2376
# 'nosuchrevision' is returned when the known-revid is not found in the
2377
# remote repo. The client translates that response to NoSuchRevision.
2378
repo, client = self.setup_fake_client_and_repository('quack')
2379
client.add_expected_call(
2380
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2381
'error', ('nosuchrevision', 'rev-foo'))
2383
errors.NoSuchRevision,
2384
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2385
self.assertFinished(client)
2387
def test_branch_fallback_locking(self):
2388
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2389
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2390
will be invoked, which will fail if the repo is unlocked.
2392
self.setup_smart_server_with_call_log()
2393
tree = self.make_branch_and_memory_tree('.')
2396
rev1 = tree.commit('First')
2397
rev2 = tree.commit('Second')
2399
branch = tree.branch
2400
self.assertFalse(branch.is_locked())
2401
self.reset_smart_call_log()
2402
verb = 'Repository.get_rev_id_for_revno'
2403
self.disable_verb(verb)
2404
self.assertEqual(rev1, branch.get_rev_id(1))
2405
self.assertLength(1, [call for call in self.hpss_calls if
2406
call.call.method == verb])
2409
class TestRepositoryIsShared(TestRemoteRepository):
2411
def test_is_shared(self):
2412
# ('yes', ) for Repository.is_shared -> 'True'.
2413
transport_path = 'quack'
2414
repo, client = self.setup_fake_client_and_repository(transport_path)
2415
client.add_success_response('yes')
2416
result = repo.is_shared()
2418
[('call', 'Repository.is_shared', ('quack/',))],
2420
self.assertEqual(True, result)
2422
def test_is_not_shared(self):
2423
# ('no', ) for Repository.is_shared -> 'False'.
2424
transport_path = 'qwack'
2425
repo, client = self.setup_fake_client_and_repository(transport_path)
2426
client.add_success_response('no')
2427
result = repo.is_shared()
2429
[('call', 'Repository.is_shared', ('qwack/',))],
2431
self.assertEqual(False, result)
2434
class TestRepositoryLockWrite(TestRemoteRepository):
2436
def test_lock_write(self):
2437
transport_path = 'quack'
2438
repo, client = self.setup_fake_client_and_repository(transport_path)
2439
client.add_success_response('ok', 'a token')
2440
token = repo.lock_write().repository_token
2442
[('call', 'Repository.lock_write', ('quack/', ''))],
2444
self.assertEqual('a token', token)
2446
def test_lock_write_already_locked(self):
2447
transport_path = 'quack'
2448
repo, client = self.setup_fake_client_and_repository(transport_path)
2449
client.add_error_response('LockContention')
2450
self.assertRaises(errors.LockContention, repo.lock_write)
2452
[('call', 'Repository.lock_write', ('quack/', ''))],
2455
def test_lock_write_unlockable(self):
2456
transport_path = 'quack'
2457
repo, client = self.setup_fake_client_and_repository(transport_path)
2458
client.add_error_response('UnlockableTransport')
2459
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2461
[('call', 'Repository.lock_write', ('quack/', ''))],
2465
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2467
def test_backwards_compat(self):
2468
self.setup_smart_server_with_call_log()
2469
repo = self.make_repository('.')
2470
self.reset_smart_call_log()
2471
verb = 'Repository.set_make_working_trees'
2472
self.disable_verb(verb)
2473
repo.set_make_working_trees(True)
2474
call_count = len([call for call in self.hpss_calls if
2475
call.call.method == verb])
2476
self.assertEqual(1, call_count)
2478
def test_current(self):
2479
transport_path = 'quack'
2480
repo, client = self.setup_fake_client_and_repository(transport_path)
2481
client.add_expected_call(
2482
'Repository.set_make_working_trees', ('quack/', 'True'),
2484
client.add_expected_call(
2485
'Repository.set_make_working_trees', ('quack/', 'False'),
2487
repo.set_make_working_trees(True)
2488
repo.set_make_working_trees(False)
2491
class TestRepositoryUnlock(TestRemoteRepository):
2493
def test_unlock(self):
2494
transport_path = 'quack'
2495
repo, client = self.setup_fake_client_and_repository(transport_path)
2496
client.add_success_response('ok', 'a token')
2497
client.add_success_response('ok')
2501
[('call', 'Repository.lock_write', ('quack/', '')),
2502
('call', 'Repository.unlock', ('quack/', 'a token'))],
2505
def test_unlock_wrong_token(self):
2506
# If somehow the token is wrong, unlock will raise TokenMismatch.
2507
transport_path = 'quack'
2508
repo, client = self.setup_fake_client_and_repository(transport_path)
2509
client.add_success_response('ok', 'a token')
2510
client.add_error_response('TokenMismatch')
2512
self.assertRaises(errors.TokenMismatch, repo.unlock)
2515
class TestRepositoryHasRevision(TestRemoteRepository):
2517
def test_none(self):
2518
# repo.has_revision(None) should not cause any traffic.
2519
transport_path = 'quack'
2520
repo, client = self.setup_fake_client_and_repository(transport_path)
2522
# The null revision is always there, so has_revision(None) == True.
2523
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2525
# The remote repo shouldn't be accessed.
2526
self.assertEqual([], client._calls)
2529
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2530
"""Base class for Repository.insert_stream and .insert_stream_1.19
2534
def checkInsertEmptyStream(self, repo, client):
2535
"""Insert an empty stream, checking the result.
2537
This checks that there are no resume_tokens or missing_keys, and that
2538
the client is finished.
2540
sink = repo._get_sink()
2541
fmt = repository.format_registry.get_default()
2542
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2543
self.assertEqual([], resume_tokens)
2544
self.assertEqual(set(), missing_keys)
2545
self.assertFinished(client)
2548
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2549
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2552
This test case is very similar to TestRepositoryInsertStream_1_19.
2556
TestRemoteRepository.setUp(self)
2557
self.disable_verb('Repository.insert_stream_1.19')
2559
def test_unlocked_repo(self):
2560
transport_path = 'quack'
2561
repo, client = self.setup_fake_client_and_repository(transport_path)
2562
client.add_expected_call(
2563
'Repository.insert_stream_1.19', ('quack/', ''),
2564
'unknown', ('Repository.insert_stream_1.19',))
2565
client.add_expected_call(
2566
'Repository.insert_stream', ('quack/', ''),
2568
client.add_expected_call(
2569
'Repository.insert_stream', ('quack/', ''),
2571
self.checkInsertEmptyStream(repo, client)
2573
def test_locked_repo_with_no_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', ''))
2579
client.add_expected_call(
2580
'Repository.insert_stream_1.19', ('quack/', ''),
2581
'unknown', ('Repository.insert_stream_1.19',))
2582
client.add_expected_call(
2583
'Repository.insert_stream', ('quack/', ''),
2585
client.add_expected_call(
2586
'Repository.insert_stream', ('quack/', ''),
2589
self.checkInsertEmptyStream(repo, client)
2591
def test_locked_repo_with_lock_token(self):
2592
transport_path = 'quack'
2593
repo, client = self.setup_fake_client_and_repository(transport_path)
2594
client.add_expected_call(
2595
'Repository.lock_write', ('quack/', ''),
2596
'success', ('ok', 'a token'))
2597
client.add_expected_call(
2598
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2599
'unknown', ('Repository.insert_stream_1.19',))
2600
client.add_expected_call(
2601
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2603
client.add_expected_call(
2604
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2607
self.checkInsertEmptyStream(repo, client)
2609
def test_stream_with_inventory_deltas(self):
2610
"""'inventory-deltas' substreams cannot be sent to the
2611
Repository.insert_stream verb, because not all servers that implement
2612
that verb will accept them. So when one is encountered the RemoteSink
2613
immediately stops using that verb and falls back to VFS insert_stream.
2615
transport_path = 'quack'
2616
repo, client = self.setup_fake_client_and_repository(transport_path)
2617
client.add_expected_call(
2618
'Repository.insert_stream_1.19', ('quack/', ''),
2619
'unknown', ('Repository.insert_stream_1.19',))
2620
client.add_expected_call(
2621
'Repository.insert_stream', ('quack/', ''),
2623
client.add_expected_call(
2624
'Repository.insert_stream', ('quack/', ''),
2626
# Create a fake real repository for insert_stream to fall back on, so
2627
# that we can directly see the records the RemoteSink passes to the
2632
def insert_stream(self, stream, src_format, resume_tokens):
2633
for substream_kind, substream in stream:
2634
self.records.append(
2635
(substream_kind, [record.key for record in substream]))
2636
return ['fake tokens'], ['fake missing keys']
2637
fake_real_sink = FakeRealSink()
2638
class FakeRealRepository:
2639
def _get_sink(self):
2640
return fake_real_sink
2641
def is_in_write_group(self):
2643
def refresh_data(self):
2645
repo._real_repository = FakeRealRepository()
2646
sink = repo._get_sink()
2647
fmt = repository.format_registry.get_default()
2648
stream = self.make_stream_with_inv_deltas(fmt)
2649
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2650
# Every record from the first inventory delta should have been sent to
2652
expected_records = [
2653
('inventory-deltas', [('rev2',), ('rev3',)]),
2654
('texts', [('some-rev', 'some-file')])]
2655
self.assertEqual(expected_records, fake_real_sink.records)
2656
# The return values from the real sink's insert_stream are propagated
2657
# back to the original caller.
2658
self.assertEqual(['fake tokens'], resume_tokens)
2659
self.assertEqual(['fake missing keys'], missing_keys)
2660
self.assertFinished(client)
2662
def make_stream_with_inv_deltas(self, fmt):
2663
"""Make a simple stream with an inventory delta followed by more
2664
records and more substreams to test that all records and substreams
2665
from that point on are used.
2667
This sends, in order:
2668
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2670
* texts substream: (some-rev, some-file)
2672
# Define a stream using generators so that it isn't rewindable.
2673
inv = inventory.Inventory(revision_id='rev1')
2674
inv.root.revision = 'rev1'
2675
def stream_with_inv_delta():
2676
yield ('inventories', inventories_substream())
2677
yield ('inventory-deltas', inventory_delta_substream())
2679
versionedfile.FulltextContentFactory(
2680
('some-rev', 'some-file'), (), None, 'content')])
2681
def inventories_substream():
2682
# An empty inventory fulltext. This will be streamed normally.
2683
text = fmt._serializer.write_inventory_to_string(inv)
2684
yield versionedfile.FulltextContentFactory(
2685
('rev1',), (), None, text)
2686
def inventory_delta_substream():
2687
# An inventory delta. This can't be streamed via this verb, so it
2688
# will trigger a fallback to VFS insert_stream.
2689
entry = inv.make_entry(
2690
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2691
entry.revision = 'ghost'
2692
delta = [(None, 'newdir', 'newdir-id', entry)]
2693
serializer = inventory_delta.InventoryDeltaSerializer(
2694
versioned_root=True, tree_references=False)
2695
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2696
yield versionedfile.ChunkedContentFactory(
2697
('rev2',), (('rev1',)), None, lines)
2699
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2700
yield versionedfile.ChunkedContentFactory(
2701
('rev3',), (('rev1',)), None, lines)
2702
return stream_with_inv_delta()
2705
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2707
def test_unlocked_repo(self):
2708
transport_path = 'quack'
2709
repo, client = self.setup_fake_client_and_repository(transport_path)
2710
client.add_expected_call(
2711
'Repository.insert_stream_1.19', ('quack/', ''),
2713
client.add_expected_call(
2714
'Repository.insert_stream_1.19', ('quack/', ''),
2716
self.checkInsertEmptyStream(repo, client)
2718
def test_locked_repo_with_no_lock_token(self):
2719
transport_path = 'quack'
2720
repo, client = self.setup_fake_client_and_repository(transport_path)
2721
client.add_expected_call(
2722
'Repository.lock_write', ('quack/', ''),
2723
'success', ('ok', ''))
2724
client.add_expected_call(
2725
'Repository.insert_stream_1.19', ('quack/', ''),
2727
client.add_expected_call(
2728
'Repository.insert_stream_1.19', ('quack/', ''),
2731
self.checkInsertEmptyStream(repo, client)
2733
def test_locked_repo_with_lock_token(self):
2734
transport_path = 'quack'
2735
repo, client = self.setup_fake_client_and_repository(transport_path)
2736
client.add_expected_call(
2737
'Repository.lock_write', ('quack/', ''),
2738
'success', ('ok', 'a token'))
2739
client.add_expected_call(
2740
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2742
client.add_expected_call(
2743
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2746
self.checkInsertEmptyStream(repo, client)
2749
class TestRepositoryTarball(TestRemoteRepository):
2751
# This is a canned tarball reponse we can validate against
2753
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2754
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2755
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2756
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2757
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2758
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2759
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2760
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2761
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2762
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2763
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2764
'nWQ7QH/F3JFOFCQ0aSPfA='
2767
def test_repository_tarball(self):
2768
# Test that Repository.tarball generates the right operations
2769
transport_path = 'repo'
2770
expected_calls = [('call_expecting_body', 'Repository.tarball',
2771
('repo/', 'bz2',),),
2773
repo, client = self.setup_fake_client_and_repository(transport_path)
2774
client.add_success_response_with_body(self.tarball_content, 'ok')
2775
# Now actually ask for the tarball
2776
tarball_file = repo._get_tarball('bz2')
2778
self.assertEqual(expected_calls, client._calls)
2779
self.assertEqual(self.tarball_content, tarball_file.read())
2781
tarball_file.close()
2784
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2785
"""RemoteRepository.copy_content_into optimizations"""
2787
def test_copy_content_remote_to_local(self):
2788
self.transport_server = test_server.SmartTCPServer_for_testing
2789
src_repo = self.make_repository('repo1')
2790
src_repo = repository.Repository.open(self.get_url('repo1'))
2791
# At the moment the tarball-based copy_content_into can't write back
2792
# into a smart server. It would be good if it could upload the
2793
# tarball; once that works we'd have to create repositories of
2794
# different formats. -- mbp 20070410
2795
dest_url = self.get_vfs_only_url('repo2')
2796
dest_bzrdir = BzrDir.create(dest_url)
2797
dest_repo = dest_bzrdir.create_repository()
2798
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2799
self.assertTrue(isinstance(src_repo, RemoteRepository))
2800
src_repo.copy_content_into(dest_repo)
2803
class _StubRealPackRepository(object):
2805
def __init__(self, calls):
2807
self._pack_collection = _StubPackCollection(calls)
2809
def is_in_write_group(self):
2812
def refresh_data(self):
2813
self.calls.append(('pack collection reload_pack_names',))
2816
class _StubPackCollection(object):
2818
def __init__(self, calls):
2822
self.calls.append(('pack collection autopack',))
2825
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2826
"""Tests for RemoteRepository.autopack implementation."""
2829
"""When the server returns 'ok' and there's no _real_repository, then
2830
nothing else happens: the autopack method is done.
2832
transport_path = 'quack'
2833
repo, client = self.setup_fake_client_and_repository(transport_path)
2834
client.add_expected_call(
2835
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2837
self.assertFinished(client)
2839
def test_ok_with_real_repo(self):
2840
"""When the server returns 'ok' and there is a _real_repository, then
2841
the _real_repository's reload_pack_name's method will be called.
2843
transport_path = 'quack'
2844
repo, client = self.setup_fake_client_and_repository(transport_path)
2845
client.add_expected_call(
2846
'PackRepository.autopack', ('quack/',),
2848
repo._real_repository = _StubRealPackRepository(client._calls)
2851
[('call', 'PackRepository.autopack', ('quack/',)),
2852
('pack collection reload_pack_names',)],
2855
def test_backwards_compatibility(self):
2856
"""If the server does not recognise the PackRepository.autopack verb,
2857
fallback to the real_repository's implementation.
2859
transport_path = 'quack'
2860
repo, client = self.setup_fake_client_and_repository(transport_path)
2861
client.add_unknown_method_response('PackRepository.autopack')
2862
def stub_ensure_real():
2863
client._calls.append(('_ensure_real',))
2864
repo._real_repository = _StubRealPackRepository(client._calls)
2865
repo._ensure_real = stub_ensure_real
2868
[('call', 'PackRepository.autopack', ('quack/',)),
2870
('pack collection autopack',)],
2873
def test_oom_error_reporting(self):
2874
"""An out-of-memory condition on the server is reported clearly"""
2875
transport_path = 'quack'
2876
repo, client = self.setup_fake_client_and_repository(transport_path)
2877
client.add_expected_call(
2878
'PackRepository.autopack', ('quack/',),
2879
'error', ('MemoryError',))
2880
err = self.assertRaises(errors.BzrError, repo.autopack)
2881
self.assertContainsRe(str(err), "^remote server out of mem")
2884
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2885
"""Base class for unit tests for bzrlib.remote._translate_error."""
2887
def translateTuple(self, error_tuple, **context):
2888
"""Call _translate_error with an ErrorFromSmartServer built from the
2891
:param error_tuple: A tuple of a smart server response, as would be
2892
passed to an ErrorFromSmartServer.
2893
:kwargs context: context items to call _translate_error with.
2895
:returns: The error raised by _translate_error.
2897
# Raise the ErrorFromSmartServer before passing it as an argument,
2898
# because _translate_error may need to re-raise it with a bare 'raise'
2900
server_error = errors.ErrorFromSmartServer(error_tuple)
2901
translated_error = self.translateErrorFromSmartServer(
2902
server_error, **context)
2903
return translated_error
2905
def translateErrorFromSmartServer(self, error_object, **context):
2906
"""Like translateTuple, but takes an already constructed
2907
ErrorFromSmartServer rather than a tuple.
2911
except errors.ErrorFromSmartServer, server_error:
2912
translated_error = self.assertRaises(
2913
errors.BzrError, remote._translate_error, server_error,
2915
return translated_error
2918
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2919
"""Unit tests for bzrlib.remote._translate_error.
2921
Given an ErrorFromSmartServer (which has an error tuple from a smart
2922
server) and some context, _translate_error raises more specific errors from
2925
This test case covers the cases where _translate_error succeeds in
2926
translating an ErrorFromSmartServer to something better. See
2927
TestErrorTranslationRobustness for other cases.
2930
def test_NoSuchRevision(self):
2931
branch = self.make_branch('')
2933
translated_error = self.translateTuple(
2934
('NoSuchRevision', revid), branch=branch)
2935
expected_error = errors.NoSuchRevision(branch, revid)
2936
self.assertEqual(expected_error, translated_error)
2938
def test_nosuchrevision(self):
2939
repository = self.make_repository('')
2941
translated_error = self.translateTuple(
2942
('nosuchrevision', revid), repository=repository)
2943
expected_error = errors.NoSuchRevision(repository, revid)
2944
self.assertEqual(expected_error, translated_error)
2946
def test_nobranch(self):
2947
bzrdir = self.make_bzrdir('')
2948
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2949
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2950
self.assertEqual(expected_error, translated_error)
2952
def test_nobranch_one_arg(self):
2953
bzrdir = self.make_bzrdir('')
2954
translated_error = self.translateTuple(
2955
('nobranch', 'extra detail'), bzrdir=bzrdir)
2956
expected_error = errors.NotBranchError(
2957
path=bzrdir.root_transport.base,
2958
detail='extra detail')
2959
self.assertEqual(expected_error, translated_error)
2961
def test_norepository(self):
2962
bzrdir = self.make_bzrdir('')
2963
translated_error = self.translateTuple(('norepository',),
2965
expected_error = errors.NoRepositoryPresent(bzrdir)
2966
self.assertEqual(expected_error, translated_error)
2968
def test_LockContention(self):
2969
translated_error = self.translateTuple(('LockContention',))
2970
expected_error = errors.LockContention('(remote lock)')
2971
self.assertEqual(expected_error, translated_error)
2973
def test_UnlockableTransport(self):
2974
bzrdir = self.make_bzrdir('')
2975
translated_error = self.translateTuple(
2976
('UnlockableTransport',), bzrdir=bzrdir)
2977
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2978
self.assertEqual(expected_error, translated_error)
2980
def test_LockFailed(self):
2981
lock = 'str() of a server lock'
2982
why = 'str() of why'
2983
translated_error = self.translateTuple(('LockFailed', lock, why))
2984
expected_error = errors.LockFailed(lock, why)
2985
self.assertEqual(expected_error, translated_error)
2987
def test_TokenMismatch(self):
2988
token = 'a lock token'
2989
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2990
expected_error = errors.TokenMismatch(token, '(remote token)')
2991
self.assertEqual(expected_error, translated_error)
2993
def test_Diverged(self):
2994
branch = self.make_branch('a')
2995
other_branch = self.make_branch('b')
2996
translated_error = self.translateTuple(
2997
('Diverged',), branch=branch, other_branch=other_branch)
2998
expected_error = errors.DivergedBranches(branch, other_branch)
2999
self.assertEqual(expected_error, translated_error)
3001
def test_NotStacked(self):
3002
branch = self.make_branch('')
3003
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3004
expected_error = errors.NotStacked(branch)
3005
self.assertEqual(expected_error, translated_error)
3007
def test_ReadError_no_args(self):
3009
translated_error = self.translateTuple(('ReadError',), path=path)
3010
expected_error = errors.ReadError(path)
3011
self.assertEqual(expected_error, translated_error)
3013
def test_ReadError(self):
3015
translated_error = self.translateTuple(('ReadError', path))
3016
expected_error = errors.ReadError(path)
3017
self.assertEqual(expected_error, translated_error)
3019
def test_IncompatibleRepositories(self):
3020
translated_error = self.translateTuple(('IncompatibleRepositories',
3021
"repo1", "repo2", "details here"))
3022
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3024
self.assertEqual(expected_error, translated_error)
3026
def test_PermissionDenied_no_args(self):
3028
translated_error = self.translateTuple(('PermissionDenied',),
3030
expected_error = errors.PermissionDenied(path)
3031
self.assertEqual(expected_error, translated_error)
3033
def test_PermissionDenied_one_arg(self):
3035
translated_error = self.translateTuple(('PermissionDenied', path))
3036
expected_error = errors.PermissionDenied(path)
3037
self.assertEqual(expected_error, translated_error)
3039
def test_PermissionDenied_one_arg_and_context(self):
3040
"""Given a choice between a path from the local context and a path on
3041
the wire, _translate_error prefers the path from the local context.
3043
local_path = 'local path'
3044
remote_path = 'remote path'
3045
translated_error = self.translateTuple(
3046
('PermissionDenied', remote_path), path=local_path)
3047
expected_error = errors.PermissionDenied(local_path)
3048
self.assertEqual(expected_error, translated_error)
3050
def test_PermissionDenied_two_args(self):
3052
extra = 'a string with extra info'
3053
translated_error = self.translateTuple(
3054
('PermissionDenied', path, extra))
3055
expected_error = errors.PermissionDenied(path, extra)
3056
self.assertEqual(expected_error, translated_error)
3058
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3060
def test_NoSuchFile_context_path(self):
3061
local_path = "local path"
3062
translated_error = self.translateTuple(('ReadError', "remote path"),
3064
expected_error = errors.ReadError(local_path)
3065
self.assertEqual(expected_error, translated_error)
3067
def test_NoSuchFile_without_context(self):
3068
remote_path = "remote path"
3069
translated_error = self.translateTuple(('ReadError', remote_path))
3070
expected_error = errors.ReadError(remote_path)
3071
self.assertEqual(expected_error, translated_error)
3073
def test_ReadOnlyError(self):
3074
translated_error = self.translateTuple(('ReadOnlyError',))
3075
expected_error = errors.TransportNotPossible("readonly transport")
3076
self.assertEqual(expected_error, translated_error)
3078
def test_MemoryError(self):
3079
translated_error = self.translateTuple(('MemoryError',))
3080
self.assertStartsWith(str(translated_error),
3081
"remote server out of memory")
3083
def test_generic_IndexError_no_classname(self):
3084
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3085
translated_error = self.translateErrorFromSmartServer(err)
3086
expected_error = errors.UnknownErrorFromSmartServer(err)
3087
self.assertEqual(expected_error, translated_error)
3089
# GZ 2011-03-02: TODO test generic non-ascii error string
3091
def test_generic_KeyError(self):
3092
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3093
translated_error = self.translateErrorFromSmartServer(err)
3094
expected_error = errors.UnknownErrorFromSmartServer(err)
3095
self.assertEqual(expected_error, translated_error)
3098
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3099
"""Unit tests for bzrlib.remote._translate_error's robustness.
3101
TestErrorTranslationSuccess is for cases where _translate_error can
3102
translate successfully. This class about how _translate_err behaves when
3103
it fails to translate: it re-raises the original error.
3106
def test_unrecognised_server_error(self):
3107
"""If the error code from the server is not recognised, the original
3108
ErrorFromSmartServer is propagated unmodified.
3110
error_tuple = ('An unknown error tuple',)
3111
server_error = errors.ErrorFromSmartServer(error_tuple)
3112
translated_error = self.translateErrorFromSmartServer(server_error)
3113
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3114
self.assertEqual(expected_error, translated_error)
3116
def test_context_missing_a_key(self):
3117
"""In case of a bug in the client, or perhaps an unexpected response
3118
from a server, _translate_error returns the original error tuple from
3119
the server and mutters a warning.
3121
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3122
# in the context dict. So let's give it an empty context dict instead
3123
# to exercise its error recovery.
3125
error_tuple = ('NoSuchRevision', 'revid')
3126
server_error = errors.ErrorFromSmartServer(error_tuple)
3127
translated_error = self.translateErrorFromSmartServer(server_error)
3128
self.assertEqual(server_error, translated_error)
3129
# In addition to re-raising ErrorFromSmartServer, some debug info has
3130
# been muttered to the log file for developer to look at.
3131
self.assertContainsRe(
3133
"Missing key 'branch' in context")
3135
def test_path_missing(self):
3136
"""Some translations (PermissionDenied, ReadError) can determine the
3137
'path' variable from either the wire or the local context. If neither
3138
has it, then an error is raised.
3140
error_tuple = ('ReadError',)
3141
server_error = errors.ErrorFromSmartServer(error_tuple)
3142
translated_error = self.translateErrorFromSmartServer(server_error)
3143
self.assertEqual(server_error, translated_error)
3144
# In addition to re-raising ErrorFromSmartServer, some debug info has
3145
# been muttered to the log file for developer to look at.
3146
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3149
class TestStacking(tests.TestCaseWithTransport):
3150
"""Tests for operations on stacked remote repositories.
3152
The underlying format type must support stacking.
3155
def test_access_stacked_remote(self):
3156
# based on <http://launchpad.net/bugs/261315>
3157
# make a branch stacked on another repository containing an empty
3158
# revision, then open it over hpss - we should be able to see that
3160
base_transport = self.get_transport()
3161
base_builder = self.make_branch_builder('base', format='1.9')
3162
base_builder.start_series()
3163
base_revid = base_builder.build_snapshot('rev-id', None,
3164
[('add', ('', None, 'directory', None))],
3166
base_builder.finish_series()
3167
stacked_branch = self.make_branch('stacked', format='1.9')
3168
stacked_branch.set_stacked_on_url('../base')
3169
# start a server looking at this
3170
smart_server = test_server.SmartTCPServer_for_testing()
3171
self.start_server(smart_server)
3172
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3173
# can get its branch and repository
3174
remote_branch = remote_bzrdir.open_branch()
3175
remote_repo = remote_branch.repository
3176
remote_repo.lock_read()
3178
# it should have an appropriate fallback repository, which should also
3179
# be a RemoteRepository
3180
self.assertLength(1, remote_repo._fallback_repositories)
3181
self.assertIsInstance(remote_repo._fallback_repositories[0],
3183
# and it has the revision committed to the underlying repository;
3184
# these have varying implementations so we try several of them
3185
self.assertTrue(remote_repo.has_revisions([base_revid]))
3186
self.assertTrue(remote_repo.has_revision(base_revid))
3187
self.assertEqual(remote_repo.get_revision(base_revid).message,
3190
remote_repo.unlock()
3192
def prepare_stacked_remote_branch(self):
3193
"""Get stacked_upon and stacked branches with content in each."""
3194
self.setup_smart_server_with_call_log()
3195
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3196
tree1.commit('rev1', rev_id='rev1')
3197
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3198
).open_workingtree()
3199
local_tree = tree2.branch.create_checkout('local')
3200
local_tree.commit('local changes make me feel good.')
3201
branch2 = Branch.open(self.get_url('tree2'))
3203
self.addCleanup(branch2.unlock)
3204
return tree1.branch, branch2
3206
def test_stacked_get_parent_map(self):
3207
# the public implementation of get_parent_map obeys stacking
3208
_, branch = self.prepare_stacked_remote_branch()
3209
repo = branch.repository
3210
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3212
def test_unstacked_get_parent_map(self):
3213
# _unstacked_provider.get_parent_map ignores stacking
3214
_, branch = self.prepare_stacked_remote_branch()
3215
provider = branch.repository._unstacked_provider
3216
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3218
def fetch_stream_to_rev_order(self, stream):
3220
for kind, substream in stream:
3221
if not kind == 'revisions':
3224
for content in substream:
3225
result.append(content.key[-1])
3228
def get_ordered_revs(self, format, order, branch_factory=None):
3229
"""Get a list of the revisions in a stream to format format.
3231
:param format: The format of the target.
3232
:param order: the order that target should have requested.
3233
:param branch_factory: A callable to create a trunk and stacked branch
3234
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3235
:result: The revision ids in the stream, in the order seen,
3236
the topological order of revisions in the source.
3238
unordered_format = bzrdir.format_registry.get(format)()
3239
target_repository_format = unordered_format.repository_format
3241
self.assertEqual(order, target_repository_format._fetch_order)
3242
if branch_factory is None:
3243
branch_factory = self.prepare_stacked_remote_branch
3244
_, stacked = branch_factory()
3245
source = stacked.repository._get_source(target_repository_format)
3246
tip = stacked.last_revision()
3247
revs = stacked.repository.get_ancestry(tip)
3248
search = graph.PendingAncestryResult([tip], stacked.repository)
3249
self.reset_smart_call_log()
3250
stream = source.get_stream(search)
3253
# We trust that if a revision is in the stream the rest of the new
3254
# content for it is too, as per our main fetch tests; here we are
3255
# checking that the revisions are actually included at all, and their
3257
return self.fetch_stream_to_rev_order(stream), revs
3259
def test_stacked_get_stream_unordered(self):
3260
# Repository._get_source.get_stream() from a stacked repository with
3261
# unordered yields the full data from both stacked and stacked upon
3263
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3264
self.assertEqual(set(expected_revs), set(rev_ord))
3265
# Getting unordered results should have made a streaming data request
3266
# from the server, then one from the backing branch.
3267
self.assertLength(2, self.hpss_calls)
3269
def test_stacked_on_stacked_get_stream_unordered(self):
3270
# Repository._get_source.get_stream() from a stacked repository which
3271
# is itself stacked yields the full data from all three sources.
3272
def make_stacked_stacked():
3273
_, stacked = self.prepare_stacked_remote_branch()
3274
tree = stacked.bzrdir.sprout('tree3', stacked=True
3275
).open_workingtree()
3276
local_tree = tree.branch.create_checkout('local-tree3')
3277
local_tree.commit('more local changes are better')
3278
branch = Branch.open(self.get_url('tree3'))
3280
self.addCleanup(branch.unlock)
3282
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3283
branch_factory=make_stacked_stacked)
3284
self.assertEqual(set(expected_revs), set(rev_ord))
3285
# Getting unordered results should have made a streaming data request
3286
# from the server, and one from each backing repo
3287
self.assertLength(3, self.hpss_calls)
3289
def test_stacked_get_stream_topological(self):
3290
# Repository._get_source.get_stream() from a stacked repository with
3291
# topological sorting yields the full data from both stacked and
3292
# stacked upon sources in topological order.
3293
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3294
self.assertEqual(expected_revs, rev_ord)
3295
# Getting topological sort requires VFS calls still - one of which is
3296
# pushing up from the bound branch.
3297
self.assertLength(13, self.hpss_calls)
3299
def test_stacked_get_stream_groupcompress(self):
3300
# Repository._get_source.get_stream() from a stacked repository with
3301
# groupcompress sorting yields the full data from both stacked and
3302
# stacked upon sources in groupcompress order.
3303
raise tests.TestSkipped('No groupcompress ordered format available')
3304
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3305
self.assertEqual(expected_revs, reversed(rev_ord))
3306
# Getting unordered results should have made a streaming data request
3307
# from the backing branch, and one from the stacked on branch.
3308
self.assertLength(2, self.hpss_calls)
3310
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3311
# When pulling some fixed amount of content that is more than the
3312
# source has (because some is coming from a fallback branch, no error
3313
# should be received. This was reported as bug 360791.
3314
# Need three branches: a trunk, a stacked branch, and a preexisting
3315
# branch pulling content from stacked and trunk.
3316
self.setup_smart_server_with_call_log()
3317
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3318
r1 = trunk.commit('start')
3319
stacked_branch = trunk.branch.create_clone_on_transport(
3320
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3321
local = self.make_branch('local', format='1.9-rich-root')
3322
local.repository.fetch(stacked_branch.repository,
3323
stacked_branch.last_revision())
3326
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3329
super(TestRemoteBranchEffort, self).setUp()
3330
# Create a smart server that publishes whatever the backing VFS server
3332
self.smart_server = test_server.SmartTCPServer_for_testing()
3333
self.start_server(self.smart_server, self.get_server())
3334
# Log all HPSS calls into self.hpss_calls.
3335
_SmartClient.hooks.install_named_hook(
3336
'call', self.capture_hpss_call, None)
3337
self.hpss_calls = []
3339
def capture_hpss_call(self, params):
3340
self.hpss_calls.append(params.method)
3342
def test_copy_content_into_avoids_revision_history(self):
3343
local = self.make_branch('local')
3344
builder = self.make_branch_builder('remote')
3345
builder.build_commit(message="Commit.")
3346
remote_branch_url = self.smart_server.get_url() + 'remote'
3347
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3348
local.repository.fetch(remote_branch.repository)
3349
self.hpss_calls = []
3350
remote_branch.copy_content_into(local)
3351
self.assertFalse('Branch.revision_history' in self.hpss_calls)
3353
def test_fetch_everything_needs_just_one_call(self):
3354
local = self.make_branch('local')
3355
builder = self.make_branch_builder('remote')
3356
builder.build_commit(message="Commit.")
3357
remote_branch_url = self.smart_server.get_url() + 'remote'
3358
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3359
self.hpss_calls = []
3360
local.repository.fetch(remote_branch.repository,
3361
fetch_spec=graph.EverythingResult(remote_branch.repository))
3362
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
3364
def override_verb(self, verb_name, verb):
3365
request_handlers = request.request_handlers
3366
orig_verb = request_handlers.get(verb_name)
3367
request_handlers.register(verb_name, verb, override_existing=True)
3368
self.addCleanup(request_handlers.register, verb_name, orig_verb,
3369
override_existing=True)
3371
def test_fetch_everything_backwards_compat(self):
3372
"""Can fetch with EverythingResult even with pre 2.4 servers.
3374
Pre-2.4 do not support 'everything' searches with the
3375
Repository.get_stream_1.19 verb.
3378
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
3379
"""A version of the Repository.get_stream_1.19 verb patched to
3380
reject 'everything' searches the way 2.3 and earlier do.
3382
def recreate_search(self, repository, search_bytes, discard_excess=False):
3383
verb_log.append(search_bytes.split('\n', 1)[0])
3384
if search_bytes == 'everything':
3385
return (None, request.FailedSmartServerResponse(('BadSearch',)))
3386
return super(OldGetStreamVerb,
3387
self).recreate_search(repository, search_bytes,
3388
discard_excess=discard_excess)
3389
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
3390
local = self.make_branch('local')
3391
builder = self.make_branch_builder('remote')
3392
builder.build_commit(message="Commit.")
3393
remote_branch_url = self.smart_server.get_url() + 'remote'
3394
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3395
self.hpss_calls = []
3396
local.repository.fetch(remote_branch.repository,
3397
fetch_spec=graph.EverythingResult(remote_branch.repository))
3398
# make sure the overridden verb was used
3399
self.assertLength(1, verb_log)
3400
# more than one HPSS call is needed, but because it's a VFS callback
3401
# its hard to predict exactly how many.
3402
self.assertTrue(len(self.hpss_calls) > 1)