1
# Copyright (C) 2006-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for remote bzrdir/branch/repo/etc
19
These are proxy objects which act on remote objects by sending messages
20
through a smart client. The proxies are to be created when attempting to open
21
the object given a transport that supports smartserver rpc operations.
23
These tests correspond to tests.test_smart, which exercises the server side.
27
from cStringIO import StringIO
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_remote_branch_revision_history(self):
125
b = BzrDir.open_from_transport(self.transport).open_branch()
127
self.applyDeprecated(deprecated_in((2, 5, 0)), b.revision_history))
128
r1 = self.local_wt.commit('1st commit')
129
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
130
self.assertEqual([r1, r2],
131
self.applyDeprecated(deprecated_in((2, 5, 0)), b.revision_history))
133
def test_find_correct_format(self):
134
"""Should open a RemoteBzrDir over a RemoteTransport"""
135
fmt = BzrDirFormat.find_format(self.transport)
136
self.assertTrue(bzrdir.RemoteBzrProber
137
in controldir.ControlDirFormat._server_probers)
138
self.assertIsInstance(fmt, RemoteBzrDirFormat)
140
def test_open_detected_smart_format(self):
141
fmt = BzrDirFormat.find_format(self.transport)
142
d = fmt.open(self.transport)
143
self.assertIsInstance(d, BzrDir)
145
def test_remote_branch_repr(self):
146
b = BzrDir.open_from_transport(self.transport).open_branch()
147
self.assertStartsWith(str(b), 'RemoteBranch(')
149
def test_remote_bzrdir_repr(self):
150
b = BzrDir.open_from_transport(self.transport)
151
self.assertStartsWith(str(b), 'RemoteBzrDir(')
153
def test_remote_branch_format_supports_stacking(self):
155
self.make_branch('unstackable', format='pack-0.92')
156
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
157
self.assertFalse(b._format.supports_stacking())
158
self.make_branch('stackable', format='1.9')
159
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
160
self.assertTrue(b._format.supports_stacking())
162
def test_remote_repo_format_supports_external_references(self):
164
bd = self.make_bzrdir('unstackable', format='pack-0.92')
165
r = bd.create_repository()
166
self.assertFalse(r._format.supports_external_lookups)
167
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
168
self.assertFalse(r._format.supports_external_lookups)
169
bd = self.make_bzrdir('stackable', format='1.9')
170
r = bd.create_repository()
171
self.assertTrue(r._format.supports_external_lookups)
172
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
173
self.assertTrue(r._format.supports_external_lookups)
175
def test_remote_branch_set_append_revisions_only(self):
176
# Make a format 1.9 branch, which supports append_revisions_only
177
branch = self.make_branch('branch', format='1.9')
178
branch.set_append_revisions_only(True)
179
config = branch.get_config_stack()
181
True, config.get('append_revisions_only'))
182
branch.set_append_revisions_only(False)
183
config = branch.get_config_stack()
185
False, config.get('append_revisions_only'))
187
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
188
branch = self.make_branch('branch', format='knit')
190
errors.UpgradeRequired, branch.set_append_revisions_only, True)
193
class FakeProtocol(object):
194
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
196
def __init__(self, body, fake_client):
198
self._body_buffer = None
199
self._fake_client = fake_client
201
def read_body_bytes(self, count=-1):
202
if self._body_buffer is None:
203
self._body_buffer = StringIO(self.body)
204
bytes = self._body_buffer.read(count)
205
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
206
self._fake_client.expecting_body = False
209
def cancel_read_body(self):
210
self._fake_client.expecting_body = False
212
def read_streamed_body(self):
216
class FakeClient(_SmartClient):
217
"""Lookalike for _SmartClient allowing testing."""
219
def __init__(self, fake_medium_base='fake base'):
220
"""Create a FakeClient."""
223
self.expecting_body = False
224
# if non-None, this is the list of expected calls, with only the
225
# method name and arguments included. the body might be hard to
226
# compute so is not included. If a call is None, that call can
228
self._expected_calls = None
229
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
231
def add_expected_call(self, call_name, call_args, response_type,
232
response_args, response_body=None):
233
if self._expected_calls is None:
234
self._expected_calls = []
235
self._expected_calls.append((call_name, call_args))
236
self.responses.append((response_type, response_args, response_body))
238
def add_success_response(self, *args):
239
self.responses.append(('success', args, None))
241
def add_success_response_with_body(self, body, *args):
242
self.responses.append(('success', args, body))
243
if self._expected_calls is not None:
244
self._expected_calls.append(None)
246
def add_error_response(self, *args):
247
self.responses.append(('error', args))
249
def add_unknown_method_response(self, verb):
250
self.responses.append(('unknown', verb))
252
def finished_test(self):
253
if self._expected_calls:
254
raise AssertionError("%r finished but was still expecting %r"
255
% (self, self._expected_calls[0]))
257
def _get_next_response(self):
259
response_tuple = self.responses.pop(0)
260
except IndexError, e:
261
raise AssertionError("%r didn't expect any more calls"
263
if response_tuple[0] == 'unknown':
264
raise errors.UnknownSmartMethod(response_tuple[1])
265
elif response_tuple[0] == 'error':
266
raise errors.ErrorFromSmartServer(response_tuple[1])
267
return response_tuple
269
def _check_call(self, method, args):
270
if self._expected_calls is None:
271
# the test should be updated to say what it expects
274
next_call = self._expected_calls.pop(0)
276
raise AssertionError("%r didn't expect any more calls "
278
% (self, method, args,))
279
if next_call is None:
281
if method != next_call[0] or args != next_call[1]:
282
raise AssertionError("%r expected %r%r "
284
% (self, next_call[0], next_call[1], method, args,))
286
def call(self, method, *args):
287
self._check_call(method, args)
288
self._calls.append(('call', method, args))
289
return self._get_next_response()[1]
291
def call_expecting_body(self, method, *args):
292
self._check_call(method, args)
293
self._calls.append(('call_expecting_body', method, args))
294
result = self._get_next_response()
295
self.expecting_body = True
296
return result[1], FakeProtocol(result[2], self)
298
def call_with_body_bytes(self, method, args, body):
299
self._check_call(method, args)
300
self._calls.append(('call_with_body_bytes', method, args, body))
301
result = self._get_next_response()
302
return result[1], FakeProtocol(result[2], self)
304
def call_with_body_bytes_expecting_body(self, method, args, body):
305
self._check_call(method, args)
306
self._calls.append(('call_with_body_bytes_expecting_body', method,
308
result = self._get_next_response()
309
self.expecting_body = True
310
return result[1], FakeProtocol(result[2], self)
312
def call_with_body_stream(self, args, stream):
313
# Explicitly consume the stream before checking for an error, because
314
# that's what happens a real medium.
315
stream = list(stream)
316
self._check_call(args[0], args[1:])
317
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
318
result = self._get_next_response()
319
# The second value returned from call_with_body_stream is supposed to
320
# be a response_handler object, but so far no tests depend on that.
321
response_handler = None
322
return result[1], response_handler
325
class FakeMedium(medium.SmartClientMedium):
327
def __init__(self, client_calls, base):
328
medium.SmartClientMedium.__init__(self, base)
329
self._client_calls = client_calls
331
def disconnect(self):
332
self._client_calls.append(('disconnect medium',))
335
class TestVfsHas(tests.TestCase):
337
def test_unicode_path(self):
338
client = FakeClient('/')
339
client.add_success_response('yes',)
340
transport = RemoteTransport('bzr://localhost/', _client=client)
341
filename = u'/hell\u00d8'.encode('utf8')
342
result = transport.has(filename)
344
[('call', 'has', (filename,))],
346
self.assertTrue(result)
349
class TestRemote(tests.TestCaseWithMemoryTransport):
351
def get_branch_format(self):
352
reference_bzrdir_format = bzrdir.format_registry.get('default')()
353
return reference_bzrdir_format.get_branch_format()
355
def get_repo_format(self):
356
reference_bzrdir_format = bzrdir.format_registry.get('default')()
357
return reference_bzrdir_format.repository_format
359
def assertFinished(self, fake_client):
360
"""Assert that all of a FakeClient's expected calls have occurred."""
361
fake_client.finished_test()
364
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
365
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
367
def assertRemotePath(self, expected, client_base, transport_base):
368
"""Assert that the result of
369
SmartClientMedium.remote_path_from_transport is the expected value for
370
a given client_base and transport_base.
372
client_medium = medium.SmartClientMedium(client_base)
373
t = transport.get_transport(transport_base)
374
result = client_medium.remote_path_from_transport(t)
375
self.assertEqual(expected, result)
377
def test_remote_path_from_transport(self):
378
"""SmartClientMedium.remote_path_from_transport calculates a URL for
379
the given transport relative to the root of the client base URL.
381
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
382
self.assertRemotePath(
383
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
385
def assertRemotePathHTTP(self, expected, transport_base, relpath):
386
"""Assert that the result of
387
HttpTransportBase.remote_path_from_transport is the expected value for
388
a given transport_base and relpath of that transport. (Note that
389
HttpTransportBase is a subclass of SmartClientMedium)
391
base_transport = transport.get_transport(transport_base)
392
client_medium = base_transport.get_smart_medium()
393
cloned_transport = base_transport.clone(relpath)
394
result = client_medium.remote_path_from_transport(cloned_transport)
395
self.assertEqual(expected, result)
397
def test_remote_path_from_transport_http(self):
398
"""Remote paths for HTTP transports are calculated differently to other
399
transports. They are just relative to the client base, not the root
400
directory of the host.
402
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
403
self.assertRemotePathHTTP(
404
'../xyz/', scheme + '//host/path', '../xyz/')
405
self.assertRemotePathHTTP(
406
'xyz/', scheme + '//host/path', 'xyz/')
409
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
410
"""Tests for the behaviour of client_medium.remote_is_at_least."""
412
def test_initially_unlimited(self):
413
"""A fresh medium assumes that the remote side supports all
416
client_medium = medium.SmartClientMedium('dummy base')
417
self.assertFalse(client_medium._is_remote_before((99, 99)))
419
def test__remember_remote_is_before(self):
420
"""Calling _remember_remote_is_before ratchets down the known remote
423
client_medium = medium.SmartClientMedium('dummy base')
424
# Mark the remote side as being less than 1.6. The remote side may
426
client_medium._remember_remote_is_before((1, 6))
427
self.assertTrue(client_medium._is_remote_before((1, 6)))
428
self.assertFalse(client_medium._is_remote_before((1, 5)))
429
# Calling _remember_remote_is_before again with a lower value works.
430
client_medium._remember_remote_is_before((1, 5))
431
self.assertTrue(client_medium._is_remote_before((1, 5)))
432
# If you call _remember_remote_is_before with a higher value it logs a
433
# warning, and continues to remember the lower value.
434
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
435
client_medium._remember_remote_is_before((1, 9))
436
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
437
self.assertTrue(client_medium._is_remote_before((1, 5)))
440
class TestBzrDirCloningMetaDir(TestRemote):
442
def test_backwards_compat(self):
443
self.setup_smart_server_with_call_log()
444
a_dir = self.make_bzrdir('.')
445
self.reset_smart_call_log()
446
verb = 'BzrDir.cloning_metadir'
447
self.disable_verb(verb)
448
format = a_dir.cloning_metadir()
449
call_count = len([call for call in self.hpss_calls if
450
call.call.method == verb])
451
self.assertEqual(1, call_count)
453
def test_branch_reference(self):
454
transport = self.get_transport('quack')
455
referenced = self.make_branch('referenced')
456
expected = referenced.bzrdir.cloning_metadir()
457
client = FakeClient(transport.base)
458
client.add_expected_call(
459
'BzrDir.cloning_metadir', ('quack/', 'False'),
460
'error', ('BranchReference',)),
461
client.add_expected_call(
462
'BzrDir.open_branchV3', ('quack/',),
463
'success', ('ref', self.get_url('referenced'))),
464
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
466
result = a_bzrdir.cloning_metadir()
467
# We should have got a control dir matching the referenced branch.
468
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
469
self.assertEqual(expected._repository_format, result._repository_format)
470
self.assertEqual(expected._branch_format, result._branch_format)
471
self.assertFinished(client)
473
def test_current_server(self):
474
transport = self.get_transport('.')
475
transport = transport.clone('quack')
476
self.make_bzrdir('quack')
477
client = FakeClient(transport.base)
478
reference_bzrdir_format = bzrdir.format_registry.get('default')()
479
control_name = reference_bzrdir_format.network_name()
480
client.add_expected_call(
481
'BzrDir.cloning_metadir', ('quack/', 'False'),
482
'success', (control_name, '', ('branch', ''))),
483
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
485
result = a_bzrdir.cloning_metadir()
486
# We should have got a reference control dir with default branch and
487
# repository formats.
488
# This pokes a little, just to be sure.
489
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
490
self.assertEqual(None, result._repository_format)
491
self.assertEqual(None, result._branch_format)
492
self.assertFinished(client)
494
def test_unknown(self):
495
transport = self.get_transport('quack')
496
referenced = self.make_branch('referenced')
497
expected = referenced.bzrdir.cloning_metadir()
498
client = FakeClient(transport.base)
499
client.add_expected_call(
500
'BzrDir.cloning_metadir', ('quack/', 'False'),
501
'success', ('unknown', 'unknown', ('branch', ''))),
502
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
504
self.assertRaises(errors.UnknownFormatError, a_bzrdir.cloning_metadir)
507
class TestBzrDirCheckoutMetaDir(TestRemote):
509
def test__get_checkout_format(self):
510
transport = MemoryTransport()
511
client = FakeClient(transport.base)
512
reference_bzrdir_format = bzrdir.format_registry.get('default')()
513
control_name = reference_bzrdir_format.network_name()
514
client.add_expected_call(
515
'BzrDir.checkout_metadir', ('quack/', ),
516
'success', (control_name, '', ''))
517
transport.mkdir('quack')
518
transport = transport.clone('quack')
519
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
521
result = a_bzrdir.checkout_metadir()
522
# We should have got a reference control dir with default branch and
523
# repository formats.
524
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
525
self.assertEqual(None, result._repository_format)
526
self.assertEqual(None, result._branch_format)
527
self.assertFinished(client)
529
def test_unknown_format(self):
530
transport = MemoryTransport()
531
client = FakeClient(transport.base)
532
client.add_expected_call(
533
'BzrDir.checkout_metadir', ('quack/',),
534
'success', ('dontknow', '', ''))
535
transport.mkdir('quack')
536
transport = transport.clone('quack')
537
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
539
self.assertRaises(errors.UnknownFormatError,
540
a_bzrdir.checkout_metadir)
541
self.assertFinished(client)
544
class TestBzrDirGetBranches(TestRemote):
546
def test_get_branches(self):
547
transport = MemoryTransport()
548
client = FakeClient(transport.base)
549
reference_bzrdir_format = bzrdir.format_registry.get('default')()
550
branch_name = reference_bzrdir_format.get_branch_format().network_name()
551
client.add_success_response_with_body(
553
"foo": ("branch", branch_name),
554
"": ("branch", branch_name)}), "success")
555
client.add_success_response(
556
'ok', '', 'no', 'no', 'no',
557
reference_bzrdir_format.repository_format.network_name())
558
client.add_error_response('NotStacked')
559
client.add_success_response(
560
'ok', '', 'no', 'no', 'no',
561
reference_bzrdir_format.repository_format.network_name())
562
client.add_error_response('NotStacked')
563
transport.mkdir('quack')
564
transport = transport.clone('quack')
565
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
567
result = a_bzrdir.get_branches()
568
self.assertEquals(["", "foo"], result.keys())
570
[('call_expecting_body', 'BzrDir.get_branches', ('quack/',)),
571
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
572
('call', 'Branch.get_stacked_on_url', ('quack/', )),
573
('call', 'BzrDir.find_repositoryV3', ('quack/', )),
574
('call', 'Branch.get_stacked_on_url', ('quack/', ))],
578
class TestBzrDirDestroyBranch(TestRemote):
580
def test_destroy_default(self):
581
transport = self.get_transport('quack')
582
referenced = self.make_branch('referenced')
583
client = FakeClient(transport.base)
584
client.add_expected_call(
585
'BzrDir.destroy_branch', ('quack/', ),
587
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
589
a_bzrdir.destroy_branch()
590
self.assertFinished(client)
593
class TestBzrDirHasWorkingTree(TestRemote):
595
def test_has_workingtree(self):
596
transport = self.get_transport('quack')
597
client = FakeClient(transport.base)
598
client.add_expected_call(
599
'BzrDir.has_workingtree', ('quack/',),
600
'success', ('yes',)),
601
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
603
self.assertTrue(a_bzrdir.has_workingtree())
604
self.assertFinished(client)
606
def test_no_workingtree(self):
607
transport = self.get_transport('quack')
608
client = FakeClient(transport.base)
609
client.add_expected_call(
610
'BzrDir.has_workingtree', ('quack/',),
612
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
614
self.assertFalse(a_bzrdir.has_workingtree())
615
self.assertFinished(client)
618
class TestBzrDirDestroyRepository(TestRemote):
620
def test_destroy_repository(self):
621
transport = self.get_transport('quack')
622
client = FakeClient(transport.base)
623
client.add_expected_call(
624
'BzrDir.destroy_repository', ('quack/',),
626
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
628
a_bzrdir.destroy_repository()
629
self.assertFinished(client)
632
class TestBzrDirOpen(TestRemote):
634
def make_fake_client_and_transport(self, path='quack'):
635
transport = MemoryTransport()
636
transport.mkdir(path)
637
transport = transport.clone(path)
638
client = FakeClient(transport.base)
639
return client, transport
641
def test_absent(self):
642
client, transport = self.make_fake_client_and_transport()
643
client.add_expected_call(
644
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
645
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
646
RemoteBzrDirFormat(), _client=client, _force_probe=True)
647
self.assertFinished(client)
649
def test_present_without_workingtree(self):
650
client, transport = self.make_fake_client_and_transport()
651
client.add_expected_call(
652
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
653
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
654
_client=client, _force_probe=True)
655
self.assertIsInstance(bd, RemoteBzrDir)
656
self.assertFalse(bd.has_workingtree())
657
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
658
self.assertFinished(client)
660
def test_present_with_workingtree(self):
661
client, transport = self.make_fake_client_and_transport()
662
client.add_expected_call(
663
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
664
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
665
_client=client, _force_probe=True)
666
self.assertIsInstance(bd, RemoteBzrDir)
667
self.assertTrue(bd.has_workingtree())
668
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
669
self.assertFinished(client)
671
def test_backwards_compat(self):
672
client, transport = self.make_fake_client_and_transport()
673
client.add_expected_call(
674
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
675
client.add_expected_call(
676
'BzrDir.open', ('quack/',), 'success', ('yes',))
677
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
678
_client=client, _force_probe=True)
679
self.assertIsInstance(bd, RemoteBzrDir)
680
self.assertFinished(client)
682
def test_backwards_compat_hpss_v2(self):
683
client, transport = self.make_fake_client_and_transport()
684
# Monkey-patch fake client to simulate real-world behaviour with v2
685
# server: upon first RPC call detect the protocol version, and because
686
# the version is 2 also do _remember_remote_is_before((1, 6)) before
687
# continuing with the RPC.
688
orig_check_call = client._check_call
689
def check_call(method, args):
690
client._medium._protocol_version = 2
691
client._medium._remember_remote_is_before((1, 6))
692
client._check_call = orig_check_call
693
client._check_call(method, args)
694
client._check_call = check_call
695
client.add_expected_call(
696
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
697
client.add_expected_call(
698
'BzrDir.open', ('quack/',), 'success', ('yes',))
699
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
700
_client=client, _force_probe=True)
701
self.assertIsInstance(bd, RemoteBzrDir)
702
self.assertFinished(client)
705
class TestBzrDirOpenBranch(TestRemote):
707
def test_backwards_compat(self):
708
self.setup_smart_server_with_call_log()
709
self.make_branch('.')
710
a_dir = BzrDir.open(self.get_url('.'))
711
self.reset_smart_call_log()
712
verb = 'BzrDir.open_branchV3'
713
self.disable_verb(verb)
714
format = a_dir.open_branch()
715
call_count = len([call for call in self.hpss_calls if
716
call.call.method == verb])
717
self.assertEqual(1, call_count)
719
def test_branch_present(self):
720
reference_format = self.get_repo_format()
721
network_name = reference_format.network_name()
722
branch_network_name = self.get_branch_format().network_name()
723
transport = MemoryTransport()
724
transport.mkdir('quack')
725
transport = transport.clone('quack')
726
client = FakeClient(transport.base)
727
client.add_expected_call(
728
'BzrDir.open_branchV3', ('quack/',),
729
'success', ('branch', branch_network_name))
730
client.add_expected_call(
731
'BzrDir.find_repositoryV3', ('quack/',),
732
'success', ('ok', '', 'no', 'no', 'no', network_name))
733
client.add_expected_call(
734
'Branch.get_stacked_on_url', ('quack/',),
735
'error', ('NotStacked',))
736
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
738
result = bzrdir.open_branch()
739
self.assertIsInstance(result, RemoteBranch)
740
self.assertEqual(bzrdir, result.bzrdir)
741
self.assertFinished(client)
743
def test_branch_missing(self):
744
transport = MemoryTransport()
745
transport.mkdir('quack')
746
transport = transport.clone('quack')
747
client = FakeClient(transport.base)
748
client.add_error_response('nobranch')
749
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
751
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
753
[('call', 'BzrDir.open_branchV3', ('quack/',))],
756
def test__get_tree_branch(self):
757
# _get_tree_branch is a form of open_branch, but it should only ask for
758
# branch opening, not any other network requests.
760
def open_branch(name=None, possible_transports=None):
761
calls.append("Called")
763
transport = MemoryTransport()
764
# no requests on the network - catches other api calls being made.
765
client = FakeClient(transport.base)
766
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
768
# patch the open_branch call to record that it was called.
769
bzrdir.open_branch = open_branch
770
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
771
self.assertEqual(["Called"], calls)
772
self.assertEqual([], client._calls)
774
def test_url_quoting_of_path(self):
775
# Relpaths on the wire should not be URL-escaped. So "~" should be
776
# transmitted as "~", not "%7E".
777
transport = RemoteTCPTransport('bzr://localhost/~hello/')
778
client = FakeClient(transport.base)
779
reference_format = self.get_repo_format()
780
network_name = reference_format.network_name()
781
branch_network_name = self.get_branch_format().network_name()
782
client.add_expected_call(
783
'BzrDir.open_branchV3', ('~hello/',),
784
'success', ('branch', branch_network_name))
785
client.add_expected_call(
786
'BzrDir.find_repositoryV3', ('~hello/',),
787
'success', ('ok', '', 'no', 'no', 'no', network_name))
788
client.add_expected_call(
789
'Branch.get_stacked_on_url', ('~hello/',),
790
'error', ('NotStacked',))
791
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
793
result = bzrdir.open_branch()
794
self.assertFinished(client)
796
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
797
reference_format = self.get_repo_format()
798
network_name = reference_format.network_name()
799
transport = MemoryTransport()
800
transport.mkdir('quack')
801
transport = transport.clone('quack')
803
rich_response = 'yes'
807
subtree_response = 'yes'
809
subtree_response = 'no'
810
client = FakeClient(transport.base)
811
client.add_success_response(
812
'ok', '', rich_response, subtree_response, external_lookup,
814
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
816
result = bzrdir.open_repository()
818
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
820
self.assertIsInstance(result, RemoteRepository)
821
self.assertEqual(bzrdir, result.bzrdir)
822
self.assertEqual(rich_root, result._format.rich_root_data)
823
self.assertEqual(subtrees, result._format.supports_tree_reference)
825
def test_open_repository_sets_format_attributes(self):
826
self.check_open_repository(True, True)
827
self.check_open_repository(False, True)
828
self.check_open_repository(True, False)
829
self.check_open_repository(False, False)
830
self.check_open_repository(False, False, 'yes')
832
def test_old_server(self):
833
"""RemoteBzrDirFormat should fail to probe if the server version is too
836
self.assertRaises(errors.NotBranchError,
837
RemoteBzrProber.probe_transport, OldServerTransport())
840
class TestBzrDirCreateBranch(TestRemote):
842
def test_backwards_compat(self):
843
self.setup_smart_server_with_call_log()
844
repo = self.make_repository('.')
845
self.reset_smart_call_log()
846
self.disable_verb('BzrDir.create_branch')
847
branch = repo.bzrdir.create_branch()
848
create_branch_call_count = len([call for call in self.hpss_calls if
849
call.call.method == 'BzrDir.create_branch'])
850
self.assertEqual(1, create_branch_call_count)
852
def test_current_server(self):
853
transport = self.get_transport('.')
854
transport = transport.clone('quack')
855
self.make_repository('quack')
856
client = FakeClient(transport.base)
857
reference_bzrdir_format = bzrdir.format_registry.get('default')()
858
reference_format = reference_bzrdir_format.get_branch_format()
859
network_name = reference_format.network_name()
860
reference_repo_fmt = reference_bzrdir_format.repository_format
861
reference_repo_name = reference_repo_fmt.network_name()
862
client.add_expected_call(
863
'BzrDir.create_branch', ('quack/', network_name),
864
'success', ('ok', network_name, '', 'no', 'no', 'yes',
865
reference_repo_name))
866
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
868
branch = a_bzrdir.create_branch()
869
# We should have got a remote branch
870
self.assertIsInstance(branch, remote.RemoteBranch)
871
# its format should have the settings from the response
872
format = branch._format
873
self.assertEqual(network_name, format.network_name())
875
def test_already_open_repo_and_reused_medium(self):
876
"""Bug 726584: create_branch(..., repository=repo) should work
877
regardless of what the smart medium's base URL is.
879
self.transport_server = test_server.SmartTCPServer_for_testing
880
transport = self.get_transport('.')
881
repo = self.make_repository('quack')
882
# Client's medium rooted a transport root (not at the bzrdir)
883
client = FakeClient(transport.base)
884
transport = transport.clone('quack')
885
reference_bzrdir_format = bzrdir.format_registry.get('default')()
886
reference_format = reference_bzrdir_format.get_branch_format()
887
network_name = reference_format.network_name()
888
reference_repo_fmt = reference_bzrdir_format.repository_format
889
reference_repo_name = reference_repo_fmt.network_name()
890
client.add_expected_call(
891
'BzrDir.create_branch', ('extra/quack/', network_name),
892
'success', ('ok', network_name, '', 'no', 'no', 'yes',
893
reference_repo_name))
894
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
896
branch = a_bzrdir.create_branch(repository=repo)
897
# We should have got a remote branch
898
self.assertIsInstance(branch, remote.RemoteBranch)
899
# its format should have the settings from the response
900
format = branch._format
901
self.assertEqual(network_name, format.network_name())
904
class TestBzrDirCreateRepository(TestRemote):
906
def test_backwards_compat(self):
907
self.setup_smart_server_with_call_log()
908
bzrdir = self.make_bzrdir('.')
909
self.reset_smart_call_log()
910
self.disable_verb('BzrDir.create_repository')
911
repo = bzrdir.create_repository()
912
create_repo_call_count = len([call for call in self.hpss_calls if
913
call.call.method == 'BzrDir.create_repository'])
914
self.assertEqual(1, create_repo_call_count)
916
def test_current_server(self):
917
transport = self.get_transport('.')
918
transport = transport.clone('quack')
919
self.make_bzrdir('quack')
920
client = FakeClient(transport.base)
921
reference_bzrdir_format = bzrdir.format_registry.get('default')()
922
reference_format = reference_bzrdir_format.repository_format
923
network_name = reference_format.network_name()
924
client.add_expected_call(
925
'BzrDir.create_repository', ('quack/',
926
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
928
'success', ('ok', 'yes', 'yes', 'yes', network_name))
929
a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
931
repo = a_bzrdir.create_repository()
932
# We should have got a remote repository
933
self.assertIsInstance(repo, remote.RemoteRepository)
934
# its format should have the settings from the response
935
format = repo._format
936
self.assertTrue(format.rich_root_data)
937
self.assertTrue(format.supports_tree_reference)
938
self.assertTrue(format.supports_external_lookups)
939
self.assertEqual(network_name, format.network_name())
942
class TestBzrDirOpenRepository(TestRemote):
944
def test_backwards_compat_1_2_3(self):
945
# fallback all the way to the first version.
946
reference_format = self.get_repo_format()
947
network_name = reference_format.network_name()
948
server_url = 'bzr://example.com/'
949
self.permit_url(server_url)
950
client = FakeClient(server_url)
951
client.add_unknown_method_response('BzrDir.find_repositoryV3')
952
client.add_unknown_method_response('BzrDir.find_repositoryV2')
953
client.add_success_response('ok', '', 'no', 'no')
954
# A real repository instance will be created to determine the network
956
client.add_success_response_with_body(
957
"Bazaar-NG meta directory, format 1\n", 'ok')
958
client.add_success_response('stat', '0', '65535')
959
client.add_success_response_with_body(
960
reference_format.get_format_string(), 'ok')
961
# PackRepository wants to do a stat
962
client.add_success_response('stat', '0', '65535')
963
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
965
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
967
repo = bzrdir.open_repository()
969
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
970
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
971
('call', 'BzrDir.find_repository', ('quack/',)),
972
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
973
('call', 'stat', ('/quack/.bzr',)),
974
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
975
('call', 'stat', ('/quack/.bzr/repository',)),
978
self.assertEqual(network_name, repo._format.network_name())
980
def test_backwards_compat_2(self):
981
# fallback to find_repositoryV2
982
reference_format = self.get_repo_format()
983
network_name = reference_format.network_name()
984
server_url = 'bzr://example.com/'
985
self.permit_url(server_url)
986
client = FakeClient(server_url)
987
client.add_unknown_method_response('BzrDir.find_repositoryV3')
988
client.add_success_response('ok', '', 'no', 'no', 'no')
989
# A real repository instance will be created to determine the network
991
client.add_success_response_with_body(
992
"Bazaar-NG meta directory, format 1\n", 'ok')
993
client.add_success_response('stat', '0', '65535')
994
client.add_success_response_with_body(
995
reference_format.get_format_string(), 'ok')
996
# PackRepository wants to do a stat
997
client.add_success_response('stat', '0', '65535')
998
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
1000
bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
1002
repo = bzrdir.open_repository()
1004
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
1005
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
1006
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
1007
('call', 'stat', ('/quack/.bzr',)),
1008
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
1009
('call', 'stat', ('/quack/.bzr/repository',)),
1012
self.assertEqual(network_name, repo._format.network_name())
1014
def test_current_server(self):
1015
reference_format = self.get_repo_format()
1016
network_name = reference_format.network_name()
1017
transport = MemoryTransport()
1018
transport.mkdir('quack')
1019
transport = transport.clone('quack')
1020
client = FakeClient(transport.base)
1021
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
1022
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1024
repo = bzrdir.open_repository()
1026
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
1028
self.assertEqual(network_name, repo._format.network_name())
1031
class TestBzrDirFormatInitializeEx(TestRemote):
1033
def test_success(self):
1034
"""Simple test for typical successful call."""
1035
fmt = RemoteBzrDirFormat()
1036
default_format_name = BzrDirFormat.get_default_format().network_name()
1037
transport = self.get_transport()
1038
client = FakeClient(transport.base)
1039
client.add_expected_call(
1040
'BzrDirFormat.initialize_ex_1.16',
1041
(default_format_name, 'path', 'False', 'False', 'False', '',
1042
'', '', '', 'False'),
1044
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
1045
'bzrdir fmt', 'False', '', '', 'repo lock token'))
1046
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1047
# it's currently hard to test that without supplying a real remote
1048
# transport connected to a real server.
1049
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
1050
transport, False, False, False, None, None, None, None, False)
1051
self.assertFinished(client)
1053
def test_error(self):
1054
"""Error responses are translated, e.g. 'PermissionDenied' raises the
1055
corresponding error from the client.
1057
fmt = RemoteBzrDirFormat()
1058
default_format_name = BzrDirFormat.get_default_format().network_name()
1059
transport = self.get_transport()
1060
client = FakeClient(transport.base)
1061
client.add_expected_call(
1062
'BzrDirFormat.initialize_ex_1.16',
1063
(default_format_name, 'path', 'False', 'False', 'False', '',
1064
'', '', '', 'False'),
1066
('PermissionDenied', 'path', 'extra info'))
1067
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
1068
# it's currently hard to test that without supplying a real remote
1069
# transport connected to a real server.
1070
err = self.assertRaises(errors.PermissionDenied,
1071
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
1072
False, False, False, None, None, None, None, False)
1073
self.assertEqual('path', err.path)
1074
self.assertEqual(': extra info', err.extra)
1075
self.assertFinished(client)
1077
def test_error_from_real_server(self):
1078
"""Integration test for error translation."""
1079
transport = self.make_smart_server('foo')
1080
transport = transport.clone('no-such-path')
1081
fmt = RemoteBzrDirFormat()
1082
err = self.assertRaises(errors.NoSuchFile,
1083
fmt.initialize_on_transport_ex, transport, create_prefix=False)
1086
class OldSmartClient(object):
1087
"""A fake smart client for test_old_version that just returns a version one
1088
response to the 'hello' (query version) command.
1091
def get_request(self):
1092
input_file = StringIO('ok\x011\n')
1093
output_file = StringIO()
1094
client_medium = medium.SmartSimplePipesClientMedium(
1095
input_file, output_file)
1096
return medium.SmartClientStreamMediumRequest(client_medium)
1098
def protocol_version(self):
1102
class OldServerTransport(object):
1103
"""A fake transport for test_old_server that reports it's smart server
1104
protocol version as version one.
1110
def get_smart_client(self):
1111
return OldSmartClient()
1114
class RemoteBzrDirTestCase(TestRemote):
1116
def make_remote_bzrdir(self, transport, client):
1117
"""Make a RemotebzrDir using 'client' as the _client."""
1118
return RemoteBzrDir(transport, RemoteBzrDirFormat(),
1122
class RemoteBranchTestCase(RemoteBzrDirTestCase):
1124
def lock_remote_branch(self, branch):
1125
"""Trick a RemoteBranch into thinking it is locked."""
1126
branch._lock_mode = 'w'
1127
branch._lock_count = 2
1128
branch._lock_token = 'branch token'
1129
branch._repo_lock_token = 'repo token'
1130
branch.repository._lock_mode = 'w'
1131
branch.repository._lock_count = 2
1132
branch.repository._lock_token = 'repo token'
1134
def make_remote_branch(self, transport, client):
1135
"""Make a RemoteBranch using 'client' as its _SmartClient.
1137
A RemoteBzrDir and RemoteRepository will also be created to fill out
1138
the RemoteBranch, albeit with stub values for some of their attributes.
1140
# we do not want bzrdir to make any remote calls, so use False as its
1141
# _client. If it tries to make a remote call, this will fail
1143
bzrdir = self.make_remote_bzrdir(transport, False)
1144
repo = RemoteRepository(bzrdir, None, _client=client)
1145
branch_format = self.get_branch_format()
1146
format = RemoteBranchFormat(network_name=branch_format.network_name())
1147
return RemoteBranch(bzrdir, repo, _client=client, format=format)
1150
class TestBranchBreakLock(RemoteBranchTestCase):
1152
def test_break_lock(self):
1153
transport_path = 'quack'
1154
transport = MemoryTransport()
1155
client = FakeClient(transport.base)
1156
client.add_expected_call(
1157
'Branch.get_stacked_on_url', ('quack/',),
1158
'error', ('NotStacked',))
1159
client.add_expected_call(
1160
'Branch.break_lock', ('quack/',),
1162
transport.mkdir('quack')
1163
transport = transport.clone('quack')
1164
branch = self.make_remote_branch(transport, client)
1166
self.assertFinished(client)
1169
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
1171
def test_get_physical_lock_status_yes(self):
1172
transport = MemoryTransport()
1173
client = FakeClient(transport.base)
1174
client.add_expected_call(
1175
'Branch.get_stacked_on_url', ('quack/',),
1176
'error', ('NotStacked',))
1177
client.add_expected_call(
1178
'Branch.get_physical_lock_status', ('quack/',),
1179
'success', ('yes',))
1180
transport.mkdir('quack')
1181
transport = transport.clone('quack')
1182
branch = self.make_remote_branch(transport, client)
1183
result = branch.get_physical_lock_status()
1184
self.assertFinished(client)
1185
self.assertEqual(True, result)
1187
def test_get_physical_lock_status_no(self):
1188
transport = MemoryTransport()
1189
client = FakeClient(transport.base)
1190
client.add_expected_call(
1191
'Branch.get_stacked_on_url', ('quack/',),
1192
'error', ('NotStacked',))
1193
client.add_expected_call(
1194
'Branch.get_physical_lock_status', ('quack/',),
1196
transport.mkdir('quack')
1197
transport = transport.clone('quack')
1198
branch = self.make_remote_branch(transport, client)
1199
result = branch.get_physical_lock_status()
1200
self.assertFinished(client)
1201
self.assertEqual(False, result)
1204
class TestBranchGetParent(RemoteBranchTestCase):
1206
def test_no_parent(self):
1207
# in an empty branch we decode the response properly
1208
transport = MemoryTransport()
1209
client = FakeClient(transport.base)
1210
client.add_expected_call(
1211
'Branch.get_stacked_on_url', ('quack/',),
1212
'error', ('NotStacked',))
1213
client.add_expected_call(
1214
'Branch.get_parent', ('quack/',),
1216
transport.mkdir('quack')
1217
transport = transport.clone('quack')
1218
branch = self.make_remote_branch(transport, client)
1219
result = branch.get_parent()
1220
self.assertFinished(client)
1221
self.assertEqual(None, result)
1223
def test_parent_relative(self):
1224
transport = MemoryTransport()
1225
client = FakeClient(transport.base)
1226
client.add_expected_call(
1227
'Branch.get_stacked_on_url', ('kwaak/',),
1228
'error', ('NotStacked',))
1229
client.add_expected_call(
1230
'Branch.get_parent', ('kwaak/',),
1231
'success', ('../foo/',))
1232
transport.mkdir('kwaak')
1233
transport = transport.clone('kwaak')
1234
branch = self.make_remote_branch(transport, client)
1235
result = branch.get_parent()
1236
self.assertEqual(transport.clone('../foo').base, result)
1238
def test_parent_absolute(self):
1239
transport = MemoryTransport()
1240
client = FakeClient(transport.base)
1241
client.add_expected_call(
1242
'Branch.get_stacked_on_url', ('kwaak/',),
1243
'error', ('NotStacked',))
1244
client.add_expected_call(
1245
'Branch.get_parent', ('kwaak/',),
1246
'success', ('http://foo/',))
1247
transport.mkdir('kwaak')
1248
transport = transport.clone('kwaak')
1249
branch = self.make_remote_branch(transport, client)
1250
result = branch.get_parent()
1251
self.assertEqual('http://foo/', result)
1252
self.assertFinished(client)
1255
class TestBranchSetParentLocation(RemoteBranchTestCase):
1257
def test_no_parent(self):
1258
# We call the verb when setting parent to None
1259
transport = MemoryTransport()
1260
client = FakeClient(transport.base)
1261
client.add_expected_call(
1262
'Branch.get_stacked_on_url', ('quack/',),
1263
'error', ('NotStacked',))
1264
client.add_expected_call(
1265
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1267
transport.mkdir('quack')
1268
transport = transport.clone('quack')
1269
branch = self.make_remote_branch(transport, client)
1270
branch._lock_token = 'b'
1271
branch._repo_lock_token = 'r'
1272
branch._set_parent_location(None)
1273
self.assertFinished(client)
1275
def test_parent(self):
1276
transport = MemoryTransport()
1277
client = FakeClient(transport.base)
1278
client.add_expected_call(
1279
'Branch.get_stacked_on_url', ('kwaak/',),
1280
'error', ('NotStacked',))
1281
client.add_expected_call(
1282
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1284
transport.mkdir('kwaak')
1285
transport = transport.clone('kwaak')
1286
branch = self.make_remote_branch(transport, client)
1287
branch._lock_token = 'b'
1288
branch._repo_lock_token = 'r'
1289
branch._set_parent_location('foo')
1290
self.assertFinished(client)
1292
def test_backwards_compat(self):
1293
self.setup_smart_server_with_call_log()
1294
branch = self.make_branch('.')
1295
self.reset_smart_call_log()
1296
verb = 'Branch.set_parent_location'
1297
self.disable_verb(verb)
1298
branch.set_parent('http://foo/')
1299
self.assertLength(13, self.hpss_calls)
1302
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1304
def test_backwards_compat(self):
1305
self.setup_smart_server_with_call_log()
1306
branch = self.make_branch('.')
1307
self.reset_smart_call_log()
1308
verb = 'Branch.get_tags_bytes'
1309
self.disable_verb(verb)
1310
branch.tags.get_tag_dict()
1311
call_count = len([call for call in self.hpss_calls if
1312
call.call.method == verb])
1313
self.assertEqual(1, call_count)
1315
def test_trivial(self):
1316
transport = MemoryTransport()
1317
client = FakeClient(transport.base)
1318
client.add_expected_call(
1319
'Branch.get_stacked_on_url', ('quack/',),
1320
'error', ('NotStacked',))
1321
client.add_expected_call(
1322
'Branch.get_tags_bytes', ('quack/',),
1324
transport.mkdir('quack')
1325
transport = transport.clone('quack')
1326
branch = self.make_remote_branch(transport, client)
1327
result = branch.tags.get_tag_dict()
1328
self.assertFinished(client)
1329
self.assertEqual({}, result)
1332
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1334
def test_trivial(self):
1335
transport = MemoryTransport()
1336
client = FakeClient(transport.base)
1337
client.add_expected_call(
1338
'Branch.get_stacked_on_url', ('quack/',),
1339
'error', ('NotStacked',))
1340
client.add_expected_call(
1341
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1343
transport.mkdir('quack')
1344
transport = transport.clone('quack')
1345
branch = self.make_remote_branch(transport, client)
1346
self.lock_remote_branch(branch)
1347
branch._set_tags_bytes('tags bytes')
1348
self.assertFinished(client)
1349
self.assertEqual('tags bytes', client._calls[-1][-1])
1351
def test_backwards_compatible(self):
1352
transport = MemoryTransport()
1353
client = FakeClient(transport.base)
1354
client.add_expected_call(
1355
'Branch.get_stacked_on_url', ('quack/',),
1356
'error', ('NotStacked',))
1357
client.add_expected_call(
1358
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1359
'unknown', ('Branch.set_tags_bytes',))
1360
transport.mkdir('quack')
1361
transport = transport.clone('quack')
1362
branch = self.make_remote_branch(transport, client)
1363
self.lock_remote_branch(branch)
1364
class StubRealBranch(object):
1367
def _set_tags_bytes(self, bytes):
1368
self.calls.append(('set_tags_bytes', bytes))
1369
real_branch = StubRealBranch()
1370
branch._real_branch = real_branch
1371
branch._set_tags_bytes('tags bytes')
1372
# Call a second time, to exercise the 'remote version already inferred'
1374
branch._set_tags_bytes('tags bytes')
1375
self.assertFinished(client)
1377
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1380
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1382
def test_uses_last_revision_info_and_tags_by_default(self):
1383
transport = MemoryTransport()
1384
client = FakeClient(transport.base)
1385
client.add_expected_call(
1386
'Branch.get_stacked_on_url', ('quack/',),
1387
'error', ('NotStacked',))
1388
client.add_expected_call(
1389
'Branch.last_revision_info', ('quack/',),
1390
'success', ('ok', '1', 'rev-tip'))
1391
client.add_expected_call(
1392
'Branch.get_config_file', ('quack/',),
1393
'success', ('ok',), '')
1394
transport.mkdir('quack')
1395
transport = transport.clone('quack')
1396
branch = self.make_remote_branch(transport, client)
1397
result = branch.heads_to_fetch()
1398
self.assertFinished(client)
1399
self.assertEqual((set(['rev-tip']), set()), result)
1401
def test_uses_last_revision_info_and_tags_when_set(self):
1402
transport = MemoryTransport()
1403
client = FakeClient(transport.base)
1404
client.add_expected_call(
1405
'Branch.get_stacked_on_url', ('quack/',),
1406
'error', ('NotStacked',))
1407
client.add_expected_call(
1408
'Branch.last_revision_info', ('quack/',),
1409
'success', ('ok', '1', 'rev-tip'))
1410
client.add_expected_call(
1411
'Branch.get_config_file', ('quack/',),
1412
'success', ('ok',), 'branch.fetch_tags = True')
1413
# XXX: this will break if the default format's serialization of tags
1414
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1415
client.add_expected_call(
1416
'Branch.get_tags_bytes', ('quack/',),
1417
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1418
transport.mkdir('quack')
1419
transport = transport.clone('quack')
1420
branch = self.make_remote_branch(transport, client)
1421
result = branch.heads_to_fetch()
1422
self.assertFinished(client)
1424
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1426
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1427
transport = MemoryTransport()
1428
client = FakeClient(transport.base)
1429
client.add_expected_call(
1430
'Branch.get_stacked_on_url', ('quack/',),
1431
'error', ('NotStacked',))
1432
client.add_expected_call(
1433
'Branch.heads_to_fetch', ('quack/',),
1434
'success', (['tip'], ['tagged-1', 'tagged-2']))
1435
transport.mkdir('quack')
1436
transport = transport.clone('quack')
1437
branch = self.make_remote_branch(transport, client)
1438
branch._format._use_default_local_heads_to_fetch = lambda: False
1439
result = branch.heads_to_fetch()
1440
self.assertFinished(client)
1441
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1443
def make_branch_with_tags(self):
1444
self.setup_smart_server_with_call_log()
1445
# Make a branch with a single revision.
1446
builder = self.make_branch_builder('foo')
1447
builder.start_series()
1448
builder.build_snapshot('tip', None, [
1449
('add', ('', 'root-id', 'directory', ''))])
1450
builder.finish_series()
1451
branch = builder.get_branch()
1452
# Add two tags to that branch
1453
branch.tags.set_tag('tag-1', 'rev-1')
1454
branch.tags.set_tag('tag-2', 'rev-2')
1457
def test_backwards_compatible(self):
1458
branch = self.make_branch_with_tags()
1459
c = branch.get_config_stack()
1460
c.set('branch.fetch_tags', True)
1461
self.addCleanup(branch.lock_read().unlock)
1462
# Disable the heads_to_fetch verb
1463
verb = 'Branch.heads_to_fetch'
1464
self.disable_verb(verb)
1465
self.reset_smart_call_log()
1466
result = branch.heads_to_fetch()
1467
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1469
['Branch.last_revision_info', 'Branch.get_config_file',
1470
'Branch.get_tags_bytes'],
1471
[call.call.method for call in self.hpss_calls])
1473
def test_backwards_compatible_no_tags(self):
1474
branch = self.make_branch_with_tags()
1475
c = branch.get_config_stack()
1476
c.set('branch.fetch_tags', False)
1477
self.addCleanup(branch.lock_read().unlock)
1478
# Disable the heads_to_fetch verb
1479
verb = 'Branch.heads_to_fetch'
1480
self.disable_verb(verb)
1481
self.reset_smart_call_log()
1482
result = branch.heads_to_fetch()
1483
self.assertEqual((set(['tip']), set()), result)
1485
['Branch.last_revision_info', 'Branch.get_config_file'],
1486
[call.call.method for call in self.hpss_calls])
1489
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1491
def test_empty_branch(self):
1492
# in an empty branch we decode the response properly
1493
transport = MemoryTransport()
1494
client = FakeClient(transport.base)
1495
client.add_expected_call(
1496
'Branch.get_stacked_on_url', ('quack/',),
1497
'error', ('NotStacked',))
1498
client.add_expected_call(
1499
'Branch.last_revision_info', ('quack/',),
1500
'success', ('ok', '0', 'null:'))
1501
transport.mkdir('quack')
1502
transport = transport.clone('quack')
1503
branch = self.make_remote_branch(transport, client)
1504
result = branch.last_revision_info()
1505
self.assertFinished(client)
1506
self.assertEqual((0, NULL_REVISION), result)
1508
def test_non_empty_branch(self):
1509
# in a non-empty branch we also decode the response properly
1510
revid = u'\xc8'.encode('utf8')
1511
transport = MemoryTransport()
1512
client = FakeClient(transport.base)
1513
client.add_expected_call(
1514
'Branch.get_stacked_on_url', ('kwaak/',),
1515
'error', ('NotStacked',))
1516
client.add_expected_call(
1517
'Branch.last_revision_info', ('kwaak/',),
1518
'success', ('ok', '2', revid))
1519
transport.mkdir('kwaak')
1520
transport = transport.clone('kwaak')
1521
branch = self.make_remote_branch(transport, client)
1522
result = branch.last_revision_info()
1523
self.assertEqual((2, revid), result)
1526
class TestBranch_get_stacked_on_url(TestRemote):
1527
"""Test Branch._get_stacked_on_url rpc"""
1529
def test_get_stacked_on_invalid_url(self):
1530
# test that asking for a stacked on url the server can't access works.
1531
# This isn't perfect, but then as we're in the same process there
1532
# really isn't anything we can do to be 100% sure that the server
1533
# doesn't just open in - this test probably needs to be rewritten using
1534
# a spawn()ed server.
1535
stacked_branch = self.make_branch('stacked', format='1.9')
1536
memory_branch = self.make_branch('base', format='1.9')
1537
vfs_url = self.get_vfs_only_url('base')
1538
stacked_branch.set_stacked_on_url(vfs_url)
1539
transport = stacked_branch.bzrdir.root_transport
1540
client = FakeClient(transport.base)
1541
client.add_expected_call(
1542
'Branch.get_stacked_on_url', ('stacked/',),
1543
'success', ('ok', vfs_url))
1544
# XXX: Multiple calls are bad, this second call documents what is
1546
client.add_expected_call(
1547
'Branch.get_stacked_on_url', ('stacked/',),
1548
'success', ('ok', vfs_url))
1549
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
1551
repo_fmt = remote.RemoteRepositoryFormat()
1552
repo_fmt._custom_format = stacked_branch.repository._format
1553
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1555
result = branch.get_stacked_on_url()
1556
self.assertEqual(vfs_url, result)
1558
def test_backwards_compatible(self):
1559
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1560
base_branch = self.make_branch('base', format='1.6')
1561
stacked_branch = self.make_branch('stacked', format='1.6')
1562
stacked_branch.set_stacked_on_url('../base')
1563
client = FakeClient(self.get_url())
1564
branch_network_name = self.get_branch_format().network_name()
1565
client.add_expected_call(
1566
'BzrDir.open_branchV3', ('stacked/',),
1567
'success', ('branch', branch_network_name))
1568
client.add_expected_call(
1569
'BzrDir.find_repositoryV3', ('stacked/',),
1570
'success', ('ok', '', 'no', 'no', 'yes',
1571
stacked_branch.repository._format.network_name()))
1572
# called twice, once from constructor and then again by us
1573
client.add_expected_call(
1574
'Branch.get_stacked_on_url', ('stacked/',),
1575
'unknown', ('Branch.get_stacked_on_url',))
1576
client.add_expected_call(
1577
'Branch.get_stacked_on_url', ('stacked/',),
1578
'unknown', ('Branch.get_stacked_on_url',))
1579
# this will also do vfs access, but that goes direct to the transport
1580
# and isn't seen by the FakeClient.
1581
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1582
RemoteBzrDirFormat(), _client=client)
1583
branch = bzrdir.open_branch()
1584
result = branch.get_stacked_on_url()
1585
self.assertEqual('../base', result)
1586
self.assertFinished(client)
1587
# it's in the fallback list both for the RemoteRepository and its vfs
1589
self.assertEqual(1, len(branch.repository._fallback_repositories))
1591
len(branch.repository._real_repository._fallback_repositories))
1593
def test_get_stacked_on_real_branch(self):
1594
base_branch = self.make_branch('base')
1595
stacked_branch = self.make_branch('stacked')
1596
stacked_branch.set_stacked_on_url('../base')
1597
reference_format = self.get_repo_format()
1598
network_name = reference_format.network_name()
1599
client = FakeClient(self.get_url())
1600
branch_network_name = self.get_branch_format().network_name()
1601
client.add_expected_call(
1602
'BzrDir.open_branchV3', ('stacked/',),
1603
'success', ('branch', branch_network_name))
1604
client.add_expected_call(
1605
'BzrDir.find_repositoryV3', ('stacked/',),
1606
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1607
# called twice, once from constructor and then again by us
1608
client.add_expected_call(
1609
'Branch.get_stacked_on_url', ('stacked/',),
1610
'success', ('ok', '../base'))
1611
client.add_expected_call(
1612
'Branch.get_stacked_on_url', ('stacked/',),
1613
'success', ('ok', '../base'))
1614
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1615
RemoteBzrDirFormat(), _client=client)
1616
branch = bzrdir.open_branch()
1617
result = branch.get_stacked_on_url()
1618
self.assertEqual('../base', result)
1619
self.assertFinished(client)
1620
# it's in the fallback list both for the RemoteRepository.
1621
self.assertEqual(1, len(branch.repository._fallback_repositories))
1622
# And we haven't had to construct a real repository.
1623
self.assertEqual(None, branch.repository._real_repository)
1626
class TestBranchSetLastRevision(RemoteBranchTestCase):
1628
def test_set_empty(self):
1629
# _set_last_revision_info('null:') is translated to calling
1630
# Branch.set_last_revision(path, '') on the wire.
1631
transport = MemoryTransport()
1632
transport.mkdir('branch')
1633
transport = transport.clone('branch')
1635
client = FakeClient(transport.base)
1636
client.add_expected_call(
1637
'Branch.get_stacked_on_url', ('branch/',),
1638
'error', ('NotStacked',))
1639
client.add_expected_call(
1640
'Branch.lock_write', ('branch/', '', ''),
1641
'success', ('ok', 'branch token', 'repo token'))
1642
client.add_expected_call(
1643
'Branch.last_revision_info',
1645
'success', ('ok', '0', 'null:'))
1646
client.add_expected_call(
1647
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1649
client.add_expected_call(
1650
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1652
branch = self.make_remote_branch(transport, client)
1654
result = branch._set_last_revision(NULL_REVISION)
1656
self.assertEqual(None, result)
1657
self.assertFinished(client)
1659
def test_set_nonempty(self):
1660
# set_last_revision_info(N, rev-idN) is translated to calling
1661
# Branch.set_last_revision(path, rev-idN) on the wire.
1662
transport = MemoryTransport()
1663
transport.mkdir('branch')
1664
transport = transport.clone('branch')
1666
client = FakeClient(transport.base)
1667
client.add_expected_call(
1668
'Branch.get_stacked_on_url', ('branch/',),
1669
'error', ('NotStacked',))
1670
client.add_expected_call(
1671
'Branch.lock_write', ('branch/', '', ''),
1672
'success', ('ok', 'branch token', 'repo token'))
1673
client.add_expected_call(
1674
'Branch.last_revision_info',
1676
'success', ('ok', '0', 'null:'))
1678
encoded_body = bz2.compress('\n'.join(lines))
1679
client.add_success_response_with_body(encoded_body, 'ok')
1680
client.add_expected_call(
1681
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1683
client.add_expected_call(
1684
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1686
branch = self.make_remote_branch(transport, client)
1687
# Lock the branch, reset the record of remote calls.
1689
result = branch._set_last_revision('rev-id2')
1691
self.assertEqual(None, result)
1692
self.assertFinished(client)
1694
def test_no_such_revision(self):
1695
transport = MemoryTransport()
1696
transport.mkdir('branch')
1697
transport = transport.clone('branch')
1698
# A response of 'NoSuchRevision' is translated into an exception.
1699
client = FakeClient(transport.base)
1700
client.add_expected_call(
1701
'Branch.get_stacked_on_url', ('branch/',),
1702
'error', ('NotStacked',))
1703
client.add_expected_call(
1704
'Branch.lock_write', ('branch/', '', ''),
1705
'success', ('ok', 'branch token', 'repo token'))
1706
client.add_expected_call(
1707
'Branch.last_revision_info',
1709
'success', ('ok', '0', 'null:'))
1710
# get_graph calls to construct the revision history, for the set_rh
1713
encoded_body = bz2.compress('\n'.join(lines))
1714
client.add_success_response_with_body(encoded_body, 'ok')
1715
client.add_expected_call(
1716
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1717
'error', ('NoSuchRevision', 'rev-id'))
1718
client.add_expected_call(
1719
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1722
branch = self.make_remote_branch(transport, client)
1725
errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
1727
self.assertFinished(client)
1729
def test_tip_change_rejected(self):
1730
"""TipChangeRejected responses cause a TipChangeRejected exception to
1733
transport = MemoryTransport()
1734
transport.mkdir('branch')
1735
transport = transport.clone('branch')
1736
client = FakeClient(transport.base)
1737
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1738
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1739
client.add_expected_call(
1740
'Branch.get_stacked_on_url', ('branch/',),
1741
'error', ('NotStacked',))
1742
client.add_expected_call(
1743
'Branch.lock_write', ('branch/', '', ''),
1744
'success', ('ok', 'branch token', 'repo token'))
1745
client.add_expected_call(
1746
'Branch.last_revision_info',
1748
'success', ('ok', '0', 'null:'))
1750
encoded_body = bz2.compress('\n'.join(lines))
1751
client.add_success_response_with_body(encoded_body, 'ok')
1752
client.add_expected_call(
1753
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1754
'error', ('TipChangeRejected', rejection_msg_utf8))
1755
client.add_expected_call(
1756
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1758
branch = self.make_remote_branch(transport, client)
1760
# The 'TipChangeRejected' error response triggered by calling
1761
# set_last_revision_info causes a TipChangeRejected exception.
1762
err = self.assertRaises(
1763
errors.TipChangeRejected,
1764
branch._set_last_revision, 'rev-id')
1765
# The UTF-8 message from the response has been decoded into a unicode
1767
self.assertIsInstance(err.msg, unicode)
1768
self.assertEqual(rejection_msg_unicode, err.msg)
1770
self.assertFinished(client)
1773
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1775
def test_set_last_revision_info(self):
1776
# set_last_revision_info(num, 'rev-id') is translated to calling
1777
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1778
transport = MemoryTransport()
1779
transport.mkdir('branch')
1780
transport = transport.clone('branch')
1781
client = FakeClient(transport.base)
1782
# get_stacked_on_url
1783
client.add_error_response('NotStacked')
1785
client.add_success_response('ok', 'branch token', 'repo token')
1786
# query the current revision
1787
client.add_success_response('ok', '0', 'null:')
1789
client.add_success_response('ok')
1791
client.add_success_response('ok')
1793
branch = self.make_remote_branch(transport, client)
1794
# Lock the branch, reset the record of remote calls.
1797
result = branch.set_last_revision_info(1234, 'a-revision-id')
1799
[('call', 'Branch.last_revision_info', ('branch/',)),
1800
('call', 'Branch.set_last_revision_info',
1801
('branch/', 'branch token', 'repo token',
1802
'1234', 'a-revision-id'))],
1804
self.assertEqual(None, result)
1806
def test_no_such_revision(self):
1807
# A response of 'NoSuchRevision' is translated into an exception.
1808
transport = MemoryTransport()
1809
transport.mkdir('branch')
1810
transport = transport.clone('branch')
1811
client = FakeClient(transport.base)
1812
# get_stacked_on_url
1813
client.add_error_response('NotStacked')
1815
client.add_success_response('ok', 'branch token', 'repo token')
1817
client.add_error_response('NoSuchRevision', 'revid')
1819
client.add_success_response('ok')
1821
branch = self.make_remote_branch(transport, client)
1822
# Lock the branch, reset the record of remote calls.
1827
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1830
def test_backwards_compatibility(self):
1831
"""If the server does not support the Branch.set_last_revision_info
1832
verb (which is new in 1.4), then the client falls back to VFS methods.
1834
# This test is a little messy. Unlike most tests in this file, it
1835
# doesn't purely test what a Remote* object sends over the wire, and
1836
# how it reacts to responses from the wire. It instead relies partly
1837
# on asserting that the RemoteBranch will call
1838
# self._real_branch.set_last_revision_info(...).
1840
# First, set up our RemoteBranch with a FakeClient that raises
1841
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1842
transport = MemoryTransport()
1843
transport.mkdir('branch')
1844
transport = transport.clone('branch')
1845
client = FakeClient(transport.base)
1846
client.add_expected_call(
1847
'Branch.get_stacked_on_url', ('branch/',),
1848
'error', ('NotStacked',))
1849
client.add_expected_call(
1850
'Branch.last_revision_info',
1852
'success', ('ok', '0', 'null:'))
1853
client.add_expected_call(
1854
'Branch.set_last_revision_info',
1855
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1856
'unknown', 'Branch.set_last_revision_info')
1858
branch = self.make_remote_branch(transport, client)
1859
class StubRealBranch(object):
1862
def set_last_revision_info(self, revno, revision_id):
1864
('set_last_revision_info', revno, revision_id))
1865
def _clear_cached_state(self):
1867
real_branch = StubRealBranch()
1868
branch._real_branch = real_branch
1869
self.lock_remote_branch(branch)
1871
# Call set_last_revision_info, and verify it behaved as expected.
1872
result = branch.set_last_revision_info(1234, 'a-revision-id')
1874
[('set_last_revision_info', 1234, 'a-revision-id')],
1876
self.assertFinished(client)
1878
def test_unexpected_error(self):
1879
# If the server sends an error the client doesn't understand, it gets
1880
# turned into an UnknownErrorFromSmartServer, which is presented as a
1881
# non-internal error to the user.
1882
transport = MemoryTransport()
1883
transport.mkdir('branch')
1884
transport = transport.clone('branch')
1885
client = FakeClient(transport.base)
1886
# get_stacked_on_url
1887
client.add_error_response('NotStacked')
1889
client.add_success_response('ok', 'branch token', 'repo token')
1891
client.add_error_response('UnexpectedError')
1893
client.add_success_response('ok')
1895
branch = self.make_remote_branch(transport, client)
1896
# Lock the branch, reset the record of remote calls.
1900
err = self.assertRaises(
1901
errors.UnknownErrorFromSmartServer,
1902
branch.set_last_revision_info, 123, 'revid')
1903
self.assertEqual(('UnexpectedError',), err.error_tuple)
1906
def test_tip_change_rejected(self):
1907
"""TipChangeRejected responses cause a TipChangeRejected exception to
1910
transport = MemoryTransport()
1911
transport.mkdir('branch')
1912
transport = transport.clone('branch')
1913
client = FakeClient(transport.base)
1914
# get_stacked_on_url
1915
client.add_error_response('NotStacked')
1917
client.add_success_response('ok', 'branch token', 'repo token')
1919
client.add_error_response('TipChangeRejected', 'rejection message')
1921
client.add_success_response('ok')
1923
branch = self.make_remote_branch(transport, client)
1924
# Lock the branch, reset the record of remote calls.
1926
self.addCleanup(branch.unlock)
1929
# The 'TipChangeRejected' error response triggered by calling
1930
# set_last_revision_info causes a TipChangeRejected exception.
1931
err = self.assertRaises(
1932
errors.TipChangeRejected,
1933
branch.set_last_revision_info, 123, 'revid')
1934
self.assertEqual('rejection message', err.msg)
1937
class TestBranchGetSetConfig(RemoteBranchTestCase):
1939
def test_get_branch_conf(self):
1940
# in an empty branch we decode the response properly
1941
client = FakeClient()
1942
client.add_expected_call(
1943
'Branch.get_stacked_on_url', ('memory:///',),
1944
'error', ('NotStacked',),)
1945
client.add_success_response_with_body('# config file body', 'ok')
1946
transport = MemoryTransport()
1947
branch = self.make_remote_branch(transport, client)
1948
config = branch.get_config()
1949
config.has_explicit_nickname()
1951
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1952
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1955
def test_get_multi_line_branch_conf(self):
1956
# Make sure that multiple-line branch.conf files are supported
1958
# https://bugs.launchpad.net/bzr/+bug/354075
1959
client = FakeClient()
1960
client.add_expected_call(
1961
'Branch.get_stacked_on_url', ('memory:///',),
1962
'error', ('NotStacked',),)
1963
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1964
transport = MemoryTransport()
1965
branch = self.make_remote_branch(transport, client)
1966
config = branch.get_config()
1967
self.assertEqual(u'2', config.get_user_option('b'))
1969
def test_set_option(self):
1970
client = FakeClient()
1971
client.add_expected_call(
1972
'Branch.get_stacked_on_url', ('memory:///',),
1973
'error', ('NotStacked',),)
1974
client.add_expected_call(
1975
'Branch.lock_write', ('memory:///', '', ''),
1976
'success', ('ok', 'branch token', 'repo token'))
1977
client.add_expected_call(
1978
'Branch.set_config_option', ('memory:///', 'branch token',
1979
'repo token', 'foo', 'bar', ''),
1981
client.add_expected_call(
1982
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1984
transport = MemoryTransport()
1985
branch = self.make_remote_branch(transport, client)
1987
config = branch._get_config()
1988
config.set_option('foo', 'bar')
1990
self.assertFinished(client)
1992
def test_set_option_with_dict(self):
1993
client = FakeClient()
1994
client.add_expected_call(
1995
'Branch.get_stacked_on_url', ('memory:///',),
1996
'error', ('NotStacked',),)
1997
client.add_expected_call(
1998
'Branch.lock_write', ('memory:///', '', ''),
1999
'success', ('ok', 'branch token', 'repo token'))
2000
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
2001
client.add_expected_call(
2002
'Branch.set_config_option_dict', ('memory:///', 'branch token',
2003
'repo token', encoded_dict_value, 'foo', ''),
2005
client.add_expected_call(
2006
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2008
transport = MemoryTransport()
2009
branch = self.make_remote_branch(transport, client)
2011
config = branch._get_config()
2013
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
2016
self.assertFinished(client)
2018
def test_backwards_compat_set_option(self):
2019
self.setup_smart_server_with_call_log()
2020
branch = self.make_branch('.')
2021
verb = 'Branch.set_config_option'
2022
self.disable_verb(verb)
2024
self.addCleanup(branch.unlock)
2025
self.reset_smart_call_log()
2026
branch._get_config().set_option('value', 'name')
2027
self.assertLength(11, self.hpss_calls)
2028
self.assertEqual('value', branch._get_config().get_option('name'))
2030
def test_backwards_compat_set_option_with_dict(self):
2031
self.setup_smart_server_with_call_log()
2032
branch = self.make_branch('.')
2033
verb = 'Branch.set_config_option_dict'
2034
self.disable_verb(verb)
2036
self.addCleanup(branch.unlock)
2037
self.reset_smart_call_log()
2038
config = branch._get_config()
2039
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
2040
config.set_option(value_dict, 'name')
2041
self.assertLength(11, self.hpss_calls)
2042
self.assertEqual(value_dict, branch._get_config().get_option('name'))
2045
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
2047
def test_get_branch_conf(self):
2048
# in an empty branch we decode the response properly
2049
client = FakeClient()
2050
client.add_expected_call(
2051
'Branch.get_stacked_on_url', ('memory:///',),
2052
'error', ('NotStacked',),)
2053
client.add_success_response_with_body('# config file body', 'ok')
2054
transport = MemoryTransport()
2055
branch = self.make_remote_branch(transport, client)
2056
config = branch.get_config_stack()
2058
config.get("log_format")
2060
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2061
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
2064
def test_set_branch_conf(self):
2065
client = FakeClient()
2066
client.add_expected_call(
2067
'Branch.get_stacked_on_url', ('memory:///',),
2068
'error', ('NotStacked',),)
2069
client.add_expected_call(
2070
'Branch.lock_write', ('memory:///', '', ''),
2071
'success', ('ok', 'branch token', 'repo token'))
2072
client.add_expected_call(
2073
'Branch.get_config_file', ('memory:///', ),
2074
'success', ('ok', ), "# line 1\n")
2075
client.add_expected_call(
2076
'Branch.put_config_file', ('memory:///', 'branch token',
2079
client.add_expected_call(
2080
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
2082
transport = MemoryTransport()
2083
branch = self.make_remote_branch(transport, client)
2085
config = branch.get_config_stack()
2086
config.set('email', 'The Dude <lebowski@example.com>')
2088
self.assertFinished(client)
2090
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
2091
('call', 'Branch.lock_write', ('memory:///', '', '')),
2092
('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
2093
('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
2094
('memory:///', 'branch token', 'repo token'),
2095
'# line 1\nemail = The Dude <lebowski@example.com>\n'),
2096
('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
2100
class TestBranchLockWrite(RemoteBranchTestCase):
2102
def test_lock_write_unlockable(self):
2103
transport = MemoryTransport()
2104
client = FakeClient(transport.base)
2105
client.add_expected_call(
2106
'Branch.get_stacked_on_url', ('quack/',),
2107
'error', ('NotStacked',),)
2108
client.add_expected_call(
2109
'Branch.lock_write', ('quack/', '', ''),
2110
'error', ('UnlockableTransport',))
2111
transport.mkdir('quack')
2112
transport = transport.clone('quack')
2113
branch = self.make_remote_branch(transport, client)
2114
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
2115
self.assertFinished(client)
2118
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
2120
def test_simple(self):
2121
transport = MemoryTransport()
2122
client = FakeClient(transport.base)
2123
client.add_expected_call(
2124
'Branch.get_stacked_on_url', ('quack/',),
2125
'error', ('NotStacked',),)
2126
client.add_expected_call(
2127
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2128
'success', ('ok', '0',),)
2129
client.add_expected_call(
2130
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2131
'error', ('NoSuchRevision', 'unknown',),)
2132
transport.mkdir('quack')
2133
transport = transport.clone('quack')
2134
branch = self.make_remote_branch(transport, client)
2135
self.assertEquals(0, branch.revision_id_to_revno('null:'))
2136
self.assertRaises(errors.NoSuchRevision,
2137
branch.revision_id_to_revno, 'unknown')
2138
self.assertFinished(client)
2140
def test_dotted(self):
2141
transport = MemoryTransport()
2142
client = FakeClient(transport.base)
2143
client.add_expected_call(
2144
'Branch.get_stacked_on_url', ('quack/',),
2145
'error', ('NotStacked',),)
2146
client.add_expected_call(
2147
'Branch.revision_id_to_revno', ('quack/', 'null:'),
2148
'success', ('ok', '0',),)
2149
client.add_expected_call(
2150
'Branch.revision_id_to_revno', ('quack/', 'unknown'),
2151
'error', ('NoSuchRevision', 'unknown',),)
2152
transport.mkdir('quack')
2153
transport = transport.clone('quack')
2154
branch = self.make_remote_branch(transport, client)
2155
self.assertEquals((0, ), branch.revision_id_to_dotted_revno('null:'))
2156
self.assertRaises(errors.NoSuchRevision,
2157
branch.revision_id_to_dotted_revno, 'unknown')
2158
self.assertFinished(client)
2160
def test_dotted_no_smart_verb(self):
2161
self.setup_smart_server_with_call_log()
2162
branch = self.make_branch('.')
2163
self.disable_verb('Branch.revision_id_to_revno')
2164
self.reset_smart_call_log()
2165
self.assertEquals((0, ),
2166
branch.revision_id_to_dotted_revno('null:'))
2167
self.assertLength(8, self.hpss_calls)
2170
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
2172
def test__get_config(self):
2173
client = FakeClient()
2174
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
2175
transport = MemoryTransport()
2176
bzrdir = self.make_remote_bzrdir(transport, client)
2177
config = bzrdir.get_config()
2178
self.assertEqual('/', config.get_default_stack_on())
2180
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
2183
def test_set_option_uses_vfs(self):
2184
self.setup_smart_server_with_call_log()
2185
bzrdir = self.make_bzrdir('.')
2186
self.reset_smart_call_log()
2187
config = bzrdir.get_config()
2188
config.set_default_stack_on('/')
2189
self.assertLength(4, self.hpss_calls)
2191
def test_backwards_compat_get_option(self):
2192
self.setup_smart_server_with_call_log()
2193
bzrdir = self.make_bzrdir('.')
2194
verb = 'BzrDir.get_config_file'
2195
self.disable_verb(verb)
2196
self.reset_smart_call_log()
2197
self.assertEqual(None,
2198
bzrdir._get_config().get_option('default_stack_on'))
2199
self.assertLength(4, self.hpss_calls)
2202
class TestTransportIsReadonly(tests.TestCase):
2204
def test_true(self):
2205
client = FakeClient()
2206
client.add_success_response('yes')
2207
transport = RemoteTransport('bzr://example.com/', medium=False,
2209
self.assertEqual(True, transport.is_readonly())
2211
[('call', 'Transport.is_readonly', ())],
2214
def test_false(self):
2215
client = FakeClient()
2216
client.add_success_response('no')
2217
transport = RemoteTransport('bzr://example.com/', medium=False,
2219
self.assertEqual(False, transport.is_readonly())
2221
[('call', 'Transport.is_readonly', ())],
2224
def test_error_from_old_server(self):
2225
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
2227
Clients should treat it as a "no" response, because is_readonly is only
2228
advisory anyway (a transport could be read-write, but then the
2229
underlying filesystem could be readonly anyway).
2231
client = FakeClient()
2232
client.add_unknown_method_response('Transport.is_readonly')
2233
transport = RemoteTransport('bzr://example.com/', medium=False,
2235
self.assertEqual(False, transport.is_readonly())
2237
[('call', 'Transport.is_readonly', ())],
2241
class TestTransportMkdir(tests.TestCase):
2243
def test_permissiondenied(self):
2244
client = FakeClient()
2245
client.add_error_response('PermissionDenied', 'remote path', 'extra')
2246
transport = RemoteTransport('bzr://example.com/', medium=False,
2248
exc = self.assertRaises(
2249
errors.PermissionDenied, transport.mkdir, 'client path')
2250
expected_error = errors.PermissionDenied('/client path', 'extra')
2251
self.assertEqual(expected_error, exc)
2254
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
2256
def test_defaults_to_none(self):
2257
t = RemoteSSHTransport('bzr+ssh://example.com')
2258
self.assertIs(None, t._get_credentials()[0])
2260
def test_uses_authentication_config(self):
2261
conf = config.AuthenticationConfig()
2262
conf._get_config().update(
2263
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
2266
t = RemoteSSHTransport('bzr+ssh://example.com')
2267
self.assertEqual('bar', t._get_credentials()[0])
2270
class TestRemoteRepository(TestRemote):
2271
"""Base for testing RemoteRepository protocol usage.
2273
These tests contain frozen requests and responses. We want any changes to
2274
what is sent or expected to be require a thoughtful update to these tests
2275
because they might break compatibility with different-versioned servers.
2278
def setup_fake_client_and_repository(self, transport_path):
2279
"""Create the fake client and repository for testing with.
2281
There's no real server here; we just have canned responses sent
2284
:param transport_path: Path below the root of the MemoryTransport
2285
where the repository will be created.
2287
transport = MemoryTransport()
2288
transport.mkdir(transport_path)
2289
client = FakeClient(transport.base)
2290
transport = transport.clone(transport_path)
2291
# we do not want bzrdir to make any remote calls
2292
bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
2294
repo = RemoteRepository(bzrdir, None, _client=client)
2298
def remoted_description(format):
2299
return 'Remote: ' + format.get_format_description()
2302
class TestBranchFormat(tests.TestCase):
2304
def test_get_format_description(self):
2305
remote_format = RemoteBranchFormat()
2306
real_format = branch.format_registry.get_default()
2307
remote_format._network_name = real_format.network_name()
2308
self.assertEqual(remoted_description(real_format),
2309
remote_format.get_format_description())
2312
class TestRepositoryFormat(TestRemoteRepository):
2314
def test_fast_delta(self):
2315
true_name = groupcompress_repo.RepositoryFormat2a().network_name()
2316
true_format = RemoteRepositoryFormat()
2317
true_format._network_name = true_name
2318
self.assertEqual(True, true_format.fast_deltas)
2319
false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
2320
false_format = RemoteRepositoryFormat()
2321
false_format._network_name = false_name
2322
self.assertEqual(False, false_format.fast_deltas)
2324
def test_get_format_description(self):
2325
remote_repo_format = RemoteRepositoryFormat()
2326
real_format = repository.format_registry.get_default()
2327
remote_repo_format._network_name = real_format.network_name()
2328
self.assertEqual(remoted_description(real_format),
2329
remote_repo_format.get_format_description())
2332
class TestRepositoryAllRevisionIds(TestRemoteRepository):
2334
def test_empty(self):
2335
transport_path = 'quack'
2336
repo, client = self.setup_fake_client_and_repository(transport_path)
2337
client.add_success_response_with_body('', 'ok')
2338
self.assertEquals([], repo.all_revision_ids())
2340
[('call_expecting_body', 'Repository.all_revision_ids',
2344
def test_with_some_content(self):
2345
transport_path = 'quack'
2346
repo, client = self.setup_fake_client_and_repository(transport_path)
2347
client.add_success_response_with_body(
2348
'rev1\nrev2\nanotherrev\n', 'ok')
2349
self.assertEquals(["rev1", "rev2", "anotherrev"],
2350
repo.all_revision_ids())
2352
[('call_expecting_body', 'Repository.all_revision_ids',
2357
class TestRepositoryGatherStats(TestRemoteRepository):
2359
def test_revid_none(self):
2360
# ('ok',), body with revisions and size
2361
transport_path = 'quack'
2362
repo, client = self.setup_fake_client_and_repository(transport_path)
2363
client.add_success_response_with_body(
2364
'revisions: 2\nsize: 18\n', 'ok')
2365
result = repo.gather_stats(None)
2367
[('call_expecting_body', 'Repository.gather_stats',
2368
('quack/','','no'))],
2370
self.assertEqual({'revisions': 2, 'size': 18}, result)
2372
def test_revid_no_committers(self):
2373
# ('ok',), body without committers
2374
body = ('firstrev: 123456.300 3600\n'
2375
'latestrev: 654231.400 0\n'
2378
transport_path = 'quick'
2379
revid = u'\xc8'.encode('utf8')
2380
repo, client = self.setup_fake_client_and_repository(transport_path)
2381
client.add_success_response_with_body(body, 'ok')
2382
result = repo.gather_stats(revid)
2384
[('call_expecting_body', 'Repository.gather_stats',
2385
('quick/', revid, 'no'))],
2387
self.assertEqual({'revisions': 2, 'size': 18,
2388
'firstrev': (123456.300, 3600),
2389
'latestrev': (654231.400, 0),},
2392
def test_revid_with_committers(self):
2393
# ('ok',), body with committers
2394
body = ('committers: 128\n'
2395
'firstrev: 123456.300 3600\n'
2396
'latestrev: 654231.400 0\n'
2399
transport_path = 'buick'
2400
revid = u'\xc8'.encode('utf8')
2401
repo, client = self.setup_fake_client_and_repository(transport_path)
2402
client.add_success_response_with_body(body, 'ok')
2403
result = repo.gather_stats(revid, True)
2405
[('call_expecting_body', 'Repository.gather_stats',
2406
('buick/', revid, 'yes'))],
2408
self.assertEqual({'revisions': 2, 'size': 18,
2410
'firstrev': (123456.300, 3600),
2411
'latestrev': (654231.400, 0),},
2415
class TestRepositoryBreakLock(TestRemoteRepository):
2417
def test_break_lock(self):
2418
transport_path = 'quack'
2419
repo, client = self.setup_fake_client_and_repository(transport_path)
2420
client.add_success_response('ok')
2423
[('call', 'Repository.break_lock', ('quack/',))],
2427
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
2429
def test_get_serializer_format(self):
2430
transport_path = 'hill'
2431
repo, client = self.setup_fake_client_and_repository(transport_path)
2432
client.add_success_response('ok', '7')
2433
self.assertEquals('7', repo.get_serializer_format())
2435
[('call', 'VersionedFileRepository.get_serializer_format',
2440
class TestRepositoryReconcile(TestRemoteRepository):
2442
def test_reconcile(self):
2443
transport_path = 'hill'
2444
repo, client = self.setup_fake_client_and_repository(transport_path)
2445
body = ("garbage_inventories: 2\n"
2446
"inconsistent_parents: 3\n")
2447
client.add_expected_call(
2448
'Repository.lock_write', ('hill/', ''),
2449
'success', ('ok', 'a token'))
2450
client.add_success_response_with_body(body, 'ok')
2451
reconciler = repo.reconcile()
2453
[('call', 'Repository.lock_write', ('hill/', '')),
2454
('call_expecting_body', 'Repository.reconcile',
2455
('hill/', 'a token'))],
2457
self.assertEquals(2, reconciler.garbage_inventories)
2458
self.assertEquals(3, reconciler.inconsistent_parents)
2461
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
2463
def test_text(self):
2464
# ('ok',), body with signature text
2465
transport_path = 'quack'
2466
repo, client = self.setup_fake_client_and_repository(transport_path)
2467
client.add_success_response_with_body(
2469
self.assertEquals("THETEXT", repo.get_signature_text("revid"))
2471
[('call_expecting_body', 'Repository.get_revision_signature_text',
2472
('quack/', 'revid'))],
2475
def test_no_signature(self):
2476
transport_path = 'quick'
2477
repo, client = self.setup_fake_client_and_repository(transport_path)
2478
client.add_error_response('nosuchrevision', 'unknown')
2479
self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
2482
[('call_expecting_body', 'Repository.get_revision_signature_text',
2483
('quick/', 'unknown'))],
2487
class TestRepositoryGetGraph(TestRemoteRepository):
2489
def test_get_graph(self):
2490
# get_graph returns a graph with a custom parents provider.
2491
transport_path = 'quack'
2492
repo, client = self.setup_fake_client_and_repository(transport_path)
2493
graph = repo.get_graph()
2494
self.assertNotEqual(graph._parents_provider, repo)
2497
class TestRepositoryAddSignatureText(TestRemoteRepository):
2499
def test_add_signature_text(self):
2500
transport_path = 'quack'
2501
repo, client = self.setup_fake_client_and_repository(transport_path)
2502
client.add_expected_call(
2503
'Repository.lock_write', ('quack/', ''),
2504
'success', ('ok', 'a token'))
2505
client.add_expected_call(
2506
'Repository.start_write_group', ('quack/', 'a token'),
2507
'success', ('ok', ('token1', )))
2508
client.add_expected_call(
2509
'Repository.add_signature_text', ('quack/', 'a token', 'rev1',
2511
'success', ('ok', ), None)
2513
repo.start_write_group()
2515
repo.add_signature_text("rev1", "every bloody emperor"))
2517
('call_with_body_bytes_expecting_body',
2518
'Repository.add_signature_text',
2519
('quack/', 'a token', 'rev1', 'token1'),
2520
'every bloody emperor'),
2524
class TestRepositoryGetParentMap(TestRemoteRepository):
2526
def test_get_parent_map_caching(self):
2527
# get_parent_map returns from cache until unlock()
2528
# setup a reponse with two revisions
2529
r1 = u'\u0e33'.encode('utf8')
2530
r2 = u'\u0dab'.encode('utf8')
2531
lines = [' '.join([r2, r1]), r1]
2532
encoded_body = bz2.compress('\n'.join(lines))
2534
transport_path = 'quack'
2535
repo, client = self.setup_fake_client_and_repository(transport_path)
2536
client.add_success_response_with_body(encoded_body, 'ok')
2537
client.add_success_response_with_body(encoded_body, 'ok')
2539
graph = repo.get_graph()
2540
parents = graph.get_parent_map([r2])
2541
self.assertEqual({r2: (r1,)}, parents)
2542
# locking and unlocking deeper should not reset
2545
parents = graph.get_parent_map([r1])
2546
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2548
[('call_with_body_bytes_expecting_body',
2549
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2553
# now we call again, and it should use the second response.
2555
graph = repo.get_graph()
2556
parents = graph.get_parent_map([r1])
2557
self.assertEqual({r1: (NULL_REVISION,)}, parents)
2559
[('call_with_body_bytes_expecting_body',
2560
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2562
('call_with_body_bytes_expecting_body',
2563
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2569
def test_get_parent_map_reconnects_if_unknown_method(self):
2570
transport_path = 'quack'
2571
rev_id = 'revision-id'
2572
repo, client = self.setup_fake_client_and_repository(transport_path)
2573
client.add_unknown_method_response('Repository.get_parent_map')
2574
client.add_success_response_with_body(rev_id, 'ok')
2575
self.assertFalse(client._medium._is_remote_before((1, 2)))
2576
parents = repo.get_parent_map([rev_id])
2578
[('call_with_body_bytes_expecting_body',
2579
'Repository.get_parent_map',
2580
('quack/', 'include-missing:', rev_id), '\n\n0'),
2581
('disconnect medium',),
2582
('call_expecting_body', 'Repository.get_revision_graph',
2585
# The medium is now marked as being connected to an older server
2586
self.assertTrue(client._medium._is_remote_before((1, 2)))
2587
self.assertEqual({rev_id: ('null:',)}, parents)
2589
def test_get_parent_map_fallback_parentless_node(self):
2590
"""get_parent_map falls back to get_revision_graph on old servers. The
2591
results from get_revision_graph are tweaked to match the get_parent_map
2594
Specifically, a {key: ()} result from get_revision_graph means "no
2595
parents" for that key, which in get_parent_map results should be
2596
represented as {key: ('null:',)}.
2598
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2600
rev_id = 'revision-id'
2601
transport_path = 'quack'
2602
repo, client = self.setup_fake_client_and_repository(transport_path)
2603
client.add_success_response_with_body(rev_id, 'ok')
2604
client._medium._remember_remote_is_before((1, 2))
2605
parents = repo.get_parent_map([rev_id])
2607
[('call_expecting_body', 'Repository.get_revision_graph',
2610
self.assertEqual({rev_id: ('null:',)}, parents)
2612
def test_get_parent_map_unexpected_response(self):
2613
repo, client = self.setup_fake_client_and_repository('path')
2614
client.add_success_response('something unexpected!')
2616
errors.UnexpectedSmartServerResponse,
2617
repo.get_parent_map, ['a-revision-id'])
2619
def test_get_parent_map_negative_caches_missing_keys(self):
2620
self.setup_smart_server_with_call_log()
2621
repo = self.make_repository('foo')
2622
self.assertIsInstance(repo, RemoteRepository)
2624
self.addCleanup(repo.unlock)
2625
self.reset_smart_call_log()
2626
graph = repo.get_graph()
2627
self.assertEqual({},
2628
graph.get_parent_map(['some-missing', 'other-missing']))
2629
self.assertLength(1, self.hpss_calls)
2630
# No call if we repeat this
2631
self.reset_smart_call_log()
2632
graph = repo.get_graph()
2633
self.assertEqual({},
2634
graph.get_parent_map(['some-missing', 'other-missing']))
2635
self.assertLength(0, self.hpss_calls)
2636
# Asking for more unknown keys makes a request.
2637
self.reset_smart_call_log()
2638
graph = repo.get_graph()
2639
self.assertEqual({},
2640
graph.get_parent_map(['some-missing', 'other-missing',
2642
self.assertLength(1, self.hpss_calls)
2644
def disableExtraResults(self):
2645
self.overrideAttr(SmartServerRepositoryGetParentMap,
2646
'no_extra_results', True)
2648
def test_null_cached_missing_and_stop_key(self):
2649
self.setup_smart_server_with_call_log()
2650
# Make a branch with a single revision.
2651
builder = self.make_branch_builder('foo')
2652
builder.start_series()
2653
builder.build_snapshot('first', None, [
2654
('add', ('', 'root-id', 'directory', ''))])
2655
builder.finish_series()
2656
branch = builder.get_branch()
2657
repo = branch.repository
2658
self.assertIsInstance(repo, RemoteRepository)
2659
# Stop the server from sending extra results.
2660
self.disableExtraResults()
2662
self.addCleanup(repo.unlock)
2663
self.reset_smart_call_log()
2664
graph = repo.get_graph()
2665
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2666
# 'first' it will be a candidate for the stop_keys of subsequent
2667
# requests, and because 'null:' was queried but not returned it will be
2668
# cached as missing.
2669
self.assertEqual({'first': ('null:',)},
2670
graph.get_parent_map(['first', 'null:']))
2671
# Now query for another key. This request will pass along a recipe of
2672
# start and stop keys describing the already cached results, and this
2673
# recipe's revision count must be correct (or else it will trigger an
2674
# error from the server).
2675
self.assertEqual({}, graph.get_parent_map(['another-key']))
2676
# This assertion guards against disableExtraResults silently failing to
2677
# work, thus invalidating the test.
2678
self.assertLength(2, self.hpss_calls)
2680
def test_get_parent_map_gets_ghosts_from_result(self):
2681
# asking for a revision should negatively cache close ghosts in its
2683
self.setup_smart_server_with_call_log()
2684
tree = self.make_branch_and_memory_tree('foo')
2687
builder = treebuilder.TreeBuilder()
2688
builder.start_tree(tree)
2690
builder.finish_tree()
2691
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2692
rev_id = tree.commit('')
2696
self.addCleanup(tree.unlock)
2697
repo = tree.branch.repository
2698
self.assertIsInstance(repo, RemoteRepository)
2700
repo.get_parent_map([rev_id])
2701
self.reset_smart_call_log()
2702
# Now asking for rev_id's ghost parent should not make calls
2703
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2704
self.assertLength(0, self.hpss_calls)
2706
def test_exposes_get_cached_parent_map(self):
2707
"""RemoteRepository exposes get_cached_parent_map from
2710
r1 = u'\u0e33'.encode('utf8')
2711
r2 = u'\u0dab'.encode('utf8')
2712
lines = [' '.join([r2, r1]), r1]
2713
encoded_body = bz2.compress('\n'.join(lines))
2715
transport_path = 'quack'
2716
repo, client = self.setup_fake_client_and_repository(transport_path)
2717
client.add_success_response_with_body(encoded_body, 'ok')
2719
# get_cached_parent_map should *not* trigger an RPC
2720
self.assertEqual({}, repo.get_cached_parent_map([r1]))
2721
self.assertEqual([], client._calls)
2722
self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
2723
self.assertEqual({r1: (NULL_REVISION,)},
2724
repo.get_cached_parent_map([r1]))
2726
[('call_with_body_bytes_expecting_body',
2727
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
2733
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2735
def test_allows_new_revisions(self):
2736
"""get_parent_map's results can be updated by commit."""
2737
smart_server = test_server.SmartTCPServer_for_testing()
2738
self.start_server(smart_server)
2739
self.make_branch('branch')
2740
branch = Branch.open(smart_server.get_url() + '/branch')
2741
tree = branch.create_checkout('tree', lightweight=True)
2743
self.addCleanup(tree.unlock)
2744
graph = tree.branch.repository.get_graph()
2745
# This provides an opportunity for the missing rev-id to be cached.
2746
self.assertEqual({}, graph.get_parent_map(['rev1']))
2747
tree.commit('message', rev_id='rev1')
2748
graph = tree.branch.repository.get_graph()
2749
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2752
class TestRepositoryGetRevisions(TestRemoteRepository):
2754
def test_hpss_missing_revision(self):
2755
transport_path = 'quack'
2756
repo, client = self.setup_fake_client_and_repository(transport_path)
2757
client.add_success_response_with_body(
2759
self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
2760
['somerev1', 'anotherrev2'])
2762
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2763
('quack/', ), "somerev1\nanotherrev2")],
2766
def test_hpss_get_single_revision(self):
2767
transport_path = 'quack'
2768
repo, client = self.setup_fake_client_and_repository(transport_path)
2769
somerev1 = Revision("somerev1")
2770
somerev1.committer = "Joe Committer <joe@example.com>"
2771
somerev1.timestamp = 1321828927
2772
somerev1.timezone = -60
2773
somerev1.inventory_sha1 = "691b39be74c67b1212a75fcb19c433aaed903c2b"
2774
somerev1.message = "Message"
2775
body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
2777
# Split up body into two bits to make sure the zlib compression object
2778
# gets data fed twice.
2779
client.add_success_response_with_body(
2780
[body[:10], body[10:]], 'ok', '10')
2781
revs = repo.get_revisions(['somerev1'])
2782
self.assertEquals(revs, [somerev1])
2784
[('call_with_body_bytes_expecting_body', 'Repository.iter_revisions',
2785
('quack/', ), "somerev1")],
2789
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2791
def test_null_revision(self):
2792
# a null revision has the predictable result {}, we should have no wire
2793
# traffic when calling it with this argument
2794
transport_path = 'empty'
2795
repo, client = self.setup_fake_client_and_repository(transport_path)
2796
client.add_success_response('notused')
2797
# actual RemoteRepository.get_revision_graph is gone, but there's an
2798
# equivalent private method for testing
2799
result = repo._get_revision_graph(NULL_REVISION)
2800
self.assertEqual([], client._calls)
2801
self.assertEqual({}, result)
2803
def test_none_revision(self):
2804
# with none we want the entire graph
2805
r1 = u'\u0e33'.encode('utf8')
2806
r2 = u'\u0dab'.encode('utf8')
2807
lines = [' '.join([r2, r1]), r1]
2808
encoded_body = '\n'.join(lines)
2810
transport_path = 'sinhala'
2811
repo, client = self.setup_fake_client_and_repository(transport_path)
2812
client.add_success_response_with_body(encoded_body, 'ok')
2813
# actual RemoteRepository.get_revision_graph is gone, but there's an
2814
# equivalent private method for testing
2815
result = repo._get_revision_graph(None)
2817
[('call_expecting_body', 'Repository.get_revision_graph',
2820
self.assertEqual({r1: (), r2: (r1, )}, result)
2822
def test_specific_revision(self):
2823
# with a specific revision we want the graph for that
2824
# with none we want the entire graph
2825
r11 = u'\u0e33'.encode('utf8')
2826
r12 = u'\xc9'.encode('utf8')
2827
r2 = u'\u0dab'.encode('utf8')
2828
lines = [' '.join([r2, r11, r12]), r11, r12]
2829
encoded_body = '\n'.join(lines)
2831
transport_path = 'sinhala'
2832
repo, client = self.setup_fake_client_and_repository(transport_path)
2833
client.add_success_response_with_body(encoded_body, 'ok')
2834
result = repo._get_revision_graph(r2)
2836
[('call_expecting_body', 'Repository.get_revision_graph',
2839
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2841
def test_no_such_revision(self):
2843
transport_path = 'sinhala'
2844
repo, client = self.setup_fake_client_and_repository(transport_path)
2845
client.add_error_response('nosuchrevision', revid)
2846
# also check that the right revision is reported in the error
2847
self.assertRaises(errors.NoSuchRevision,
2848
repo._get_revision_graph, revid)
2850
[('call_expecting_body', 'Repository.get_revision_graph',
2851
('sinhala/', revid))],
2854
def test_unexpected_error(self):
2856
transport_path = 'sinhala'
2857
repo, client = self.setup_fake_client_and_repository(transport_path)
2858
client.add_error_response('AnUnexpectedError')
2859
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2860
repo._get_revision_graph, revid)
2861
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2864
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2867
repo, client = self.setup_fake_client_and_repository('quack')
2868
client.add_expected_call(
2869
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2870
'success', ('ok', 'rev-five'))
2871
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2872
self.assertEqual((True, 'rev-five'), result)
2873
self.assertFinished(client)
2875
def test_history_incomplete(self):
2876
repo, client = self.setup_fake_client_and_repository('quack')
2877
client.add_expected_call(
2878
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2879
'success', ('history-incomplete', 10, 'rev-ten'))
2880
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2881
self.assertEqual((False, (10, 'rev-ten')), result)
2882
self.assertFinished(client)
2884
def test_history_incomplete_with_fallback(self):
2885
"""A 'history-incomplete' response causes the fallback repository to be
2886
queried too, if one is set.
2888
# Make a repo with a fallback repo, both using a FakeClient.
2889
format = remote.response_tuple_to_repo_format(
2890
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2891
repo, client = self.setup_fake_client_and_repository('quack')
2892
repo._format = format
2893
fallback_repo, ignored = self.setup_fake_client_and_repository(
2895
fallback_repo._client = client
2896
fallback_repo._format = format
2897
repo.add_fallback_repository(fallback_repo)
2898
# First the client should ask the primary repo
2899
client.add_expected_call(
2900
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2901
'success', ('history-incomplete', 2, 'rev-two'))
2902
# Then it should ask the fallback, using revno/revid from the
2903
# history-incomplete response as the known revno/revid.
2904
client.add_expected_call(
2905
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2906
'success', ('ok', 'rev-one'))
2907
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2908
self.assertEqual((True, 'rev-one'), result)
2909
self.assertFinished(client)
2911
def test_nosuchrevision(self):
2912
# 'nosuchrevision' is returned when the known-revid is not found in the
2913
# remote repo. The client translates that response to NoSuchRevision.
2914
repo, client = self.setup_fake_client_and_repository('quack')
2915
client.add_expected_call(
2916
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2917
'error', ('nosuchrevision', 'rev-foo'))
2919
errors.NoSuchRevision,
2920
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2921
self.assertFinished(client)
2923
def test_branch_fallback_locking(self):
2924
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2925
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2926
will be invoked, which will fail if the repo is unlocked.
2928
self.setup_smart_server_with_call_log()
2929
tree = self.make_branch_and_memory_tree('.')
2932
rev1 = tree.commit('First')
2933
rev2 = tree.commit('Second')
2935
branch = tree.branch
2936
self.assertFalse(branch.is_locked())
2937
self.reset_smart_call_log()
2938
verb = 'Repository.get_rev_id_for_revno'
2939
self.disable_verb(verb)
2940
self.assertEqual(rev1, branch.get_rev_id(1))
2941
self.assertLength(1, [call for call in self.hpss_calls if
2942
call.call.method == verb])
2945
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
2947
def test_has_signature_for_revision_id(self):
2948
# ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
2949
transport_path = 'quack'
2950
repo, client = self.setup_fake_client_and_repository(transport_path)
2951
client.add_success_response('yes')
2952
result = repo.has_signature_for_revision_id('A')
2954
[('call', 'Repository.has_signature_for_revision_id',
2957
self.assertEqual(True, result)
2959
def test_is_not_shared(self):
2960
# ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
2961
transport_path = 'qwack'
2962
repo, client = self.setup_fake_client_and_repository(transport_path)
2963
client.add_success_response('no')
2964
result = repo.has_signature_for_revision_id('A')
2966
[('call', 'Repository.has_signature_for_revision_id',
2969
self.assertEqual(False, result)
2972
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
2974
def test_get_physical_lock_status_yes(self):
2975
transport_path = 'qwack'
2976
repo, client = self.setup_fake_client_and_repository(transport_path)
2977
client.add_success_response('yes')
2978
result = repo.get_physical_lock_status()
2980
[('call', 'Repository.get_physical_lock_status',
2983
self.assertEqual(True, result)
2985
def test_get_physical_lock_status_no(self):
2986
transport_path = 'qwack'
2987
repo, client = self.setup_fake_client_and_repository(transport_path)
2988
client.add_success_response('no')
2989
result = repo.get_physical_lock_status()
2991
[('call', 'Repository.get_physical_lock_status',
2994
self.assertEqual(False, result)
2997
class TestRepositoryIsShared(TestRemoteRepository):
2999
def test_is_shared(self):
3000
# ('yes', ) for Repository.is_shared -> 'True'.
3001
transport_path = 'quack'
3002
repo, client = self.setup_fake_client_and_repository(transport_path)
3003
client.add_success_response('yes')
3004
result = repo.is_shared()
3006
[('call', 'Repository.is_shared', ('quack/',))],
3008
self.assertEqual(True, result)
3010
def test_is_not_shared(self):
3011
# ('no', ) for Repository.is_shared -> 'False'.
3012
transport_path = 'qwack'
3013
repo, client = self.setup_fake_client_and_repository(transport_path)
3014
client.add_success_response('no')
3015
result = repo.is_shared()
3017
[('call', 'Repository.is_shared', ('qwack/',))],
3019
self.assertEqual(False, result)
3022
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
3024
def test_make_working_trees(self):
3025
# ('yes', ) for Repository.make_working_trees -> 'True'.
3026
transport_path = 'quack'
3027
repo, client = self.setup_fake_client_and_repository(transport_path)
3028
client.add_success_response('yes')
3029
result = repo.make_working_trees()
3031
[('call', 'Repository.make_working_trees', ('quack/',))],
3033
self.assertEqual(True, result)
3035
def test_no_working_trees(self):
3036
# ('no', ) for Repository.make_working_trees -> 'False'.
3037
transport_path = 'qwack'
3038
repo, client = self.setup_fake_client_and_repository(transport_path)
3039
client.add_success_response('no')
3040
result = repo.make_working_trees()
3042
[('call', 'Repository.make_working_trees', ('qwack/',))],
3044
self.assertEqual(False, result)
3047
class TestRepositoryLockWrite(TestRemoteRepository):
3049
def test_lock_write(self):
3050
transport_path = 'quack'
3051
repo, client = self.setup_fake_client_and_repository(transport_path)
3052
client.add_success_response('ok', 'a token')
3053
token = repo.lock_write().repository_token
3055
[('call', 'Repository.lock_write', ('quack/', ''))],
3057
self.assertEqual('a token', token)
3059
def test_lock_write_already_locked(self):
3060
transport_path = 'quack'
3061
repo, client = self.setup_fake_client_and_repository(transport_path)
3062
client.add_error_response('LockContention')
3063
self.assertRaises(errors.LockContention, repo.lock_write)
3065
[('call', 'Repository.lock_write', ('quack/', ''))],
3068
def test_lock_write_unlockable(self):
3069
transport_path = 'quack'
3070
repo, client = self.setup_fake_client_and_repository(transport_path)
3071
client.add_error_response('UnlockableTransport')
3072
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
3074
[('call', 'Repository.lock_write', ('quack/', ''))],
3078
class TestRepositoryWriteGroups(TestRemoteRepository):
3080
def test_start_write_group(self):
3081
transport_path = 'quack'
3082
repo, client = self.setup_fake_client_and_repository(transport_path)
3083
client.add_expected_call(
3084
'Repository.lock_write', ('quack/', ''),
3085
'success', ('ok', 'a token'))
3086
client.add_expected_call(
3087
'Repository.start_write_group', ('quack/', 'a token'),
3088
'success', ('ok', ('token1', )))
3090
repo.start_write_group()
3092
def test_start_write_group_unsuspendable(self):
3093
# Some repositories do not support suspending write
3094
# groups. For those, fall back to the "real" repository.
3095
transport_path = 'quack'
3096
repo, client = self.setup_fake_client_and_repository(transport_path)
3097
def stub_ensure_real():
3098
client._calls.append(('_ensure_real',))
3099
repo._real_repository = _StubRealPackRepository(client._calls)
3100
repo._ensure_real = stub_ensure_real
3101
client.add_expected_call(
3102
'Repository.lock_write', ('quack/', ''),
3103
'success', ('ok', 'a token'))
3104
client.add_expected_call(
3105
'Repository.start_write_group', ('quack/', 'a token'),
3106
'error', ('UnsuspendableWriteGroup',))
3108
repo.start_write_group()
3109
self.assertEquals(client._calls[-2:], [
3111
('start_write_group',)])
3113
def test_commit_write_group(self):
3114
transport_path = 'quack'
3115
repo, client = self.setup_fake_client_and_repository(transport_path)
3116
client.add_expected_call(
3117
'Repository.lock_write', ('quack/', ''),
3118
'success', ('ok', 'a token'))
3119
client.add_expected_call(
3120
'Repository.start_write_group', ('quack/', 'a token'),
3121
'success', ('ok', ['token1']))
3122
client.add_expected_call(
3123
'Repository.commit_write_group', ('quack/', 'a token', ['token1']),
3126
repo.start_write_group()
3127
repo.commit_write_group()
3129
def test_abort_write_group(self):
3130
transport_path = 'quack'
3131
repo, client = self.setup_fake_client_and_repository(transport_path)
3132
client.add_expected_call(
3133
'Repository.lock_write', ('quack/', ''),
3134
'success', ('ok', 'a token'))
3135
client.add_expected_call(
3136
'Repository.start_write_group', ('quack/', 'a token'),
3137
'success', ('ok', ['token1']))
3138
client.add_expected_call(
3139
'Repository.abort_write_group', ('quack/', 'a token', ['token1']),
3142
repo.start_write_group()
3143
repo.abort_write_group(False)
3145
def test_suspend_write_group(self):
3146
transport_path = 'quack'
3147
repo, client = self.setup_fake_client_and_repository(transport_path)
3148
self.assertEquals([], repo.suspend_write_group())
3150
def test_resume_write_group(self):
3151
transport_path = 'quack'
3152
repo, client = self.setup_fake_client_and_repository(transport_path)
3153
client.add_expected_call(
3154
'Repository.lock_write', ('quack/', ''),
3155
'success', ('ok', 'a token'))
3156
client.add_expected_call(
3157
'Repository.check_write_group', ('quack/', 'a token', ['token1']),
3160
repo.resume_write_group(['token1'])
3163
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
3165
def test_backwards_compat(self):
3166
self.setup_smart_server_with_call_log()
3167
repo = self.make_repository('.')
3168
self.reset_smart_call_log()
3169
verb = 'Repository.set_make_working_trees'
3170
self.disable_verb(verb)
3171
repo.set_make_working_trees(True)
3172
call_count = len([call for call in self.hpss_calls if
3173
call.call.method == verb])
3174
self.assertEqual(1, call_count)
3176
def test_current(self):
3177
transport_path = 'quack'
3178
repo, client = self.setup_fake_client_and_repository(transport_path)
3179
client.add_expected_call(
3180
'Repository.set_make_working_trees', ('quack/', 'True'),
3182
client.add_expected_call(
3183
'Repository.set_make_working_trees', ('quack/', 'False'),
3185
repo.set_make_working_trees(True)
3186
repo.set_make_working_trees(False)
3189
class TestRepositoryUnlock(TestRemoteRepository):
3191
def test_unlock(self):
3192
transport_path = 'quack'
3193
repo, client = self.setup_fake_client_and_repository(transport_path)
3194
client.add_success_response('ok', 'a token')
3195
client.add_success_response('ok')
3199
[('call', 'Repository.lock_write', ('quack/', '')),
3200
('call', 'Repository.unlock', ('quack/', 'a token'))],
3203
def test_unlock_wrong_token(self):
3204
# If somehow the token is wrong, unlock will raise TokenMismatch.
3205
transport_path = 'quack'
3206
repo, client = self.setup_fake_client_and_repository(transport_path)
3207
client.add_success_response('ok', 'a token')
3208
client.add_error_response('TokenMismatch')
3210
self.assertRaises(errors.TokenMismatch, repo.unlock)
3213
class TestRepositoryHasRevision(TestRemoteRepository):
3215
def test_none(self):
3216
# repo.has_revision(None) should not cause any traffic.
3217
transport_path = 'quack'
3218
repo, client = self.setup_fake_client_and_repository(transport_path)
3220
# The null revision is always there, so has_revision(None) == True.
3221
self.assertEqual(True, repo.has_revision(NULL_REVISION))
3223
# The remote repo shouldn't be accessed.
3224
self.assertEqual([], client._calls)
3227
class TestRepositoryIterFilesBytes(TestRemoteRepository):
3228
"""Test Repository.iter_file_bytes."""
3230
def test_single(self):
3231
transport_path = 'quack'
3232
repo, client = self.setup_fake_client_and_repository(transport_path)
3233
client.add_expected_call(
3234
'Repository.iter_files_bytes', ('quack/', ),
3235
'success', ('ok',), iter(["ok\x000", "\n", zlib.compress("mydata" * 10)]))
3236
for (identifier, byte_stream) in repo.iter_files_bytes([("somefile",
3237
"somerev", "myid")]):
3238
self.assertEquals("myid", identifier)
3239
self.assertEquals("".join(byte_stream), "mydata" * 10)
3241
def test_missing(self):
3242
transport_path = 'quack'
3243
repo, client = self.setup_fake_client_and_repository(transport_path)
3244
client.add_expected_call(
3245
'Repository.iter_files_bytes',
3247
'error', ('RevisionNotPresent', 'somefile', 'somerev'),
3248
iter(["absent\0somefile\0somerev\n"]))
3249
self.assertRaises(errors.RevisionNotPresent, list,
3250
repo.iter_files_bytes(
3251
[("somefile", "somerev", "myid")]))
3254
class TestRepositoryInsertStreamBase(TestRemoteRepository):
3255
"""Base class for Repository.insert_stream and .insert_stream_1.19
3259
def checkInsertEmptyStream(self, repo, client):
3260
"""Insert an empty stream, checking the result.
3262
This checks that there are no resume_tokens or missing_keys, and that
3263
the client is finished.
3265
sink = repo._get_sink()
3266
fmt = repository.format_registry.get_default()
3267
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
3268
self.assertEqual([], resume_tokens)
3269
self.assertEqual(set(), missing_keys)
3270
self.assertFinished(client)
3273
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
3274
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
3277
This test case is very similar to TestRepositoryInsertStream_1_19.
3281
TestRemoteRepository.setUp(self)
3282
self.disable_verb('Repository.insert_stream_1.19')
3284
def test_unlocked_repo(self):
3285
transport_path = 'quack'
3286
repo, client = self.setup_fake_client_and_repository(transport_path)
3287
client.add_expected_call(
3288
'Repository.insert_stream_1.19', ('quack/', ''),
3289
'unknown', ('Repository.insert_stream_1.19',))
3290
client.add_expected_call(
3291
'Repository.insert_stream', ('quack/', ''),
3293
client.add_expected_call(
3294
'Repository.insert_stream', ('quack/', ''),
3296
self.checkInsertEmptyStream(repo, client)
3298
def test_locked_repo_with_no_lock_token(self):
3299
transport_path = 'quack'
3300
repo, client = self.setup_fake_client_and_repository(transport_path)
3301
client.add_expected_call(
3302
'Repository.lock_write', ('quack/', ''),
3303
'success', ('ok', ''))
3304
client.add_expected_call(
3305
'Repository.insert_stream_1.19', ('quack/', ''),
3306
'unknown', ('Repository.insert_stream_1.19',))
3307
client.add_expected_call(
3308
'Repository.insert_stream', ('quack/', ''),
3310
client.add_expected_call(
3311
'Repository.insert_stream', ('quack/', ''),
3314
self.checkInsertEmptyStream(repo, client)
3316
def test_locked_repo_with_lock_token(self):
3317
transport_path = 'quack'
3318
repo, client = self.setup_fake_client_and_repository(transport_path)
3319
client.add_expected_call(
3320
'Repository.lock_write', ('quack/', ''),
3321
'success', ('ok', 'a token'))
3322
client.add_expected_call(
3323
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3324
'unknown', ('Repository.insert_stream_1.19',))
3325
client.add_expected_call(
3326
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3328
client.add_expected_call(
3329
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
3332
self.checkInsertEmptyStream(repo, client)
3334
def test_stream_with_inventory_deltas(self):
3335
"""'inventory-deltas' substreams cannot be sent to the
3336
Repository.insert_stream verb, because not all servers that implement
3337
that verb will accept them. So when one is encountered the RemoteSink
3338
immediately stops using that verb and falls back to VFS insert_stream.
3340
transport_path = 'quack'
3341
repo, client = self.setup_fake_client_and_repository(transport_path)
3342
client.add_expected_call(
3343
'Repository.insert_stream_1.19', ('quack/', ''),
3344
'unknown', ('Repository.insert_stream_1.19',))
3345
client.add_expected_call(
3346
'Repository.insert_stream', ('quack/', ''),
3348
client.add_expected_call(
3349
'Repository.insert_stream', ('quack/', ''),
3351
# Create a fake real repository for insert_stream to fall back on, so
3352
# that we can directly see the records the RemoteSink passes to the
3357
def insert_stream(self, stream, src_format, resume_tokens):
3358
for substream_kind, substream in stream:
3359
self.records.append(
3360
(substream_kind, [record.key for record in substream]))
3361
return ['fake tokens'], ['fake missing keys']
3362
fake_real_sink = FakeRealSink()
3363
class FakeRealRepository:
3364
def _get_sink(self):
3365
return fake_real_sink
3366
def is_in_write_group(self):
3368
def refresh_data(self):
3370
repo._real_repository = FakeRealRepository()
3371
sink = repo._get_sink()
3372
fmt = repository.format_registry.get_default()
3373
stream = self.make_stream_with_inv_deltas(fmt)
3374
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
3375
# Every record from the first inventory delta should have been sent to
3377
expected_records = [
3378
('inventory-deltas', [('rev2',), ('rev3',)]),
3379
('texts', [('some-rev', 'some-file')])]
3380
self.assertEqual(expected_records, fake_real_sink.records)
3381
# The return values from the real sink's insert_stream are propagated
3382
# back to the original caller.
3383
self.assertEqual(['fake tokens'], resume_tokens)
3384
self.assertEqual(['fake missing keys'], missing_keys)
3385
self.assertFinished(client)
3387
def make_stream_with_inv_deltas(self, fmt):
3388
"""Make a simple stream with an inventory delta followed by more
3389
records and more substreams to test that all records and substreams
3390
from that point on are used.
3392
This sends, in order:
3393
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
3395
* texts substream: (some-rev, some-file)
3397
# Define a stream using generators so that it isn't rewindable.
3398
inv = inventory.Inventory(revision_id='rev1')
3399
inv.root.revision = 'rev1'
3400
def stream_with_inv_delta():
3401
yield ('inventories', inventories_substream())
3402
yield ('inventory-deltas', inventory_delta_substream())
3404
versionedfile.FulltextContentFactory(
3405
('some-rev', 'some-file'), (), None, 'content')])
3406
def inventories_substream():
3407
# An empty inventory fulltext. This will be streamed normally.
3408
text = fmt._serializer.write_inventory_to_string(inv)
3409
yield versionedfile.FulltextContentFactory(
3410
('rev1',), (), None, text)
3411
def inventory_delta_substream():
3412
# An inventory delta. This can't be streamed via this verb, so it
3413
# will trigger a fallback to VFS insert_stream.
3414
entry = inv.make_entry(
3415
'directory', 'newdir', inv.root.file_id, 'newdir-id')
3416
entry.revision = 'ghost'
3417
delta = [(None, 'newdir', 'newdir-id', entry)]
3418
serializer = inventory_delta.InventoryDeltaSerializer(
3419
versioned_root=True, tree_references=False)
3420
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
3421
yield versionedfile.ChunkedContentFactory(
3422
('rev2',), (('rev1',)), None, lines)
3424
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
3425
yield versionedfile.ChunkedContentFactory(
3426
('rev3',), (('rev1',)), None, lines)
3427
return stream_with_inv_delta()
3430
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
3432
def test_unlocked_repo(self):
3433
transport_path = 'quack'
3434
repo, client = self.setup_fake_client_and_repository(transport_path)
3435
client.add_expected_call(
3436
'Repository.insert_stream_1.19', ('quack/', ''),
3438
client.add_expected_call(
3439
'Repository.insert_stream_1.19', ('quack/', ''),
3441
self.checkInsertEmptyStream(repo, client)
3443
def test_locked_repo_with_no_lock_token(self):
3444
transport_path = 'quack'
3445
repo, client = self.setup_fake_client_and_repository(transport_path)
3446
client.add_expected_call(
3447
'Repository.lock_write', ('quack/', ''),
3448
'success', ('ok', ''))
3449
client.add_expected_call(
3450
'Repository.insert_stream_1.19', ('quack/', ''),
3452
client.add_expected_call(
3453
'Repository.insert_stream_1.19', ('quack/', ''),
3456
self.checkInsertEmptyStream(repo, client)
3458
def test_locked_repo_with_lock_token(self):
3459
transport_path = 'quack'
3460
repo, client = self.setup_fake_client_and_repository(transport_path)
3461
client.add_expected_call(
3462
'Repository.lock_write', ('quack/', ''),
3463
'success', ('ok', 'a token'))
3464
client.add_expected_call(
3465
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3467
client.add_expected_call(
3468
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
3471
self.checkInsertEmptyStream(repo, client)
3474
class TestRepositoryTarball(TestRemoteRepository):
3476
# This is a canned tarball reponse we can validate against
3478
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
3479
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
3480
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
3481
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
3482
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
3483
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
3484
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
3485
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
3486
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
3487
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
3488
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
3489
'nWQ7QH/F3JFOFCQ0aSPfA='
3492
def test_repository_tarball(self):
3493
# Test that Repository.tarball generates the right operations
3494
transport_path = 'repo'
3495
expected_calls = [('call_expecting_body', 'Repository.tarball',
3496
('repo/', 'bz2',),),
3498
repo, client = self.setup_fake_client_and_repository(transport_path)
3499
client.add_success_response_with_body(self.tarball_content, 'ok')
3500
# Now actually ask for the tarball
3501
tarball_file = repo._get_tarball('bz2')
3503
self.assertEqual(expected_calls, client._calls)
3504
self.assertEqual(self.tarball_content, tarball_file.read())
3506
tarball_file.close()
3509
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
3510
"""RemoteRepository.copy_content_into optimizations"""
3512
def test_copy_content_remote_to_local(self):
3513
self.transport_server = test_server.SmartTCPServer_for_testing
3514
src_repo = self.make_repository('repo1')
3515
src_repo = repository.Repository.open(self.get_url('repo1'))
3516
# At the moment the tarball-based copy_content_into can't write back
3517
# into a smart server. It would be good if it could upload the
3518
# tarball; once that works we'd have to create repositories of
3519
# different formats. -- mbp 20070410
3520
dest_url = self.get_vfs_only_url('repo2')
3521
dest_bzrdir = BzrDir.create(dest_url)
3522
dest_repo = dest_bzrdir.create_repository()
3523
self.assertFalse(isinstance(dest_repo, RemoteRepository))
3524
self.assertTrue(isinstance(src_repo, RemoteRepository))
3525
src_repo.copy_content_into(dest_repo)
3528
class _StubRealPackRepository(object):
3530
def __init__(self, calls):
3532
self._pack_collection = _StubPackCollection(calls)
3534
def start_write_group(self):
3535
self.calls.append(('start_write_group',))
3537
def is_in_write_group(self):
3540
def refresh_data(self):
3541
self.calls.append(('pack collection reload_pack_names',))
3544
class _StubPackCollection(object):
3546
def __init__(self, calls):
3550
self.calls.append(('pack collection autopack',))
3553
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
3554
"""Tests for RemoteRepository.autopack implementation."""
3557
"""When the server returns 'ok' and there's no _real_repository, then
3558
nothing else happens: the autopack method is done.
3560
transport_path = 'quack'
3561
repo, client = self.setup_fake_client_and_repository(transport_path)
3562
client.add_expected_call(
3563
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
3565
self.assertFinished(client)
3567
def test_ok_with_real_repo(self):
3568
"""When the server returns 'ok' and there is a _real_repository, then
3569
the _real_repository's reload_pack_name's method will be called.
3571
transport_path = 'quack'
3572
repo, client = self.setup_fake_client_and_repository(transport_path)
3573
client.add_expected_call(
3574
'PackRepository.autopack', ('quack/',),
3576
repo._real_repository = _StubRealPackRepository(client._calls)
3579
[('call', 'PackRepository.autopack', ('quack/',)),
3580
('pack collection reload_pack_names',)],
3583
def test_backwards_compatibility(self):
3584
"""If the server does not recognise the PackRepository.autopack verb,
3585
fallback to the real_repository's implementation.
3587
transport_path = 'quack'
3588
repo, client = self.setup_fake_client_and_repository(transport_path)
3589
client.add_unknown_method_response('PackRepository.autopack')
3590
def stub_ensure_real():
3591
client._calls.append(('_ensure_real',))
3592
repo._real_repository = _StubRealPackRepository(client._calls)
3593
repo._ensure_real = stub_ensure_real
3596
[('call', 'PackRepository.autopack', ('quack/',)),
3598
('pack collection autopack',)],
3601
def test_oom_error_reporting(self):
3602
"""An out-of-memory condition on the server is reported clearly"""
3603
transport_path = 'quack'
3604
repo, client = self.setup_fake_client_and_repository(transport_path)
3605
client.add_expected_call(
3606
'PackRepository.autopack', ('quack/',),
3607
'error', ('MemoryError',))
3608
err = self.assertRaises(errors.BzrError, repo.autopack)
3609
self.assertContainsRe(str(err), "^remote server out of mem")
3612
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
3613
"""Base class for unit tests for bzrlib.remote._translate_error."""
3615
def translateTuple(self, error_tuple, **context):
3616
"""Call _translate_error with an ErrorFromSmartServer built from the
3619
:param error_tuple: A tuple of a smart server response, as would be
3620
passed to an ErrorFromSmartServer.
3621
:kwargs context: context items to call _translate_error with.
3623
:returns: The error raised by _translate_error.
3625
# Raise the ErrorFromSmartServer before passing it as an argument,
3626
# because _translate_error may need to re-raise it with a bare 'raise'
3628
server_error = errors.ErrorFromSmartServer(error_tuple)
3629
translated_error = self.translateErrorFromSmartServer(
3630
server_error, **context)
3631
return translated_error
3633
def translateErrorFromSmartServer(self, error_object, **context):
3634
"""Like translateTuple, but takes an already constructed
3635
ErrorFromSmartServer rather than a tuple.
3639
except errors.ErrorFromSmartServer, server_error:
3640
translated_error = self.assertRaises(
3641
errors.BzrError, remote._translate_error, server_error,
3643
return translated_error
3646
class TestErrorTranslationSuccess(TestErrorTranslationBase):
3647
"""Unit tests for bzrlib.remote._translate_error.
3649
Given an ErrorFromSmartServer (which has an error tuple from a smart
3650
server) and some context, _translate_error raises more specific errors from
3653
This test case covers the cases where _translate_error succeeds in
3654
translating an ErrorFromSmartServer to something better. See
3655
TestErrorTranslationRobustness for other cases.
3658
def test_NoSuchRevision(self):
3659
branch = self.make_branch('')
3661
translated_error = self.translateTuple(
3662
('NoSuchRevision', revid), branch=branch)
3663
expected_error = errors.NoSuchRevision(branch, revid)
3664
self.assertEqual(expected_error, translated_error)
3666
def test_nosuchrevision(self):
3667
repository = self.make_repository('')
3669
translated_error = self.translateTuple(
3670
('nosuchrevision', revid), repository=repository)
3671
expected_error = errors.NoSuchRevision(repository, revid)
3672
self.assertEqual(expected_error, translated_error)
3674
def test_nobranch(self):
3675
bzrdir = self.make_bzrdir('')
3676
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
3677
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
3678
self.assertEqual(expected_error, translated_error)
3680
def test_nobranch_one_arg(self):
3681
bzrdir = self.make_bzrdir('')
3682
translated_error = self.translateTuple(
3683
('nobranch', 'extra detail'), bzrdir=bzrdir)
3684
expected_error = errors.NotBranchError(
3685
path=bzrdir.root_transport.base,
3686
detail='extra detail')
3687
self.assertEqual(expected_error, translated_error)
3689
def test_norepository(self):
3690
bzrdir = self.make_bzrdir('')
3691
translated_error = self.translateTuple(('norepository',),
3693
expected_error = errors.NoRepositoryPresent(bzrdir)
3694
self.assertEqual(expected_error, translated_error)
3696
def test_LockContention(self):
3697
translated_error = self.translateTuple(('LockContention',))
3698
expected_error = errors.LockContention('(remote lock)')
3699
self.assertEqual(expected_error, translated_error)
3701
def test_UnlockableTransport(self):
3702
bzrdir = self.make_bzrdir('')
3703
translated_error = self.translateTuple(
3704
('UnlockableTransport',), bzrdir=bzrdir)
3705
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
3706
self.assertEqual(expected_error, translated_error)
3708
def test_LockFailed(self):
3709
lock = 'str() of a server lock'
3710
why = 'str() of why'
3711
translated_error = self.translateTuple(('LockFailed', lock, why))
3712
expected_error = errors.LockFailed(lock, why)
3713
self.assertEqual(expected_error, translated_error)
3715
def test_TokenMismatch(self):
3716
token = 'a lock token'
3717
translated_error = self.translateTuple(('TokenMismatch',), token=token)
3718
expected_error = errors.TokenMismatch(token, '(remote token)')
3719
self.assertEqual(expected_error, translated_error)
3721
def test_Diverged(self):
3722
branch = self.make_branch('a')
3723
other_branch = self.make_branch('b')
3724
translated_error = self.translateTuple(
3725
('Diverged',), branch=branch, other_branch=other_branch)
3726
expected_error = errors.DivergedBranches(branch, other_branch)
3727
self.assertEqual(expected_error, translated_error)
3729
def test_NotStacked(self):
3730
branch = self.make_branch('')
3731
translated_error = self.translateTuple(('NotStacked',), branch=branch)
3732
expected_error = errors.NotStacked(branch)
3733
self.assertEqual(expected_error, translated_error)
3735
def test_ReadError_no_args(self):
3737
translated_error = self.translateTuple(('ReadError',), path=path)
3738
expected_error = errors.ReadError(path)
3739
self.assertEqual(expected_error, translated_error)
3741
def test_ReadError(self):
3743
translated_error = self.translateTuple(('ReadError', path))
3744
expected_error = errors.ReadError(path)
3745
self.assertEqual(expected_error, translated_error)
3747
def test_IncompatibleRepositories(self):
3748
translated_error = self.translateTuple(('IncompatibleRepositories',
3749
"repo1", "repo2", "details here"))
3750
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
3752
self.assertEqual(expected_error, translated_error)
3754
def test_PermissionDenied_no_args(self):
3756
translated_error = self.translateTuple(('PermissionDenied',),
3758
expected_error = errors.PermissionDenied(path)
3759
self.assertEqual(expected_error, translated_error)
3761
def test_PermissionDenied_one_arg(self):
3763
translated_error = self.translateTuple(('PermissionDenied', path))
3764
expected_error = errors.PermissionDenied(path)
3765
self.assertEqual(expected_error, translated_error)
3767
def test_PermissionDenied_one_arg_and_context(self):
3768
"""Given a choice between a path from the local context and a path on
3769
the wire, _translate_error prefers the path from the local context.
3771
local_path = 'local path'
3772
remote_path = 'remote path'
3773
translated_error = self.translateTuple(
3774
('PermissionDenied', remote_path), path=local_path)
3775
expected_error = errors.PermissionDenied(local_path)
3776
self.assertEqual(expected_error, translated_error)
3778
def test_PermissionDenied_two_args(self):
3780
extra = 'a string with extra info'
3781
translated_error = self.translateTuple(
3782
('PermissionDenied', path, extra))
3783
expected_error = errors.PermissionDenied(path, extra)
3784
self.assertEqual(expected_error, translated_error)
3786
# GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
3788
def test_NoSuchFile_context_path(self):
3789
local_path = "local path"
3790
translated_error = self.translateTuple(('ReadError', "remote path"),
3792
expected_error = errors.ReadError(local_path)
3793
self.assertEqual(expected_error, translated_error)
3795
def test_NoSuchFile_without_context(self):
3796
remote_path = "remote path"
3797
translated_error = self.translateTuple(('ReadError', remote_path))
3798
expected_error = errors.ReadError(remote_path)
3799
self.assertEqual(expected_error, translated_error)
3801
def test_ReadOnlyError(self):
3802
translated_error = self.translateTuple(('ReadOnlyError',))
3803
expected_error = errors.TransportNotPossible("readonly transport")
3804
self.assertEqual(expected_error, translated_error)
3806
def test_MemoryError(self):
3807
translated_error = self.translateTuple(('MemoryError',))
3808
self.assertStartsWith(str(translated_error),
3809
"remote server out of memory")
3811
def test_generic_IndexError_no_classname(self):
3812
err = errors.ErrorFromSmartServer(('error', "list index out of range"))
3813
translated_error = self.translateErrorFromSmartServer(err)
3814
expected_error = errors.UnknownErrorFromSmartServer(err)
3815
self.assertEqual(expected_error, translated_error)
3817
# GZ 2011-03-02: TODO test generic non-ascii error string
3819
def test_generic_KeyError(self):
3820
err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
3821
translated_error = self.translateErrorFromSmartServer(err)
3822
expected_error = errors.UnknownErrorFromSmartServer(err)
3823
self.assertEqual(expected_error, translated_error)
3826
class TestErrorTranslationRobustness(TestErrorTranslationBase):
3827
"""Unit tests for bzrlib.remote._translate_error's robustness.
3829
TestErrorTranslationSuccess is for cases where _translate_error can
3830
translate successfully. This class about how _translate_err behaves when
3831
it fails to translate: it re-raises the original error.
3834
def test_unrecognised_server_error(self):
3835
"""If the error code from the server is not recognised, the original
3836
ErrorFromSmartServer is propagated unmodified.
3838
error_tuple = ('An unknown error tuple',)
3839
server_error = errors.ErrorFromSmartServer(error_tuple)
3840
translated_error = self.translateErrorFromSmartServer(server_error)
3841
expected_error = errors.UnknownErrorFromSmartServer(server_error)
3842
self.assertEqual(expected_error, translated_error)
3844
def test_context_missing_a_key(self):
3845
"""In case of a bug in the client, or perhaps an unexpected response
3846
from a server, _translate_error returns the original error tuple from
3847
the server and mutters a warning.
3849
# To translate a NoSuchRevision error _translate_error needs a 'branch'
3850
# in the context dict. So let's give it an empty context dict instead
3851
# to exercise its error recovery.
3853
error_tuple = ('NoSuchRevision', 'revid')
3854
server_error = errors.ErrorFromSmartServer(error_tuple)
3855
translated_error = self.translateErrorFromSmartServer(server_error)
3856
self.assertEqual(server_error, translated_error)
3857
# In addition to re-raising ErrorFromSmartServer, some debug info has
3858
# been muttered to the log file for developer to look at.
3859
self.assertContainsRe(
3861
"Missing key 'branch' in context")
3863
def test_path_missing(self):
3864
"""Some translations (PermissionDenied, ReadError) can determine the
3865
'path' variable from either the wire or the local context. If neither
3866
has it, then an error is raised.
3868
error_tuple = ('ReadError',)
3869
server_error = errors.ErrorFromSmartServer(error_tuple)
3870
translated_error = self.translateErrorFromSmartServer(server_error)
3871
self.assertEqual(server_error, translated_error)
3872
# In addition to re-raising ErrorFromSmartServer, some debug info has
3873
# been muttered to the log file for developer to look at.
3874
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
3877
class TestStacking(tests.TestCaseWithTransport):
3878
"""Tests for operations on stacked remote repositories.
3880
The underlying format type must support stacking.
3883
def test_access_stacked_remote(self):
3884
# based on <http://launchpad.net/bugs/261315>
3885
# make a branch stacked on another repository containing an empty
3886
# revision, then open it over hpss - we should be able to see that
3888
base_transport = self.get_transport()
3889
base_builder = self.make_branch_builder('base', format='1.9')
3890
base_builder.start_series()
3891
base_revid = base_builder.build_snapshot('rev-id', None,
3892
[('add', ('', None, 'directory', None))],
3894
base_builder.finish_series()
3895
stacked_branch = self.make_branch('stacked', format='1.9')
3896
stacked_branch.set_stacked_on_url('../base')
3897
# start a server looking at this
3898
smart_server = test_server.SmartTCPServer_for_testing()
3899
self.start_server(smart_server)
3900
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3901
# can get its branch and repository
3902
remote_branch = remote_bzrdir.open_branch()
3903
remote_repo = remote_branch.repository
3904
remote_repo.lock_read()
3906
# it should have an appropriate fallback repository, which should also
3907
# be a RemoteRepository
3908
self.assertLength(1, remote_repo._fallback_repositories)
3909
self.assertIsInstance(remote_repo._fallback_repositories[0],
3911
# and it has the revision committed to the underlying repository;
3912
# these have varying implementations so we try several of them
3913
self.assertTrue(remote_repo.has_revisions([base_revid]))
3914
self.assertTrue(remote_repo.has_revision(base_revid))
3915
self.assertEqual(remote_repo.get_revision(base_revid).message,
3918
remote_repo.unlock()
3920
def prepare_stacked_remote_branch(self):
3921
"""Get stacked_upon and stacked branches with content in each."""
3922
self.setup_smart_server_with_call_log()
3923
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3924
tree1.commit('rev1', rev_id='rev1')
3925
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3926
).open_workingtree()
3927
local_tree = tree2.branch.create_checkout('local')
3928
local_tree.commit('local changes make me feel good.')
3929
branch2 = Branch.open(self.get_url('tree2'))
3931
self.addCleanup(branch2.unlock)
3932
return tree1.branch, branch2
3934
def test_stacked_get_parent_map(self):
3935
# the public implementation of get_parent_map obeys stacking
3936
_, branch = self.prepare_stacked_remote_branch()
3937
repo = branch.repository
3938
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3940
def test_unstacked_get_parent_map(self):
3941
# _unstacked_provider.get_parent_map ignores stacking
3942
_, branch = self.prepare_stacked_remote_branch()
3943
provider = branch.repository._unstacked_provider
3944
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3946
def fetch_stream_to_rev_order(self, stream):
3948
for kind, substream in stream:
3949
if not kind == 'revisions':
3952
for content in substream:
3953
result.append(content.key[-1])
3956
def get_ordered_revs(self, format, order, branch_factory=None):
3957
"""Get a list of the revisions in a stream to format format.
3959
:param format: The format of the target.
3960
:param order: the order that target should have requested.
3961
:param branch_factory: A callable to create a trunk and stacked branch
3962
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3963
:result: The revision ids in the stream, in the order seen,
3964
the topological order of revisions in the source.
3966
unordered_format = bzrdir.format_registry.get(format)()
3967
target_repository_format = unordered_format.repository_format
3969
self.assertEqual(order, target_repository_format._fetch_order)
3970
if branch_factory is None:
3971
branch_factory = self.prepare_stacked_remote_branch
3972
_, stacked = branch_factory()
3973
source = stacked.repository._get_source(target_repository_format)
3974
tip = stacked.last_revision()
3975
stacked.repository._ensure_real()
3976
graph = stacked.repository.get_graph()
3977
revs = [r for (r,ps) in graph.iter_ancestry([tip])
3978
if r != NULL_REVISION]
3980
search = vf_search.PendingAncestryResult([tip], stacked.repository)
3981
self.reset_smart_call_log()
3982
stream = source.get_stream(search)
3983
# We trust that if a revision is in the stream the rest of the new
3984
# content for it is too, as per our main fetch tests; here we are
3985
# checking that the revisions are actually included at all, and their
3987
return self.fetch_stream_to_rev_order(stream), revs
3989
def test_stacked_get_stream_unordered(self):
3990
# Repository._get_source.get_stream() from a stacked repository with
3991
# unordered yields the full data from both stacked and stacked upon
3993
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3994
self.assertEqual(set(expected_revs), set(rev_ord))
3995
# Getting unordered results should have made a streaming data request
3996
# from the server, then one from the backing branch.
3997
self.assertLength(2, self.hpss_calls)
3999
def test_stacked_on_stacked_get_stream_unordered(self):
4000
# Repository._get_source.get_stream() from a stacked repository which
4001
# is itself stacked yields the full data from all three sources.
4002
def make_stacked_stacked():
4003
_, stacked = self.prepare_stacked_remote_branch()
4004
tree = stacked.bzrdir.sprout('tree3', stacked=True
4005
).open_workingtree()
4006
local_tree = tree.branch.create_checkout('local-tree3')
4007
local_tree.commit('more local changes are better')
4008
branch = Branch.open(self.get_url('tree3'))
4010
self.addCleanup(branch.unlock)
4012
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
4013
branch_factory=make_stacked_stacked)
4014
self.assertEqual(set(expected_revs), set(rev_ord))
4015
# Getting unordered results should have made a streaming data request
4016
# from the server, and one from each backing repo
4017
self.assertLength(3, self.hpss_calls)
4019
def test_stacked_get_stream_topological(self):
4020
# Repository._get_source.get_stream() from a stacked repository with
4021
# topological sorting yields the full data from both stacked and
4022
# stacked upon sources in topological order.
4023
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
4024
self.assertEqual(expected_revs, rev_ord)
4025
# Getting topological sort requires VFS calls still - one of which is
4026
# pushing up from the bound branch.
4027
self.assertLength(14, self.hpss_calls)
4029
def test_stacked_get_stream_groupcompress(self):
4030
# Repository._get_source.get_stream() from a stacked repository with
4031
# groupcompress sorting yields the full data from both stacked and
4032
# stacked upon sources in groupcompress order.
4033
raise tests.TestSkipped('No groupcompress ordered format available')
4034
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
4035
self.assertEqual(expected_revs, reversed(rev_ord))
4036
# Getting unordered results should have made a streaming data request
4037
# from the backing branch, and one from the stacked on branch.
4038
self.assertLength(2, self.hpss_calls)
4040
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
4041
# When pulling some fixed amount of content that is more than the
4042
# source has (because some is coming from a fallback branch, no error
4043
# should be received. This was reported as bug 360791.
4044
# Need three branches: a trunk, a stacked branch, and a preexisting
4045
# branch pulling content from stacked and trunk.
4046
self.setup_smart_server_with_call_log()
4047
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
4048
r1 = trunk.commit('start')
4049
stacked_branch = trunk.branch.create_clone_on_transport(
4050
self.get_transport('stacked'), stacked_on=trunk.branch.base)
4051
local = self.make_branch('local', format='1.9-rich-root')
4052
local.repository.fetch(stacked_branch.repository,
4053
stacked_branch.last_revision())
4056
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
4059
super(TestRemoteBranchEffort, self).setUp()
4060
# Create a smart server that publishes whatever the backing VFS server
4062
self.smart_server = test_server.SmartTCPServer_for_testing()
4063
self.start_server(self.smart_server, self.get_server())
4064
# Log all HPSS calls into self.hpss_calls.
4065
_SmartClient.hooks.install_named_hook(
4066
'call', self.capture_hpss_call, None)
4067
self.hpss_calls = []
4069
def capture_hpss_call(self, params):
4070
self.hpss_calls.append(params.method)
4072
def test_copy_content_into_avoids_revision_history(self):
4073
local = self.make_branch('local')
4074
builder = self.make_branch_builder('remote')
4075
builder.build_commit(message="Commit.")
4076
remote_branch_url = self.smart_server.get_url() + 'remote'
4077
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4078
local.repository.fetch(remote_branch.repository)
4079
self.hpss_calls = []
4080
remote_branch.copy_content_into(local)
4081
self.assertFalse('Branch.revision_history' in self.hpss_calls)
4083
def test_fetch_everything_needs_just_one_call(self):
4084
local = self.make_branch('local')
4085
builder = self.make_branch_builder('remote')
4086
builder.build_commit(message="Commit.")
4087
remote_branch_url = self.smart_server.get_url() + 'remote'
4088
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4089
self.hpss_calls = []
4090
local.repository.fetch(
4091
remote_branch.repository,
4092
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4093
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
4095
def override_verb(self, verb_name, verb):
4096
request_handlers = request.request_handlers
4097
orig_verb = request_handlers.get(verb_name)
4098
orig_info = request_handlers.get_info(verb_name)
4099
request_handlers.register(verb_name, verb, override_existing=True)
4100
self.addCleanup(request_handlers.register, verb_name, orig_verb,
4101
override_existing=True, info=orig_info)
4103
def test_fetch_everything_backwards_compat(self):
4104
"""Can fetch with EverythingResult even with pre 2.4 servers.
4106
Pre-2.4 do not support 'everything' searches with the
4107
Repository.get_stream_1.19 verb.
4110
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
4111
"""A version of the Repository.get_stream_1.19 verb patched to
4112
reject 'everything' searches the way 2.3 and earlier do.
4114
def recreate_search(self, repository, search_bytes,
4115
discard_excess=False):
4116
verb_log.append(search_bytes.split('\n', 1)[0])
4117
if search_bytes == 'everything':
4119
request.FailedSmartServerResponse(('BadSearch',)))
4120
return super(OldGetStreamVerb,
4121
self).recreate_search(repository, search_bytes,
4122
discard_excess=discard_excess)
4123
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
4124
local = self.make_branch('local')
4125
builder = self.make_branch_builder('remote')
4126
builder.build_commit(message="Commit.")
4127
remote_branch_url = self.smart_server.get_url() + 'remote'
4128
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
4129
self.hpss_calls = []
4130
local.repository.fetch(
4131
remote_branch.repository,
4132
fetch_spec=vf_search.EverythingResult(remote_branch.repository))
4133
# make sure the overridden verb was used
4134
self.assertLength(1, verb_log)
4135
# more than one HPSS call is needed, but because it's a VFS callback
4136
# its hard to predict exactly how many.
4137
self.assertTrue(len(self.hpss_calls) > 1)
4140
class TestUpdateBoundBranchWithModifiedBoundLocation(
4141
tests.TestCaseWithTransport):
4142
"""Ensure correct handling of bound_location modifications.
4144
This is tested against a smart server as http://pad.lv/786980 was about a
4145
ReadOnlyError (write attempt during a read-only transaction) which can only
4146
happen in this context.
4150
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
4151
self.transport_server = test_server.SmartTCPServer_for_testing
4153
def make_master_and_checkout(self, master_name, checkout_name):
4154
# Create the master branch and its associated checkout
4155
self.master = self.make_branch_and_tree(master_name)
4156
self.checkout = self.master.branch.create_checkout(checkout_name)
4157
# Modify the master branch so there is something to update
4158
self.master.commit('add stuff')
4159
self.last_revid = self.master.commit('even more stuff')
4160
self.bound_location = self.checkout.branch.get_bound_location()
4162
def assertUpdateSucceeds(self, new_location):
4163
self.checkout.branch.set_bound_location(new_location)
4164
self.checkout.update()
4165
self.assertEquals(self.last_revid, self.checkout.last_revision())
4167
def test_without_final_slash(self):
4168
self.make_master_and_checkout('master', 'checkout')
4169
# For unclear reasons some users have a bound_location without a final
4170
# '/', simulate that by forcing such a value
4171
self.assertEndsWith(self.bound_location, '/')
4172
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
4174
def test_plus_sign(self):
4175
self.make_master_and_checkout('+master', 'checkout')
4176
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
4178
def test_tilda(self):
4179
# Embed ~ in the middle of the path just to avoid any $HOME
4181
self.make_master_and_checkout('mas~ter', 'checkout')
4182
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
4185
class TestWithCustomErrorHandler(RemoteBranchTestCase):
4187
def test_no_context(self):
4188
class OutOfCoffee(errors.BzrError):
4189
"""A dummy exception for testing."""
4191
def __init__(self, urgency):
4192
self.urgency = urgency
4193
remote.no_context_error_translators.register("OutOfCoffee",
4194
lambda err: OutOfCoffee(err.error_args[0]))
4195
transport = MemoryTransport()
4196
client = FakeClient(transport.base)
4197
client.add_expected_call(
4198
'Branch.get_stacked_on_url', ('quack/',),
4199
'error', ('NotStacked',))
4200
client.add_expected_call(
4201
'Branch.last_revision_info',
4203
'error', ('OutOfCoffee', 'low'))
4204
transport.mkdir('quack')
4205
transport = transport.clone('quack')
4206
branch = self.make_remote_branch(transport, client)
4207
self.assertRaises(OutOfCoffee, branch.last_revision_info)
4208
self.assertFinished(client)
4210
def test_with_context(self):
4211
class OutOfTea(errors.BzrError):
4212
def __init__(self, branch, urgency):
4213
self.branch = branch
4214
self.urgency = urgency
4215
remote.error_translators.register("OutOfTea",
4216
lambda err, find, path: OutOfTea(err.error_args[0],
4218
transport = MemoryTransport()
4219
client = FakeClient(transport.base)
4220
client.add_expected_call(
4221
'Branch.get_stacked_on_url', ('quack/',),
4222
'error', ('NotStacked',))
4223
client.add_expected_call(
4224
'Branch.last_revision_info',
4226
'error', ('OutOfTea', 'low'))
4227
transport.mkdir('quack')
4228
transport = transport.clone('quack')
4229
branch = self.make_remote_branch(transport, client)
4230
self.assertRaises(OutOfTea, branch.last_revision_info)
4231
self.assertFinished(client)
4234
class TestRepositoryPack(TestRemoteRepository):
4236
def test_pack(self):
4237
transport_path = 'quack'
4238
repo, client = self.setup_fake_client_and_repository(transport_path)
4239
client.add_expected_call(
4240
'Repository.lock_write', ('quack/', ''),
4241
'success', ('ok', 'token'))
4242
client.add_expected_call(
4243
'Repository.pack', ('quack/', 'token', 'False'),
4244
'success', ('ok',), )
4245
client.add_expected_call(
4246
'Repository.unlock', ('quack/', 'token'),
4247
'success', ('ok', ))
4250
def test_pack_with_hint(self):
4251
transport_path = 'quack'
4252
repo, client = self.setup_fake_client_and_repository(transport_path)
4253
client.add_expected_call(
4254
'Repository.lock_write', ('quack/', ''),
4255
'success', ('ok', 'token'))
4256
client.add_expected_call(
4257
'Repository.pack', ('quack/', 'token', 'False'),
4258
'success', ('ok',), )
4259
client.add_expected_call(
4260
'Repository.unlock', ('quack/', 'token', 'False'),
4261
'success', ('ok', ))
4262
repo.pack(['hinta', 'hintb'])
4265
class TestRepositoryIterInventories(TestRemoteRepository):
4266
"""Test Repository.iter_inventories."""
4268
def _serialize_inv_delta(self, old_name, new_name, delta):
4269
serializer = inventory_delta.InventoryDeltaSerializer(True, False)
4270
return "".join(serializer.delta_to_lines(old_name, new_name, delta))
4272
def test_single_empty(self):
4273
transport_path = 'quack'
4274
repo, client = self.setup_fake_client_and_repository(transport_path)
4275
fmt = bzrdir.format_registry.get('2a')().repository_format
4277
stream = [('inventory-deltas', [
4278
versionedfile.FulltextContentFactory('somerevid', None, None,
4279
self._serialize_inv_delta('null:', 'somerevid', []))])]
4280
client.add_expected_call(
4281
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4282
'success', ('ok', ),
4283
_stream_to_byte_stream(stream, fmt))
4284
ret = list(repo.iter_inventories(["somerevid"]))
4285
self.assertLength(1, ret)
4287
self.assertEquals("somerevid", inv.revision_id)
4289
def test_empty(self):
4290
transport_path = 'quack'
4291
repo, client = self.setup_fake_client_and_repository(transport_path)
4292
ret = list(repo.iter_inventories([]))
4293
self.assertEquals(ret, [])
4295
def test_missing(self):
4296
transport_path = 'quack'
4297
repo, client = self.setup_fake_client_and_repository(transport_path)
4298
client.add_expected_call(
4299
'VersionedFileRepository.get_inventories', ('quack/', 'unordered'),
4300
'success', ('ok', ), iter([]))
4301
self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(