1
# Copyright (C) 2006-2013, 2016 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
47
from bzrlib.branch import Branch
48
from bzrlib.bzrdir import (
53
from bzrlib.chk_serializer import chk_bencode_serializer
54
from bzrlib.remote import (
60
RemoteRepositoryFormat,
62
from bzrlib.repofmt import groupcompress_repo, knitpack_repo
63
from bzrlib.revision import (
67
from bzrlib.smart import medium, request
68
from bzrlib.smart.client import _SmartClient
69
from bzrlib.smart.repository import (
70
SmartServerRepositoryGetParentMap,
71
SmartServerRepositoryGetStream_1_19,
72
_stream_to_byte_stream,
74
from bzrlib.symbol_versioning import deprecated_in
75
from bzrlib.tests import (
78
from bzrlib.tests.scenarios import load_tests_apply_scenarios
79
from bzrlib.transport.memory import MemoryTransport
80
from bzrlib.transport.remote import (
87
load_tests = load_tests_apply_scenarios
90
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
94
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
96
{'transport_server': test_server.SmartTCPServer_for_testing})]
100
super(BasicRemoteObjectTests, self).setUp()
101
self.transport = self.get_transport()
102
# make a branch that can be opened over the smart transport
103
self.local_wt = BzrDir.create_standalone_workingtree('.')
104
self.addCleanup(self.transport.disconnect)
106
def test_create_remote_bzrdir(self):
107
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
108
self.assertIsInstance(b, BzrDir)
110
def test_open_remote_branch(self):
111
# open a standalone branch in the working directory
112
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
113
branch = b.open_branch()
114
self.assertIsInstance(branch, Branch)
116
def test_remote_repository(self):
117
b = BzrDir.open_from_transport(self.transport)
118
repo = b.open_repository()
119
revid = u'\xc823123123'.encode('utf8')
120
self.assertFalse(repo.has_revision(revid))
121
self.local_wt.commit(message='test commit', rev_id=revid)
122
self.assertTrue(repo.has_revision(revid))
124
def test_find_correct_format(self):
125
"""Should open a RemoteBzrDir over a RemoteTransport"""
126
fmt = BzrDirFormat.find_format(self.transport)
127
self.assertTrue(bzrdir.RemoteBzrProber
128
in controldir.ControlDirFormat._server_probers)
129
self.assertIsInstance(fmt, RemoteBzrDirFormat)
131
def test_open_detected_smart_format(self):
132
fmt = BzrDirFormat.find_format(self.transport)
133
d = fmt.open(self.transport)
134
self.assertIsInstance(d, BzrDir)
136
def test_remote_branch_repr(self):
137
b = BzrDir.open_from_transport(self.transport).open_branch()
138
self.assertStartsWith(str(b), 'RemoteBranch(')
140
def test_remote_bzrdir_repr(self):
141
b = BzrDir.open_from_transport(self.transport)
142
self.assertStartsWith(str(b), 'RemoteBzrDir(')
144
def test_remote_branch_format_supports_stacking(self):
146
self.make_branch('unstackable', format='pack-0.92')
147
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
148
self.assertFalse(b._format.supports_stacking())
149
self.make_branch('stackable', format='1.9')
150
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
151
self.assertTrue(b._format.supports_stacking())
153
def test_remote_repo_format_supports_external_references(self):
155
bd = self.make_bzrdir('unstackable', format='pack-0.92')
156
r = bd.create_repository()
157
self.assertFalse(r._format.supports_external_lookups)
158
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
159
self.assertFalse(r._format.supports_external_lookups)
160
bd = self.make_bzrdir('stackable', format='1.9')
161
r = bd.create_repository()
162
self.assertTrue(r._format.supports_external_lookups)
163
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
164
self.assertTrue(r._format.supports_external_lookups)
166
def test_remote_branch_set_append_revisions_only(self):
167
# Make a format 1.9 branch, which supports append_revisions_only
168
branch = self.make_branch('branch', format='1.9')
169
branch.set_append_revisions_only(True)
170
config = branch.get_config_stack()
172
True, config.get('append_revisions_only'))
173
branch.set_append_revisions_only(False)
174
config = branch.get_config_stack()
176
False, config.get('append_revisions_only'))
178
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
179
branch = self.make_branch('branch', format='knit')
181
errors.UpgradeRequired, branch.set_append_revisions_only, True)
184
class FakeProtocol(object):
185
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
187
def __init__(self, body, fake_client):
189
self._body_buffer = None
190
self._fake_client = fake_client
192
def read_body_bytes(self, count=-1):
193
if self._body_buffer is None:
194
self._body_buffer = StringIO(self.body)
195
bytes = self._body_buffer.read(count)
196
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
197
self._fake_client.expecting_body = False
200
def cancel_read_body(self):
201
self._fake_client.expecting_body = False
203
def read_streamed_body(self):
207
class FakeClient(_SmartClient):
208
"""Lookalike for _SmartClient allowing testing."""
210
def __init__(self, fake_medium_base='fake base'):
211
"""Create a FakeClient."""
214
self.expecting_body = False
215
# if non-None, this is the list of expected calls, with only the
216
# method name and arguments included. the body might be hard to
217
# compute so is not included. If a call is None, that call can
219
self._expected_calls = None
220
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
222
def add_expected_call(self, call_name, call_args, response_type,
223
response_args, response_body=None):
224
if self._expected_calls is None:
225
self._expected_calls = []
226
self._expected_calls.append((call_name, call_args))
227
self.responses.append((response_type, response_args, response_body))
229
def add_success_response(self, *args):
230
self.responses.append(('success', args, None))
232
def add_success_response_with_body(self, body, *args):
233
self.responses.append(('success', args, body))
234
if self._expected_calls is not None:
235
self._expected_calls.append(None)
237
def add_error_response(self, *args):
238
self.responses.append(('error', args))
240
def add_unknown_method_response(self, verb):
241
self.responses.append(('unknown', verb))
243
def finished_test(self):
244
if self._expected_calls:
245
raise AssertionError("%r finished but was still expecting %r"
246
% (self, self._expected_calls[0]))
248
def _get_next_response(self):
250
response_tuple = self.responses.pop(0)
251
except IndexError, e:
252
raise AssertionError("%r didn't expect any more calls"
254
if response_tuple[0] == 'unknown':
255
raise errors.UnknownSmartMethod(response_tuple[1])
256
elif response_tuple[0] == 'error':
257
raise errors.ErrorFromSmartServer(response_tuple[1])
258
return response_tuple
260
def _check_call(self, method, args):
261
if self._expected_calls is None:
262
# the test should be updated to say what it expects
265
next_call = self._expected_calls.pop(0)
267
raise AssertionError("%r didn't expect any more calls "
269
% (self, method, args,))
270
if next_call is None:
272
if method != next_call[0] or args != next_call[1]:
273
raise AssertionError("%r expected %r%r "
275
% (self, next_call[0], next_call[1], method, args,))
277
def call(self, method, *args):
278
self._check_call(method, args)
279
self._calls.append(('call', method, args))
280
return self._get_next_response()[1]
282
def call_expecting_body(self, method, *args):
283
self._check_call(method, args)
284
self._calls.append(('call_expecting_body', method, args))
285
result = self._get_next_response()
286
self.expecting_body = True
287
return result[1], FakeProtocol(result[2], self)
289
def call_with_body_bytes(self, method, args, body):
290
self._check_call(method, args)
291
self._calls.append(('call_with_body_bytes', method, args, body))
292
result = self._get_next_response()
293
return result[1], FakeProtocol(result[2], self)
295
def call_with_body_bytes_expecting_body(self, method, args, body):
296
self._check_call(method, args)
297
self._calls.append(('call_with_body_bytes_expecting_body', method,
299
result = self._get_next_response()
300
self.expecting_body = True
301
return result[1], FakeProtocol(result[2], self)
303
def call_with_body_stream(self, args, stream):
304
# Explicitly consume the stream before checking for an error, because
305
# that's what happens a real medium.
306
stream = list(stream)
307
self._check_call(args[0], args[1:])
308
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
309
result = self._get_next_response()
310
# The second value returned from call_with_body_stream is supposed to
311
# be a response_handler object, but so far no tests depend on that.
312
response_handler = None
313
return result[1], response_handler
316
class FakeMedium(medium.SmartClientMedium):
318
def __init__(self, client_calls, base):
319
medium.SmartClientMedium.__init__(self, base)
320
self._client_calls = client_calls
322
def disconnect(self):
323
self._client_calls.append(('disconnect medium',))
326
class TestVfsHas(tests.TestCase):
328
def test_unicode_path(self):
329
client = FakeClient('/')
330
client.add_success_response('yes',)
331
transport = RemoteTransport('bzr://localhost/', _client=client)
332
filename = u'/hell\u00d8'.encode('utf8')
333
result = transport.has(filename)
335
[('call', 'has', (filename,))],
337
self.assertTrue(result)
340
class TestRemote(tests.TestCaseWithMemoryTransport):
342
def get_branch_format(self):
343
reference_bzrdir_format = controldir.format_registry.get('default')()
344
return reference_bzrdir_format.get_branch_format()
346
def get_repo_format(self):
347
reference_bzrdir_format = controldir.format_registry.get('default')()
348
return reference_bzrdir_format.repository_format
350
def assertFinished(self, fake_client):
351
"""Assert that all of a FakeClient's expected calls have occurred."""
352
fake_client.finished_test()
355
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
356
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
358
def assertRemotePath(self, expected, client_base, transport_base):
359
"""Assert that the result of
360
SmartClientMedium.remote_path_from_transport is the expected value for
361
a given client_base and transport_base.
363
client_medium = medium.SmartClientMedium(client_base)
364
t = transport.get_transport(transport_base)
365
result = client_medium.remote_path_from_transport(t)
366
self.assertEqual(expected, result)
368
def test_remote_path_from_transport(self):
369
"""SmartClientMedium.remote_path_from_transport calculates a URL for
370
the given transport relative to the root of the client base URL.
372
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
373
self.assertRemotePath(
374
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
376
def assertRemotePathHTTP(self, expected, transport_base, relpath):
377
"""Assert that the result of
378
HttpTransportBase.remote_path_from_transport is the expected value for
379
a given transport_base and relpath of that transport. (Note that
380
HttpTransportBase is a subclass of SmartClientMedium)
382
base_transport = transport.get_transport(transport_base)
383
client_medium = base_transport.get_smart_medium()
384
cloned_transport = base_transport.clone(relpath)
385
result = client_medium.remote_path_from_transport(cloned_transport)
386
self.assertEqual(expected, result)
388
def test_remote_path_from_transport_http(self):
389
"""Remote paths for HTTP transports are calculated differently to other
390
transports. They are just relative to the client base, not the root
391
directory of the host.
393
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
394
self.assertRemotePathHTTP(
395
'../xyz/', scheme + '//host/path', '../xyz/')
396
self.assertRemotePathHTTP(
397
'xyz/', scheme + '//host/path', 'xyz/')
400
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
401
"""Tests for the behaviour of client_medium.remote_is_at_least."""
403
def test_initially_unlimited(self):
404
"""A fresh medium assumes that the remote side supports all
407
client_medium = medium.SmartClientMedium('dummy base')
408
self.assertFalse(client_medium._is_remote_before((99, 99)))
410
def test__remember_remote_is_before(self):
411
"""Calling _remember_remote_is_before ratchets down the known remote
414
client_medium = medium.SmartClientMedium('dummy base')
415
# Mark the remote side as being less than 1.6. The remote side may
417
client_medium._remember_remote_is_before((1, 6))
418
self.assertTrue(client_medium._is_remote_before((1, 6)))
419
self.assertFalse(client_medium._is_remote_before((1, 5)))
420
# Calling _remember_remote_is_before again with a lower value works.
421
client_medium._remember_remote_is_before((1, 5))
422
self.assertTrue(client_medium._is_remote_before((1, 5)))
423
# If you call _remember_remote_is_before with a higher value it logs a
424
# warning, and continues to remember the lower value.
425
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
426
client_medium._remember_remote_is_before((1, 9))
427
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
428
self.assertTrue(client_medium._is_remote_before((1, 5)))
431
class TestBzrDirCloningMetaDir(TestRemote):
433
def test_backwards_compat(self):
434
self.setup_smart_server_with_call_log()
435
a_dir = self.make_bzrdir('.')
436
self.reset_smart_call_log()
437
verb = 'BzrDir.cloning_metadir'
438
self.disable_verb(verb)
439
format = a_dir.cloning_metadir()
440
call_count = len([call for call in self.hpss_calls if
441
call.call.method == verb])
442
self.assertEqual(1, call_count)
444
def test_branch_reference(self):
445
transport = self.get_transport('quack')
446
referenced = self.make_branch('referenced')
447
expected = referenced.bzrdir.cloning_metadir()
448
client = FakeClient(transport.base)
449
client.add_expected_call(
450
'BzrDir.cloning_metadir', ('quack/', 'False'),
451
'error', ('BranchReference',)),
452
client.add_expected_call(
453
'BzrDir.open_branchV3', ('quack/',),
454
'success', ('ref', self.get_url('referenced'))),
455
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
457
result = a_bzrdir.cloning_metadir()
458
# We should have got a control dir matching the referenced branch.
459
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
460
self.assertEqual(expected._repository_format, result._repository_format)
461
self.assertEqual(expected._branch_format, result._branch_format)
462
self.assertFinished(client)
464
def test_current_server(self):
465
transport = self.get_transport('.')
466
transport = transport.clone('quack')
467
self.make_bzrdir('quack')
468
client = FakeClient(transport.base)
469
reference_bzrdir_format = controldir.format_registry.get('default')()
470
control_name = reference_bzrdir_format.network_name()
471
client.add_expected_call(
472
'BzrDir.cloning_metadir', ('quack/', 'False'),
473
'success', (control_name, '', ('branch', ''))),
474
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
476
result = a_bzrdir.cloning_metadir()
477
# We should have got a reference control dir with default branch and
478
# repository formats.
479
# This pokes a little, just to be sure.
480
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
481
self.assertEqual(None, result._repository_format)
482
self.assertEqual(None, result._branch_format)
483
self.assertFinished(client)
485
def test_unknown(self):
486
transport = self.get_transport('quack')
487
referenced = self.make_branch('referenced')
488
expected = referenced.bzrdir.cloning_metadir()
489
client = FakeClient(transport.base)
490
client.add_expected_call(
491
'BzrDir.cloning_metadir', ('quack/', 'False'),
492
'success', ('unknown', 'unknown', ('branch', ''))),
493
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
495
self.assertRaises(errors.UnknownFormatError, a_bzrdir.cloning_metadir)
498
class TestBzrDirCheckoutMetaDir(TestRemote):
500
def test__get_checkout_format(self):
501
transport = MemoryTransport()
502
client = FakeClient(transport.base)
503
reference_bzrdir_format = controldir.format_registry.get('default')()
504
control_name = reference_bzrdir_format.network_name()
505
client.add_expected_call(
506
'BzrDir.checkout_metadir', ('quack/', ),
507
'success', (control_name, '', ''))
508
transport.mkdir('quack')
509
transport = transport.clone('quack')
510
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
512
result = a_bzrdir.checkout_metadir()
513
# We should have got a reference control dir with default branch and
514
# repository formats.
515
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
516
self.assertEqual(None, result._repository_format)
517
self.assertEqual(None, result._branch_format)
518
self.assertFinished(client)
520
def test_unknown_format(self):
521
transport = MemoryTransport()
522
client = FakeClient(transport.base)
523
client.add_expected_call(
524
'BzrDir.checkout_metadir', ('quack/',),
525
'success', ('dontknow', '', ''))
526
transport.mkdir('quack')
527
transport = transport.clone('quack')
528
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
530
self.assertRaises(errors.UnknownFormatError,
531
a_bzrdir.checkout_metadir)
532
self.assertFinished(client)
535
class TestBzrDirGetBranches(TestRemote):
537
def test_get_branches(self):
538
transport = MemoryTransport()
539
client = FakeClient(transport.base)
540
reference_bzrdir_format = controldir.format_registry.get('default')()
541
branch_name = reference_bzrdir_format.get_branch_format().network_name()
542
client.add_success_response_with_body(
544
"foo": ("branch", branch_name),
545
"": ("branch", branch_name)}), "success")
546
client.add_success_response(
547
'ok', '', 'no', 'no', 'no',
548
reference_bzrdir_format.repository_format.network_name())
549
client.add_error_response('NotStacked')
550
client.add_success_response(
551
'ok', '', 'no', 'no', 'no',
552
reference_bzrdir_format.repository_format.network_name())
553
client.add_error_response('NotStacked')
554
transport.mkdir('quack')
555
transport = transport.clone('quack')
556
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
558
result = a_bzrdir.get_branches()
559
self.assertEqual(set(["", "foo"]), set(result.keys()))
561
[('call_expecting_body', 'BzrDir.get_branches', ('quack/',)),
562
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
563
('call', 'Branch.get_stacked_on_url', ('quack/', )),
564
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
565
('call', 'Branch.get_stacked_on_url', ('quack/', ))],
569
class TestBzrDirDestroyBranch(TestRemote):
571
def test_destroy_default(self):
572
transport = self.get_transport('quack')
573
referenced = self.make_branch('referenced')
574
client = FakeClient(transport.base)
575
client.add_expected_call(
576
'BzrDir.destroy_branch', ('quack/', ),
578
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
580
a_bzrdir.destroy_branch()
581
self.assertFinished(client)
584
class TestBzrDirHasWorkingTree(TestRemote):
586
def test_has_workingtree(self):
587
transport = self.get_transport('quack')
588
client = FakeClient(transport.base)
589
client.add_expected_call(
590
'BzrDir.has_workingtree', ('quack/',),
591
'success', ('yes',)),
592
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
594
self.assertTrue(a_bzrdir.has_workingtree())
595
self.assertFinished(client)
597
def test_no_workingtree(self):
598
transport = self.get_transport('quack')
599
client = FakeClient(transport.base)
600
client.add_expected_call(
601
'BzrDir.has_workingtree', ('quack/',),
603
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
605
self.assertFalse(a_bzrdir.has_workingtree())
606
self.assertFinished(client)
609
class TestBzrDirDestroyRepository(TestRemote):
611
def test_destroy_repository(self):
612
transport = self.get_transport('quack')
613
client = FakeClient(transport.base)
614
client.add_expected_call(
615
'BzrDir.destroy_repository', ('quack/',),
617
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
619
a_bzrdir.destroy_repository()
620
self.assertFinished(client)
623
class TestBzrDirOpen(TestRemote):
625
def make_fake_client_and_transport(self, path='quack'):
626
transport = MemoryTransport()
627
transport.mkdir(path)
628
transport = transport.clone(path)
629
client = FakeClient(transport.base)
630
return client, transport
632
def test_absent(self):
633
client, transport = self.make_fake_client_and_transport()
634
client.add_expected_call(
635
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
636
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
637
RemoteBzrDirFormat(), _client=client, _force_probe=True)
638
self.assertFinished(client)
640
def test_present_without_workingtree(self):
641
client, transport = self.make_fake_client_and_transport()
642
client.add_expected_call(
643
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
644
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
645
_client=client, _force_probe=True)
646
self.assertIsInstance(bd, RemoteBzrDir)
647
self.assertFalse(bd.has_workingtree())
648
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
649
self.assertFinished(client)
651
def test_present_with_workingtree(self):
652
client, transport = self.make_fake_client_and_transport()
653
client.add_expected_call(
654
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
655
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
656
_client=client, _force_probe=True)
657
self.assertIsInstance(bd, RemoteBzrDir)
658
self.assertTrue(bd.has_workingtree())
659
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
660
self.assertFinished(client)
662
def test_backwards_compat(self):
663
client, transport = self.make_fake_client_and_transport()
664
client.add_expected_call(
665
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
666
client.add_expected_call(
667
'BzrDir.open', ('quack/',), 'success', ('yes',))
668
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
669
_client=client, _force_probe=True)
670
self.assertIsInstance(bd, RemoteBzrDir)
671
self.assertFinished(client)
673
def test_backwards_compat_hpss_v2(self):
674
client, transport = self.make_fake_client_and_transport()
675
# Monkey-patch fake client to simulate real-world behaviour with v2
676
# server: upon first RPC call detect the protocol version, and because
677
# the version is 2 also do _remember_remote_is_before((1, 6)) before
678
# continuing with the RPC.
679
orig_check_call = client._check_call
680
def check_call(method, args):
681
client._medium._protocol_version = 2
682
client._medium._remember_remote_is_before((1, 6))
683
client._check_call = orig_check_call
684
client._check_call(method, args)
685
client._check_call = check_call
686
client.add_expected_call(
687
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
688
client.add_expected_call(
689
'BzrDir.open', ('quack/',), 'success', ('yes',))
690
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
691
_client=client, _force_probe=True)
692
self.assertIsInstance(bd, RemoteBzrDir)
693
self.assertFinished(client)
696
class TestBzrDirOpenBranch(TestRemote):
698
def test_backwards_compat(self):
699
self.setup_smart_server_with_call_log()
700
self.make_branch('.')
701
a_dir = BzrDir.open(self.get_url('.'))
702
self.reset_smart_call_log()
703
verb = 'BzrDir.open_branchV3'
704
self.disable_verb(verb)
705
format = a_dir.open_branch()
706
call_count = len([call for call in self.hpss_calls if
707
call.call.method == verb])
708
self.assertEqual(1, call_count)
710
def test_branch_present(self):
711
reference_format = self.get_repo_format()
712
network_name = reference_format.network_name()
713
branch_network_name = self.get_branch_format().network_name()
714
transport = MemoryTransport()
715
transport.mkdir('quack')
716
transport = transport.clone('quack')
717
client = FakeClient(transport.base)
718
client.add_expected_call(
719
'BzrDir.open_branchV3', ('quack/',),
720
'success', ('branch', branch_network_name))
721
client.add_expected_call(
722
'BzrDir.find_repositoryV3', ('quack/',),
723
'success', ('ok', '', 'no', 'no', 'no', network_name))
724
client.add_expected_call(
725
'Branch.get_stacked_on_url', ('quack/',),
726
'error', ('NotStacked',))
727
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
729
result = bzrdir.open_branch()
730
self.assertIsInstance(result, RemoteBranch)
731
self.assertEqual(bzrdir, result.bzrdir)
732
self.assertFinished(client)
734
def test_branch_missing(self):
735
transport = MemoryTransport()
736
transport.mkdir('quack')
737
transport = transport.clone('quack')
738
client = FakeClient(transport.base)
739
client.add_error_response('nobranch')
740
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
742
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
744
[('call', 'BzrDir.open_branchV3', ('quack/',))],
747
def test__get_tree_branch(self):
748
# _get_tree_branch is a form of open_branch, but it should only ask for
749
# branch opening, not any other network requests.
751
def open_branch(name=None, possible_transports=None):
752
calls.append("Called")
754
transport = MemoryTransport()
755
# no requests on the network - catches other api calls being made.
756
client = FakeClient(transport.base)
757
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
759
# patch the open_branch call to record that it was called.
760
bzrdir.open_branch = open_branch
761
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
762
self.assertEqual(["Called"], calls)
763
self.assertEqual([], client._calls)
765
def test_url_quoting_of_path(self):
766
# Relpaths on the wire should not be URL-escaped. So "~" should be
767
# transmitted as "~", not "%7E".
768
transport = RemoteTCPTransport('bzr://localhost/~hello/')
769
client = FakeClient(transport.base)
770
reference_format = self.get_repo_format()
771
network_name = reference_format.network_name()
772
branch_network_name = self.get_branch_format().network_name()
773
client.add_expected_call(
774
'BzrDir.open_branchV3', ('~hello/',),
775
'success', ('branch', branch_network_name))
776
client.add_expected_call(
777
'BzrDir.find_repositoryV3', ('~hello/',),
778
'success', ('ok', '', 'no', 'no', 'no', network_name))
779
client.add_expected_call(
780
'Branch.get_stacked_on_url', ('~hello/',),
781
'error', ('NotStacked',))
782
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
784
result = bzrdir.open_branch()
785
self.assertFinished(client)
787
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
788
reference_format = self.get_repo_format()
789
network_name = reference_format.network_name()
790
transport = MemoryTransport()
791
transport.mkdir('quack')
792
transport = transport.clone('quack')
794
rich_response = 'yes'
798
subtree_response = 'yes'
800
subtree_response = 'no'
801
client = FakeClient(transport.base)
802
client.add_success_response(
803
'ok', '', rich_response, subtree_response, external_lookup,
805
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
807
result = bzrdir.open_repository()
809
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
811
self.assertIsInstance(result, RemoteRepository)
812
self.assertEqual(bzrdir, result.bzrdir)
813
self.assertEqual(rich_root, result._format.rich_root_data)
814
self.assertEqual(subtrees, result._format.supports_tree_reference)
816
def test_open_repository_sets_format_attributes(self):
817
self.check_open_repository(True, True)
818
self.check_open_repository(False, True)
819
self.check_open_repository(True, False)
820
self.check_open_repository(False, False)
821
self.check_open_repository(False, False, 'yes')
823
def test_old_server(self):
824
"""RemoteBzrDirFormat should fail to probe if the server version is too
827
self.assertRaises(errors.NotBranchError,
828
RemoteBzrProber.probe_transport, OldServerTransport())
831
class TestBzrDirCreateBranch(TestRemote):
833
def test_backwards_compat(self):
834
self.setup_smart_server_with_call_log()
835
repo = self.make_repository('.')
836
self.reset_smart_call_log()
837
self.disable_verb('BzrDir.create_branch')
838
branch = repo.bzrdir.create_branch()
839
create_branch_call_count = len([call for call in self.hpss_calls if
840
call.call.method == 'BzrDir.create_branch'])
841
self.assertEqual(1, create_branch_call_count)
843
def test_current_server(self):
844
transport = self.get_transport('.')
845
transport = transport.clone('quack')
846
self.make_repository('quack')
847
client = FakeClient(transport.base)
848
reference_bzrdir_format = controldir.format_registry.get('default')()
849
reference_format = reference_bzrdir_format.get_branch_format()
850
network_name = reference_format.network_name()
851
reference_repo_fmt = reference_bzrdir_format.repository_format
852
reference_repo_name = reference_repo_fmt.network_name()
853
client.add_expected_call(
854
'BzrDir.create_branch', ('quack/', network_name),
855
'success', ('ok', network_name, '', 'no', 'no', 'yes',
856
reference_repo_name))
857
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
859
branch = a_bzrdir.create_branch()
860
# We should have got a remote branch
861
self.assertIsInstance(branch, remote.RemoteBranch)
862
# its format should have the settings from the response
863
format = branch._format
864
self.assertEqual(network_name, format.network_name())
866
def test_already_open_repo_and_reused_medium(self):
867
"""Bug 726584: create_branch(..., repository=repo) should work
868
regardless of what the smart medium's base URL is.
870
self.transport_server = test_server.SmartTCPServer_for_testing
871
transport = self.get_transport('.')
872
repo = self.make_repository('quack')
873
# Client's medium rooted a transport root (not at the bzrdir)
874
client = FakeClient(transport.base)
875
transport = transport.clone('quack')
876
reference_bzrdir_format = controldir.format_registry.get('default')()
877
reference_format = reference_bzrdir_format.get_branch_format()
878
network_name = reference_format.network_name()
879
reference_repo_fmt = reference_bzrdir_format.repository_format
880
reference_repo_name = reference_repo_fmt.network_name()
881
client.add_expected_call(
882
'BzrDir.create_branch', ('extra/quack/', network_name),
883
'success', ('ok', network_name, '', 'no', 'no', 'yes',
884
reference_repo_name))
885
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
887
branch = a_bzrdir.create_branch(repository=repo)
888
# We should have got a remote branch
889
self.assertIsInstance(branch, remote.RemoteBranch)
890
# its format should have the settings from the response
891
format = branch._format
892
self.assertEqual(network_name, format.network_name())
895
class TestBzrDirCreateRepository(TestRemote):
897
def test_backwards_compat(self):
898
self.setup_smart_server_with_call_log()
899
bzrdir = self.make_bzrdir('.')
900
self.reset_smart_call_log()
901
self.disable_verb('BzrDir.create_repository')
902
repo = bzrdir.create_repository()
903
create_repo_call_count = len([call for call in self.hpss_calls if
904
call.call.method == 'BzrDir.create_repository'])
905
self.assertEqual(1, create_repo_call_count)
907
def test_current_server(self):
908
transport = self.get_transport('.')
909
transport = transport.clone('quack')
910
self.make_bzrdir('quack')
911
client = FakeClient(transport.base)
912
reference_bzrdir_format = controldir.format_registry.get('default')()
913
reference_format = reference_bzrdir_format.repository_format
914
network_name = reference_format.network_name()
915
client.add_expected_call(
916
'BzrDir.create_repository', ('quack/',
917
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
919
'success', ('ok', 'yes', 'yes', 'yes', network_name))
920
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
922
repo = a_bzrdir.create_repository()
923
# We should have got a remote repository
924
self.assertIsInstance(repo, remote.RemoteRepository)
925
# its format should have the settings from the response
926
format = repo._format
927
self.assertTrue(format.rich_root_data)
928
self.assertTrue(format.supports_tree_reference)
929
self.assertTrue(format.supports_external_lookups)
930
self.assertEqual(network_name, format.network_name())
933
class TestBzrDirOpenRepository(TestRemote):
935
def test_backwards_compat_1_2_3(self):
936
# fallback all the way to the first version.
937
reference_format = self.get_repo_format()
938
network_name = reference_format.network_name()
939
server_url = 'bzr://example.com/'
940
self.permit_url(server_url)
941
client = FakeClient(server_url)
942
client.add_unknown_method_response('BzrDir.find_repositoryV3')
943
client.add_unknown_method_response('BzrDir.find_repositoryV2')
944
client.add_success_response('ok', '', 'no', 'no')
945
# A real repository instance will be created to determine the network
947
client.add_success_response_with_body(
948
"Bazaar-NG meta directory, format 1\n", 'ok')
949
client.add_success_response('stat', '0', '65535')
950
client.add_success_response_with_body(
951
reference_format.get_format_string(), 'ok')
952
# PackRepository wants to do a stat
953
client.add_success_response('stat', '0', '65535')
954
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
956
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
958
repo = bzrdir.open_repository()
960
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
961
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
962
('call', 'BzrDir.find_repository', ('quack/',)),
963
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
964
('call', 'stat', ('/quack/.bzr',)),
965
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
966
('call', 'stat', ('/quack/.bzr/repository',)),
969
self.assertEqual(network_name, repo._format.network_name())
971
def test_backwards_compat_2(self):
972
# fallback to find_repositoryV2
973
reference_format = self.get_repo_format()
974
network_name = reference_format.network_name()
975
server_url = 'bzr://example.com/'
976
self.permit_url(server_url)
977
client = FakeClient(server_url)
978
client.add_unknown_method_response('BzrDir.find_repositoryV3')
979
client.add_success_response('ok', '', 'no', 'no', 'no')
980
# A real repository instance will be created to determine the network
982
client.add_success_response_with_body(
983
"Bazaar-NG meta directory, format 1\n", 'ok')
984
client.add_success_response('stat', '0', '65535')
985
client.add_success_response_with_body(
986
reference_format.get_format_string(), 'ok')
987
# PackRepository wants to do a stat
988
client.add_success_response('stat', '0', '65535')
989
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
991
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
993
repo = bzrdir.open_repository()
995
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
996
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
997
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
998
('call', 'stat', ('/quack/.bzr',)),
999
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
1000
('call', 'stat', ('/quack/.bzr/repository',)),
1003
self.assertEqual(network_name, repo._format.network_name())
1005
def test_current_server(self):
1006
reference_format = self.get_repo_format()
1007
network_name = reference_format.network_name()
1008
transport = MemoryTransport()
1009
transport.mkdir('quack')
1010
transport = transport.clone('quack')
1011
client = FakeClient(transport.base)
1012
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
1013
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1015
repo = bzrdir.open_repository()
1017
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
1019
self.assertEqual(network_name, repo._format.network_name())
1022
class TestBzrDirFormatInitializeEx(TestRemote):
1024
def test_success(self):
1025
"""Simple test for typical successful call."""
1026
fmt = RemoteBzrDirFormat()
1027
default_format_name = BzrDirFormat.get_default_format().network_name()
1028
transport = self.get_transport()
1029
client = FakeClient(transport.base)
1030
client.add_expected_call(
1031
'BzrDirFormat.initialize_ex_1.16',
1032
(default_format_name, 'path', 'False', 'False', 'False', '',
1033
'', '', '', 'False'),
1035
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
1036
'bzrdir fmt', 'False', '', '', 'repo lock token'))
1037
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1038
# it's currently hard to test that without supplying a real remote
1039
# transport connected to a real server.
1040
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1041
transport, False, False, False, None, None, None, None, False)
1042
self.assertFinished(client)
1044
def test_error(self):
1045
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1046
corresponding error from the client.
1048
fmt = RemoteBzrDirFormat()
1049
default_format_name = BzrDirFormat.get_default_format().network_name()
1050
transport = self.get_transport()
1051
client = FakeClient(transport.base)
1052
client.add_expected_call(
1053
'BzrDirFormat.initialize_ex_1.16',
1054
(default_format_name, 'path', 'False', 'False', 'False', '',
1055
'', '', '', 'False'),
1057
('PermissionDenied', 'path', 'extra info'))
1058
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1059
# it's currently hard to test that without supplying a real remote
1060
# transport connected to a real server.
1061
err = self.assertRaises(errors.PermissionDenied,
1062
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1063
False, False, False, None, None, None, None, False)
1064
self.assertEqual('path', err.path)
1065
self.assertEqual(': extra info', err.extra)
1066
self.assertFinished(client)
1068
def test_error_from_real_server(self):
1069
"""Integration test for error translation."""
1070
transport = self.make_smart_server('foo')
1071
transport = transport.clone('no-such-path')
1072
fmt = RemoteBzrDirFormat()
1073
err = self.assertRaises(errors.NoSuchFile,
1074
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1077
class OldSmartClient(object):
1078
"""A fake smart client for test_old_version that just returns a version one
1079
response to the 'hello' (query version) command.
1082
def get_request(self):
1083
input_file = StringIO('ok\x011\n')
1084
output_file = StringIO()
1085
client_medium = medium.SmartSimplePipesClientMedium(
1086
input_file, output_file)
1087
return medium.SmartClientStreamMediumRequest(client_medium)
1089
def protocol_version(self):
1093
class OldServerTransport(object):
1094
"""A fake transport for test_old_server that reports it's smart server
1095
protocol version as version one.
1101
def get_smart_client(self):
1102
return OldSmartClient()
1105
class RemoteBzrDirTestCase(TestRemote):
1107
def make_remote_bzrdir(self, transport, client):
1108
"""Make a RemotebzrDir using 'client' as the _client."""
1109
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1113
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1115
def lock_remote_branch(self, branch):
1116
"""Trick a RemoteBranch into thinking it is locked."""
1117
branch._lock_mode = 'w'
1118
branch._lock_count = 2
1119
branch._lock_token = 'branch token'
1120
branch._repo_lock_token = 'repo token'
1121
branch.repository._lock_mode = 'w'
1122
branch.repository._lock_count = 2
1123
branch.repository._lock_token = 'repo token'
1125
def make_remote_branch(self, transport, client):
1126
"""Make a RemoteBranch using 'client' as its _SmartClient.
1128
A RemoteBzrDir and RemoteRepository will also be created to fill out
1129
the RemoteBranch, albeit with stub values for some of their attributes.
1131
# we do not want bzrdir to make any remote calls, so use False as its
1132
# _client. If it tries to make a remote call, this will fail
1134
bzrdir = self.make_remote_bzrdir(transport, False)
1135
repo = RemoteRepository(bzrdir, None, _client=client)
1136
branch_format = self.get_branch_format()
1137
format = RemoteBranchFormat(network_name=branch_format.network_name())
1138
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1141
class TestBranchBreakLock(RemoteBranchTestCase):
1143
def test_break_lock(self):
1144
transport_path = 'quack'
1145
transport = MemoryTransport()
1146
client = FakeClient(transport.base)
1147
client.add_expected_call(
1148
'Branch.get_stacked_on_url', ('quack/',),
1149
'error', ('NotStacked',))
1150
client.add_expected_call(
1151
'Branch.break_lock', ('quack/',),
1153
transport.mkdir('quack')
1154
transport = transport.clone('quack')
1155
branch = self.make_remote_branch(transport, client)
1157
self.assertFinished(client)
1160
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1162
def test_get_physical_lock_status_yes(self):
1163
transport = MemoryTransport()
1164
client = FakeClient(transport.base)
1165
client.add_expected_call(
1166
'Branch.get_stacked_on_url', ('quack/',),
1167
'error', ('NotStacked',))
1168
client.add_expected_call(
1169
'Branch.get_physical_lock_status', ('quack/',),
1170
'success', ('yes',))
1171
transport.mkdir('quack')
1172
transport = transport.clone('quack')
1173
branch = self.make_remote_branch(transport, client)
1174
result = branch.get_physical_lock_status()
1175
self.assertFinished(client)
1176
self.assertEqual(True, result)
1178
def test_get_physical_lock_status_no(self):
1179
transport = MemoryTransport()
1180
client = FakeClient(transport.base)
1181
client.add_expected_call(
1182
'Branch.get_stacked_on_url', ('quack/',),
1183
'error', ('NotStacked',))
1184
client.add_expected_call(
1185
'Branch.get_physical_lock_status', ('quack/',),
1187
transport.mkdir('quack')
1188
transport = transport.clone('quack')
1189
branch = self.make_remote_branch(transport, client)
1190
result = branch.get_physical_lock_status()
1191
self.assertFinished(client)
1192
self.assertEqual(False, result)
1195
class TestBranchGetParent(RemoteBranchTestCase):
1197
def test_no_parent(self):
1198
# in an empty branch we decode the response properly
1199
transport = MemoryTransport()
1200
client = FakeClient(transport.base)
1201
client.add_expected_call(
1202
'Branch.get_stacked_on_url', ('quack/',),
1203
'error', ('NotStacked',))
1204
client.add_expected_call(
1205
'Branch.get_parent', ('quack/',),
1207
transport.mkdir('quack')
1208
transport = transport.clone('quack')
1209
branch = self.make_remote_branch(transport, client)
1210
result = branch.get_parent()
1211
self.assertFinished(client)
1212
self.assertEqual(None, result)
1214
def test_parent_relative(self):
1215
transport = MemoryTransport()
1216
client = FakeClient(transport.base)
1217
client.add_expected_call(
1218
'Branch.get_stacked_on_url', ('kwaak/',),
1219
'error', ('NotStacked',))
1220
client.add_expected_call(
1221
'Branch.get_parent', ('kwaak/',),
1222
'success', ('../foo/',))
1223
transport.mkdir('kwaak')
1224
transport = transport.clone('kwaak')
1225
branch = self.make_remote_branch(transport, client)
1226
result = branch.get_parent()
1227
self.assertEqual(transport.clone('../foo').base, result)
1229
def test_parent_absolute(self):
1230
transport = MemoryTransport()
1231
client = FakeClient(transport.base)
1232
client.add_expected_call(
1233
'Branch.get_stacked_on_url', ('kwaak/',),
1234
'error', ('NotStacked',))
1235
client.add_expected_call(
1236
'Branch.get_parent', ('kwaak/',),
1237
'success', ('http://foo/',))
1238
transport.mkdir('kwaak')
1239
transport = transport.clone('kwaak')
1240
branch = self.make_remote_branch(transport, client)
1241
result = branch.get_parent()
1242
self.assertEqual('http://foo/', result)
1243
self.assertFinished(client)
1246
class TestBranchSetParentLocation(RemoteBranchTestCase):
1248
def test_no_parent(self):
1249
# We call the verb when setting parent to None
1250
transport = MemoryTransport()
1251
client = FakeClient(transport.base)
1252
client.add_expected_call(
1253
'Branch.get_stacked_on_url', ('quack/',),
1254
'error', ('NotStacked',))
1255
client.add_expected_call(
1256
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1258
transport.mkdir('quack')
1259
transport = transport.clone('quack')
1260
branch = self.make_remote_branch(transport, client)
1261
branch._lock_token = 'b'
1262
branch._repo_lock_token = 'r'
1263
branch._set_parent_location(None)
1264
self.assertFinished(client)
1266
def test_parent(self):
1267
transport = MemoryTransport()
1268
client = FakeClient(transport.base)
1269
client.add_expected_call(
1270
'Branch.get_stacked_on_url', ('kwaak/',),
1271
'error', ('NotStacked',))
1272
client.add_expected_call(
1273
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1275
transport.mkdir('kwaak')
1276
transport = transport.clone('kwaak')
1277
branch = self.make_remote_branch(transport, client)
1278
branch._lock_token = 'b'
1279
branch._repo_lock_token = 'r'
1280
branch._set_parent_location('foo')
1281
self.assertFinished(client)
1283
def test_backwards_compat(self):
1284
self.setup_smart_server_with_call_log()
1285
branch = self.make_branch('.')
1286
self.reset_smart_call_log()
1287
verb = 'Branch.set_parent_location'
1288
self.disable_verb(verb)
1289
branch.set_parent('http://foo/')
1290
self.assertLength(14, self.hpss_calls)
1293
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1295
def test_backwards_compat(self):
1296
self.setup_smart_server_with_call_log()
1297
branch = self.make_branch('.')
1298
self.reset_smart_call_log()
1299
verb = 'Branch.get_tags_bytes'
1300
self.disable_verb(verb)
1301
branch.tags.get_tag_dict()
1302
call_count = len([call for call in self.hpss_calls if
1303
call.call.method == verb])
1304
self.assertEqual(1, call_count)
1306
def test_trivial(self):
1307
transport = MemoryTransport()
1308
client = FakeClient(transport.base)
1309
client.add_expected_call(
1310
'Branch.get_stacked_on_url', ('quack/',),
1311
'error', ('NotStacked',))
1312
client.add_expected_call(
1313
'Branch.get_tags_bytes', ('quack/',),
1315
transport.mkdir('quack')
1316
transport = transport.clone('quack')
1317
branch = self.make_remote_branch(transport, client)
1318
result = branch.tags.get_tag_dict()
1319
self.assertFinished(client)
1320
self.assertEqual({}, result)
1323
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1325
def test_trivial(self):
1326
transport = MemoryTransport()
1327
client = FakeClient(transport.base)
1328
client.add_expected_call(
1329
'Branch.get_stacked_on_url', ('quack/',),
1330
'error', ('NotStacked',))
1331
client.add_expected_call(
1332
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1334
transport.mkdir('quack')
1335
transport = transport.clone('quack')
1336
branch = self.make_remote_branch(transport, client)
1337
self.lock_remote_branch(branch)
1338
branch._set_tags_bytes('tags bytes')
1339
self.assertFinished(client)
1340
self.assertEqual('tags bytes', client._calls[-1][-1])
1342
def test_backwards_compatible(self):
1343
transport = MemoryTransport()
1344
client = FakeClient(transport.base)
1345
client.add_expected_call(
1346
'Branch.get_stacked_on_url', ('quack/',),
1347
'error', ('NotStacked',))
1348
client.add_expected_call(
1349
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1350
'unknown', ('Branch.set_tags_bytes',))
1351
transport.mkdir('quack')
1352
transport = transport.clone('quack')
1353
branch = self.make_remote_branch(transport, client)
1354
self.lock_remote_branch(branch)
1355
class StubRealBranch(object):
1358
def _set_tags_bytes(self, bytes):
1359
self.calls.append(('set_tags_bytes', bytes))
1360
real_branch = StubRealBranch()
1361
branch._real_branch = real_branch
1362
branch._set_tags_bytes('tags bytes')
1363
# Call a second time, to exercise the 'remote version already inferred'
1365
branch._set_tags_bytes('tags bytes')
1366
self.assertFinished(client)
1368
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1371
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1373
def test_uses_last_revision_info_and_tags_by_default(self):
1374
transport = MemoryTransport()
1375
client = FakeClient(transport.base)
1376
client.add_expected_call(
1377
'Branch.get_stacked_on_url', ('quack/',),
1378
'error', ('NotStacked',))
1379
client.add_expected_call(
1380
'Branch.last_revision_info', ('quack/',),
1381
'success', ('ok', '1', 'rev-tip'))
1382
client.add_expected_call(
1383
'Branch.get_config_file', ('quack/',),
1384
'success', ('ok',), '')
1385
transport.mkdir('quack')
1386
transport = transport.clone('quack')
1387
branch = self.make_remote_branch(transport, client)
1388
result = branch.heads_to_fetch()
1389
self.assertFinished(client)
1390
self.assertEqual((set(['rev-tip']), set()), result)
1392
def test_uses_last_revision_info_and_tags_when_set(self):
1393
transport = MemoryTransport()
1394
client = FakeClient(transport.base)
1395
client.add_expected_call(
1396
'Branch.get_stacked_on_url', ('quack/',),
1397
'error', ('NotStacked',))
1398
client.add_expected_call(
1399
'Branch.last_revision_info', ('quack/',),
1400
'success', ('ok', '1', 'rev-tip'))
1401
client.add_expected_call(
1402
'Branch.get_config_file', ('quack/',),
1403
'success', ('ok',), 'branch.fetch_tags = True')
1404
# XXX: this will break if the default format's serialization of tags
1405
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1406
client.add_expected_call(
1407
'Branch.get_tags_bytes', ('quack/',),
1408
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1409
transport.mkdir('quack')
1410
transport = transport.clone('quack')
1411
branch = self.make_remote_branch(transport, client)
1412
result = branch.heads_to_fetch()
1413
self.assertFinished(client)
1415
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1417
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1418
transport = MemoryTransport()
1419
client = FakeClient(transport.base)
1420
client.add_expected_call(
1421
'Branch.get_stacked_on_url', ('quack/',),
1422
'error', ('NotStacked',))
1423
client.add_expected_call(
1424
'Branch.heads_to_fetch', ('quack/',),
1425
'success', (['tip'], ['tagged-1', 'tagged-2']))
1426
transport.mkdir('quack')
1427
transport = transport.clone('quack')
1428
branch = self.make_remote_branch(transport, client)
1429
branch._format._use_default_local_heads_to_fetch = lambda: False
1430
result = branch.heads_to_fetch()
1431
self.assertFinished(client)
1432
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1434
def make_branch_with_tags(self):
1435
self.setup_smart_server_with_call_log()
1436
# Make a branch with a single revision.
1437
builder = self.make_branch_builder('foo')
1438
builder.start_series()
1439
builder.build_snapshot('tip', None, [
1440
('add', ('', 'root-id', 'directory', ''))])
1441
builder.finish_series()
1442
branch = builder.get_branch()
1443
# Add two tags to that branch
1444
branch.tags.set_tag('tag-1', 'rev-1')
1445
branch.tags.set_tag('tag-2', 'rev-2')
1448
def test_backwards_compatible(self):
1449
br = self.make_branch_with_tags()
1450
br.get_config_stack().set('branch.fetch_tags', True)
1451
self.addCleanup(br.lock_read().unlock)
1452
# Disable the heads_to_fetch verb
1453
verb = 'Branch.heads_to_fetch'
1454
self.disable_verb(verb)
1455
self.reset_smart_call_log()
1456
result = br.heads_to_fetch()
1457
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1459
['Branch.last_revision_info', 'Branch.get_tags_bytes'],
1460
[call.call.method for call in self.hpss_calls])
1462
def test_backwards_compatible_no_tags(self):
1463
br = self.make_branch_with_tags()
1464
br.get_config_stack().set('branch.fetch_tags', False)
1465
self.addCleanup(br.lock_read().unlock)
1466
# Disable the heads_to_fetch verb
1467
verb = 'Branch.heads_to_fetch'
1468
self.disable_verb(verb)
1469
self.reset_smart_call_log()
1470
result = br.heads_to_fetch()
1471
self.assertEqual((set(['tip']), set()), result)
1473
['Branch.last_revision_info'],
1474
[call.call.method for call in self.hpss_calls])
1477
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1479
def test_empty_branch(self):
1480
# in an empty branch we decode the response properly
1481
transport = MemoryTransport()
1482
client = FakeClient(transport.base)
1483
client.add_expected_call(
1484
'Branch.get_stacked_on_url', ('quack/',),
1485
'error', ('NotStacked',))
1486
client.add_expected_call(
1487
'Branch.last_revision_info', ('quack/',),
1488
'success', ('ok', '0', 'null:'))
1489
transport.mkdir('quack')
1490
transport = transport.clone('quack')
1491
branch = self.make_remote_branch(transport, client)
1492
result = branch.last_revision_info()
1493
self.assertFinished(client)
1494
self.assertEqual((0, NULL_REVISION), result)
1496
def test_non_empty_branch(self):
1497
# in a non-empty branch we also decode the response properly
1498
revid = u'\xc8'.encode('utf8')
1499
transport = MemoryTransport()
1500
client = FakeClient(transport.base)
1501
client.add_expected_call(
1502
'Branch.get_stacked_on_url', ('kwaak/',),
1503
'error', ('NotStacked',))
1504
client.add_expected_call(
1505
'Branch.last_revision_info', ('kwaak/',),
1506
'success', ('ok', '2', revid))
1507
transport.mkdir('kwaak')
1508
transport = transport.clone('kwaak')
1509
branch = self.make_remote_branch(transport, client)
1510
result = branch.last_revision_info()
1511
self.assertEqual((2, revid), result)
1514
class TestBranch_get_stacked_on_url(TestRemote):
1515
"""Test Branch._get_stacked_on_url rpc"""
1517
def test_get_stacked_on_invalid_url(self):
1518
# test that asking for a stacked on url the server can't access works.
1519
# This isn't perfect, but then as we're in the same process there
1520
# really isn't anything we can do to be 100% sure that the server
1521
# doesn't just open in - this test probably needs to be rewritten using
1522
# a spawn()ed server.
1523
stacked_branch = self.make_branch('stacked', format='1.9')
1524
memory_branch = self.make_branch('base', format='1.9')
1525
vfs_url = self.get_vfs_only_url('base')
1526
stacked_branch.set_stacked_on_url(vfs_url)
1527
transport = stacked_branch.bzrdir.root_transport
1528
client = FakeClient(transport.base)
1529
client.add_expected_call(
1530
'Branch.get_stacked_on_url', ('stacked/',),
1531
'success', ('ok', vfs_url))
1532
# XXX: Multiple calls are bad, this second call documents what is
1534
client.add_expected_call(
1535
'Branch.get_stacked_on_url', ('stacked/',),
1536
'success', ('ok', vfs_url))
1537
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1539
repo_fmt = remote.RemoteRepositoryFormat()
1540
repo_fmt._custom_format = stacked_branch.repository._format
1541
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1543
result = branch.get_stacked_on_url()
1544
self.assertEqual(vfs_url, result)
1546
def test_backwards_compatible(self):
1547
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1548
base_branch = self.make_branch('base', format='1.6')
1549
stacked_branch = self.make_branch('stacked', format='1.6')
1550
stacked_branch.set_stacked_on_url('../base')
1551
client = FakeClient(self.get_url())
1552
branch_network_name = self.get_branch_format().network_name()
1553
client.add_expected_call(
1554
'BzrDir.open_branchV3', ('stacked/',),
1555
'success', ('branch', branch_network_name))
1556
client.add_expected_call(
1557
'BzrDir.find_repositoryV3', ('stacked/',),
1558
'success', ('ok', '', 'no', 'no', 'yes',
1559
stacked_branch.repository._format.network_name()))
1560
# called twice, once from constructor and then again by us
1561
client.add_expected_call(
1562
'Branch.get_stacked_on_url', ('stacked/',),
1563
'unknown', ('Branch.get_stacked_on_url',))
1564
client.add_expected_call(
1565
'Branch.get_stacked_on_url', ('stacked/',),
1566
'unknown', ('Branch.get_stacked_on_url',))
1567
# this will also do vfs access, but that goes direct to the transport
1568
# and isn't seen by the FakeClient.
1569
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1570
RemoteBzrDirFormat(), _client=client)
1571
branch = bzrdir.open_branch()
1572
result = branch.get_stacked_on_url()
1573
self.assertEqual('../base', result)
1574
self.assertFinished(client)
1575
# it's in the fallback list both for the RemoteRepository and its vfs
1577
self.assertEqual(1, len(branch.repository._fallback_repositories))
1579
len(branch.repository._real_repository._fallback_repositories))
1581
def test_get_stacked_on_real_branch(self):
1582
base_branch = self.make_branch('base')
1583
stacked_branch = self.make_branch('stacked')
1584
stacked_branch.set_stacked_on_url('../base')
1585
reference_format = self.get_repo_format()
1586
network_name = reference_format.network_name()
1587
client = FakeClient(self.get_url())
1588
branch_network_name = self.get_branch_format().network_name()
1589
client.add_expected_call(
1590
'BzrDir.open_branchV3', ('stacked/',),
1591
'success', ('branch', branch_network_name))
1592
client.add_expected_call(
1593
'BzrDir.find_repositoryV3', ('stacked/',),
1594
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1595
# called twice, once from constructor and then again by us
1596
client.add_expected_call(
1597
'Branch.get_stacked_on_url', ('stacked/',),
1598
'success', ('ok', '../base'))
1599
client.add_expected_call(
1600
'Branch.get_stacked_on_url', ('stacked/',),
1601
'success', ('ok', '../base'))
1602
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1603
RemoteBzrDirFormat(), _client=client)
1604
branch = bzrdir.open_branch()
1605
result = branch.get_stacked_on_url()
1606
self.assertEqual('../base', result)
1607
self.assertFinished(client)
1608
# it's in the fallback list both for the RemoteRepository.
1609
self.assertEqual(1, len(branch.repository._fallback_repositories))
1610
# And we haven't had to construct a real repository.
1611
self.assertEqual(None, branch.repository._real_repository)
1614
class TestBranchSetLastRevision(RemoteBranchTestCase):
1616
def test_set_empty(self):
1617
# _set_last_revision_info('null:') is translated to calling
1618
# Branch.set_last_revision(path, '') on the wire.
1619
transport = MemoryTransport()
1620
transport.mkdir('branch')
1621
transport = transport.clone('branch')
1623
client = FakeClient(transport.base)
1624
client.add_expected_call(
1625
'Branch.get_stacked_on_url', ('branch/',),
1626
'error', ('NotStacked',))
1627
client.add_expected_call(
1628
'Branch.lock_write', ('branch/', '', ''),
1629
'success', ('ok', 'branch token', 'repo token'))
1630
client.add_expected_call(
1631
'Branch.last_revision_info',
1633
'success', ('ok', '0', 'null:'))
1634
client.add_expected_call(
1635
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1637
client.add_expected_call(
1638
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1640
branch = self.make_remote_branch(transport, client)
1642
result = branch._set_last_revision(NULL_REVISION)
1644
self.assertEqual(None, result)
1645
self.assertFinished(client)
1647
def test_set_nonempty(self):
1648
# set_last_revision_info(N, rev-idN) is translated to calling
1649
# Branch.set_last_revision(path, rev-idN) on the wire.
1650
transport = MemoryTransport()
1651
transport.mkdir('branch')
1652
transport = transport.clone('branch')
1654
client = FakeClient(transport.base)
1655
client.add_expected_call(
1656
'Branch.get_stacked_on_url', ('branch/',),
1657
'error', ('NotStacked',))
1658
client.add_expected_call(
1659
'Branch.lock_write', ('branch/', '', ''),
1660
'success', ('ok', 'branch token', 'repo token'))
1661
client.add_expected_call(
1662
'Branch.last_revision_info',
1664
'success', ('ok', '0', 'null:'))
1666
encoded_body = bz2.compress('\n'.join(lines))
1667
client.add_success_response_with_body(encoded_body, 'ok')
1668
client.add_expected_call(
1669
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1671
client.add_expected_call(
1672
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1674
branch = self.make_remote_branch(transport, client)
1675
# Lock the branch, reset the record of remote calls.
1677
result = branch._set_last_revision('rev-id2')
1679
self.assertEqual(None, result)
1680
self.assertFinished(client)
1682
def test_no_such_revision(self):
1683
transport = MemoryTransport()
1684
transport.mkdir('branch')
1685
transport = transport.clone('branch')
1686
# A response of 'NoSuchRevision' is translated into an exception.
1687
client = FakeClient(transport.base)
1688
client.add_expected_call(
1689
'Branch.get_stacked_on_url', ('branch/',),
1690
'error', ('NotStacked',))
1691
client.add_expected_call(
1692
'Branch.lock_write', ('branch/', '', ''),
1693
'success', ('ok', 'branch token', 'repo token'))
1694
client.add_expected_call(
1695
'Branch.last_revision_info',
1697
'success', ('ok', '0', 'null:'))
1698
# get_graph calls to construct the revision history, for the set_rh
1701
encoded_body = bz2.compress('\n'.join(lines))
1702
client.add_success_response_with_body(encoded_body, 'ok')
1703
client.add_expected_call(
1704
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1705
'error', ('NoSuchRevision', 'rev-id'))
1706
client.add_expected_call(
1707
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1710
branch = self.make_remote_branch(transport, client)
1713
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1715
self.assertFinished(client)
1717
def test_tip_change_rejected(self):
1718
"""TipChangeRejected responses cause a TipChangeRejected exception to
1721
transport = MemoryTransport()
1722
transport.mkdir('branch')
1723
transport = transport.clone('branch')
1724
client = FakeClient(transport.base)
1725
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1726
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1727
client.add_expected_call(
1728
'Branch.get_stacked_on_url', ('branch/',),
1729
'error', ('NotStacked',))
1730
client.add_expected_call(
1731
'Branch.lock_write', ('branch/', '', ''),
1732
'success', ('ok', 'branch token', 'repo token'))
1733
client.add_expected_call(
1734
'Branch.last_revision_info',
1736
'success', ('ok', '0', 'null:'))
1738
encoded_body = bz2.compress('\n'.join(lines))
1739
client.add_success_response_with_body(encoded_body, 'ok')
1740
client.add_expected_call(
1741
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1742
'error', ('TipChangeRejected', rejection_msg_utf8))
1743
client.add_expected_call(
1744
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1746
branch = self.make_remote_branch(transport, client)
1748
# The 'TipChangeRejected' error response triggered by calling
1749
# set_last_revision_info causes a TipChangeRejected exception.
1750
err = self.assertRaises(
1751
errors.TipChangeRejected,
1752
branch._set_last_revision, 'rev-id')
1753
# The UTF-8 message from the response has been decoded into a unicode
1755
self.assertIsInstance(err.msg, unicode)
1756
self.assertEqual(rejection_msg_unicode, err.msg)
1758
self.assertFinished(client)
1761
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1763
def test_set_last_revision_info(self):
1764
# set_last_revision_info(num, 'rev-id') is translated to calling
1765
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1766
transport = MemoryTransport()
1767
transport.mkdir('branch')
1768
transport = transport.clone('branch')
1769
client = FakeClient(transport.base)
1770
# get_stacked_on_url
1771
client.add_error_response('NotStacked')
1773
client.add_success_response('ok', 'branch token', 'repo token')
1774
# query the current revision
1775
client.add_success_response('ok', '0', 'null:')
1777
client.add_success_response('ok')
1779
client.add_success_response('ok')
1781
branch = self.make_remote_branch(transport, client)
1782
# Lock the branch, reset the record of remote calls.
1785
result = branch.set_last_revision_info(1234, 'a-revision-id')
1787
[('call', 'Branch.last_revision_info', ('branch/',)),
1788
('call', 'Branch.set_last_revision_info',
1789
('branch/', 'branch token', 'repo token',
1790
'1234', 'a-revision-id'))],
1792
self.assertEqual(None, result)
1794
def test_no_such_revision(self):
1795
# A response of 'NoSuchRevision' is translated into an exception.
1796
transport = MemoryTransport()
1797
transport.mkdir('branch')
1798
transport = transport.clone('branch')
1799
client = FakeClient(transport.base)
1800
# get_stacked_on_url
1801
client.add_error_response('NotStacked')
1803
client.add_success_response('ok', 'branch token', 'repo token')
1805
client.add_error_response('NoSuchRevision', 'revid')
1807
client.add_success_response('ok')
1809
branch = self.make_remote_branch(transport, client)
1810
# Lock the branch, reset the record of remote calls.
1815
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1818
def test_backwards_compatibility(self):
1819
"""If the server does not support the Branch.set_last_revision_info
1820
verb (which is new in 1.4), then the client falls back to VFS methods.
1822
# This test is a little messy. Unlike most tests in this file, it
1823
# doesn't purely test what a Remote* object sends over the wire, and
1824
# how it reacts to responses from the wire. It instead relies partly
1825
# on asserting that the RemoteBranch will call
1826
# self._real_branch.set_last_revision_info(...).
1828
# First, set up our RemoteBranch with a FakeClient that raises
1829
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1830
transport = MemoryTransport()
1831
transport.mkdir('branch')
1832
transport = transport.clone('branch')
1833
client = FakeClient(transport.base)
1834
client.add_expected_call(
1835
'Branch.get_stacked_on_url', ('branch/',),
1836
'error', ('NotStacked',))
1837
client.add_expected_call(
1838
'Branch.last_revision_info',
1840
'success', ('ok', '0', 'null:'))
1841
client.add_expected_call(
1842
'Branch.set_last_revision_info',
1843
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1844
'unknown', 'Branch.set_last_revision_info')
1846
branch = self.make_remote_branch(transport, client)
1847
class StubRealBranch(object):
1850
def set_last_revision_info(self, revno, revision_id):
1852
('set_last_revision_info', revno, revision_id))
1853
def _clear_cached_state(self):
1855
real_branch = StubRealBranch()
1856
branch._real_branch = real_branch
1857
self.lock_remote_branch(branch)
1859
# Call set_last_revision_info, and verify it behaved as expected.
1860
result = branch.set_last_revision_info(1234, 'a-revision-id')
1862
[('set_last_revision_info', 1234, 'a-revision-id')],
1864
self.assertFinished(client)
1866
def test_unexpected_error(self):
1867
# If the server sends an error the client doesn't understand, it gets
1868
# turned into an UnknownErrorFromSmartServer, which is presented as a
1869
# non-internal error to the user.
1870
transport = MemoryTransport()
1871
transport.mkdir('branch')
1872
transport = transport.clone('branch')
1873
client = FakeClient(transport.base)
1874
# get_stacked_on_url
1875
client.add_error_response('NotStacked')
1877
client.add_success_response('ok', 'branch token', 'repo token')
1879
client.add_error_response('UnexpectedError')
1881
client.add_success_response('ok')
1883
branch = self.make_remote_branch(transport, client)
1884
# Lock the branch, reset the record of remote calls.
1888
err = self.assertRaises(
1889
errors.UnknownErrorFromSmartServer,
1890
branch.set_last_revision_info, 123, 'revid')
1891
self.assertEqual(('UnexpectedError',), err.error_tuple)
1894
def test_tip_change_rejected(self):
1895
"""TipChangeRejected responses cause a TipChangeRejected exception to
1898
transport = MemoryTransport()
1899
transport.mkdir('branch')
1900
transport = transport.clone('branch')
1901
client = FakeClient(transport.base)
1902
# get_stacked_on_url
1903
client.add_error_response('NotStacked')
1905
client.add_success_response('ok', 'branch token', 'repo token')
1907
client.add_error_response('TipChangeRejected', 'rejection message')
1909
client.add_success_response('ok')
1911
branch = self.make_remote_branch(transport, client)
1912
# Lock the branch, reset the record of remote calls.
1914
self.addCleanup(branch.unlock)
1917
# The 'TipChangeRejected' error response triggered by calling
1918
# set_last_revision_info causes a TipChangeRejected exception.
1919
err = self.assertRaises(
1920
errors.TipChangeRejected,
1921
branch.set_last_revision_info, 123, 'revid')
1922
self.assertEqual('rejection message', err.msg)
1925
class TestBranchGetSetConfig(RemoteBranchTestCase):
1927
def test_get_branch_conf(self):
1928
# in an empty branch we decode the response properly
1929
client = FakeClient()
1930
client.add_expected_call(
1931
'Branch.get_stacked_on_url', ('memory:///',),
1932
'error', ('NotStacked',),)
1933
client.add_success_response_with_body('# config file body', 'ok')
1934
transport = MemoryTransport()
1935
branch = self.make_remote_branch(transport, client)
1936
config = branch.get_config()
1937
config.has_explicit_nickname()
1939
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1940
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1943
def test_get_multi_line_branch_conf(self):
1944
# Make sure that multiple-line branch.conf files are supported
1946
# https://bugs.launchpad.net/bzr/+bug/354075
1947
client = FakeClient()
1948
client.add_expected_call(
1949
'Branch.get_stacked_on_url', ('memory:///',),
1950
'error', ('NotStacked',),)
1951
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1952
transport = MemoryTransport()
1953
branch = self.make_remote_branch(transport, client)
1954
config = branch.get_config()
1955
self.assertEqual(u'2', config.get_user_option('b'))
1957
def test_set_option(self):
1958
client = FakeClient()
1959
client.add_expected_call(
1960
'Branch.get_stacked_on_url', ('memory:///',),
1961
'error', ('NotStacked',),)
1962
client.add_expected_call(
1963
'Branch.lock_write', ('memory:///', '', ''),
1964
'success', ('ok', 'branch token', 'repo token'))
1965
client.add_expected_call(
1966
'Branch.set_config_option', ('memory:///', 'branch token',
1967
'repo token', 'foo', 'bar', ''),
1969
client.add_expected_call(
1970
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1972
transport = MemoryTransport()
1973
branch = self.make_remote_branch(transport, client)
1975
config = branch._get_config()
1976
config.set_option('foo', 'bar')
1978
self.assertFinished(client)
1980
def test_set_option_with_dict(self):
1981
client = FakeClient()
1982
client.add_expected_call(
1983
'Branch.get_stacked_on_url', ('memory:///',),
1984
'error', ('NotStacked',),)
1985
client.add_expected_call(
1986
'Branch.lock_write', ('memory:///', '', ''),
1987
'success', ('ok', 'branch token', 'repo token'))
1988
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1989
client.add_expected_call(
1990
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1991
'repo token', encoded_dict_value, 'foo', ''),
1993
client.add_expected_call(
1994
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1996
transport = MemoryTransport()
1997
branch = self.make_remote_branch(transport, client)
1999
config = branch._get_config()
2001
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2004
self.assertFinished(client)
2006
def test_backwards_compat_set_option(self):
2007
self.setup_smart_server_with_call_log()
2008
branch = self.make_branch('.')
2009
verb = 'Branch.set_config_option'
2010
self.disable_verb(verb)
2012
self.addCleanup(branch.unlock)
2013
self.reset_smart_call_log()
2014
branch._get_config().set_option('value', 'name')
2015
self.assertLength(11, self.hpss_calls)
2016
self.assertEqual('value', branch._get_config().get_option('name'))
2018
def test_backwards_compat_set_option_with_dict(self):
2019
self.setup_smart_server_with_call_log()
2020
branch = self.make_branch('.')
2021
verb = 'Branch.set_config_option_dict'
2022
self.disable_verb(verb)
2024
self.addCleanup(branch.unlock)
2025
self.reset_smart_call_log()
2026
config = branch._get_config()
2027
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2028
config.set_option(value_dict, 'name')
2029
self.assertLength(11, self.hpss_calls)
2030
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2033
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2035
def test_get_branch_conf(self):
2036
# in an empty branch we decode the response properly
2037
client = FakeClient()
2038
client.add_expected_call(
2039
'Branch.get_stacked_on_url', ('memory:///',),
2040
'error', ('NotStacked',),)
2041
client.add_success_response_with_body('# config file body', 'ok')
2042
transport = MemoryTransport()
2043
branch = self.make_remote_branch(transport, client)
2044
config = branch.get_config_stack()
2046
config.get("log_format")
2048
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2049
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2052
def test_set_branch_conf(self):
2053
client = FakeClient()
2054
client.add_expected_call(
2055
'Branch.get_stacked_on_url', ('memory:///',),
2056
'error', ('NotStacked',),)
2057
client.add_expected_call(
2058
'Branch.lock_write', ('memory:///', '', ''),
2059
'success', ('ok', 'branch token', 'repo token'))
2060
client.add_expected_call(
2061
'Branch.get_config_file', ('memory:///', ),
2062
'success', ('ok', ), "# line 1\n")
2063
client.add_expected_call(
2064
'Branch.get_config_file', ('memory:///', ),
2065
'success', ('ok', ), "# line 1\n")
2066
client.add_expected_call(
2067
'Branch.put_config_file', ('memory:///', 'branch token',
2070
client.add_expected_call(
2071
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2073
transport = MemoryTransport()
2074
branch = self.make_remote_branch(transport, client)
2076
config = branch.get_config_stack()
2077
config.set('email', 'The Dude <lebowski@example.com>')
2079
self.assertFinished(client)
2081
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2082
('call', 'Branch.lock_write', ('memory:///', '', '')),
2083
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2084
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2085
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2086
('memory:///', 'branch token', 'repo token'),
2087
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2088
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2092
class TestBranchLockWrite(RemoteBranchTestCase):
2094
def test_lock_write_unlockable(self):
2095
transport = MemoryTransport()
2096
client = FakeClient(transport.base)
2097
client.add_expected_call(
2098
'Branch.get_stacked_on_url', ('quack/',),
2099
'error', ('NotStacked',),)
2100
client.add_expected_call(
2101
'Branch.lock_write', ('quack/', '', ''),
2102
'error', ('UnlockableTransport',))
2103
transport.mkdir('quack')
2104
transport = transport.clone('quack')
2105
branch = self.make_remote_branch(transport, client)
2106
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2107
self.assertFinished(client)
2110
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2112
def test_simple(self):
2113
transport = MemoryTransport()
2114
client = FakeClient(transport.base)
2115
client.add_expected_call(
2116
'Branch.get_stacked_on_url', ('quack/',),
2117
'error', ('NotStacked',),)
2118
client.add_expected_call(
2119
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2120
'success', ('ok', '0',),)
2121
client.add_expected_call(
2122
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2123
'error', ('NoSuchRevision', 'unknown',),)
2124
transport.mkdir('quack')
2125
transport = transport.clone('quack')
2126
branch = self.make_remote_branch(transport, client)
2127
self.assertEqual(0, branch.revision_id_to_revno('null:'))
2128
self.assertRaises(errors.NoSuchRevision,
2129
branch.revision_id_to_revno, 'unknown')
2130
self.assertFinished(client)
2132
def test_dotted(self):
2133
transport = MemoryTransport()
2134
client = FakeClient(transport.base)
2135
client.add_expected_call(
2136
'Branch.get_stacked_on_url', ('quack/',),
2137
'error', ('NotStacked',),)
2138
client.add_expected_call(
2139
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2140
'success', ('ok', '0',),)
2141
client.add_expected_call(
2142
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2143
'error', ('NoSuchRevision', 'unknown',),)
2144
transport.mkdir('quack')
2145
transport = transport.clone('quack')
2146
branch = self.make_remote_branch(transport, client)
2147
self.assertEqual((0, ), branch.revision_id_to_dotted_revno('null:'))
2148
self.assertRaises(errors.NoSuchRevision,
2149
branch.revision_id_to_dotted_revno, 'unknown')
2150
self.assertFinished(client)
2152
def test_dotted_no_smart_verb(self):
2153
self.setup_smart_server_with_call_log()
2154
branch = self.make_branch('.')
2155
self.disable_verb('Branch.revision_id_to_revno')
2156
self.reset_smart_call_log()
2157
self.assertEqual((0, ),
2158
branch.revision_id_to_dotted_revno('null:'))
2159
self.assertLength(8, self.hpss_calls)
2162
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2164
def test__get_config(self):
2165
client = FakeClient()
2166
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2167
transport = MemoryTransport()
2168
bzrdir = self.make_remote_bzrdir(transport, client)
2169
config = bzrdir.get_config()
2170
self.assertEqual('/', config.get_default_stack_on())
2172
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2175
def test_set_option_uses_vfs(self):
2176
self.setup_smart_server_with_call_log()
2177
bzrdir = self.make_bzrdir('.')
2178
self.reset_smart_call_log()
2179
config = bzrdir.get_config()
2180
config.set_default_stack_on('/')
2181
self.assertLength(4, self.hpss_calls)
2183
def test_backwards_compat_get_option(self):
2184
self.setup_smart_server_with_call_log()
2185
bzrdir = self.make_bzrdir('.')
2186
verb = 'BzrDir.get_config_file'
2187
self.disable_verb(verb)
2188
self.reset_smart_call_log()
2189
self.assertEqual(None,
2190
bzrdir._get_config().get_option('default_stack_on'))
2191
self.assertLength(4, self.hpss_calls)
2194
class TestTransportIsReadonly(tests.TestCase):
2196
def test_true(self):
2197
client = FakeClient()
2198
client.add_success_response('yes')
2199
transport = RemoteTransport('bzr://example.com/', medium=False,
2201
self.assertEqual(True, transport.is_readonly())
2203
[('call', 'Transport.is_readonly', ())],
2206
def test_false(self):
2207
client = FakeClient()
2208
client.add_success_response('no')
2209
transport = RemoteTransport('bzr://example.com/', medium=False,
2211
self.assertEqual(False, transport.is_readonly())
2213
[('call', 'Transport.is_readonly', ())],
2216
def test_error_from_old_server(self):
2217
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2219
Clients should treat it as a "no" response, because is_readonly is only
2220
advisory anyway (a transport could be read-write, but then the
2221
underlying filesystem could be readonly anyway).
2223
client = FakeClient()
2224
client.add_unknown_method_response('Transport.is_readonly')
2225
transport = RemoteTransport('bzr://example.com/', medium=False,
2227
self.assertEqual(False, transport.is_readonly())
2229
[('call', 'Transport.is_readonly', ())],
2233
class TestTransportMkdir(tests.TestCase):
2235
def test_permissiondenied(self):
2236
client = FakeClient()
2237
client.add_error_response('PermissionDenied', 'remote path', 'extra')
2238
transport = RemoteTransport('bzr://example.com/', medium=False,
2240
exc = self.assertRaises(
2241
errors.PermissionDenied, transport.mkdir, 'client path')
2242
expected_error = errors.PermissionDenied('/client path', 'extra')
2243
self.assertEqual(expected_error, exc)
2246
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2248
def test_defaults_to_none(self):
2249
t = RemoteSSHTransport('bzr+ssh://example.com')
2250
self.assertIs(None, t._get_credentials()[0])
2252
def test_uses_authentication_config(self):
2253
conf = config.AuthenticationConfig()
2254
conf._get_config().update(
2255
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2258
t = RemoteSSHTransport('bzr+ssh://example.com')
2259
self.assertEqual('bar', t._get_credentials()[0])
2262
class TestRemoteRepository(TestRemote):
2263
"""Base for testing RemoteRepository protocol usage.
2265
These tests contain frozen requests and responses. We want any changes to
2266
what is sent or expected to be require a thoughtful update to these tests
2267
because they might break compatibility with different-versioned servers.
2270
def setup_fake_client_and_repository(self, transport_path):
2271
"""Create the fake client and repository for testing with.
2273
There's no real server here; we just have canned responses sent
2276
:param transport_path: Path below the root of the MemoryTransport
2277
where the repository will be created.
2279
transport = MemoryTransport()
2280
transport.mkdir(transport_path)
2281
client = FakeClient(transport.base)
2282
transport = transport.clone(transport_path)
2283
# we do not want bzrdir to make any remote calls
2284
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2286
repo = RemoteRepository(bzrdir, None, _client=client)
2290
def remoted_description(format):
2291
return 'Remote: ' + format.get_format_description()
2294
class TestBranchFormat(tests.TestCase):
2296
def test_get_format_description(self):
2297
remote_format = RemoteBranchFormat()
2298
real_format = branch.format_registry.get_default()
2299
remote_format._network_name = real_format.network_name()
2300
self.assertEqual(remoted_description(real_format),
2301
remote_format.get_format_description())
2304
class TestRepositoryFormat(TestRemoteRepository):
2306
def test_fast_delta(self):
2307
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2308
true_format = RemoteRepositoryFormat()
2309
true_format._network_name = true_name
2310
self.assertEqual(True, true_format.fast_deltas)
2311
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2312
false_format = RemoteRepositoryFormat()
2313
false_format._network_name = false_name
2314
self.assertEqual(False, false_format.fast_deltas)
2316
def test_get_format_description(self):
2317
remote_repo_format = RemoteRepositoryFormat()
2318
real_format = repository.format_registry.get_default()
2319
remote_repo_format._network_name = real_format.network_name()
2320
self.assertEqual(remoted_description(real_format),
2321
remote_repo_format.get_format_description())
2324
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2326
def test_empty(self):
2327
transport_path = 'quack'
2328
repo, client = self.setup_fake_client_and_repository(transport_path)
2329
client.add_success_response_with_body('', 'ok')
2330
self.assertEqual([], repo.all_revision_ids())
2332
[('call_expecting_body', 'Repository.all_revision_ids',
2336
def test_with_some_content(self):
2337
transport_path = 'quack'
2338
repo, client = self.setup_fake_client_and_repository(transport_path)
2339
client.add_success_response_with_body(
2340
'rev1\nrev2\nanotherrev\n', 'ok')
2341
self.assertEqual(["rev1", "rev2", "anotherrev"],
2342
repo.all_revision_ids())
2344
[('call_expecting_body', 'Repository.all_revision_ids',
2349
class TestRepositoryGatherStats(TestRemoteRepository):
2351
def test_revid_none(self):
2352
# ('ok',), body with revisions and size
2353
transport_path = 'quack'
2354
repo, client = self.setup_fake_client_and_repository(transport_path)
2355
client.add_success_response_with_body(
2356
'revisions: 2\nsize: 18\n', 'ok')
2357
result = repo.gather_stats(None)
2359
[('call_expecting_body', 'Repository.gather_stats',
2360
('quack/','','no'))],
2362
self.assertEqual({'revisions': 2, 'size': 18}, result)
2364
def test_revid_no_committers(self):
2365
# ('ok',), body without committers
2366
body = ('firstrev: 123456.300 3600\n'
2367
'latestrev: 654231.400 0\n'
2370
transport_path = 'quick'
2371
revid = u'\xc8'.encode('utf8')
2372
repo, client = self.setup_fake_client_and_repository(transport_path)
2373
client.add_success_response_with_body(body, 'ok')
2374
result = repo.gather_stats(revid)
2376
[('call_expecting_body', 'Repository.gather_stats',
2377
('quick/', revid, 'no'))],
2379
self.assertEqual({'revisions': 2, 'size': 18,
2380
'firstrev': (123456.300, 3600),
2381
'latestrev': (654231.400, 0),},
2384
def test_revid_with_committers(self):
2385
# ('ok',), body with committers
2386
body = ('committers: 128\n'
2387
'firstrev: 123456.300 3600\n'
2388
'latestrev: 654231.400 0\n'
2391
transport_path = 'buick'
2392
revid = u'\xc8'.encode('utf8')
2393
repo, client = self.setup_fake_client_and_repository(transport_path)
2394
client.add_success_response_with_body(body, 'ok')
2395
result = repo.gather_stats(revid, True)
2397
[('call_expecting_body', 'Repository.gather_stats',
2398
('buick/', revid, 'yes'))],
2400
self.assertEqual({'revisions': 2, 'size': 18,
2402
'firstrev': (123456.300, 3600),
2403
'latestrev': (654231.400, 0),},
2407
class TestRepositoryBreakLock(TestRemoteRepository):
2409
def test_break_lock(self):
2410
transport_path = 'quack'
2411
repo, client = self.setup_fake_client_and_repository(transport_path)
2412
client.add_success_response('ok')
2415
[('call', 'Repository.break_lock', ('quack/',))],
2419
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2421
def test_get_serializer_format(self):
2422
transport_path = 'hill'
2423
repo, client = self.setup_fake_client_and_repository(transport_path)
2424
client.add_success_response('ok', '7')
2425
self.assertEqual('7', repo.get_serializer_format())
2427
[('call', 'VersionedFileRepository.get_serializer_format',
2432
class TestRepositoryReconcile(TestRemoteRepository):
2434
def test_reconcile(self):
2435
transport_path = 'hill'
2436
repo, client = self.setup_fake_client_and_repository(transport_path)
2437
body = ("garbage_inventories: 2\n"
2438
"inconsistent_parents: 3\n")
2439
client.add_expected_call(
2440
'Repository.lock_write', ('hill/', ''),
2441
'success', ('ok', 'a token'))
2442
client.add_success_response_with_body(body, 'ok')
2443
reconciler = repo.reconcile()
2445
[('call', 'Repository.lock_write', ('hill/', '')),
2446
('call_expecting_body', 'Repository.reconcile',
2447
('hill/', 'a token'))],
2449
self.assertEqual(2, reconciler.garbage_inventories)
2450
self.assertEqual(3, reconciler.inconsistent_parents)
2453
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2455
def test_text(self):
2456
# ('ok',), body with signature text
2457
transport_path = 'quack'
2458
repo, client = self.setup_fake_client_and_repository(transport_path)
2459
client.add_success_response_with_body(
2461
self.assertEqual("THETEXT", repo.get_signature_text("revid"))
2463
[('call_expecting_body', 'Repository.get_revision_signature_text',
2464
('quack/', 'revid'))],
2467
def test_no_signature(self):
2468
transport_path = 'quick'
2469
repo, client = self.setup_fake_client_and_repository(transport_path)
2470
client.add_error_response('nosuchrevision', 'unknown')
2471
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2474
[('call_expecting_body', 'Repository.get_revision_signature_text',
2475
('quick/', 'unknown'))],
2479
class TestRepositoryGetGraph(TestRemoteRepository):
2481
def test_get_graph(self):
2482
# get_graph returns a graph with a custom parents provider.
2483
transport_path = 'quack'
2484
repo, client = self.setup_fake_client_and_repository(transport_path)
2485
graph = repo.get_graph()
2486
self.assertNotEqual(graph._parents_provider, repo)
2489
class TestRepositoryAddSignatureText(TestRemoteRepository):
2491
def test_add_signature_text(self):
2492
transport_path = 'quack'
2493
repo, client = self.setup_fake_client_and_repository(transport_path)
2494
client.add_expected_call(
2495
'Repository.lock_write', ('quack/', ''),
2496
'success', ('ok', 'a token'))
2497
client.add_expected_call(
2498
'Repository.start_write_group', ('quack/', 'a token'),
2499
'success', ('ok', ('token1', )))
2500
client.add_expected_call(
2501
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2503
'success', ('ok', ), None)
2505
repo.start_write_group()
2507
repo.add_signature_text("rev1", "every bloody emperor"))
2509
('call_with_body_bytes_expecting_body',
2510
'Repository.add_signature_text',
2511
('quack/', 'a token', 'rev1', 'token1'),
2512
'every bloody emperor'),
2516
class TestRepositoryGetParentMap(TestRemoteRepository):
2518
def test_get_parent_map_caching(self):
2519
# get_parent_map returns from cache until unlock()
2520
# setup a reponse with two revisions
2521
r1 = u'\u0e33'.encode('utf8')
2522
r2 = u'\u0dab'.encode('utf8')
2523
lines = [' '.join([r2, r1]), r1]
2524
encoded_body = bz2.compress('\n'.join(lines))
2526
transport_path = 'quack'
2527
repo, client = self.setup_fake_client_and_repository(transport_path)
2528
client.add_success_response_with_body(encoded_body, 'ok')
2529
client.add_success_response_with_body(encoded_body, 'ok')
2531
graph = repo.get_graph()
2532
parents = graph.get_parent_map([r2])
2533
self.assertEqual({r2: (r1,)}, parents)
2534
# locking and unlocking deeper should not reset
2537
parents = graph.get_parent_map([r1])
2538
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2540
[('call_with_body_bytes_expecting_body',
2541
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2545
# now we call again, and it should use the second response.
2547
graph = repo.get_graph()
2548
parents = graph.get_parent_map([r1])
2549
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2551
[('call_with_body_bytes_expecting_body',
2552
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2554
('call_with_body_bytes_expecting_body',
2555
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2561
def test_get_parent_map_reconnects_if_unknown_method(self):
2562
transport_path = 'quack'
2563
rev_id = 'revision-id'
2564
repo, client = self.setup_fake_client_and_repository(transport_path)
2565
client.add_unknown_method_response('Repository.get_parent_map')
2566
client.add_success_response_with_body(rev_id, 'ok')
2567
self.assertFalse(client._medium._is_remote_before((1, 2)))
2568
parents = repo.get_parent_map([rev_id])
2570
[('call_with_body_bytes_expecting_body',
2571
'Repository.get_parent_map',
2572
('quack/', 'include-missing:', rev_id), '\n\n0'),
2573
('disconnect medium',),
2574
('call_expecting_body', 'Repository.get_revision_graph',
2577
# The medium is now marked as being connected to an older server
2578
self.assertTrue(client._medium._is_remote_before((1, 2)))
2579
self.assertEqual({rev_id: ('null:',)}, parents)
2581
def test_get_parent_map_fallback_parentless_node(self):
2582
"""get_parent_map falls back to get_revision_graph on old servers. The
2583
results from get_revision_graph are tweaked to match the get_parent_map
2586
Specifically, a {key: ()} result from get_revision_graph means "no
2587
parents" for that key, which in get_parent_map results should be
2588
represented as {key: ('null:',)}.
2590
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2592
rev_id = 'revision-id'
2593
transport_path = 'quack'
2594
repo, client = self.setup_fake_client_and_repository(transport_path)
2595
client.add_success_response_with_body(rev_id, 'ok')
2596
client._medium._remember_remote_is_before((1, 2))
2597
parents = repo.get_parent_map([rev_id])
2599
[('call_expecting_body', 'Repository.get_revision_graph',
2602
self.assertEqual({rev_id: ('null:',)}, parents)
2604
def test_get_parent_map_unexpected_response(self):
2605
repo, client = self.setup_fake_client_and_repository('path')
2606
client.add_success_response('something unexpected!')
2608
errors.UnexpectedSmartServerResponse,
2609
repo.get_parent_map, ['a-revision-id'])
2611
def test_get_parent_map_negative_caches_missing_keys(self):
2612
self.setup_smart_server_with_call_log()
2613
repo = self.make_repository('foo')
2614
self.assertIsInstance(repo, RemoteRepository)
2616
self.addCleanup(repo.unlock)
2617
self.reset_smart_call_log()
2618
graph = repo.get_graph()
2619
self.assertEqual({},
2620
graph.get_parent_map(['some-missing', 'other-missing']))
2621
self.assertLength(1, self.hpss_calls)
2622
# No call if we repeat this
2623
self.reset_smart_call_log()
2624
graph = repo.get_graph()
2625
self.assertEqual({},
2626
graph.get_parent_map(['some-missing', 'other-missing']))
2627
self.assertLength(0, self.hpss_calls)
2628
# Asking for more unknown keys makes a request.
2629
self.reset_smart_call_log()
2630
graph = repo.get_graph()
2631
self.assertEqual({},
2632
graph.get_parent_map(['some-missing', 'other-missing',
2634
self.assertLength(1, self.hpss_calls)
2636
def disableExtraResults(self):
2637
self.overrideAttr(SmartServerRepositoryGetParentMap,
2638
'no_extra_results', True)
2640
def test_null_cached_missing_and_stop_key(self):
2641
self.setup_smart_server_with_call_log()
2642
# Make a branch with a single revision.
2643
builder = self.make_branch_builder('foo')
2644
builder.start_series()
2645
builder.build_snapshot('first', None, [
2646
('add', ('', 'root-id', 'directory', ''))])
2647
builder.finish_series()
2648
branch = builder.get_branch()
2649
repo = branch.repository
2650
self.assertIsInstance(repo, RemoteRepository)
2651
# Stop the server from sending extra results.
2652
self.disableExtraResults()
2654
self.addCleanup(repo.unlock)
2655
self.reset_smart_call_log()
2656
graph = repo.get_graph()
2657
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2658
# 'first' it will be a candidate for the stop_keys of subsequent
2659
# requests, and because 'null:' was queried but not returned it will be
2660
# cached as missing.
2661
self.assertEqual({'first': ('null:',)},
2662
graph.get_parent_map(['first', 'null:']))
2663
# Now query for another key. This request will pass along a recipe of
2664
# start and stop keys describing the already cached results, and this
2665
# recipe's revision count must be correct (or else it will trigger an
2666
# error from the server).
2667
self.assertEqual({}, graph.get_parent_map(['another-key']))
2668
# This assertion guards against disableExtraResults silently failing to
2669
# work, thus invalidating the test.
2670
self.assertLength(2, self.hpss_calls)
2672
def test_get_parent_map_gets_ghosts_from_result(self):
2673
# asking for a revision should negatively cache close ghosts in its
2675
self.setup_smart_server_with_call_log()
2676
tree = self.make_branch_and_memory_tree('foo')
2679
builder = treebuilder.TreeBuilder()
2680
builder.start_tree(tree)
2682
builder.finish_tree()
2683
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2684
rev_id = tree.commit('')
2688
self.addCleanup(tree.unlock)
2689
repo = tree.branch.repository
2690
self.assertIsInstance(repo, RemoteRepository)
2692
repo.get_parent_map([rev_id])
2693
self.reset_smart_call_log()
2694
# Now asking for rev_id's ghost parent should not make calls
2695
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2696
self.assertLength(0, self.hpss_calls)
2698
def test_exposes_get_cached_parent_map(self):
2699
"""RemoteRepository exposes get_cached_parent_map from
2702
r1 = u'\u0e33'.encode('utf8')
2703
r2 = u'\u0dab'.encode('utf8')
2704
lines = [' '.join([r2, r1]), r1]
2705
encoded_body = bz2.compress('\n'.join(lines))
2707
transport_path = 'quack'
2708
repo, client = self.setup_fake_client_and_repository(transport_path)
2709
client.add_success_response_with_body(encoded_body, 'ok')
2711
# get_cached_parent_map should *not* trigger an RPC
2712
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2713
self.assertEqual([], client._calls)
2714
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2715
self.assertEqual({r1: (NULL_REVISION,)},
2716
repo.get_cached_parent_map([r1]))
2718
[('call_with_body_bytes_expecting_body',
2719
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2725
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2727
def test_allows_new_revisions(self):
2728
"""get_parent_map's results can be updated by commit."""
2729
smart_server = test_server.SmartTCPServer_for_testing()
2730
self.start_server(smart_server)
2731
self.make_branch('branch')
2732
branch = Branch.open(smart_server.get_url() + '/branch')
2733
tree = branch.create_checkout('tree', lightweight=True)
2735
self.addCleanup(tree.unlock)
2736
graph = tree.branch.repository.get_graph()
2737
# This provides an opportunity for the missing rev-id to be cached.
2738
self.assertEqual({}, graph.get_parent_map(['rev1']))
2739
tree.commit('message', rev_id='rev1')
2740
graph = tree.branch.repository.get_graph()
2741
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2744
class TestRepositoryGetRevisions(TestRemoteRepository):
2746
def test_hpss_missing_revision(self):
2747
transport_path = 'quack'
2748
repo, client = self.setup_fake_client_and_repository(transport_path)
2749
client.add_success_response_with_body(
2751
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2752
['somerev1', 'anotherrev2'])
2754
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2755
('quack/', ), "somerev1\nanotherrev2")],
2758
def test_hpss_get_single_revision(self):
2759
transport_path = 'quack'
2760
repo, client = self.setup_fake_client_and_repository(transport_path)
2761
somerev1 = Revision("somerev1")
2762
somerev1.committer = "Joe Committer <joe@example.com>"
2763
somerev1.timestamp = 1321828927
2764
somerev1.timezone = -60
2765
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2766
somerev1.message = "Message"
2767
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2769
# Split up body into two bits to make sure the zlib compression object
2770
# gets data fed twice.
2771
client.add_success_response_with_body(
2772
[body[:10], body[10:]], 'ok', '10')
2773
revs = repo.get_revisions(['somerev1'])
2774
self.assertEqual(revs, [somerev1])
2776
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2777
('quack/', ), "somerev1")],
2781
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2783
def test_null_revision(self):
2784
# a null revision has the predictable result {}, we should have no wire
2785
# traffic when calling it with this argument
2786
transport_path = 'empty'
2787
repo, client = self.setup_fake_client_and_repository(transport_path)
2788
client.add_success_response('notused')
2789
# actual RemoteRepository.get_revision_graph is gone, but there's an
2790
# equivalent private method for testing
2791
result = repo._get_revision_graph(NULL_REVISION)
2792
self.assertEqual([], client._calls)
2793
self.assertEqual({}, result)
2795
def test_none_revision(self):
2796
# with none we want the entire graph
2797
r1 = u'\u0e33'.encode('utf8')
2798
r2 = u'\u0dab'.encode('utf8')
2799
lines = [' '.join([r2, r1]), r1]
2800
encoded_body = '\n'.join(lines)
2802
transport_path = 'sinhala'
2803
repo, client = self.setup_fake_client_and_repository(transport_path)
2804
client.add_success_response_with_body(encoded_body, 'ok')
2805
# actual RemoteRepository.get_revision_graph is gone, but there's an
2806
# equivalent private method for testing
2807
result = repo._get_revision_graph(None)
2809
[('call_expecting_body', 'Repository.get_revision_graph',
2812
self.assertEqual({r1: (), r2: (r1, )}, result)
2814
def test_specific_revision(self):
2815
# with a specific revision we want the graph for that
2816
# with none we want the entire graph
2817
r11 = u'\u0e33'.encode('utf8')
2818
r12 = u'\xc9'.encode('utf8')
2819
r2 = u'\u0dab'.encode('utf8')
2820
lines = [' '.join([r2, r11, r12]), r11, r12]
2821
encoded_body = '\n'.join(lines)
2823
transport_path = 'sinhala'
2824
repo, client = self.setup_fake_client_and_repository(transport_path)
2825
client.add_success_response_with_body(encoded_body, 'ok')
2826
result = repo._get_revision_graph(r2)
2828
[('call_expecting_body', 'Repository.get_revision_graph',
2831
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2833
def test_no_such_revision(self):
2835
transport_path = 'sinhala'
2836
repo, client = self.setup_fake_client_and_repository(transport_path)
2837
client.add_error_response('nosuchrevision', revid)
2838
# also check that the right revision is reported in the error
2839
self.assertRaises(errors.NoSuchRevision,
2840
repo._get_revision_graph, revid)
2842
[('call_expecting_body', 'Repository.get_revision_graph',
2843
('sinhala/', revid))],
2846
def test_unexpected_error(self):
2848
transport_path = 'sinhala'
2849
repo, client = self.setup_fake_client_and_repository(transport_path)
2850
client.add_error_response('AnUnexpectedError')
2851
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2852
repo._get_revision_graph, revid)
2853
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2856
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2859
repo, client = self.setup_fake_client_and_repository('quack')
2860
client.add_expected_call(
2861
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2862
'success', ('ok', 'rev-five'))
2863
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2864
self.assertEqual((True, 'rev-five'), result)
2865
self.assertFinished(client)
2867
def test_history_incomplete(self):
2868
repo, client = self.setup_fake_client_and_repository('quack')
2869
client.add_expected_call(
2870
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2871
'success', ('history-incomplete', 10, 'rev-ten'))
2872
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2873
self.assertEqual((False, (10, 'rev-ten')), result)
2874
self.assertFinished(client)
2876
def test_history_incomplete_with_fallback(self):
2877
"""A 'history-incomplete' response causes the fallback repository to be
2878
queried too, if one is set.
2880
# Make a repo with a fallback repo, both using a FakeClient.
2881
format = remote.response_tuple_to_repo_format(
2882
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2883
repo, client = self.setup_fake_client_and_repository('quack')
2884
repo._format = format
2885
fallback_repo, ignored = self.setup_fake_client_and_repository(
2887
fallback_repo._client = client
2888
fallback_repo._format = format
2889
repo.add_fallback_repository(fallback_repo)
2890
# First the client should ask the primary repo
2891
client.add_expected_call(
2892
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2893
'success', ('history-incomplete', 2, 'rev-two'))
2894
# Then it should ask the fallback, using revno/revid from the
2895
# history-incomplete response as the known revno/revid.
2896
client.add_expected_call(
2897
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2898
'success', ('ok', 'rev-one'))
2899
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2900
self.assertEqual((True, 'rev-one'), result)
2901
self.assertFinished(client)
2903
def test_nosuchrevision(self):
2904
# 'nosuchrevision' is returned when the known-revid is not found in the
2905
# remote repo. The client translates that response to NoSuchRevision.
2906
repo, client = self.setup_fake_client_and_repository('quack')
2907
client.add_expected_call(
2908
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2909
'error', ('nosuchrevision', 'rev-foo'))
2911
errors.NoSuchRevision,
2912
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2913
self.assertFinished(client)
2915
def test_branch_fallback_locking(self):
2916
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2917
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2918
will be invoked, which will fail if the repo is unlocked.
2920
self.setup_smart_server_with_call_log()
2921
tree = self.make_branch_and_memory_tree('.')
2924
rev1 = tree.commit('First')
2925
rev2 = tree.commit('Second')
2927
branch = tree.branch
2928
self.assertFalse(branch.is_locked())
2929
self.reset_smart_call_log()
2930
verb = 'Repository.get_rev_id_for_revno'
2931
self.disable_verb(verb)
2932
self.assertEqual(rev1, branch.get_rev_id(1))
2933
self.assertLength(1, [call for call in self.hpss_calls if
2934
call.call.method == verb])
2937
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2939
def test_has_signature_for_revision_id(self):
2940
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2941
transport_path = 'quack'
2942
repo, client = self.setup_fake_client_and_repository(transport_path)
2943
client.add_success_response('yes')
2944
result = repo.has_signature_for_revision_id('A')
2946
[('call', 'Repository.has_signature_for_revision_id',
2949
self.assertEqual(True, result)
2951
def test_is_not_shared(self):
2952
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2953
transport_path = 'qwack'
2954
repo, client = self.setup_fake_client_and_repository(transport_path)
2955
client.add_success_response('no')
2956
result = repo.has_signature_for_revision_id('A')
2958
[('call', 'Repository.has_signature_for_revision_id',
2961
self.assertEqual(False, result)
2964
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2966
def test_get_physical_lock_status_yes(self):
2967
transport_path = 'qwack'
2968
repo, client = self.setup_fake_client_and_repository(transport_path)
2969
client.add_success_response('yes')
2970
result = repo.get_physical_lock_status()
2972
[('call', 'Repository.get_physical_lock_status',
2975
self.assertEqual(True, result)
2977
def test_get_physical_lock_status_no(self):
2978
transport_path = 'qwack'
2979
repo, client = self.setup_fake_client_and_repository(transport_path)
2980
client.add_success_response('no')
2981
result = repo.get_physical_lock_status()
2983
[('call', 'Repository.get_physical_lock_status',
2986
self.assertEqual(False, result)
2989
class TestRepositoryIsShared(TestRemoteRepository):
2991
def test_is_shared(self):
2992
# ('yes', ) for Repository.is_shared -> 'True'.
2993
transport_path = 'quack'
2994
repo, client = self.setup_fake_client_and_repository(transport_path)
2995
client.add_success_response('yes')
2996
result = repo.is_shared()
2998
[('call', 'Repository.is_shared', ('quack/',))],
3000
self.assertEqual(True, result)
3002
def test_is_not_shared(self):
3003
# ('no', ) for Repository.is_shared -> 'False'.
3004
transport_path = 'qwack'
3005
repo, client = self.setup_fake_client_and_repository(transport_path)
3006
client.add_success_response('no')
3007
result = repo.is_shared()
3009
[('call', 'Repository.is_shared', ('qwack/',))],
3011
self.assertEqual(False, result)
3014
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3016
def test_make_working_trees(self):
3017
# ('yes', ) for Repository.make_working_trees -> 'True'.
3018
transport_path = 'quack'
3019
repo, client = self.setup_fake_client_and_repository(transport_path)
3020
client.add_success_response('yes')
3021
result = repo.make_working_trees()
3023
[('call', 'Repository.make_working_trees', ('quack/',))],
3025
self.assertEqual(True, result)
3027
def test_no_working_trees(self):
3028
# ('no', ) for Repository.make_working_trees -> 'False'.
3029
transport_path = 'qwack'
3030
repo, client = self.setup_fake_client_and_repository(transport_path)
3031
client.add_success_response('no')
3032
result = repo.make_working_trees()
3034
[('call', 'Repository.make_working_trees', ('qwack/',))],
3036
self.assertEqual(False, result)
3039
class TestRepositoryLockWrite(TestRemoteRepository):
3041
def test_lock_write(self):
3042
transport_path = 'quack'
3043
repo, client = self.setup_fake_client_and_repository(transport_path)
3044
client.add_success_response('ok', 'a token')
3045
token = repo.lock_write().repository_token
3047
[('call', 'Repository.lock_write', ('quack/', ''))],
3049
self.assertEqual('a token', token)
3051
def test_lock_write_already_locked(self):
3052
transport_path = 'quack'
3053
repo, client = self.setup_fake_client_and_repository(transport_path)
3054
client.add_error_response('LockContention')
3055
self.assertRaises(errors.LockContention, repo.lock_write)
3057
[('call', 'Repository.lock_write', ('quack/', ''))],
3060
def test_lock_write_unlockable(self):
3061
transport_path = 'quack'
3062
repo, client = self.setup_fake_client_and_repository(transport_path)
3063
client.add_error_response('UnlockableTransport')
3064
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3066
[('call', 'Repository.lock_write', ('quack/', ''))],
3070
class TestRepositoryWriteGroups(TestRemoteRepository):
3072
def test_start_write_group(self):
3073
transport_path = 'quack'
3074
repo, client = self.setup_fake_client_and_repository(transport_path)
3075
client.add_expected_call(
3076
'Repository.lock_write', ('quack/', ''),
3077
'success', ('ok', 'a token'))
3078
client.add_expected_call(
3079
'Repository.start_write_group', ('quack/', 'a token'),
3080
'success', ('ok', ('token1', )))
3082
repo.start_write_group()
3084
def test_start_write_group_unsuspendable(self):
3085
# Some repositories do not support suspending write
3086
# groups. For those, fall back to the "real" repository.
3087
transport_path = 'quack'
3088
repo, client = self.setup_fake_client_and_repository(transport_path)
3089
def stub_ensure_real():
3090
client._calls.append(('_ensure_real',))
3091
repo._real_repository = _StubRealPackRepository(client._calls)
3092
repo._ensure_real = stub_ensure_real
3093
client.add_expected_call(
3094
'Repository.lock_write', ('quack/', ''),
3095
'success', ('ok', 'a token'))
3096
client.add_expected_call(
3097
'Repository.start_write_group', ('quack/', 'a token'),
3098
'error', ('UnsuspendableWriteGroup',))
3100
repo.start_write_group()
3101
self.assertEqual(client._calls[-2:], [
3103
('start_write_group',)])
3105
def test_commit_write_group(self):
3106
transport_path = 'quack'
3107
repo, client = self.setup_fake_client_and_repository(transport_path)
3108
client.add_expected_call(
3109
'Repository.lock_write', ('quack/', ''),
3110
'success', ('ok', 'a token'))
3111
client.add_expected_call(
3112
'Repository.start_write_group', ('quack/', 'a token'),
3113
'success', ('ok', ['token1']))
3114
client.add_expected_call(
3115
'Repository.commit_write_group', ('quack/', 'a token', ['token1']),
3118
repo.start_write_group()
3119
repo.commit_write_group()
3121
def test_abort_write_group(self):
3122
transport_path = 'quack'
3123
repo, client = self.setup_fake_client_and_repository(transport_path)
3124
client.add_expected_call(
3125
'Repository.lock_write', ('quack/', ''),
3126
'success', ('ok', 'a token'))
3127
client.add_expected_call(
3128
'Repository.start_write_group', ('quack/', 'a token'),
3129
'success', ('ok', ['token1']))
3130
client.add_expected_call(
3131
'Repository.abort_write_group', ('quack/', 'a token', ['token1']),
3134
repo.start_write_group()
3135
repo.abort_write_group(False)
3137
def test_suspend_write_group(self):
3138
transport_path = 'quack'
3139
repo, client = self.setup_fake_client_and_repository(transport_path)
3140
self.assertEqual([], repo.suspend_write_group())
3142
def test_resume_write_group(self):
3143
transport_path = 'quack'
3144
repo, client = self.setup_fake_client_and_repository(transport_path)
3145
client.add_expected_call(
3146
'Repository.lock_write', ('quack/', ''),
3147
'success', ('ok', 'a token'))
3148
client.add_expected_call(
3149
'Repository.check_write_group', ('quack/', 'a token', ['token1']),
3152
repo.resume_write_group(['token1'])
3155
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3157
def test_backwards_compat(self):
3158
self.setup_smart_server_with_call_log()
3159
repo = self.make_repository('.')
3160
self.reset_smart_call_log()
3161
verb = 'Repository.set_make_working_trees'
3162
self.disable_verb(verb)
3163
repo.set_make_working_trees(True)
3164
call_count = len([call for call in self.hpss_calls if
3165
call.call.method == verb])
3166
self.assertEqual(1, call_count)
3168
def test_current(self):
3169
transport_path = 'quack'
3170
repo, client = self.setup_fake_client_and_repository(transport_path)
3171
client.add_expected_call(
3172
'Repository.set_make_working_trees', ('quack/', 'True'),
3174
client.add_expected_call(
3175
'Repository.set_make_working_trees', ('quack/', 'False'),
3177
repo.set_make_working_trees(True)
3178
repo.set_make_working_trees(False)
3181
class TestRepositoryUnlock(TestRemoteRepository):
3183
def test_unlock(self):
3184
transport_path = 'quack'
3185
repo, client = self.setup_fake_client_and_repository(transport_path)
3186
client.add_success_response('ok', 'a token')
3187
client.add_success_response('ok')
3191
[('call', 'Repository.lock_write', ('quack/', '')),
3192
('call', 'Repository.unlock', ('quack/', 'a token'))],
3195
def test_unlock_wrong_token(self):
3196
# If somehow the token is wrong, unlock will raise TokenMismatch.
3197
transport_path = 'quack'
3198
repo, client = self.setup_fake_client_and_repository(transport_path)
3199
client.add_success_response('ok', 'a token')
3200
client.add_error_response('TokenMismatch')
3202
self.assertRaises(errors.TokenMismatch, repo.unlock)
3205
class TestRepositoryHasRevision(TestRemoteRepository):
3207
def test_none(self):
3208
# repo.has_revision(None) should not cause any traffic.
3209
transport_path = 'quack'
3210
repo, client = self.setup_fake_client_and_repository(transport_path)
3212
# The null revision is always there, so has_revision(None) == True.
3213
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3215
# The remote repo shouldn't be accessed.
3216
self.assertEqual([], client._calls)
3219
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3220
"""Test Repository.iter_file_bytes."""
3222
def test_single(self):
3223
transport_path = 'quack'
3224
repo, client = self.setup_fake_client_and_repository(transport_path)
3225
client.add_expected_call(
3226
'Repository.iter_files_bytes', ('quack/', ),
3227
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3228
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3229
"somerev", "myid")]):
3230
self.assertEqual("myid", identifier)
3231
self.assertEqual("".join(byte_stream), "mydata" * 10)
3233
def test_missing(self):
3234
transport_path = 'quack'
3235
repo, client = self.setup_fake_client_and_repository(transport_path)
3236
client.add_expected_call(
3237
'Repository.iter_files_bytes',
3239
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3240
iter(["absent\0somefile\0somerev\n"]))
3241
self.assertRaises(errors.RevisionNotPresent, list,
3242
repo.iter_files_bytes(
3243
[("somefile", "somerev", "myid")]))
3246
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3247
"""Base class for Repository.insert_stream and .insert_stream_1.19
3251
def checkInsertEmptyStream(self, repo, client):
3252
"""Insert an empty stream, checking the result.
3254
This checks that there are no resume_tokens or missing_keys, and that
3255
the client is finished.
3257
sink = repo._get_sink()
3258
fmt = repository.format_registry.get_default()
3259
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3260
self.assertEqual([], resume_tokens)
3261
self.assertEqual(set(), missing_keys)
3262
self.assertFinished(client)
3265
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3266
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3269
This test case is very similar to TestRepositoryInsertStream_1_19.
3273
super(TestRepositoryInsertStream, self).setUp()
3274
self.disable_verb('Repository.insert_stream_1.19')
3276
def test_unlocked_repo(self):
3277
transport_path = 'quack'
3278
repo, client = self.setup_fake_client_and_repository(transport_path)
3279
client.add_expected_call(
3280
'Repository.insert_stream_1.19', ('quack/', ''),
3281
'unknown', ('Repository.insert_stream_1.19',))
3282
client.add_expected_call(
3283
'Repository.insert_stream', ('quack/', ''),
3285
client.add_expected_call(
3286
'Repository.insert_stream', ('quack/', ''),
3288
self.checkInsertEmptyStream(repo, client)
3290
def test_locked_repo_with_no_lock_token(self):
3291
transport_path = 'quack'
3292
repo, client = self.setup_fake_client_and_repository(transport_path)
3293
client.add_expected_call(
3294
'Repository.lock_write', ('quack/', ''),
3295
'success', ('ok', ''))
3296
client.add_expected_call(
3297
'Repository.insert_stream_1.19', ('quack/', ''),
3298
'unknown', ('Repository.insert_stream_1.19',))
3299
client.add_expected_call(
3300
'Repository.insert_stream', ('quack/', ''),
3302
client.add_expected_call(
3303
'Repository.insert_stream', ('quack/', ''),
3306
self.checkInsertEmptyStream(repo, client)
3308
def test_locked_repo_with_lock_token(self):
3309
transport_path = 'quack'
3310
repo, client = self.setup_fake_client_and_repository(transport_path)
3311
client.add_expected_call(
3312
'Repository.lock_write', ('quack/', ''),
3313
'success', ('ok', 'a token'))
3314
client.add_expected_call(
3315
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3316
'unknown', ('Repository.insert_stream_1.19',))
3317
client.add_expected_call(
3318
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3320
client.add_expected_call(
3321
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3324
self.checkInsertEmptyStream(repo, client)
3326
def test_stream_with_inventory_deltas(self):
3327
"""'inventory-deltas' substreams cannot be sent to the
3328
Repository.insert_stream verb, because not all servers that implement
3329
that verb will accept them. So when one is encountered the RemoteSink
3330
immediately stops using that verb and falls back to VFS insert_stream.
3332
transport_path = 'quack'
3333
repo, client = self.setup_fake_client_and_repository(transport_path)
3334
client.add_expected_call(
3335
'Repository.insert_stream_1.19', ('quack/', ''),
3336
'unknown', ('Repository.insert_stream_1.19',))
3337
client.add_expected_call(
3338
'Repository.insert_stream', ('quack/', ''),
3340
client.add_expected_call(
3341
'Repository.insert_stream', ('quack/', ''),
3343
# Create a fake real repository for insert_stream to fall back on, so
3344
# that we can directly see the records the RemoteSink passes to the
3349
def insert_stream(self, stream, src_format, resume_tokens):
3350
for substream_kind, substream in stream:
3351
self.records.append(
3352
(substream_kind, [record.key for record in substream]))
3353
return ['fake tokens'], ['fake missing keys']
3354
fake_real_sink = FakeRealSink()
3355
class FakeRealRepository:
3356
def _get_sink(self):
3357
return fake_real_sink
3358
def is_in_write_group(self):
3360
def refresh_data(self):
3362
repo._real_repository = FakeRealRepository()
3363
sink = repo._get_sink()
3364
fmt = repository.format_registry.get_default()
3365
stream = self.make_stream_with_inv_deltas(fmt)
3366
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3367
# Every record from the first inventory delta should have been sent to
3369
expected_records = [
3370
('inventory-deltas', [('rev2',), ('rev3',)]),
3371
('texts', [('some-rev', 'some-file')])]
3372
self.assertEqual(expected_records, fake_real_sink.records)
3373
# The return values from the real sink's insert_stream are propagated
3374
# back to the original caller.
3375
self.assertEqual(['fake tokens'], resume_tokens)
3376
self.assertEqual(['fake missing keys'], missing_keys)
3377
self.assertFinished(client)
3379
def make_stream_with_inv_deltas(self, fmt):
3380
"""Make a simple stream with an inventory delta followed by more
3381
records and more substreams to test that all records and substreams
3382
from that point on are used.
3384
This sends, in order:
3385
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3387
* texts substream: (some-rev, some-file)
3389
# Define a stream using generators so that it isn't rewindable.
3390
inv = inventory.Inventory(revision_id='rev1')
3391
inv.root.revision = 'rev1'
3392
def stream_with_inv_delta():
3393
yield ('inventories', inventories_substream())
3394
yield ('inventory-deltas', inventory_delta_substream())
3396
versionedfile.FulltextContentFactory(
3397
('some-rev', 'some-file'), (), None, 'content')])
3398
def inventories_substream():
3399
# An empty inventory fulltext. This will be streamed normally.
3400
text = fmt._serializer.write_inventory_to_string(inv)
3401
yield versionedfile.FulltextContentFactory(
3402
('rev1',), (), None, text)
3403
def inventory_delta_substream():
3404
# An inventory delta. This can't be streamed via this verb, so it
3405
# will trigger a fallback to VFS insert_stream.
3406
entry = inv.make_entry(
3407
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3408
entry.revision = 'ghost'
3409
delta = [(None, 'newdir', 'newdir-id', entry)]
3410
serializer = inventory_delta.InventoryDeltaSerializer(
3411
versioned_root=True, tree_references=False)
3412
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3413
yield versionedfile.ChunkedContentFactory(
3414
('rev2',), (('rev1',)), None, lines)
3416
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3417
yield versionedfile.ChunkedContentFactory(
3418
('rev3',), (('rev1',)), None, lines)
3419
return stream_with_inv_delta()
3422
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3424
def test_unlocked_repo(self):
3425
transport_path = 'quack'
3426
repo, client = self.setup_fake_client_and_repository(transport_path)
3427
client.add_expected_call(
3428
'Repository.insert_stream_1.19', ('quack/', ''),
3430
client.add_expected_call(
3431
'Repository.insert_stream_1.19', ('quack/', ''),
3433
self.checkInsertEmptyStream(repo, client)
3435
def test_locked_repo_with_no_lock_token(self):
3436
transport_path = 'quack'
3437
repo, client = self.setup_fake_client_and_repository(transport_path)
3438
client.add_expected_call(
3439
'Repository.lock_write', ('quack/', ''),
3440
'success', ('ok', ''))
3441
client.add_expected_call(
3442
'Repository.insert_stream_1.19', ('quack/', ''),
3444
client.add_expected_call(
3445
'Repository.insert_stream_1.19', ('quack/', ''),
3448
self.checkInsertEmptyStream(repo, client)
3450
def test_locked_repo_with_lock_token(self):
3451
transport_path = 'quack'
3452
repo, client = self.setup_fake_client_and_repository(transport_path)
3453
client.add_expected_call(
3454
'Repository.lock_write', ('quack/', ''),
3455
'success', ('ok', 'a token'))
3456
client.add_expected_call(
3457
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3459
client.add_expected_call(
3460
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3463
self.checkInsertEmptyStream(repo, client)
3466
class TestRepositoryTarball(TestRemoteRepository):
3468
# This is a canned tarball reponse we can validate against
3470
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3471
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3472
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3473
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3474
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3475
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3476
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3477
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3478
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3479
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3480
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3481
'nWQ7QH/F3JFOFCQ0aSPfA='
3484
def test_repository_tarball(self):
3485
# Test that Repository.tarball generates the right operations
3486
transport_path = 'repo'
3487
expected_calls = [('call_expecting_body', 'Repository.tarball',
3488
('repo/', 'bz2',),),
3490
repo, client = self.setup_fake_client_and_repository(transport_path)
3491
client.add_success_response_with_body(self.tarball_content, 'ok')
3492
# Now actually ask for the tarball
3493
tarball_file = repo._get_tarball('bz2')
3495
self.assertEqual(expected_calls, client._calls)
3496
self.assertEqual(self.tarball_content, tarball_file.read())
3498
tarball_file.close()
3501
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3502
"""RemoteRepository.copy_content_into optimizations"""
3504
def test_copy_content_remote_to_local(self):
3505
self.transport_server = test_server.SmartTCPServer_for_testing
3506
src_repo = self.make_repository('repo1')
3507
src_repo = repository.Repository.open(self.get_url('repo1'))
3508
# At the moment the tarball-based copy_content_into can't write back
3509
# into a smart server. It would be good if it could upload the
3510
# tarball; once that works we'd have to create repositories of
3511
# different formats. -- mbp 20070410
3512
dest_url = self.get_vfs_only_url('repo2')
3513
dest_bzrdir = BzrDir.create(dest_url)
3514
dest_repo = dest_bzrdir.create_repository()
3515
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3516
self.assertTrue(isinstance(src_repo, RemoteRepository))
3517
src_repo.copy_content_into(dest_repo)
3520
class _StubRealPackRepository(object):
3522
def __init__(self, calls):
3524
self._pack_collection = _StubPackCollection(calls)
3526
def start_write_group(self):
3527
self.calls.append(('start_write_group',))
3529
def is_in_write_group(self):
3532
def refresh_data(self):
3533
self.calls.append(('pack collection reload_pack_names',))
3536
class _StubPackCollection(object):
3538
def __init__(self, calls):
3542
self.calls.append(('pack collection autopack',))
3545
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3546
"""Tests for RemoteRepository.autopack implementation."""
3549
"""When the server returns 'ok' and there's no _real_repository, then
3550
nothing else happens: the autopack method is done.
3552
transport_path = 'quack'
3553
repo, client = self.setup_fake_client_and_repository(transport_path)
3554
client.add_expected_call(
3555
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3557
self.assertFinished(client)
3559
def test_ok_with_real_repo(self):
3560
"""When the server returns 'ok' and there is a _real_repository, then
3561
the _real_repository's reload_pack_name's method will be called.
3563
transport_path = 'quack'
3564
repo, client = self.setup_fake_client_and_repository(transport_path)
3565
client.add_expected_call(
3566
'PackRepository.autopack', ('quack/',),
3568
repo._real_repository = _StubRealPackRepository(client._calls)
3571
[('call', 'PackRepository.autopack', ('quack/',)),
3572
('pack collection reload_pack_names',)],
3575
def test_backwards_compatibility(self):
3576
"""If the server does not recognise the PackRepository.autopack verb,
3577
fallback to the real_repository's implementation.
3579
transport_path = 'quack'
3580
repo, client = self.setup_fake_client_and_repository(transport_path)
3581
client.add_unknown_method_response('PackRepository.autopack')
3582
def stub_ensure_real():
3583
client._calls.append(('_ensure_real',))
3584
repo._real_repository = _StubRealPackRepository(client._calls)
3585
repo._ensure_real = stub_ensure_real
3588
[('call', 'PackRepository.autopack', ('quack/',)),
3590
('pack collection autopack',)],
3593
def test_oom_error_reporting(self):
3594
"""An out-of-memory condition on the server is reported clearly"""
3595
transport_path = 'quack'
3596
repo, client = self.setup_fake_client_and_repository(transport_path)
3597
client.add_expected_call(
3598
'PackRepository.autopack', ('quack/',),
3599
'error', ('MemoryError',))
3600
err = self.assertRaises(errors.BzrError, repo.autopack)
3601
self.assertContainsRe(str(err), "^remote server out of mem")
3604
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3605
"""Base class for unit tests for bzrlib.remote._translate_error."""
3607
def translateTuple(self, error_tuple, **context):
3608
"""Call _translate_error with an ErrorFromSmartServer built from the
3611
:param error_tuple: A tuple of a smart server response, as would be
3612
passed to an ErrorFromSmartServer.
3613
:kwargs context: context items to call _translate_error with.
3615
:returns: The error raised by _translate_error.
3617
# Raise the ErrorFromSmartServer before passing it as an argument,
3618
# because _translate_error may need to re-raise it with a bare 'raise'
3620
server_error = errors.ErrorFromSmartServer(error_tuple)
3621
translated_error = self.translateErrorFromSmartServer(
3622
server_error, **context)
3623
return translated_error
3625
def translateErrorFromSmartServer(self, error_object, **context):
3626
"""Like translateTuple, but takes an already constructed
3627
ErrorFromSmartServer rather than a tuple.
3631
except errors.ErrorFromSmartServer, server_error:
3632
translated_error = self.assertRaises(
3633
errors.BzrError, remote._translate_error, server_error,
3635
return translated_error
3638
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3639
"""Unit tests for bzrlib.remote._translate_error.
3641
Given an ErrorFromSmartServer (which has an error tuple from a smart
3642
server) and some context, _translate_error raises more specific errors from
3645
This test case covers the cases where _translate_error succeeds in
3646
translating an ErrorFromSmartServer to something better. See
3647
TestErrorTranslationRobustness for other cases.
3650
def test_NoSuchRevision(self):
3651
branch = self.make_branch('')
3653
translated_error = self.translateTuple(
3654
('NoSuchRevision', revid), branch=branch)
3655
expected_error = errors.NoSuchRevision(branch, revid)
3656
self.assertEqual(expected_error, translated_error)
3658
def test_nosuchrevision(self):
3659
repository = self.make_repository('')
3661
translated_error = self.translateTuple(
3662
('nosuchrevision', revid), repository=repository)
3663
expected_error = errors.NoSuchRevision(repository, revid)
3664
self.assertEqual(expected_error, translated_error)
3666
def test_nobranch(self):
3667
bzrdir = self.make_bzrdir('')
3668
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3669
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3670
self.assertEqual(expected_error, translated_error)
3672
def test_nobranch_one_arg(self):
3673
bzrdir = self.make_bzrdir('')
3674
translated_error = self.translateTuple(
3675
('nobranch', 'extra detail'), bzrdir=bzrdir)
3676
expected_error = errors.NotBranchError(
3677
path=bzrdir.root_transport.base,
3678
detail='extra detail')
3679
self.assertEqual(expected_error, translated_error)
3681
def test_norepository(self):
3682
bzrdir = self.make_bzrdir('')
3683
translated_error = self.translateTuple(('norepository',),
3685
expected_error = errors.NoRepositoryPresent(bzrdir)
3686
self.assertEqual(expected_error, translated_error)
3688
def test_LockContention(self):
3689
translated_error = self.translateTuple(('LockContention',))
3690
expected_error = errors.LockContention('(remote lock)')
3691
self.assertEqual(expected_error, translated_error)
3693
def test_UnlockableTransport(self):
3694
bzrdir = self.make_bzrdir('')
3695
translated_error = self.translateTuple(
3696
('UnlockableTransport',), bzrdir=bzrdir)
3697
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3698
self.assertEqual(expected_error, translated_error)
3700
def test_LockFailed(self):
3701
lock = 'str() of a server lock'
3702
why = 'str() of why'
3703
translated_error = self.translateTuple(('LockFailed', lock, why))
3704
expected_error = errors.LockFailed(lock, why)
3705
self.assertEqual(expected_error, translated_error)
3707
def test_TokenMismatch(self):
3708
token = 'a lock token'
3709
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3710
expected_error = errors.TokenMismatch(token, '(remote token)')
3711
self.assertEqual(expected_error, translated_error)
3713
def test_Diverged(self):
3714
branch = self.make_branch('a')
3715
other_branch = self.make_branch('b')
3716
translated_error = self.translateTuple(
3717
('Diverged',), branch=branch, other_branch=other_branch)
3718
expected_error = errors.DivergedBranches(branch, other_branch)
3719
self.assertEqual(expected_error, translated_error)
3721
def test_NotStacked(self):
3722
branch = self.make_branch('')
3723
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3724
expected_error = errors.NotStacked(branch)
3725
self.assertEqual(expected_error, translated_error)
3727
def test_ReadError_no_args(self):
3729
translated_error = self.translateTuple(('ReadError',), path=path)
3730
expected_error = errors.ReadError(path)
3731
self.assertEqual(expected_error, translated_error)
3733
def test_ReadError(self):
3735
translated_error = self.translateTuple(('ReadError', path))
3736
expected_error = errors.ReadError(path)
3737
self.assertEqual(expected_error, translated_error)
3739
def test_IncompatibleRepositories(self):
3740
translated_error = self.translateTuple(('IncompatibleRepositories',
3741
"repo1", "repo2", "details here"))
3742
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3744
self.assertEqual(expected_error, translated_error)
3746
def test_PermissionDenied_no_args(self):
3748
translated_error = self.translateTuple(('PermissionDenied',),
3750
expected_error = errors.PermissionDenied(path)
3751
self.assertEqual(expected_error, translated_error)
3753
def test_PermissionDenied_one_arg(self):
3755
translated_error = self.translateTuple(('PermissionDenied', path))
3756
expected_error = errors.PermissionDenied(path)
3757
self.assertEqual(expected_error, translated_error)
3759
def test_PermissionDenied_one_arg_and_context(self):
3760
"""Given a choice between a path from the local context and a path on
3761
the wire, _translate_error prefers the path from the local context.
3763
local_path = 'local path'
3764
remote_path = 'remote path'
3765
translated_error = self.translateTuple(
3766
('PermissionDenied', remote_path), path=local_path)
3767
expected_error = errors.PermissionDenied(local_path)
3768
self.assertEqual(expected_error, translated_error)
3770
def test_PermissionDenied_two_args(self):
3772
extra = 'a string with extra info'
3773
translated_error = self.translateTuple(
3774
('PermissionDenied', path, extra))
3775
expected_error = errors.PermissionDenied(path, extra)
3776
self.assertEqual(expected_error, translated_error)
3778
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3780
def test_NoSuchFile_context_path(self):
3781
local_path = "local path"
3782
translated_error = self.translateTuple(('ReadError', "remote path"),
3784
expected_error = errors.ReadError(local_path)
3785
self.assertEqual(expected_error, translated_error)
3787
def test_NoSuchFile_without_context(self):
3788
remote_path = "remote path"
3789
translated_error = self.translateTuple(('ReadError', remote_path))
3790
expected_error = errors.ReadError(remote_path)
3791
self.assertEqual(expected_error, translated_error)
3793
def test_ReadOnlyError(self):
3794
translated_error = self.translateTuple(('ReadOnlyError',))
3795
expected_error = errors.TransportNotPossible("readonly transport")
3796
self.assertEqual(expected_error, translated_error)
3798
def test_MemoryError(self):
3799
translated_error = self.translateTuple(('MemoryError',))
3800
self.assertStartsWith(str(translated_error),
3801
"remote server out of memory")
3803
def test_generic_IndexError_no_classname(self):
3804
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3805
translated_error = self.translateErrorFromSmartServer(err)
3806
expected_error = errors.UnknownErrorFromSmartServer(err)
3807
self.assertEqual(expected_error, translated_error)
3809
# GZ 2011-03-02: TODO test generic non-ascii error string
3811
def test_generic_KeyError(self):
3812
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3813
translated_error = self.translateErrorFromSmartServer(err)
3814
expected_error = errors.UnknownErrorFromSmartServer(err)
3815
self.assertEqual(expected_error, translated_error)
3818
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3819
"""Unit tests for bzrlib.remote._translate_error's robustness.
3821
TestErrorTranslationSuccess is for cases where _translate_error can
3822
translate successfully. This class about how _translate_err behaves when
3823
it fails to translate: it re-raises the original error.
3826
def test_unrecognised_server_error(self):
3827
"""If the error code from the server is not recognised, the original
3828
ErrorFromSmartServer is propagated unmodified.
3830
error_tuple = ('An unknown error tuple',)
3831
server_error = errors.ErrorFromSmartServer(error_tuple)
3832
translated_error = self.translateErrorFromSmartServer(server_error)
3833
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3834
self.assertEqual(expected_error, translated_error)
3836
def test_context_missing_a_key(self):
3837
"""In case of a bug in the client, or perhaps an unexpected response
3838
from a server, _translate_error returns the original error tuple from
3839
the server and mutters a warning.
3841
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3842
# in the context dict. So let's give it an empty context dict instead
3843
# to exercise its error recovery.
3845
error_tuple = ('NoSuchRevision', 'revid')
3846
server_error = errors.ErrorFromSmartServer(error_tuple)
3847
translated_error = self.translateErrorFromSmartServer(server_error)
3848
self.assertEqual(server_error, translated_error)
3849
# In addition to re-raising ErrorFromSmartServer, some debug info has
3850
# been muttered to the log file for developer to look at.
3851
self.assertContainsRe(
3853
"Missing key 'branch' in context")
3855
def test_path_missing(self):
3856
"""Some translations (PermissionDenied, ReadError) can determine the
3857
'path' variable from either the wire or the local context. If neither
3858
has it, then an error is raised.
3860
error_tuple = ('ReadError',)
3861
server_error = errors.ErrorFromSmartServer(error_tuple)
3862
translated_error = self.translateErrorFromSmartServer(server_error)
3863
self.assertEqual(server_error, translated_error)
3864
# In addition to re-raising ErrorFromSmartServer, some debug info has
3865
# been muttered to the log file for developer to look at.
3866
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3869
class TestStacking(tests.TestCaseWithTransport):
3870
"""Tests for operations on stacked remote repositories.
3872
The underlying format type must support stacking.
3875
def test_access_stacked_remote(self):
3876
# based on <http://launchpad.net/bugs/261315>
3877
# make a branch stacked on another repository containing an empty
3878
# revision, then open it over hpss - we should be able to see that
3880
base_transport = self.get_transport()
3881
base_builder = self.make_branch_builder('base', format='1.9')
3882
base_builder.start_series()
3883
base_revid = base_builder.build_snapshot('rev-id', None,
3884
[('add', ('', None, 'directory', None))],
3886
base_builder.finish_series()
3887
stacked_branch = self.make_branch('stacked', format='1.9')
3888
stacked_branch.set_stacked_on_url('../base')
3889
# start a server looking at this
3890
smart_server = test_server.SmartTCPServer_for_testing()
3891
self.start_server(smart_server)
3892
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3893
# can get its branch and repository
3894
remote_branch = remote_bzrdir.open_branch()
3895
remote_repo = remote_branch.repository
3896
remote_repo.lock_read()
3898
# it should have an appropriate fallback repository, which should also
3899
# be a RemoteRepository
3900
self.assertLength(1, remote_repo._fallback_repositories)
3901
self.assertIsInstance(remote_repo._fallback_repositories[0],
3903
# and it has the revision committed to the underlying repository;
3904
# these have varying implementations so we try several of them
3905
self.assertTrue(remote_repo.has_revisions([base_revid]))
3906
self.assertTrue(remote_repo.has_revision(base_revid))
3907
self.assertEqual(remote_repo.get_revision(base_revid).message,
3910
remote_repo.unlock()
3912
def prepare_stacked_remote_branch(self):
3913
"""Get stacked_upon and stacked branches with content in each."""
3914
self.setup_smart_server_with_call_log()
3915
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3916
tree1.commit('rev1', rev_id='rev1')
3917
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3918
).open_workingtree()
3919
local_tree = tree2.branch.create_checkout('local')
3920
local_tree.commit('local changes make me feel good.')
3921
branch2 = Branch.open(self.get_url('tree2'))
3923
self.addCleanup(branch2.unlock)
3924
return tree1.branch, branch2
3926
def test_stacked_get_parent_map(self):
3927
# the public implementation of get_parent_map obeys stacking
3928
_, branch = self.prepare_stacked_remote_branch()
3929
repo = branch.repository
3930
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3932
def test_unstacked_get_parent_map(self):
3933
# _unstacked_provider.get_parent_map ignores stacking
3934
_, branch = self.prepare_stacked_remote_branch()
3935
provider = branch.repository._unstacked_provider
3936
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3938
def fetch_stream_to_rev_order(self, stream):
3940
for kind, substream in stream:
3941
if not kind == 'revisions':
3944
for content in substream:
3945
result.append(content.key[-1])
3948
def get_ordered_revs(self, format, order, branch_factory=None):
3949
"""Get a list of the revisions in a stream to format format.
3951
:param format: The format of the target.
3952
:param order: the order that target should have requested.
3953
:param branch_factory: A callable to create a trunk and stacked branch
3954
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3955
:result: The revision ids in the stream, in the order seen,
3956
the topological order of revisions in the source.
3958
unordered_format = controldir.format_registry.get(format)()
3959
target_repository_format = unordered_format.repository_format
3961
self.assertEqual(order, target_repository_format._fetch_order)
3962
if branch_factory is None:
3963
branch_factory = self.prepare_stacked_remote_branch
3964
_, stacked = branch_factory()
3965
source = stacked.repository._get_source(target_repository_format)
3966
tip = stacked.last_revision()
3967
stacked.repository._ensure_real()
3968
graph = stacked.repository.get_graph()
3969
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3970
if r != NULL_REVISION]
3972
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3973
self.reset_smart_call_log()
3974
stream = source.get_stream(search)
3975
# We trust that if a revision is in the stream the rest of the new
3976
# content for it is too, as per our main fetch tests; here we are
3977
# checking that the revisions are actually included at all, and their
3979
return self.fetch_stream_to_rev_order(stream), revs
3981
def test_stacked_get_stream_unordered(self):
3982
# Repository._get_source.get_stream() from a stacked repository with
3983
# unordered yields the full data from both stacked and stacked upon
3985
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3986
self.assertEqual(set(expected_revs), set(rev_ord))
3987
# Getting unordered results should have made a streaming data request
3988
# from the server, then one from the backing branch.
3989
self.assertLength(2, self.hpss_calls)
3991
def test_stacked_on_stacked_get_stream_unordered(self):
3992
# Repository._get_source.get_stream() from a stacked repository which
3993
# is itself stacked yields the full data from all three sources.
3994
def make_stacked_stacked():
3995
_, stacked = self.prepare_stacked_remote_branch()
3996
tree = stacked.bzrdir.sprout('tree3', stacked=True
3997
).open_workingtree()
3998
local_tree = tree.branch.create_checkout('local-tree3')
3999
local_tree.commit('more local changes are better')
4000
branch = Branch.open(self.get_url('tree3'))
4002
self.addCleanup(branch.unlock)
4004
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
4005
branch_factory=make_stacked_stacked)
4006
self.assertEqual(set(expected_revs), set(rev_ord))
4007
# Getting unordered results should have made a streaming data request
4008
# from the server, and one from each backing repo
4009
self.assertLength(3, self.hpss_calls)
4011
def test_stacked_get_stream_topological(self):
4012
# Repository._get_source.get_stream() from a stacked repository with
4013
# topological sorting yields the full data from both stacked and
4014
# stacked upon sources in topological order.
4015
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4016
self.assertEqual(expected_revs, rev_ord)
4017
# Getting topological sort requires VFS calls still - one of which is
4018
# pushing up from the bound branch.
4019
self.assertLength(14, self.hpss_calls)
4021
def test_stacked_get_stream_groupcompress(self):
4022
# Repository._get_source.get_stream() from a stacked repository with
4023
# groupcompress sorting yields the full data from both stacked and
4024
# stacked upon sources in groupcompress order.
4025
raise tests.TestSkipped('No groupcompress ordered format available')
4026
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4027
self.assertEqual(expected_revs, reversed(rev_ord))
4028
# Getting unordered results should have made a streaming data request
4029
# from the backing branch, and one from the stacked on branch.
4030
self.assertLength(2, self.hpss_calls)
4032
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4033
# When pulling some fixed amount of content that is more than the
4034
# source has (because some is coming from a fallback branch, no error
4035
# should be received. This was reported as bug 360791.
4036
# Need three branches: a trunk, a stacked branch, and a preexisting
4037
# branch pulling content from stacked and trunk.
4038
self.setup_smart_server_with_call_log()
4039
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4040
r1 = trunk.commit('start')
4041
stacked_branch = trunk.branch.create_clone_on_transport(
4042
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4043
local = self.make_branch('local', format='1.9-rich-root')
4044
local.repository.fetch(stacked_branch.repository,
4045
stacked_branch.last_revision())
4048
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4051
super(TestRemoteBranchEffort, self).setUp()
4052
# Create a smart server that publishes whatever the backing VFS server
4054
self.smart_server = test_server.SmartTCPServer_for_testing()
4055
self.start_server(self.smart_server, self.get_server())
4056
# Log all HPSS calls into self.hpss_calls.
4057
_SmartClient.hooks.install_named_hook(
4058
'call', self.capture_hpss_call, None)
4059
self.hpss_calls = []
4061
def capture_hpss_call(self, params):
4062
self.hpss_calls.append(params.method)
4064
def test_copy_content_into_avoids_revision_history(self):
4065
local = self.make_branch('local')
4066
builder = self.make_branch_builder('remote')
4067
builder.build_commit(message="Commit.")
4068
remote_branch_url = self.smart_server.get_url() + 'remote'
4069
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4070
local.repository.fetch(remote_branch.repository)
4071
self.hpss_calls = []
4072
remote_branch.copy_content_into(local)
4073
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4075
def test_fetch_everything_needs_just_one_call(self):
4076
local = self.make_branch('local')
4077
builder = self.make_branch_builder('remote')
4078
builder.build_commit(message="Commit.")
4079
remote_branch_url = self.smart_server.get_url() + 'remote'
4080
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4081
self.hpss_calls = []
4082
local.repository.fetch(
4083
remote_branch.repository,
4084
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4085
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4087
def override_verb(self, verb_name, verb):
4088
request_handlers = request.request_handlers
4089
orig_verb = request_handlers.get(verb_name)
4090
orig_info = request_handlers.get_info(verb_name)
4091
request_handlers.register(verb_name, verb, override_existing=True)
4092
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4093
override_existing=True, info=orig_info)
4095
def test_fetch_everything_backwards_compat(self):
4096
"""Can fetch with EverythingResult even with pre 2.4 servers.
4098
Pre-2.4 do not support 'everything' searches with the
4099
Repository.get_stream_1.19 verb.
4102
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4103
"""A version of the Repository.get_stream_1.19 verb patched to
4104
reject 'everything' searches the way 2.3 and earlier do.
4106
def recreate_search(self, repository, search_bytes,
4107
discard_excess=False):
4108
verb_log.append(search_bytes.split('\n', 1)[0])
4109
if search_bytes == 'everything':
4111
request.FailedSmartServerResponse(('BadSearch',)))
4112
return super(OldGetStreamVerb,
4113
self).recreate_search(repository, search_bytes,
4114
discard_excess=discard_excess)
4115
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4116
local = self.make_branch('local')
4117
builder = self.make_branch_builder('remote')
4118
builder.build_commit(message="Commit.")
4119
remote_branch_url = self.smart_server.get_url() + 'remote'
4120
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4121
self.hpss_calls = []
4122
local.repository.fetch(
4123
remote_branch.repository,
4124
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4125
# make sure the overridden verb was used
4126
self.assertLength(1, verb_log)
4127
# more than one HPSS call is needed, but because it's a VFS callback
4128
# its hard to predict exactly how many.
4129
self.assertTrue(len(self.hpss_calls) > 1)
4132
class TestUpdateBoundBranchWithModifiedBoundLocation(
4133
tests.TestCaseWithTransport):
4134
"""Ensure correct handling of bound_location modifications.
4136
This is tested against a smart server as http://pad.lv/786980 was about a
4137
ReadOnlyError (write attempt during a read-only transaction) which can only
4138
happen in this context.
4142
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4143
self.transport_server = test_server.SmartTCPServer_for_testing
4145
def make_master_and_checkout(self, master_name, checkout_name):
4146
# Create the master branch and its associated checkout
4147
self.master = self.make_branch_and_tree(master_name)
4148
self.checkout = self.master.branch.create_checkout(checkout_name)
4149
# Modify the master branch so there is something to update
4150
self.master.commit('add stuff')
4151
self.last_revid = self.master.commit('even more stuff')
4152
self.bound_location = self.checkout.branch.get_bound_location()
4154
def assertUpdateSucceeds(self, new_location):
4155
self.checkout.branch.set_bound_location(new_location)
4156
self.checkout.update()
4157
self.assertEqual(self.last_revid, self.checkout.last_revision())
4159
def test_without_final_slash(self):
4160
self.make_master_and_checkout('master', 'checkout')
4161
# For unclear reasons some users have a bound_location without a final
4162
# '/', simulate that by forcing such a value
4163
self.assertEndsWith(self.bound_location, '/')
4164
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4166
def test_plus_sign(self):
4167
self.make_master_and_checkout('+master', 'checkout')
4168
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4170
def test_tilda(self):
4171
# Embed ~ in the middle of the path just to avoid any $HOME
4173
self.make_master_and_checkout('mas~ter', 'checkout')
4174
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4177
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4179
def test_no_context(self):
4180
class OutOfCoffee(errors.BzrError):
4181
"""A dummy exception for testing."""
4183
def __init__(self, urgency):
4184
self.urgency = urgency
4185
remote.no_context_error_translators.register("OutOfCoffee",
4186
lambda err: OutOfCoffee(err.error_args[0]))
4187
transport = MemoryTransport()
4188
client = FakeClient(transport.base)
4189
client.add_expected_call(
4190
'Branch.get_stacked_on_url', ('quack/',),
4191
'error', ('NotStacked',))
4192
client.add_expected_call(
4193
'Branch.last_revision_info',
4195
'error', ('OutOfCoffee', 'low'))
4196
transport.mkdir('quack')
4197
transport = transport.clone('quack')
4198
branch = self.make_remote_branch(transport, client)
4199
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4200
self.assertFinished(client)
4202
def test_with_context(self):
4203
class OutOfTea(errors.BzrError):
4204
def __init__(self, branch, urgency):
4205
self.branch = branch
4206
self.urgency = urgency
4207
remote.error_translators.register("OutOfTea",
4208
lambda err, find, path: OutOfTea(err.error_args[0],
4210
transport = MemoryTransport()
4211
client = FakeClient(transport.base)
4212
client.add_expected_call(
4213
'Branch.get_stacked_on_url', ('quack/',),
4214
'error', ('NotStacked',))
4215
client.add_expected_call(
4216
'Branch.last_revision_info',
4218
'error', ('OutOfTea', 'low'))
4219
transport.mkdir('quack')
4220
transport = transport.clone('quack')
4221
branch = self.make_remote_branch(transport, client)
4222
self.assertRaises(OutOfTea, branch.last_revision_info)
4223
self.assertFinished(client)
4226
class TestRepositoryPack(TestRemoteRepository):
4228
def test_pack(self):
4229
transport_path = 'quack'
4230
repo, client = self.setup_fake_client_and_repository(transport_path)
4231
client.add_expected_call(
4232
'Repository.lock_write', ('quack/', ''),
4233
'success', ('ok', 'token'))
4234
client.add_expected_call(
4235
'Repository.pack', ('quack/', 'token', 'False'),
4236
'success', ('ok',), )
4237
client.add_expected_call(
4238
'Repository.unlock', ('quack/', 'token'),
4239
'success', ('ok', ))
4242
def test_pack_with_hint(self):
4243
transport_path = 'quack'
4244
repo, client = self.setup_fake_client_and_repository(transport_path)
4245
client.add_expected_call(
4246
'Repository.lock_write', ('quack/', ''),
4247
'success', ('ok', 'token'))
4248
client.add_expected_call(
4249
'Repository.pack', ('quack/', 'token', 'False'),
4250
'success', ('ok',), )
4251
client.add_expected_call(
4252
'Repository.unlock', ('quack/', 'token', 'False'),
4253
'success', ('ok', ))
4254
repo.pack(['hinta', 'hintb'])
4257
class TestRepositoryIterInventories(TestRemoteRepository):
4258
"""Test Repository.iter_inventories."""
4260
def _serialize_inv_delta(self, old_name, new_name, delta):
4261
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4262
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4264
def test_single_empty(self):
4265
transport_path = 'quack'
4266
repo, client = self.setup_fake_client_and_repository(transport_path)
4267
fmt = controldir.format_registry.get('2a')().repository_format
4269
stream = [('inventory-deltas', [
4270
versionedfile.FulltextContentFactory('somerevid', None, None,
4271
self._serialize_inv_delta('null:', 'somerevid', []))])]
4272
client.add_expected_call(
4273
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4274
'success', ('ok', ),
4275
_stream_to_byte_stream(stream, fmt))
4276
ret = list(repo.iter_inventories(["somerevid"]))
4277
self.assertLength(1, ret)
4279
self.assertEqual("somerevid", inv.revision_id)
4281
def test_empty(self):
4282
transport_path = 'quack'
4283
repo, client = self.setup_fake_client_and_repository(transport_path)
4284
ret = list(repo.iter_inventories([]))
4285
self.assertEqual(ret, [])
4287
def test_missing(self):
4288
transport_path = 'quack'
4289
repo, client = self.setup_fake_client_and_repository(transport_path)
4290
client.add_expected_call(
4291
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4292
'success', ('ok', ), iter([]))
4293
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(