1
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for remote bzrdir/branch/repo/etc
19
These are proxy objects which act on remote objects by sending messages
20
through a smart client. The proxies are to be created when attempting to open
21
the object given a transport that supports smartserver rpc operations.
23
These tests correspond to tests.test_smart, which exercises the server side.
27
from cStringIO import StringIO
41
from bzrlib.branch import Branch
42
from bzrlib.bzrdir import BzrDir, BzrDirFormat
43
from bzrlib.remote import (
49
RemoteRepositoryFormat,
51
from bzrlib.repofmt import pack_repo
52
from bzrlib.revision import NULL_REVISION
53
from bzrlib.smart import server, medium
54
from bzrlib.smart.client import _SmartClient
55
from bzrlib.tests import (
57
split_suite_by_condition,
60
from bzrlib.transport import get_transport, http
61
from bzrlib.transport.memory import MemoryTransport
62
from bzrlib.transport.remote import (
68
def load_tests(standard_tests, module, loader):
69
to_adapt, result = split_suite_by_condition(
70
standard_tests, condition_isinstance(BasicRemoteObjectTests))
71
smart_server_version_scenarios = [
73
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
75
{'transport_server': server.SmartTCPServer_for_testing})]
76
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
79
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
82
super(BasicRemoteObjectTests, self).setUp()
83
self.transport = self.get_transport()
84
# make a branch that can be opened over the smart transport
85
self.local_wt = BzrDir.create_standalone_workingtree('.')
88
self.transport.disconnect()
89
tests.TestCaseWithTransport.tearDown(self)
91
def test_create_remote_bzrdir(self):
92
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
93
self.assertIsInstance(b, BzrDir)
95
def test_open_remote_branch(self):
96
# open a standalone branch in the working directory
97
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
98
branch = b.open_branch()
99
self.assertIsInstance(branch, Branch)
101
def test_remote_repository(self):
102
b = BzrDir.open_from_transport(self.transport)
103
repo = b.open_repository()
104
revid = u'\xc823123123'.encode('utf8')
105
self.assertFalse(repo.has_revision(revid))
106
self.local_wt.commit(message='test commit', rev_id=revid)
107
self.assertTrue(repo.has_revision(revid))
109
def test_remote_branch_revision_history(self):
110
b = BzrDir.open_from_transport(self.transport).open_branch()
111
self.assertEqual([], b.revision_history())
112
r1 = self.local_wt.commit('1st commit')
113
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
114
self.assertEqual([r1, r2], b.revision_history())
116
def test_find_correct_format(self):
117
"""Should open a RemoteBzrDir over a RemoteTransport"""
118
fmt = BzrDirFormat.find_format(self.transport)
119
self.assertTrue(RemoteBzrDirFormat
120
in BzrDirFormat._control_server_formats)
121
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
123
def test_open_detected_smart_format(self):
124
fmt = BzrDirFormat.find_format(self.transport)
125
d = fmt.open(self.transport)
126
self.assertIsInstance(d, BzrDir)
128
def test_remote_branch_repr(self):
129
b = BzrDir.open_from_transport(self.transport).open_branch()
130
self.assertStartsWith(str(b), 'RemoteBranch(')
132
def test_remote_branch_format_supports_stacking(self):
134
self.make_branch('unstackable', format='pack-0.92')
135
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
136
self.assertFalse(b._format.supports_stacking())
137
self.make_branch('stackable', format='1.9')
138
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
139
self.assertTrue(b._format.supports_stacking())
141
def test_remote_repo_format_supports_external_references(self):
143
bd = self.make_bzrdir('unstackable', format='pack-0.92')
144
r = bd.create_repository()
145
self.assertFalse(r._format.supports_external_lookups)
146
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
147
self.assertFalse(r._format.supports_external_lookups)
148
bd = self.make_bzrdir('stackable', format='1.9')
149
r = bd.create_repository()
150
self.assertTrue(r._format.supports_external_lookups)
151
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
152
self.assertTrue(r._format.supports_external_lookups)
155
class FakeProtocol(object):
156
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
158
def __init__(self, body, fake_client):
160
self._body_buffer = None
161
self._fake_client = fake_client
163
def read_body_bytes(self, count=-1):
164
if self._body_buffer is None:
165
self._body_buffer = StringIO(self.body)
166
bytes = self._body_buffer.read(count)
167
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
168
self._fake_client.expecting_body = False
171
def cancel_read_body(self):
172
self._fake_client.expecting_body = False
174
def read_streamed_body(self):
178
class FakeClient(_SmartClient):
179
"""Lookalike for _SmartClient allowing testing."""
181
def __init__(self, fake_medium_base='fake base'):
182
"""Create a FakeClient."""
185
self.expecting_body = False
186
# if non-None, this is the list of expected calls, with only the
187
# method name and arguments included. the body might be hard to
188
# compute so is not included. If a call is None, that call can
190
self._expected_calls = None
191
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
193
def add_expected_call(self, call_name, call_args, response_type,
194
response_args, response_body=None):
195
if self._expected_calls is None:
196
self._expected_calls = []
197
self._expected_calls.append((call_name, call_args))
198
self.responses.append((response_type, response_args, response_body))
200
def add_success_response(self, *args):
201
self.responses.append(('success', args, None))
203
def add_success_response_with_body(self, body, *args):
204
self.responses.append(('success', args, body))
205
if self._expected_calls is not None:
206
self._expected_calls.append(None)
208
def add_error_response(self, *args):
209
self.responses.append(('error', args))
211
def add_unknown_method_response(self, verb):
212
self.responses.append(('unknown', verb))
214
def finished_test(self):
215
if self._expected_calls:
216
raise AssertionError("%r finished but was still expecting %r"
217
% (self, self._expected_calls[0]))
219
def _get_next_response(self):
221
response_tuple = self.responses.pop(0)
222
except IndexError, e:
223
raise AssertionError("%r didn't expect any more calls"
225
if response_tuple[0] == 'unknown':
226
raise errors.UnknownSmartMethod(response_tuple[1])
227
elif response_tuple[0] == 'error':
228
raise errors.ErrorFromSmartServer(response_tuple[1])
229
return response_tuple
231
def _check_call(self, method, args):
232
if self._expected_calls is None:
233
# the test should be updated to say what it expects
236
next_call = self._expected_calls.pop(0)
238
raise AssertionError("%r didn't expect any more calls "
240
% (self, method, args,))
241
if next_call is None:
243
if method != next_call[0] or args != next_call[1]:
244
raise AssertionError("%r expected %r%r "
246
% (self, next_call[0], next_call[1], method, args,))
248
def call(self, method, *args):
249
self._check_call(method, args)
250
self._calls.append(('call', method, args))
251
return self._get_next_response()[1]
253
def call_expecting_body(self, method, *args):
254
self._check_call(method, args)
255
self._calls.append(('call_expecting_body', method, args))
256
result = self._get_next_response()
257
self.expecting_body = True
258
return result[1], FakeProtocol(result[2], self)
260
def call_with_body_bytes_expecting_body(self, method, args, body):
261
self._check_call(method, args)
262
self._calls.append(('call_with_body_bytes_expecting_body', method,
264
result = self._get_next_response()
265
self.expecting_body = True
266
return result[1], FakeProtocol(result[2], self)
268
def call_with_body_stream(self, args, stream):
269
# Explicitly consume the stream before checking for an error, because
270
# that's what happens a real medium.
271
stream = list(stream)
272
self._check_call(args[0], args[1:])
273
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
274
result = self._get_next_response()
275
# The second value returned from call_with_body_stream is supposed to
276
# be a response_handler object, but so far no tests depend on that.
277
response_handler = None
278
return result[1], response_handler
281
class FakeMedium(medium.SmartClientMedium):
283
def __init__(self, client_calls, base):
284
medium.SmartClientMedium.__init__(self, base)
285
self._client_calls = client_calls
287
def disconnect(self):
288
self._client_calls.append(('disconnect medium',))
291
class TestVfsHas(tests.TestCase):
293
def test_unicode_path(self):
294
client = FakeClient('/')
295
client.add_success_response('yes',)
296
transport = RemoteTransport('bzr://localhost/', _client=client)
297
filename = u'/hell\u00d8'.encode('utf8')
298
result = transport.has(filename)
300
[('call', 'has', (filename,))],
302
self.assertTrue(result)
305
class TestRemote(tests.TestCaseWithMemoryTransport):
307
def get_branch_format(self):
308
reference_bzrdir_format = bzrdir.format_registry.get('default')()
309
return reference_bzrdir_format.get_branch_format()
311
def get_repo_format(self):
312
reference_bzrdir_format = bzrdir.format_registry.get('default')()
313
return reference_bzrdir_format.repository_format
315
def disable_verb(self, verb):
316
"""Disable a verb for one test."""
317
request_handlers = smart.request.request_handlers
318
orig_method = request_handlers.get(verb)
319
request_handlers.remove(verb)
321
request_handlers.register(verb, orig_method)
322
self.addCleanup(restoreVerb)
325
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
326
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
328
def assertRemotePath(self, expected, client_base, transport_base):
329
"""Assert that the result of
330
SmartClientMedium.remote_path_from_transport is the expected value for
331
a given client_base and transport_base.
333
client_medium = medium.SmartClientMedium(client_base)
334
transport = get_transport(transport_base)
335
result = client_medium.remote_path_from_transport(transport)
336
self.assertEqual(expected, result)
338
def test_remote_path_from_transport(self):
339
"""SmartClientMedium.remote_path_from_transport calculates a URL for
340
the given transport relative to the root of the client base URL.
342
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
343
self.assertRemotePath(
344
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
346
def assertRemotePathHTTP(self, expected, transport_base, relpath):
347
"""Assert that the result of
348
HttpTransportBase.remote_path_from_transport is the expected value for
349
a given transport_base and relpath of that transport. (Note that
350
HttpTransportBase is a subclass of SmartClientMedium)
352
base_transport = get_transport(transport_base)
353
client_medium = base_transport.get_smart_medium()
354
cloned_transport = base_transport.clone(relpath)
355
result = client_medium.remote_path_from_transport(cloned_transport)
356
self.assertEqual(expected, result)
358
def test_remote_path_from_transport_http(self):
359
"""Remote paths for HTTP transports are calculated differently to other
360
transports. They are just relative to the client base, not the root
361
directory of the host.
363
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
364
self.assertRemotePathHTTP(
365
'../xyz/', scheme + '//host/path', '../xyz/')
366
self.assertRemotePathHTTP(
367
'xyz/', scheme + '//host/path', 'xyz/')
370
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
371
"""Tests for the behaviour of client_medium.remote_is_at_least."""
373
def test_initially_unlimited(self):
374
"""A fresh medium assumes that the remote side supports all
377
client_medium = medium.SmartClientMedium('dummy base')
378
self.assertFalse(client_medium._is_remote_before((99, 99)))
380
def test__remember_remote_is_before(self):
381
"""Calling _remember_remote_is_before ratchets down the known remote
384
client_medium = medium.SmartClientMedium('dummy base')
385
# Mark the remote side as being less than 1.6. The remote side may
387
client_medium._remember_remote_is_before((1, 6))
388
self.assertTrue(client_medium._is_remote_before((1, 6)))
389
self.assertFalse(client_medium._is_remote_before((1, 5)))
390
# Calling _remember_remote_is_before again with a lower value works.
391
client_medium._remember_remote_is_before((1, 5))
392
self.assertTrue(client_medium._is_remote_before((1, 5)))
393
# You cannot call _remember_remote_is_before with a larger value.
395
AssertionError, client_medium._remember_remote_is_before, (1, 9))
398
class TestBzrDirCloningMetaDir(TestRemote):
400
def test_backwards_compat(self):
401
self.setup_smart_server_with_call_log()
402
a_dir = self.make_bzrdir('.')
403
self.reset_smart_call_log()
404
verb = 'BzrDir.cloning_metadir'
405
self.disable_verb(verb)
406
format = a_dir.cloning_metadir()
407
call_count = len([call for call in self.hpss_calls if
408
call.call.method == verb])
409
self.assertEqual(1, call_count)
411
def test_branch_reference(self):
412
transport = self.get_transport('quack')
413
referenced = self.make_branch('referenced')
414
expected = referenced.bzrdir.cloning_metadir()
415
client = FakeClient(transport.base)
416
client.add_expected_call(
417
'BzrDir.cloning_metadir', ('quack/', 'False'),
418
'error', ('BranchReference',)),
419
client.add_expected_call(
420
'BzrDir.open_branchV2', ('quack/',),
421
'success', ('ref', self.get_url('referenced'))),
422
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
424
result = a_bzrdir.cloning_metadir()
425
# We should have got a control dir matching the referenced branch.
426
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
427
self.assertEqual(expected._repository_format, result._repository_format)
428
self.assertEqual(expected._branch_format, result._branch_format)
429
client.finished_test()
431
def test_current_server(self):
432
transport = self.get_transport('.')
433
transport = transport.clone('quack')
434
self.make_bzrdir('quack')
435
client = FakeClient(transport.base)
436
reference_bzrdir_format = bzrdir.format_registry.get('default')()
437
control_name = reference_bzrdir_format.network_name()
438
client.add_expected_call(
439
'BzrDir.cloning_metadir', ('quack/', 'False'),
440
'success', (control_name, '', ('branch', ''))),
441
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
443
result = a_bzrdir.cloning_metadir()
444
# We should have got a reference control dir with default branch and
445
# repository formats.
446
# This pokes a little, just to be sure.
447
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
448
self.assertEqual(None, result._repository_format)
449
self.assertEqual(None, result._branch_format)
450
client.finished_test()
453
class TestBzrDirOpenBranch(TestRemote):
455
def test_backwards_compat(self):
456
self.setup_smart_server_with_call_log()
457
self.make_branch('.')
458
a_dir = BzrDir.open(self.get_url('.'))
459
self.reset_smart_call_log()
460
verb = 'BzrDir.open_branchV2'
461
self.disable_verb(verb)
462
format = a_dir.open_branch()
463
call_count = len([call for call in self.hpss_calls if
464
call.call.method == verb])
465
self.assertEqual(1, call_count)
467
def test_branch_present(self):
468
reference_format = self.get_repo_format()
469
network_name = reference_format.network_name()
470
branch_network_name = self.get_branch_format().network_name()
471
transport = MemoryTransport()
472
transport.mkdir('quack')
473
transport = transport.clone('quack')
474
client = FakeClient(transport.base)
475
client.add_expected_call(
476
'BzrDir.open_branchV2', ('quack/',),
477
'success', ('branch', branch_network_name))
478
client.add_expected_call(
479
'BzrDir.find_repositoryV3', ('quack/',),
480
'success', ('ok', '', 'no', 'no', 'no', network_name))
481
client.add_expected_call(
482
'Branch.get_stacked_on_url', ('quack/',),
483
'error', ('NotStacked',))
484
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
486
result = bzrdir.open_branch()
487
self.assertIsInstance(result, RemoteBranch)
488
self.assertEqual(bzrdir, result.bzrdir)
489
client.finished_test()
491
def test_branch_missing(self):
492
transport = MemoryTransport()
493
transport.mkdir('quack')
494
transport = transport.clone('quack')
495
client = FakeClient(transport.base)
496
client.add_error_response('nobranch')
497
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
499
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
501
[('call', 'BzrDir.open_branchV2', ('quack/',))],
504
def test__get_tree_branch(self):
505
# _get_tree_branch is a form of open_branch, but it should only ask for
506
# branch opening, not any other network requests.
509
calls.append("Called")
511
transport = MemoryTransport()
512
# no requests on the network - catches other api calls being made.
513
client = FakeClient(transport.base)
514
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
516
# patch the open_branch call to record that it was called.
517
bzrdir.open_branch = open_branch
518
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
519
self.assertEqual(["Called"], calls)
520
self.assertEqual([], client._calls)
522
def test_url_quoting_of_path(self):
523
# Relpaths on the wire should not be URL-escaped. So "~" should be
524
# transmitted as "~", not "%7E".
525
transport = RemoteTCPTransport('bzr://localhost/~hello/')
526
client = FakeClient(transport.base)
527
reference_format = self.get_repo_format()
528
network_name = reference_format.network_name()
529
branch_network_name = self.get_branch_format().network_name()
530
client.add_expected_call(
531
'BzrDir.open_branchV2', ('~hello/',),
532
'success', ('branch', branch_network_name))
533
client.add_expected_call(
534
'BzrDir.find_repositoryV3', ('~hello/',),
535
'success', ('ok', '', 'no', 'no', 'no', network_name))
536
client.add_expected_call(
537
'Branch.get_stacked_on_url', ('~hello/',),
538
'error', ('NotStacked',))
539
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
541
result = bzrdir.open_branch()
542
client.finished_test()
544
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
545
reference_format = self.get_repo_format()
546
network_name = reference_format.network_name()
547
transport = MemoryTransport()
548
transport.mkdir('quack')
549
transport = transport.clone('quack')
551
rich_response = 'yes'
555
subtree_response = 'yes'
557
subtree_response = 'no'
558
client = FakeClient(transport.base)
559
client.add_success_response(
560
'ok', '', rich_response, subtree_response, external_lookup,
562
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
564
result = bzrdir.open_repository()
566
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
568
self.assertIsInstance(result, RemoteRepository)
569
self.assertEqual(bzrdir, result.bzrdir)
570
self.assertEqual(rich_root, result._format.rich_root_data)
571
self.assertEqual(subtrees, result._format.supports_tree_reference)
573
def test_open_repository_sets_format_attributes(self):
574
self.check_open_repository(True, True)
575
self.check_open_repository(False, True)
576
self.check_open_repository(True, False)
577
self.check_open_repository(False, False)
578
self.check_open_repository(False, False, 'yes')
580
def test_old_server(self):
581
"""RemoteBzrDirFormat should fail to probe if the server version is too
584
self.assertRaises(errors.NotBranchError,
585
RemoteBzrDirFormat.probe_transport, OldServerTransport())
588
class TestBzrDirCreateBranch(TestRemote):
590
def test_backwards_compat(self):
591
self.setup_smart_server_with_call_log()
592
repo = self.make_repository('.')
593
self.reset_smart_call_log()
594
self.disable_verb('BzrDir.create_branch')
595
branch = repo.bzrdir.create_branch()
596
create_branch_call_count = len([call for call in self.hpss_calls if
597
call.call.method == 'BzrDir.create_branch'])
598
self.assertEqual(1, create_branch_call_count)
600
def test_current_server(self):
601
transport = self.get_transport('.')
602
transport = transport.clone('quack')
603
self.make_repository('quack')
604
client = FakeClient(transport.base)
605
reference_bzrdir_format = bzrdir.format_registry.get('default')()
606
reference_format = reference_bzrdir_format.get_branch_format()
607
network_name = reference_format.network_name()
608
reference_repo_fmt = reference_bzrdir_format.repository_format
609
reference_repo_name = reference_repo_fmt.network_name()
610
client.add_expected_call(
611
'BzrDir.create_branch', ('quack/', network_name),
612
'success', ('ok', network_name, '', 'no', 'no', 'yes',
613
reference_repo_name))
614
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
616
branch = a_bzrdir.create_branch()
617
# We should have got a remote branch
618
self.assertIsInstance(branch, remote.RemoteBranch)
619
# its format should have the settings from the response
620
format = branch._format
621
self.assertEqual(network_name, format.network_name())
624
class TestBzrDirCreateRepository(TestRemote):
626
def test_backwards_compat(self):
627
self.setup_smart_server_with_call_log()
628
bzrdir = self.make_bzrdir('.')
629
self.reset_smart_call_log()
630
self.disable_verb('BzrDir.create_repository')
631
repo = bzrdir.create_repository()
632
create_repo_call_count = len([call for call in self.hpss_calls if
633
call.call.method == 'BzrDir.create_repository'])
634
self.assertEqual(1, create_repo_call_count)
636
def test_current_server(self):
637
transport = self.get_transport('.')
638
transport = transport.clone('quack')
639
self.make_bzrdir('quack')
640
client = FakeClient(transport.base)
641
reference_bzrdir_format = bzrdir.format_registry.get('default')()
642
reference_format = reference_bzrdir_format.repository_format
643
network_name = reference_format.network_name()
644
client.add_expected_call(
645
'BzrDir.create_repository', ('quack/',
646
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
647
'success', ('ok', 'no', 'no', 'no', network_name))
648
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
650
repo = a_bzrdir.create_repository()
651
# We should have got a remote repository
652
self.assertIsInstance(repo, remote.RemoteRepository)
653
# its format should have the settings from the response
654
format = repo._format
655
self.assertFalse(format.rich_root_data)
656
self.assertFalse(format.supports_tree_reference)
657
self.assertFalse(format.supports_external_lookups)
658
self.assertEqual(network_name, format.network_name())
661
class TestBzrDirOpenRepository(TestRemote):
663
def test_backwards_compat_1_2_3(self):
664
# fallback all the way to the first version.
665
reference_format = self.get_repo_format()
666
network_name = reference_format.network_name()
667
client = FakeClient('bzr://example.com/')
668
client.add_unknown_method_response('BzrDir.find_repositoryV3')
669
client.add_unknown_method_response('BzrDir.find_repositoryV2')
670
client.add_success_response('ok', '', 'no', 'no')
671
# A real repository instance will be created to determine the network
673
client.add_success_response_with_body(
674
"Bazaar-NG meta directory, format 1\n", 'ok')
675
client.add_success_response_with_body(
676
reference_format.get_format_string(), 'ok')
677
# PackRepository wants to do a stat
678
client.add_success_response('stat', '0', '65535')
679
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
681
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
683
repo = bzrdir.open_repository()
685
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
686
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
687
('call', 'BzrDir.find_repository', ('quack/',)),
688
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
689
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
690
('call', 'stat', ('/quack/.bzr/repository',)),
693
self.assertEqual(network_name, repo._format.network_name())
695
def test_backwards_compat_2(self):
696
# fallback to find_repositoryV2
697
reference_format = self.get_repo_format()
698
network_name = reference_format.network_name()
699
client = FakeClient('bzr://example.com/')
700
client.add_unknown_method_response('BzrDir.find_repositoryV3')
701
client.add_success_response('ok', '', 'no', 'no', 'no')
702
# A real repository instance will be created to determine the network
704
client.add_success_response_with_body(
705
"Bazaar-NG meta directory, format 1\n", 'ok')
706
client.add_success_response_with_body(
707
reference_format.get_format_string(), 'ok')
708
# PackRepository wants to do a stat
709
client.add_success_response('stat', '0', '65535')
710
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
712
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
714
repo = bzrdir.open_repository()
716
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
717
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
718
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
719
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
720
('call', 'stat', ('/quack/.bzr/repository',)),
723
self.assertEqual(network_name, repo._format.network_name())
725
def test_current_server(self):
726
reference_format = self.get_repo_format()
727
network_name = reference_format.network_name()
728
transport = MemoryTransport()
729
transport.mkdir('quack')
730
transport = transport.clone('quack')
731
client = FakeClient(transport.base)
732
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
733
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
735
repo = bzrdir.open_repository()
737
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
739
self.assertEqual(network_name, repo._format.network_name())
742
class OldSmartClient(object):
743
"""A fake smart client for test_old_version that just returns a version one
744
response to the 'hello' (query version) command.
747
def get_request(self):
748
input_file = StringIO('ok\x011\n')
749
output_file = StringIO()
750
client_medium = medium.SmartSimplePipesClientMedium(
751
input_file, output_file)
752
return medium.SmartClientStreamMediumRequest(client_medium)
754
def protocol_version(self):
758
class OldServerTransport(object):
759
"""A fake transport for test_old_server that reports it's smart server
760
protocol version as version one.
766
def get_smart_client(self):
767
return OldSmartClient()
770
class RemoteBranchTestCase(TestRemote):
772
def make_remote_branch(self, transport, client):
773
"""Make a RemoteBranch using 'client' as its _SmartClient.
775
A RemoteBzrDir and RemoteRepository will also be created to fill out
776
the RemoteBranch, albeit with stub values for some of their attributes.
778
# we do not want bzrdir to make any remote calls, so use False as its
779
# _client. If it tries to make a remote call, this will fail
781
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
783
repo = RemoteRepository(bzrdir, None, _client=client)
784
branch_format = self.get_branch_format()
785
format = RemoteBranchFormat(network_name=branch_format.network_name())
786
return RemoteBranch(bzrdir, repo, _client=client, format=format)
789
class TestBranchGetParent(RemoteBranchTestCase):
791
def test_no_parent(self):
792
# in an empty branch we decode the response properly
793
transport = MemoryTransport()
794
client = FakeClient(transport.base)
795
client.add_expected_call(
796
'Branch.get_stacked_on_url', ('quack/',),
797
'error', ('NotStacked',))
798
client.add_expected_call(
799
'Branch.get_parent', ('quack/',),
801
transport.mkdir('quack')
802
transport = transport.clone('quack')
803
branch = self.make_remote_branch(transport, client)
804
result = branch.get_parent()
805
client.finished_test()
806
self.assertEqual(None, result)
808
def test_parent_relative(self):
809
transport = MemoryTransport()
810
client = FakeClient(transport.base)
811
client.add_expected_call(
812
'Branch.get_stacked_on_url', ('kwaak/',),
813
'error', ('NotStacked',))
814
client.add_expected_call(
815
'Branch.get_parent', ('kwaak/',),
816
'success', ('../foo/',))
817
transport.mkdir('kwaak')
818
transport = transport.clone('kwaak')
819
branch = self.make_remote_branch(transport, client)
820
result = branch.get_parent()
821
self.assertEqual(transport.clone('../foo').base, result)
823
def test_parent_absolute(self):
824
transport = MemoryTransport()
825
client = FakeClient(transport.base)
826
client.add_expected_call(
827
'Branch.get_stacked_on_url', ('kwaak/',),
828
'error', ('NotStacked',))
829
client.add_expected_call(
830
'Branch.get_parent', ('kwaak/',),
831
'success', ('http://foo/',))
832
transport.mkdir('kwaak')
833
transport = transport.clone('kwaak')
834
branch = self.make_remote_branch(transport, client)
835
result = branch.get_parent()
836
self.assertEqual('http://foo/', result)
839
class TestBranchGetTagsBytes(RemoteBranchTestCase):
841
def test_backwards_compat(self):
842
self.setup_smart_server_with_call_log()
843
branch = self.make_branch('.')
844
self.reset_smart_call_log()
845
verb = 'Branch.get_tags_bytes'
846
self.disable_verb(verb)
847
branch.tags.get_tag_dict()
848
call_count = len([call for call in self.hpss_calls if
849
call.call.method == verb])
850
self.assertEqual(1, call_count)
852
def test_trivial(self):
853
transport = MemoryTransport()
854
client = FakeClient(transport.base)
855
client.add_expected_call(
856
'Branch.get_stacked_on_url', ('quack/',),
857
'error', ('NotStacked',))
858
client.add_expected_call(
859
'Branch.get_tags_bytes', ('quack/',),
861
transport.mkdir('quack')
862
transport = transport.clone('quack')
863
branch = self.make_remote_branch(transport, client)
864
result = branch.tags.get_tag_dict()
865
client.finished_test()
866
self.assertEqual({}, result)
869
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
871
def test_empty_branch(self):
872
# in an empty branch we decode the response properly
873
transport = MemoryTransport()
874
client = FakeClient(transport.base)
875
client.add_expected_call(
876
'Branch.get_stacked_on_url', ('quack/',),
877
'error', ('NotStacked',))
878
client.add_expected_call(
879
'Branch.last_revision_info', ('quack/',),
880
'success', ('ok', '0', 'null:'))
881
transport.mkdir('quack')
882
transport = transport.clone('quack')
883
branch = self.make_remote_branch(transport, client)
884
result = branch.last_revision_info()
885
client.finished_test()
886
self.assertEqual((0, NULL_REVISION), result)
888
def test_non_empty_branch(self):
889
# in a non-empty branch we also decode the response properly
890
revid = u'\xc8'.encode('utf8')
891
transport = MemoryTransport()
892
client = FakeClient(transport.base)
893
client.add_expected_call(
894
'Branch.get_stacked_on_url', ('kwaak/',),
895
'error', ('NotStacked',))
896
client.add_expected_call(
897
'Branch.last_revision_info', ('kwaak/',),
898
'success', ('ok', '2', revid))
899
transport.mkdir('kwaak')
900
transport = transport.clone('kwaak')
901
branch = self.make_remote_branch(transport, client)
902
result = branch.last_revision_info()
903
self.assertEqual((2, revid), result)
906
class TestBranch_get_stacked_on_url(TestRemote):
907
"""Test Branch._get_stacked_on_url rpc"""
909
def test_get_stacked_on_invalid_url(self):
910
# test that asking for a stacked on url the server can't access works.
911
# This isn't perfect, but then as we're in the same process there
912
# really isn't anything we can do to be 100% sure that the server
913
# doesn't just open in - this test probably needs to be rewritten using
914
# a spawn()ed server.
915
stacked_branch = self.make_branch('stacked', format='1.9')
916
memory_branch = self.make_branch('base', format='1.9')
917
vfs_url = self.get_vfs_only_url('base')
918
stacked_branch.set_stacked_on_url(vfs_url)
919
transport = stacked_branch.bzrdir.root_transport
920
client = FakeClient(transport.base)
921
client.add_expected_call(
922
'Branch.get_stacked_on_url', ('stacked/',),
923
'success', ('ok', vfs_url))
924
# XXX: Multiple calls are bad, this second call documents what is
926
client.add_expected_call(
927
'Branch.get_stacked_on_url', ('stacked/',),
928
'success', ('ok', vfs_url))
929
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
931
repo_fmt = remote.RemoteRepositoryFormat()
932
repo_fmt._custom_format = stacked_branch.repository._format
933
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
935
result = branch.get_stacked_on_url()
936
self.assertEqual(vfs_url, result)
938
def test_backwards_compatible(self):
939
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
940
base_branch = self.make_branch('base', format='1.6')
941
stacked_branch = self.make_branch('stacked', format='1.6')
942
stacked_branch.set_stacked_on_url('../base')
943
client = FakeClient(self.get_url())
944
branch_network_name = self.get_branch_format().network_name()
945
client.add_expected_call(
946
'BzrDir.open_branchV2', ('stacked/',),
947
'success', ('branch', branch_network_name))
948
client.add_expected_call(
949
'BzrDir.find_repositoryV3', ('stacked/',),
950
'success', ('ok', '', 'no', 'no', 'yes',
951
stacked_branch.repository._format.network_name()))
952
# called twice, once from constructor and then again by us
953
client.add_expected_call(
954
'Branch.get_stacked_on_url', ('stacked/',),
955
'unknown', ('Branch.get_stacked_on_url',))
956
client.add_expected_call(
957
'Branch.get_stacked_on_url', ('stacked/',),
958
'unknown', ('Branch.get_stacked_on_url',))
959
# this will also do vfs access, but that goes direct to the transport
960
# and isn't seen by the FakeClient.
961
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
962
remote.RemoteBzrDirFormat(), _client=client)
963
branch = bzrdir.open_branch()
964
result = branch.get_stacked_on_url()
965
self.assertEqual('../base', result)
966
client.finished_test()
967
# it's in the fallback list both for the RemoteRepository and its vfs
969
self.assertEqual(1, len(branch.repository._fallback_repositories))
971
len(branch.repository._real_repository._fallback_repositories))
973
def test_get_stacked_on_real_branch(self):
974
base_branch = self.make_branch('base', format='1.6')
975
stacked_branch = self.make_branch('stacked', format='1.6')
976
stacked_branch.set_stacked_on_url('../base')
977
reference_format = self.get_repo_format()
978
network_name = reference_format.network_name()
979
client = FakeClient(self.get_url())
980
branch_network_name = self.get_branch_format().network_name()
981
client.add_expected_call(
982
'BzrDir.open_branchV2', ('stacked/',),
983
'success', ('branch', branch_network_name))
984
client.add_expected_call(
985
'BzrDir.find_repositoryV3', ('stacked/',),
986
'success', ('ok', '', 'no', 'no', 'yes', network_name))
987
# called twice, once from constructor and then again by us
988
client.add_expected_call(
989
'Branch.get_stacked_on_url', ('stacked/',),
990
'success', ('ok', '../base'))
991
client.add_expected_call(
992
'Branch.get_stacked_on_url', ('stacked/',),
993
'success', ('ok', '../base'))
994
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
995
remote.RemoteBzrDirFormat(), _client=client)
996
branch = bzrdir.open_branch()
997
result = branch.get_stacked_on_url()
998
self.assertEqual('../base', result)
999
client.finished_test()
1000
# it's in the fallback list both for the RemoteRepository and its vfs
1002
self.assertEqual(1, len(branch.repository._fallback_repositories))
1004
len(branch.repository._real_repository._fallback_repositories))
1007
class TestBranchSetLastRevision(RemoteBranchTestCase):
1009
def test_set_empty(self):
1010
# set_revision_history([]) is translated to calling
1011
# Branch.set_last_revision(path, '') on the wire.
1012
transport = MemoryTransport()
1013
transport.mkdir('branch')
1014
transport = transport.clone('branch')
1016
client = FakeClient(transport.base)
1017
client.add_expected_call(
1018
'Branch.get_stacked_on_url', ('branch/',),
1019
'error', ('NotStacked',))
1020
client.add_expected_call(
1021
'Branch.lock_write', ('branch/', '', ''),
1022
'success', ('ok', 'branch token', 'repo token'))
1023
client.add_expected_call(
1024
'Branch.last_revision_info',
1026
'success', ('ok', '0', 'null:'))
1027
client.add_expected_call(
1028
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1030
client.add_expected_call(
1031
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1033
branch = self.make_remote_branch(transport, client)
1034
# This is a hack to work around the problem that RemoteBranch currently
1035
# unnecessarily invokes _ensure_real upon a call to lock_write.
1036
branch._ensure_real = lambda: None
1038
result = branch.set_revision_history([])
1040
self.assertEqual(None, result)
1041
client.finished_test()
1043
def test_set_nonempty(self):
1044
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1045
# Branch.set_last_revision(path, rev-idN) on the wire.
1046
transport = MemoryTransport()
1047
transport.mkdir('branch')
1048
transport = transport.clone('branch')
1050
client = FakeClient(transport.base)
1051
client.add_expected_call(
1052
'Branch.get_stacked_on_url', ('branch/',),
1053
'error', ('NotStacked',))
1054
client.add_expected_call(
1055
'Branch.lock_write', ('branch/', '', ''),
1056
'success', ('ok', 'branch token', 'repo token'))
1057
client.add_expected_call(
1058
'Branch.last_revision_info',
1060
'success', ('ok', '0', 'null:'))
1062
encoded_body = bz2.compress('\n'.join(lines))
1063
client.add_success_response_with_body(encoded_body, 'ok')
1064
client.add_expected_call(
1065
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1067
client.add_expected_call(
1068
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1070
branch = self.make_remote_branch(transport, client)
1071
# This is a hack to work around the problem that RemoteBranch currently
1072
# unnecessarily invokes _ensure_real upon a call to lock_write.
1073
branch._ensure_real = lambda: None
1074
# Lock the branch, reset the record of remote calls.
1076
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1078
self.assertEqual(None, result)
1079
client.finished_test()
1081
def test_no_such_revision(self):
1082
transport = MemoryTransport()
1083
transport.mkdir('branch')
1084
transport = transport.clone('branch')
1085
# A response of 'NoSuchRevision' is translated into an exception.
1086
client = FakeClient(transport.base)
1087
client.add_expected_call(
1088
'Branch.get_stacked_on_url', ('branch/',),
1089
'error', ('NotStacked',))
1090
client.add_expected_call(
1091
'Branch.lock_write', ('branch/', '', ''),
1092
'success', ('ok', 'branch token', 'repo token'))
1093
client.add_expected_call(
1094
'Branch.last_revision_info',
1096
'success', ('ok', '0', 'null:'))
1097
# get_graph calls to construct the revision history, for the set_rh
1100
encoded_body = bz2.compress('\n'.join(lines))
1101
client.add_success_response_with_body(encoded_body, 'ok')
1102
client.add_expected_call(
1103
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1104
'error', ('NoSuchRevision', 'rev-id'))
1105
client.add_expected_call(
1106
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1109
branch = self.make_remote_branch(transport, client)
1112
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1114
client.finished_test()
1116
def test_tip_change_rejected(self):
1117
"""TipChangeRejected responses cause a TipChangeRejected exception to
1120
transport = MemoryTransport()
1121
transport.mkdir('branch')
1122
transport = transport.clone('branch')
1123
client = FakeClient(transport.base)
1124
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1125
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1126
client.add_expected_call(
1127
'Branch.get_stacked_on_url', ('branch/',),
1128
'error', ('NotStacked',))
1129
client.add_expected_call(
1130
'Branch.lock_write', ('branch/', '', ''),
1131
'success', ('ok', 'branch token', 'repo token'))
1132
client.add_expected_call(
1133
'Branch.last_revision_info',
1135
'success', ('ok', '0', 'null:'))
1137
encoded_body = bz2.compress('\n'.join(lines))
1138
client.add_success_response_with_body(encoded_body, 'ok')
1139
client.add_expected_call(
1140
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1141
'error', ('TipChangeRejected', rejection_msg_utf8))
1142
client.add_expected_call(
1143
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1145
branch = self.make_remote_branch(transport, client)
1146
branch._ensure_real = lambda: None
1148
# The 'TipChangeRejected' error response triggered by calling
1149
# set_revision_history causes a TipChangeRejected exception.
1150
err = self.assertRaises(
1151
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1152
# The UTF-8 message from the response has been decoded into a unicode
1154
self.assertIsInstance(err.msg, unicode)
1155
self.assertEqual(rejection_msg_unicode, err.msg)
1157
client.finished_test()
1160
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1162
def test_set_last_revision_info(self):
1163
# set_last_revision_info(num, 'rev-id') is translated to calling
1164
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1165
transport = MemoryTransport()
1166
transport.mkdir('branch')
1167
transport = transport.clone('branch')
1168
client = FakeClient(transport.base)
1169
# get_stacked_on_url
1170
client.add_error_response('NotStacked')
1172
client.add_success_response('ok', 'branch token', 'repo token')
1173
# query the current revision
1174
client.add_success_response('ok', '0', 'null:')
1176
client.add_success_response('ok')
1178
client.add_success_response('ok')
1180
branch = self.make_remote_branch(transport, client)
1181
# Lock the branch, reset the record of remote calls.
1184
result = branch.set_last_revision_info(1234, 'a-revision-id')
1186
[('call', 'Branch.last_revision_info', ('branch/',)),
1187
('call', 'Branch.set_last_revision_info',
1188
('branch/', 'branch token', 'repo token',
1189
'1234', 'a-revision-id'))],
1191
self.assertEqual(None, result)
1193
def test_no_such_revision(self):
1194
# A response of 'NoSuchRevision' is translated into an exception.
1195
transport = MemoryTransport()
1196
transport.mkdir('branch')
1197
transport = transport.clone('branch')
1198
client = FakeClient(transport.base)
1199
# get_stacked_on_url
1200
client.add_error_response('NotStacked')
1202
client.add_success_response('ok', 'branch token', 'repo token')
1204
client.add_error_response('NoSuchRevision', 'revid')
1206
client.add_success_response('ok')
1208
branch = self.make_remote_branch(transport, client)
1209
# Lock the branch, reset the record of remote calls.
1214
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1217
def lock_remote_branch(self, branch):
1218
"""Trick a RemoteBranch into thinking it is locked."""
1219
branch._lock_mode = 'w'
1220
branch._lock_count = 2
1221
branch._lock_token = 'branch token'
1222
branch._repo_lock_token = 'repo token'
1223
branch.repository._lock_mode = 'w'
1224
branch.repository._lock_count = 2
1225
branch.repository._lock_token = 'repo token'
1227
def test_backwards_compatibility(self):
1228
"""If the server does not support the Branch.set_last_revision_info
1229
verb (which is new in 1.4), then the client falls back to VFS methods.
1231
# This test is a little messy. Unlike most tests in this file, it
1232
# doesn't purely test what a Remote* object sends over the wire, and
1233
# how it reacts to responses from the wire. It instead relies partly
1234
# on asserting that the RemoteBranch will call
1235
# self._real_branch.set_last_revision_info(...).
1237
# First, set up our RemoteBranch with a FakeClient that raises
1238
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1239
transport = MemoryTransport()
1240
transport.mkdir('branch')
1241
transport = transport.clone('branch')
1242
client = FakeClient(transport.base)
1243
client.add_expected_call(
1244
'Branch.get_stacked_on_url', ('branch/',),
1245
'error', ('NotStacked',))
1246
client.add_expected_call(
1247
'Branch.last_revision_info',
1249
'success', ('ok', '0', 'null:'))
1250
client.add_expected_call(
1251
'Branch.set_last_revision_info',
1252
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1253
'unknown', 'Branch.set_last_revision_info')
1255
branch = self.make_remote_branch(transport, client)
1256
class StubRealBranch(object):
1259
def set_last_revision_info(self, revno, revision_id):
1261
('set_last_revision_info', revno, revision_id))
1262
def _clear_cached_state(self):
1264
real_branch = StubRealBranch()
1265
branch._real_branch = real_branch
1266
self.lock_remote_branch(branch)
1268
# Call set_last_revision_info, and verify it behaved as expected.
1269
result = branch.set_last_revision_info(1234, 'a-revision-id')
1271
[('set_last_revision_info', 1234, 'a-revision-id')],
1273
client.finished_test()
1275
def test_unexpected_error(self):
1276
# If the server sends an error the client doesn't understand, it gets
1277
# turned into an UnknownErrorFromSmartServer, which is presented as a
1278
# non-internal error to the user.
1279
transport = MemoryTransport()
1280
transport.mkdir('branch')
1281
transport = transport.clone('branch')
1282
client = FakeClient(transport.base)
1283
# get_stacked_on_url
1284
client.add_error_response('NotStacked')
1286
client.add_success_response('ok', 'branch token', 'repo token')
1288
client.add_error_response('UnexpectedError')
1290
client.add_success_response('ok')
1292
branch = self.make_remote_branch(transport, client)
1293
# Lock the branch, reset the record of remote calls.
1297
err = self.assertRaises(
1298
errors.UnknownErrorFromSmartServer,
1299
branch.set_last_revision_info, 123, 'revid')
1300
self.assertEqual(('UnexpectedError',), err.error_tuple)
1303
def test_tip_change_rejected(self):
1304
"""TipChangeRejected responses cause a TipChangeRejected exception to
1307
transport = MemoryTransport()
1308
transport.mkdir('branch')
1309
transport = transport.clone('branch')
1310
client = FakeClient(transport.base)
1311
# get_stacked_on_url
1312
client.add_error_response('NotStacked')
1314
client.add_success_response('ok', 'branch token', 'repo token')
1316
client.add_error_response('TipChangeRejected', 'rejection message')
1318
client.add_success_response('ok')
1320
branch = self.make_remote_branch(transport, client)
1321
# Lock the branch, reset the record of remote calls.
1323
self.addCleanup(branch.unlock)
1326
# The 'TipChangeRejected' error response triggered by calling
1327
# set_last_revision_info causes a TipChangeRejected exception.
1328
err = self.assertRaises(
1329
errors.TipChangeRejected,
1330
branch.set_last_revision_info, 123, 'revid')
1331
self.assertEqual('rejection message', err.msg)
1334
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1335
"""Getting the branch configuration should use an abstract method not vfs.
1338
def test_get_branch_conf(self):
1339
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1340
## # We should see that branch.get_config() does a single rpc to get the
1341
## # remote configuration file, abstracting away where that is stored on
1342
## # the server. However at the moment it always falls back to using the
1343
## # vfs, and this would need some changes in config.py.
1345
## # in an empty branch we decode the response properly
1346
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1347
## # we need to make a real branch because the remote_branch.control_files
1348
## # will trigger _ensure_real.
1349
## branch = self.make_branch('quack')
1350
## transport = branch.bzrdir.root_transport
1351
## # we do not want bzrdir to make any remote calls
1352
## bzrdir = RemoteBzrDir(transport, _client=False)
1353
## branch = RemoteBranch(bzrdir, None, _client=client)
1354
## config = branch.get_config()
1355
## self.assertEqual(
1356
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1360
class TestBranchLockWrite(RemoteBranchTestCase):
1362
def test_lock_write_unlockable(self):
1363
transport = MemoryTransport()
1364
client = FakeClient(transport.base)
1365
client.add_expected_call(
1366
'Branch.get_stacked_on_url', ('quack/',),
1367
'error', ('NotStacked',),)
1368
client.add_expected_call(
1369
'Branch.lock_write', ('quack/', '', ''),
1370
'error', ('UnlockableTransport',))
1371
transport.mkdir('quack')
1372
transport = transport.clone('quack')
1373
branch = self.make_remote_branch(transport, client)
1374
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1375
client.finished_test()
1378
class TestTransportIsReadonly(tests.TestCase):
1380
def test_true(self):
1381
client = FakeClient()
1382
client.add_success_response('yes')
1383
transport = RemoteTransport('bzr://example.com/', medium=False,
1385
self.assertEqual(True, transport.is_readonly())
1387
[('call', 'Transport.is_readonly', ())],
1390
def test_false(self):
1391
client = FakeClient()
1392
client.add_success_response('no')
1393
transport = RemoteTransport('bzr://example.com/', medium=False,
1395
self.assertEqual(False, transport.is_readonly())
1397
[('call', 'Transport.is_readonly', ())],
1400
def test_error_from_old_server(self):
1401
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1403
Clients should treat it as a "no" response, because is_readonly is only
1404
advisory anyway (a transport could be read-write, but then the
1405
underlying filesystem could be readonly anyway).
1407
client = FakeClient()
1408
client.add_unknown_method_response('Transport.is_readonly')
1409
transport = RemoteTransport('bzr://example.com/', medium=False,
1411
self.assertEqual(False, transport.is_readonly())
1413
[('call', 'Transport.is_readonly', ())],
1417
class TestTransportMkdir(tests.TestCase):
1419
def test_permissiondenied(self):
1420
client = FakeClient()
1421
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1422
transport = RemoteTransport('bzr://example.com/', medium=False,
1424
exc = self.assertRaises(
1425
errors.PermissionDenied, transport.mkdir, 'client path')
1426
expected_error = errors.PermissionDenied('/client path', 'extra')
1427
self.assertEqual(expected_error, exc)
1430
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1432
def test_defaults_to_none(self):
1433
t = RemoteSSHTransport('bzr+ssh://example.com')
1434
self.assertIs(None, t._get_credentials()[0])
1436
def test_uses_authentication_config(self):
1437
conf = config.AuthenticationConfig()
1438
conf._get_config().update(
1439
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1442
t = RemoteSSHTransport('bzr+ssh://example.com')
1443
self.assertEqual('bar', t._get_credentials()[0])
1446
class TestRemoteRepository(TestRemote):
1447
"""Base for testing RemoteRepository protocol usage.
1449
These tests contain frozen requests and responses. We want any changes to
1450
what is sent or expected to be require a thoughtful update to these tests
1451
because they might break compatibility with different-versioned servers.
1454
def setup_fake_client_and_repository(self, transport_path):
1455
"""Create the fake client and repository for testing with.
1457
There's no real server here; we just have canned responses sent
1460
:param transport_path: Path below the root of the MemoryTransport
1461
where the repository will be created.
1463
transport = MemoryTransport()
1464
transport.mkdir(transport_path)
1465
client = FakeClient(transport.base)
1466
transport = transport.clone(transport_path)
1467
# we do not want bzrdir to make any remote calls
1468
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1470
repo = RemoteRepository(bzrdir, None, _client=client)
1474
class TestRepositoryFormat(TestRemoteRepository):
1476
def test_fast_delta(self):
1477
true_name = pack_repo.RepositoryFormatPackDevelopment2().network_name()
1478
true_format = RemoteRepositoryFormat()
1479
true_format._network_name = true_name
1480
self.assertEqual(True, true_format.fast_deltas)
1481
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1482
false_format = RemoteRepositoryFormat()
1483
false_format._network_name = false_name
1484
self.assertEqual(False, false_format.fast_deltas)
1487
class TestRepositoryGatherStats(TestRemoteRepository):
1489
def test_revid_none(self):
1490
# ('ok',), body with revisions and size
1491
transport_path = 'quack'
1492
repo, client = self.setup_fake_client_and_repository(transport_path)
1493
client.add_success_response_with_body(
1494
'revisions: 2\nsize: 18\n', 'ok')
1495
result = repo.gather_stats(None)
1497
[('call_expecting_body', 'Repository.gather_stats',
1498
('quack/','','no'))],
1500
self.assertEqual({'revisions': 2, 'size': 18}, result)
1502
def test_revid_no_committers(self):
1503
# ('ok',), body without committers
1504
body = ('firstrev: 123456.300 3600\n'
1505
'latestrev: 654231.400 0\n'
1508
transport_path = 'quick'
1509
revid = u'\xc8'.encode('utf8')
1510
repo, client = self.setup_fake_client_and_repository(transport_path)
1511
client.add_success_response_with_body(body, 'ok')
1512
result = repo.gather_stats(revid)
1514
[('call_expecting_body', 'Repository.gather_stats',
1515
('quick/', revid, 'no'))],
1517
self.assertEqual({'revisions': 2, 'size': 18,
1518
'firstrev': (123456.300, 3600),
1519
'latestrev': (654231.400, 0),},
1522
def test_revid_with_committers(self):
1523
# ('ok',), body with committers
1524
body = ('committers: 128\n'
1525
'firstrev: 123456.300 3600\n'
1526
'latestrev: 654231.400 0\n'
1529
transport_path = 'buick'
1530
revid = u'\xc8'.encode('utf8')
1531
repo, client = self.setup_fake_client_and_repository(transport_path)
1532
client.add_success_response_with_body(body, 'ok')
1533
result = repo.gather_stats(revid, True)
1535
[('call_expecting_body', 'Repository.gather_stats',
1536
('buick/', revid, 'yes'))],
1538
self.assertEqual({'revisions': 2, 'size': 18,
1540
'firstrev': (123456.300, 3600),
1541
'latestrev': (654231.400, 0),},
1545
class TestRepositoryGetGraph(TestRemoteRepository):
1547
def test_get_graph(self):
1548
# get_graph returns a graph with a custom parents provider.
1549
transport_path = 'quack'
1550
repo, client = self.setup_fake_client_and_repository(transport_path)
1551
graph = repo.get_graph()
1552
self.assertNotEqual(graph._parents_provider, repo)
1555
class TestRepositoryGetParentMap(TestRemoteRepository):
1557
def test_get_parent_map_caching(self):
1558
# get_parent_map returns from cache until unlock()
1559
# setup a reponse with two revisions
1560
r1 = u'\u0e33'.encode('utf8')
1561
r2 = u'\u0dab'.encode('utf8')
1562
lines = [' '.join([r2, r1]), r1]
1563
encoded_body = bz2.compress('\n'.join(lines))
1565
transport_path = 'quack'
1566
repo, client = self.setup_fake_client_and_repository(transport_path)
1567
client.add_success_response_with_body(encoded_body, 'ok')
1568
client.add_success_response_with_body(encoded_body, 'ok')
1570
graph = repo.get_graph()
1571
parents = graph.get_parent_map([r2])
1572
self.assertEqual({r2: (r1,)}, parents)
1573
# locking and unlocking deeper should not reset
1576
parents = graph.get_parent_map([r1])
1577
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1579
[('call_with_body_bytes_expecting_body',
1580
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
1583
# now we call again, and it should use the second response.
1585
graph = repo.get_graph()
1586
parents = graph.get_parent_map([r1])
1587
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1589
[('call_with_body_bytes_expecting_body',
1590
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
1591
('call_with_body_bytes_expecting_body',
1592
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
1597
def test_get_parent_map_reconnects_if_unknown_method(self):
1598
transport_path = 'quack'
1599
rev_id = 'revision-id'
1600
repo, client = self.setup_fake_client_and_repository(transport_path)
1601
client.add_unknown_method_response('Repository.get_parent_map')
1602
client.add_success_response_with_body(rev_id, 'ok')
1603
self.assertFalse(client._medium._is_remote_before((1, 2)))
1604
parents = repo.get_parent_map([rev_id])
1606
[('call_with_body_bytes_expecting_body',
1607
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
1608
('disconnect medium',),
1609
('call_expecting_body', 'Repository.get_revision_graph',
1612
# The medium is now marked as being connected to an older server
1613
self.assertTrue(client._medium._is_remote_before((1, 2)))
1614
self.assertEqual({rev_id: ('null:',)}, parents)
1616
def test_get_parent_map_fallback_parentless_node(self):
1617
"""get_parent_map falls back to get_revision_graph on old servers. The
1618
results from get_revision_graph are tweaked to match the get_parent_map
1621
Specifically, a {key: ()} result from get_revision_graph means "no
1622
parents" for that key, which in get_parent_map results should be
1623
represented as {key: ('null:',)}.
1625
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1627
rev_id = 'revision-id'
1628
transport_path = 'quack'
1629
repo, client = self.setup_fake_client_and_repository(transport_path)
1630
client.add_success_response_with_body(rev_id, 'ok')
1631
client._medium._remember_remote_is_before((1, 2))
1632
parents = repo.get_parent_map([rev_id])
1634
[('call_expecting_body', 'Repository.get_revision_graph',
1637
self.assertEqual({rev_id: ('null:',)}, parents)
1639
def test_get_parent_map_unexpected_response(self):
1640
repo, client = self.setup_fake_client_and_repository('path')
1641
client.add_success_response('something unexpected!')
1643
errors.UnexpectedSmartServerResponse,
1644
repo.get_parent_map, ['a-revision-id'])
1647
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1649
def test_allows_new_revisions(self):
1650
"""get_parent_map's results can be updated by commit."""
1651
smart_server = server.SmartTCPServer_for_testing()
1652
smart_server.setUp()
1653
self.addCleanup(smart_server.tearDown)
1654
self.make_branch('branch')
1655
branch = Branch.open(smart_server.get_url() + '/branch')
1656
tree = branch.create_checkout('tree', lightweight=True)
1658
self.addCleanup(tree.unlock)
1659
graph = tree.branch.repository.get_graph()
1660
# This provides an opportunity for the missing rev-id to be cached.
1661
self.assertEqual({}, graph.get_parent_map(['rev1']))
1662
tree.commit('message', rev_id='rev1')
1663
graph = tree.branch.repository.get_graph()
1664
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1667
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1669
def test_null_revision(self):
1670
# a null revision has the predictable result {}, we should have no wire
1671
# traffic when calling it with this argument
1672
transport_path = 'empty'
1673
repo, client = self.setup_fake_client_and_repository(transport_path)
1674
client.add_success_response('notused')
1675
# actual RemoteRepository.get_revision_graph is gone, but there's an
1676
# equivalent private method for testing
1677
result = repo._get_revision_graph(NULL_REVISION)
1678
self.assertEqual([], client._calls)
1679
self.assertEqual({}, result)
1681
def test_none_revision(self):
1682
# with none we want the entire graph
1683
r1 = u'\u0e33'.encode('utf8')
1684
r2 = u'\u0dab'.encode('utf8')
1685
lines = [' '.join([r2, r1]), r1]
1686
encoded_body = '\n'.join(lines)
1688
transport_path = 'sinhala'
1689
repo, client = self.setup_fake_client_and_repository(transport_path)
1690
client.add_success_response_with_body(encoded_body, 'ok')
1691
# actual RemoteRepository.get_revision_graph is gone, but there's an
1692
# equivalent private method for testing
1693
result = repo._get_revision_graph(None)
1695
[('call_expecting_body', 'Repository.get_revision_graph',
1698
self.assertEqual({r1: (), r2: (r1, )}, result)
1700
def test_specific_revision(self):
1701
# with a specific revision we want the graph for that
1702
# with none we want the entire graph
1703
r11 = u'\u0e33'.encode('utf8')
1704
r12 = u'\xc9'.encode('utf8')
1705
r2 = u'\u0dab'.encode('utf8')
1706
lines = [' '.join([r2, r11, r12]), r11, r12]
1707
encoded_body = '\n'.join(lines)
1709
transport_path = 'sinhala'
1710
repo, client = self.setup_fake_client_and_repository(transport_path)
1711
client.add_success_response_with_body(encoded_body, 'ok')
1712
result = repo._get_revision_graph(r2)
1714
[('call_expecting_body', 'Repository.get_revision_graph',
1717
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1719
def test_no_such_revision(self):
1721
transport_path = 'sinhala'
1722
repo, client = self.setup_fake_client_and_repository(transport_path)
1723
client.add_error_response('nosuchrevision', revid)
1724
# also check that the right revision is reported in the error
1725
self.assertRaises(errors.NoSuchRevision,
1726
repo._get_revision_graph, revid)
1728
[('call_expecting_body', 'Repository.get_revision_graph',
1729
('sinhala/', revid))],
1732
def test_unexpected_error(self):
1734
transport_path = 'sinhala'
1735
repo, client = self.setup_fake_client_and_repository(transport_path)
1736
client.add_error_response('AnUnexpectedError')
1737
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1738
repo._get_revision_graph, revid)
1739
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1742
class TestRepositoryIsShared(TestRemoteRepository):
1744
def test_is_shared(self):
1745
# ('yes', ) for Repository.is_shared -> 'True'.
1746
transport_path = 'quack'
1747
repo, client = self.setup_fake_client_and_repository(transport_path)
1748
client.add_success_response('yes')
1749
result = repo.is_shared()
1751
[('call', 'Repository.is_shared', ('quack/',))],
1753
self.assertEqual(True, result)
1755
def test_is_not_shared(self):
1756
# ('no', ) for Repository.is_shared -> 'False'.
1757
transport_path = 'qwack'
1758
repo, client = self.setup_fake_client_and_repository(transport_path)
1759
client.add_success_response('no')
1760
result = repo.is_shared()
1762
[('call', 'Repository.is_shared', ('qwack/',))],
1764
self.assertEqual(False, result)
1767
class TestRepositoryLockWrite(TestRemoteRepository):
1769
def test_lock_write(self):
1770
transport_path = 'quack'
1771
repo, client = self.setup_fake_client_and_repository(transport_path)
1772
client.add_success_response('ok', 'a token')
1773
result = repo.lock_write()
1775
[('call', 'Repository.lock_write', ('quack/', ''))],
1777
self.assertEqual('a token', result)
1779
def test_lock_write_already_locked(self):
1780
transport_path = 'quack'
1781
repo, client = self.setup_fake_client_and_repository(transport_path)
1782
client.add_error_response('LockContention')
1783
self.assertRaises(errors.LockContention, repo.lock_write)
1785
[('call', 'Repository.lock_write', ('quack/', ''))],
1788
def test_lock_write_unlockable(self):
1789
transport_path = 'quack'
1790
repo, client = self.setup_fake_client_and_repository(transport_path)
1791
client.add_error_response('UnlockableTransport')
1792
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1794
[('call', 'Repository.lock_write', ('quack/', ''))],
1798
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1800
def test_backwards_compat(self):
1801
self.setup_smart_server_with_call_log()
1802
repo = self.make_repository('.')
1803
self.reset_smart_call_log()
1804
verb = 'Repository.set_make_working_trees'
1805
self.disable_verb(verb)
1806
repo.set_make_working_trees(True)
1807
call_count = len([call for call in self.hpss_calls if
1808
call.call.method == verb])
1809
self.assertEqual(1, call_count)
1811
def test_current(self):
1812
transport_path = 'quack'
1813
repo, client = self.setup_fake_client_and_repository(transport_path)
1814
client.add_expected_call(
1815
'Repository.set_make_working_trees', ('quack/', 'True'),
1817
client.add_expected_call(
1818
'Repository.set_make_working_trees', ('quack/', 'False'),
1820
repo.set_make_working_trees(True)
1821
repo.set_make_working_trees(False)
1824
class TestRepositoryUnlock(TestRemoteRepository):
1826
def test_unlock(self):
1827
transport_path = 'quack'
1828
repo, client = self.setup_fake_client_and_repository(transport_path)
1829
client.add_success_response('ok', 'a token')
1830
client.add_success_response('ok')
1834
[('call', 'Repository.lock_write', ('quack/', '')),
1835
('call', 'Repository.unlock', ('quack/', 'a token'))],
1838
def test_unlock_wrong_token(self):
1839
# If somehow the token is wrong, unlock will raise TokenMismatch.
1840
transport_path = 'quack'
1841
repo, client = self.setup_fake_client_and_repository(transport_path)
1842
client.add_success_response('ok', 'a token')
1843
client.add_error_response('TokenMismatch')
1845
self.assertRaises(errors.TokenMismatch, repo.unlock)
1848
class TestRepositoryHasRevision(TestRemoteRepository):
1850
def test_none(self):
1851
# repo.has_revision(None) should not cause any traffic.
1852
transport_path = 'quack'
1853
repo, client = self.setup_fake_client_and_repository(transport_path)
1855
# The null revision is always there, so has_revision(None) == True.
1856
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1858
# The remote repo shouldn't be accessed.
1859
self.assertEqual([], client._calls)
1862
class TestRepositoryInsertStream(TestRemoteRepository):
1864
def test_unlocked_repo(self):
1865
transport_path = 'quack'
1866
repo, client = self.setup_fake_client_and_repository(transport_path)
1867
client.add_expected_call(
1868
'Repository.insert_stream', ('quack/', ''),
1870
client.add_expected_call(
1871
'Repository.insert_stream', ('quack/', ''),
1873
sink = repo._get_sink()
1874
fmt = repository.RepositoryFormat.get_default_format()
1875
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1876
self.assertEqual([], resume_tokens)
1877
self.assertEqual(set(), missing_keys)
1878
client.finished_test()
1880
def test_locked_repo_with_no_lock_token(self):
1881
transport_path = 'quack'
1882
repo, client = self.setup_fake_client_and_repository(transport_path)
1883
client.add_expected_call(
1884
'Repository.lock_write', ('quack/', ''),
1885
'success', ('ok', ''))
1886
client.add_expected_call(
1887
'Repository.insert_stream', ('quack/', ''),
1889
client.add_expected_call(
1890
'Repository.insert_stream', ('quack/', ''),
1893
sink = repo._get_sink()
1894
fmt = repository.RepositoryFormat.get_default_format()
1895
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1896
self.assertEqual([], resume_tokens)
1897
self.assertEqual(set(), missing_keys)
1898
client.finished_test()
1900
def test_locked_repo_with_lock_token(self):
1901
transport_path = 'quack'
1902
repo, client = self.setup_fake_client_and_repository(transport_path)
1903
client.add_expected_call(
1904
'Repository.lock_write', ('quack/', ''),
1905
'success', ('ok', 'a token'))
1906
client.add_expected_call(
1907
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
1909
client.add_expected_call(
1910
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
1913
sink = repo._get_sink()
1914
fmt = repository.RepositoryFormat.get_default_format()
1915
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1916
self.assertEqual([], resume_tokens)
1917
self.assertEqual(set(), missing_keys)
1918
client.finished_test()
1921
class TestRepositoryTarball(TestRemoteRepository):
1923
# This is a canned tarball reponse we can validate against
1925
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1926
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1927
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1928
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1929
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1930
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1931
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1932
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1933
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1934
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1935
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1936
'nWQ7QH/F3JFOFCQ0aSPfA='
1939
def test_repository_tarball(self):
1940
# Test that Repository.tarball generates the right operations
1941
transport_path = 'repo'
1942
expected_calls = [('call_expecting_body', 'Repository.tarball',
1943
('repo/', 'bz2',),),
1945
repo, client = self.setup_fake_client_and_repository(transport_path)
1946
client.add_success_response_with_body(self.tarball_content, 'ok')
1947
# Now actually ask for the tarball
1948
tarball_file = repo._get_tarball('bz2')
1950
self.assertEqual(expected_calls, client._calls)
1951
self.assertEqual(self.tarball_content, tarball_file.read())
1953
tarball_file.close()
1956
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
1957
"""RemoteRepository.copy_content_into optimizations"""
1959
def test_copy_content_remote_to_local(self):
1960
self.transport_server = server.SmartTCPServer_for_testing
1961
src_repo = self.make_repository('repo1')
1962
src_repo = repository.Repository.open(self.get_url('repo1'))
1963
# At the moment the tarball-based copy_content_into can't write back
1964
# into a smart server. It would be good if it could upload the
1965
# tarball; once that works we'd have to create repositories of
1966
# different formats. -- mbp 20070410
1967
dest_url = self.get_vfs_only_url('repo2')
1968
dest_bzrdir = BzrDir.create(dest_url)
1969
dest_repo = dest_bzrdir.create_repository()
1970
self.assertFalse(isinstance(dest_repo, RemoteRepository))
1971
self.assertTrue(isinstance(src_repo, RemoteRepository))
1972
src_repo.copy_content_into(dest_repo)
1975
class _StubRealPackRepository(object):
1977
def __init__(self, calls):
1979
self._pack_collection = _StubPackCollection(calls)
1981
def is_in_write_group(self):
1984
def refresh_data(self):
1985
self.calls.append(('pack collection reload_pack_names',))
1988
class _StubPackCollection(object):
1990
def __init__(self, calls):
1994
self.calls.append(('pack collection autopack',))
1997
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1998
"""Tests for RemoteRepository.autopack implementation."""
2001
"""When the server returns 'ok' and there's no _real_repository, then
2002
nothing else happens: the autopack method is done.
2004
transport_path = 'quack'
2005
repo, client = self.setup_fake_client_and_repository(transport_path)
2006
client.add_expected_call(
2007
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2009
client.finished_test()
2011
def test_ok_with_real_repo(self):
2012
"""When the server returns 'ok' and there is a _real_repository, then
2013
the _real_repository's reload_pack_name's method will be called.
2015
transport_path = 'quack'
2016
repo, client = self.setup_fake_client_and_repository(transport_path)
2017
client.add_expected_call(
2018
'PackRepository.autopack', ('quack/',),
2020
repo._real_repository = _StubRealPackRepository(client._calls)
2023
[('call', 'PackRepository.autopack', ('quack/',)),
2024
('pack collection reload_pack_names',)],
2027
def test_backwards_compatibility(self):
2028
"""If the server does not recognise the PackRepository.autopack verb,
2029
fallback to the real_repository's implementation.
2031
transport_path = 'quack'
2032
repo, client = self.setup_fake_client_and_repository(transport_path)
2033
client.add_unknown_method_response('PackRepository.autopack')
2034
def stub_ensure_real():
2035
client._calls.append(('_ensure_real',))
2036
repo._real_repository = _StubRealPackRepository(client._calls)
2037
repo._ensure_real = stub_ensure_real
2040
[('call', 'PackRepository.autopack', ('quack/',)),
2042
('pack collection autopack',)],
2046
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2047
"""Base class for unit tests for bzrlib.remote._translate_error."""
2049
def translateTuple(self, error_tuple, **context):
2050
"""Call _translate_error with an ErrorFromSmartServer built from the
2053
:param error_tuple: A tuple of a smart server response, as would be
2054
passed to an ErrorFromSmartServer.
2055
:kwargs context: context items to call _translate_error with.
2057
:returns: The error raised by _translate_error.
2059
# Raise the ErrorFromSmartServer before passing it as an argument,
2060
# because _translate_error may need to re-raise it with a bare 'raise'
2062
server_error = errors.ErrorFromSmartServer(error_tuple)
2063
translated_error = self.translateErrorFromSmartServer(
2064
server_error, **context)
2065
return translated_error
2067
def translateErrorFromSmartServer(self, error_object, **context):
2068
"""Like translateTuple, but takes an already constructed
2069
ErrorFromSmartServer rather than a tuple.
2073
except errors.ErrorFromSmartServer, server_error:
2074
translated_error = self.assertRaises(
2075
errors.BzrError, remote._translate_error, server_error,
2077
return translated_error
2080
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2081
"""Unit tests for bzrlib.remote._translate_error.
2083
Given an ErrorFromSmartServer (which has an error tuple from a smart
2084
server) and some context, _translate_error raises more specific errors from
2087
This test case covers the cases where _translate_error succeeds in
2088
translating an ErrorFromSmartServer to something better. See
2089
TestErrorTranslationRobustness for other cases.
2092
def test_NoSuchRevision(self):
2093
branch = self.make_branch('')
2095
translated_error = self.translateTuple(
2096
('NoSuchRevision', revid), branch=branch)
2097
expected_error = errors.NoSuchRevision(branch, revid)
2098
self.assertEqual(expected_error, translated_error)
2100
def test_nosuchrevision(self):
2101
repository = self.make_repository('')
2103
translated_error = self.translateTuple(
2104
('nosuchrevision', revid), repository=repository)
2105
expected_error = errors.NoSuchRevision(repository, revid)
2106
self.assertEqual(expected_error, translated_error)
2108
def test_nobranch(self):
2109
bzrdir = self.make_bzrdir('')
2110
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2111
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2112
self.assertEqual(expected_error, translated_error)
2114
def test_LockContention(self):
2115
translated_error = self.translateTuple(('LockContention',))
2116
expected_error = errors.LockContention('(remote lock)')
2117
self.assertEqual(expected_error, translated_error)
2119
def test_UnlockableTransport(self):
2120
bzrdir = self.make_bzrdir('')
2121
translated_error = self.translateTuple(
2122
('UnlockableTransport',), bzrdir=bzrdir)
2123
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2124
self.assertEqual(expected_error, translated_error)
2126
def test_LockFailed(self):
2127
lock = 'str() of a server lock'
2128
why = 'str() of why'
2129
translated_error = self.translateTuple(('LockFailed', lock, why))
2130
expected_error = errors.LockFailed(lock, why)
2131
self.assertEqual(expected_error, translated_error)
2133
def test_TokenMismatch(self):
2134
token = 'a lock token'
2135
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2136
expected_error = errors.TokenMismatch(token, '(remote token)')
2137
self.assertEqual(expected_error, translated_error)
2139
def test_Diverged(self):
2140
branch = self.make_branch('a')
2141
other_branch = self.make_branch('b')
2142
translated_error = self.translateTuple(
2143
('Diverged',), branch=branch, other_branch=other_branch)
2144
expected_error = errors.DivergedBranches(branch, other_branch)
2145
self.assertEqual(expected_error, translated_error)
2147
def test_ReadError_no_args(self):
2149
translated_error = self.translateTuple(('ReadError',), path=path)
2150
expected_error = errors.ReadError(path)
2151
self.assertEqual(expected_error, translated_error)
2153
def test_ReadError(self):
2155
translated_error = self.translateTuple(('ReadError', path))
2156
expected_error = errors.ReadError(path)
2157
self.assertEqual(expected_error, translated_error)
2159
def test_PermissionDenied_no_args(self):
2161
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2162
expected_error = errors.PermissionDenied(path)
2163
self.assertEqual(expected_error, translated_error)
2165
def test_PermissionDenied_one_arg(self):
2167
translated_error = self.translateTuple(('PermissionDenied', path))
2168
expected_error = errors.PermissionDenied(path)
2169
self.assertEqual(expected_error, translated_error)
2171
def test_PermissionDenied_one_arg_and_context(self):
2172
"""Given a choice between a path from the local context and a path on
2173
the wire, _translate_error prefers the path from the local context.
2175
local_path = 'local path'
2176
remote_path = 'remote path'
2177
translated_error = self.translateTuple(
2178
('PermissionDenied', remote_path), path=local_path)
2179
expected_error = errors.PermissionDenied(local_path)
2180
self.assertEqual(expected_error, translated_error)
2182
def test_PermissionDenied_two_args(self):
2184
extra = 'a string with extra info'
2185
translated_error = self.translateTuple(
2186
('PermissionDenied', path, extra))
2187
expected_error = errors.PermissionDenied(path, extra)
2188
self.assertEqual(expected_error, translated_error)
2191
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2192
"""Unit tests for bzrlib.remote._translate_error's robustness.
2194
TestErrorTranslationSuccess is for cases where _translate_error can
2195
translate successfully. This class about how _translate_err behaves when
2196
it fails to translate: it re-raises the original error.
2199
def test_unrecognised_server_error(self):
2200
"""If the error code from the server is not recognised, the original
2201
ErrorFromSmartServer is propagated unmodified.
2203
error_tuple = ('An unknown error tuple',)
2204
server_error = errors.ErrorFromSmartServer(error_tuple)
2205
translated_error = self.translateErrorFromSmartServer(server_error)
2206
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2207
self.assertEqual(expected_error, translated_error)
2209
def test_context_missing_a_key(self):
2210
"""In case of a bug in the client, or perhaps an unexpected response
2211
from a server, _translate_error returns the original error tuple from
2212
the server and mutters a warning.
2214
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2215
# in the context dict. So let's give it an empty context dict instead
2216
# to exercise its error recovery.
2218
error_tuple = ('NoSuchRevision', 'revid')
2219
server_error = errors.ErrorFromSmartServer(error_tuple)
2220
translated_error = self.translateErrorFromSmartServer(server_error)
2221
self.assertEqual(server_error, translated_error)
2222
# In addition to re-raising ErrorFromSmartServer, some debug info has
2223
# been muttered to the log file for developer to look at.
2224
self.assertContainsRe(
2225
self._get_log(keep_log_file=True),
2226
"Missing key 'branch' in context")
2228
def test_path_missing(self):
2229
"""Some translations (PermissionDenied, ReadError) can determine the
2230
'path' variable from either the wire or the local context. If neither
2231
has it, then an error is raised.
2233
error_tuple = ('ReadError',)
2234
server_error = errors.ErrorFromSmartServer(error_tuple)
2235
translated_error = self.translateErrorFromSmartServer(server_error)
2236
self.assertEqual(server_error, translated_error)
2237
# In addition to re-raising ErrorFromSmartServer, some debug info has
2238
# been muttered to the log file for developer to look at.
2239
self.assertContainsRe(
2240
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2243
class TestStacking(tests.TestCaseWithTransport):
2244
"""Tests for operations on stacked remote repositories.
2246
The underlying format type must support stacking.
2249
def test_access_stacked_remote(self):
2250
# based on <http://launchpad.net/bugs/261315>
2251
# make a branch stacked on another repository containing an empty
2252
# revision, then open it over hpss - we should be able to see that
2254
base_transport = self.get_transport()
2255
base_builder = self.make_branch_builder('base', format='1.9')
2256
base_builder.start_series()
2257
base_revid = base_builder.build_snapshot('rev-id', None,
2258
[('add', ('', None, 'directory', None))],
2260
base_builder.finish_series()
2261
stacked_branch = self.make_branch('stacked', format='1.9')
2262
stacked_branch.set_stacked_on_url('../base')
2263
# start a server looking at this
2264
smart_server = server.SmartTCPServer_for_testing()
2265
smart_server.setUp()
2266
self.addCleanup(smart_server.tearDown)
2267
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2268
# can get its branch and repository
2269
remote_branch = remote_bzrdir.open_branch()
2270
remote_repo = remote_branch.repository
2271
remote_repo.lock_read()
2273
# it should have an appropriate fallback repository, which should also
2274
# be a RemoteRepository
2275
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2276
self.assertIsInstance(remote_repo._fallback_repositories[0],
2278
# and it has the revision committed to the underlying repository;
2279
# these have varying implementations so we try several of them
2280
self.assertTrue(remote_repo.has_revisions([base_revid]))
2281
self.assertTrue(remote_repo.has_revision(base_revid))
2282
self.assertEqual(remote_repo.get_revision(base_revid).message,
2285
remote_repo.unlock()
2287
def prepare_stacked_remote_branch(self):
2288
"""Get stacked_upon and stacked branches with content in each."""
2289
self.setup_smart_server_with_call_log()
2290
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2291
tree1.commit('rev1', rev_id='rev1')
2292
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2293
).open_workingtree()
2294
tree2.commit('local changes make me feel good.')
2295
branch2 = Branch.open(self.get_url('tree2'))
2297
self.addCleanup(branch2.unlock)
2298
return tree1.branch, branch2
2300
def test_stacked_get_parent_map(self):
2301
# the public implementation of get_parent_map obeys stacking
2302
_, branch = self.prepare_stacked_remote_branch()
2303
repo = branch.repository
2304
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2306
def test_unstacked_get_parent_map(self):
2307
# _unstacked_provider.get_parent_map ignores stacking
2308
_, branch = self.prepare_stacked_remote_branch()
2309
provider = branch.repository._unstacked_provider
2310
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2312
def fetch_stream_to_rev_order(self, stream):
2314
for kind, substream in stream:
2315
if not kind == 'revisions':
2318
for content in substream:
2319
result.append(content.key[-1])
2322
def get_ordered_revs(self, format, order):
2323
"""Get a list of the revisions in a stream to format format.
2325
:param format: The format of the target.
2326
:param order: the order that target should have requested.
2327
:result: The revision ids in the stream, in the order seen,
2328
the topological order of revisions in the source.
2330
unordered_format = bzrdir.format_registry.get(format)()
2331
target_repository_format = unordered_format.repository_format
2333
self.assertEqual(order, target_repository_format._fetch_order)
2334
trunk, stacked = self.prepare_stacked_remote_branch()
2335
source = stacked.repository._get_source(target_repository_format)
2336
tip = stacked.last_revision()
2337
revs = stacked.repository.get_ancestry(tip)
2338
search = graph.PendingAncestryResult([tip], stacked.repository)
2339
self.reset_smart_call_log()
2340
stream = source.get_stream(search)
2343
# We trust that if a revision is in the stream the rest of the new
2344
# content for it is too, as per our main fetch tests; here we are
2345
# checking that the revisions are actually included at all, and their
2347
return self.fetch_stream_to_rev_order(stream), revs
2349
def test_stacked_get_stream_unordered(self):
2350
# Repository._get_source.get_stream() from a stacked repository with
2351
# unordered yields the full data from both stacked and stacked upon
2353
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2354
self.assertEqual(set(expected_revs), set(rev_ord))
2355
# Getting unordered results should have made a streaming data request
2356
# from the server, then one from the backing branch.
2357
self.assertLength(2, self.hpss_calls)
2359
def test_stacked_get_stream_topological(self):
2360
# Repository._get_source.get_stream() from a stacked repository with
2361
# topological sorting yields the full data from both stacked and
2362
# stacked upon sources in topological order.
2363
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2364
self.assertEqual(expected_revs, rev_ord)
2365
# Getting topological sort requires VFS calls still
2366
self.assertLength(14, self.hpss_calls)
2368
def test_stacked_get_stream_groupcompress(self):
2369
# Repository._get_source.get_stream() from a stacked repository with
2370
# groupcompress sorting yields the full data from both stacked and
2371
# stacked upon sources in groupcompress order.
2372
raise tests.TestSkipped('No groupcompress ordered format available')
2373
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2374
self.assertEqual(expected_revs, reversed(rev_ord))
2375
# Getting unordered results should have made a streaming data request
2376
# from the backing branch, and one from the stacked on branch.
2377
self.assertLength(2, self.hpss_calls)
2380
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2383
super(TestRemoteBranchEffort, self).setUp()
2384
# Create a smart server that publishes whatever the backing VFS server
2386
self.smart_server = server.SmartTCPServer_for_testing()
2387
self.smart_server.setUp(self.get_server())
2388
self.addCleanup(self.smart_server.tearDown)
2389
# Log all HPSS calls into self.hpss_calls.
2390
_SmartClient.hooks.install_named_hook(
2391
'call', self.capture_hpss_call, None)
2392
self.hpss_calls = []
2394
def capture_hpss_call(self, params):
2395
self.hpss_calls.append(params.method)
2397
def test_copy_content_into_avoids_revision_history(self):
2398
local = self.make_branch('local')
2399
remote_backing_tree = self.make_branch_and_tree('remote')
2400
remote_backing_tree.commit("Commit.")
2401
remote_branch_url = self.smart_server.get_url() + 'remote'
2402
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2403
local.repository.fetch(remote_branch.repository)
2404
self.hpss_calls = []
2405
remote_branch.copy_content_into(local)
2406
self.assertFalse('Branch.revision_history' in self.hpss_calls)