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
43
from bzrlib.branch import Branch
44
from bzrlib.bzrdir import BzrDir, BzrDirFormat
45
from bzrlib.remote import (
51
RemoteRepositoryFormat,
53
from bzrlib.repofmt import groupcompress_repo, pack_repo
54
from bzrlib.revision import NULL_REVISION
55
from bzrlib.smart import server, medium
56
from bzrlib.smart.client import _SmartClient
57
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
58
from bzrlib.tests import (
60
split_suite_by_condition,
63
from bzrlib.transport import get_transport, http
64
from bzrlib.transport.memory import MemoryTransport
65
from bzrlib.transport.remote import (
71
def load_tests(standard_tests, module, loader):
72
to_adapt, result = split_suite_by_condition(
73
standard_tests, condition_isinstance(BasicRemoteObjectTests))
74
smart_server_version_scenarios = [
76
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
78
{'transport_server': server.SmartTCPServer_for_testing})]
79
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
82
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
85
super(BasicRemoteObjectTests, self).setUp()
86
self.transport = self.get_transport()
87
# make a branch that can be opened over the smart transport
88
self.local_wt = BzrDir.create_standalone_workingtree('.')
91
self.transport.disconnect()
92
tests.TestCaseWithTransport.tearDown(self)
94
def test_create_remote_bzrdir(self):
95
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
96
self.assertIsInstance(b, BzrDir)
98
def test_open_remote_branch(self):
99
# open a standalone branch in the working directory
100
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
101
branch = b.open_branch()
102
self.assertIsInstance(branch, Branch)
104
def test_remote_repository(self):
105
b = BzrDir.open_from_transport(self.transport)
106
repo = b.open_repository()
107
revid = u'\xc823123123'.encode('utf8')
108
self.assertFalse(repo.has_revision(revid))
109
self.local_wt.commit(message='test commit', rev_id=revid)
110
self.assertTrue(repo.has_revision(revid))
112
def test_remote_branch_revision_history(self):
113
b = BzrDir.open_from_transport(self.transport).open_branch()
114
self.assertEqual([], b.revision_history())
115
r1 = self.local_wt.commit('1st commit')
116
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
117
self.assertEqual([r1, r2], b.revision_history())
119
def test_find_correct_format(self):
120
"""Should open a RemoteBzrDir over a RemoteTransport"""
121
fmt = BzrDirFormat.find_format(self.transport)
122
self.assertTrue(RemoteBzrDirFormat
123
in BzrDirFormat._control_server_formats)
124
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
126
def test_open_detected_smart_format(self):
127
fmt = BzrDirFormat.find_format(self.transport)
128
d = fmt.open(self.transport)
129
self.assertIsInstance(d, BzrDir)
131
def test_remote_branch_repr(self):
132
b = BzrDir.open_from_transport(self.transport).open_branch()
133
self.assertStartsWith(str(b), 'RemoteBranch(')
135
def test_remote_branch_format_supports_stacking(self):
137
self.make_branch('unstackable', format='pack-0.92')
138
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
139
self.assertFalse(b._format.supports_stacking())
140
self.make_branch('stackable', format='1.9')
141
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
142
self.assertTrue(b._format.supports_stacking())
144
def test_remote_repo_format_supports_external_references(self):
146
bd = self.make_bzrdir('unstackable', format='pack-0.92')
147
r = bd.create_repository()
148
self.assertFalse(r._format.supports_external_lookups)
149
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
150
self.assertFalse(r._format.supports_external_lookups)
151
bd = self.make_bzrdir('stackable', format='1.9')
152
r = bd.create_repository()
153
self.assertTrue(r._format.supports_external_lookups)
154
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
155
self.assertTrue(r._format.supports_external_lookups)
158
class FakeProtocol(object):
159
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
161
def __init__(self, body, fake_client):
163
self._body_buffer = None
164
self._fake_client = fake_client
166
def read_body_bytes(self, count=-1):
167
if self._body_buffer is None:
168
self._body_buffer = StringIO(self.body)
169
bytes = self._body_buffer.read(count)
170
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
171
self._fake_client.expecting_body = False
174
def cancel_read_body(self):
175
self._fake_client.expecting_body = False
177
def read_streamed_body(self):
181
class FakeClient(_SmartClient):
182
"""Lookalike for _SmartClient allowing testing."""
184
def __init__(self, fake_medium_base='fake base'):
185
"""Create a FakeClient."""
188
self.expecting_body = False
189
# if non-None, this is the list of expected calls, with only the
190
# method name and arguments included. the body might be hard to
191
# compute so is not included. If a call is None, that call can
193
self._expected_calls = None
194
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
196
def add_expected_call(self, call_name, call_args, response_type,
197
response_args, response_body=None):
198
if self._expected_calls is None:
199
self._expected_calls = []
200
self._expected_calls.append((call_name, call_args))
201
self.responses.append((response_type, response_args, response_body))
203
def add_success_response(self, *args):
204
self.responses.append(('success', args, None))
206
def add_success_response_with_body(self, body, *args):
207
self.responses.append(('success', args, body))
208
if self._expected_calls is not None:
209
self._expected_calls.append(None)
211
def add_error_response(self, *args):
212
self.responses.append(('error', args))
214
def add_unknown_method_response(self, verb):
215
self.responses.append(('unknown', verb))
217
def finished_test(self):
218
if self._expected_calls:
219
raise AssertionError("%r finished but was still expecting %r"
220
% (self, self._expected_calls[0]))
222
def _get_next_response(self):
224
response_tuple = self.responses.pop(0)
225
except IndexError, e:
226
raise AssertionError("%r didn't expect any more calls"
228
if response_tuple[0] == 'unknown':
229
raise errors.UnknownSmartMethod(response_tuple[1])
230
elif response_tuple[0] == 'error':
231
raise errors.ErrorFromSmartServer(response_tuple[1])
232
return response_tuple
234
def _check_call(self, method, args):
235
if self._expected_calls is None:
236
# the test should be updated to say what it expects
239
next_call = self._expected_calls.pop(0)
241
raise AssertionError("%r didn't expect any more calls "
243
% (self, method, args,))
244
if next_call is None:
246
if method != next_call[0] or args != next_call[1]:
247
raise AssertionError("%r expected %r%r "
249
% (self, next_call[0], next_call[1], method, args,))
251
def call(self, method, *args):
252
self._check_call(method, args)
253
self._calls.append(('call', method, args))
254
return self._get_next_response()[1]
256
def call_expecting_body(self, method, *args):
257
self._check_call(method, args)
258
self._calls.append(('call_expecting_body', method, args))
259
result = self._get_next_response()
260
self.expecting_body = True
261
return result[1], FakeProtocol(result[2], self)
263
def call_with_body_bytes_expecting_body(self, method, args, body):
264
self._check_call(method, args)
265
self._calls.append(('call_with_body_bytes_expecting_body', method,
267
result = self._get_next_response()
268
self.expecting_body = True
269
return result[1], FakeProtocol(result[2], self)
271
def call_with_body_stream(self, args, stream):
272
# Explicitly consume the stream before checking for an error, because
273
# that's what happens a real medium.
274
stream = list(stream)
275
self._check_call(args[0], args[1:])
276
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
277
result = self._get_next_response()
278
# The second value returned from call_with_body_stream is supposed to
279
# be a response_handler object, but so far no tests depend on that.
280
response_handler = None
281
return result[1], response_handler
284
class FakeMedium(medium.SmartClientMedium):
286
def __init__(self, client_calls, base):
287
medium.SmartClientMedium.__init__(self, base)
288
self._client_calls = client_calls
290
def disconnect(self):
291
self._client_calls.append(('disconnect medium',))
294
class TestVfsHas(tests.TestCase):
296
def test_unicode_path(self):
297
client = FakeClient('/')
298
client.add_success_response('yes',)
299
transport = RemoteTransport('bzr://localhost/', _client=client)
300
filename = u'/hell\u00d8'.encode('utf8')
301
result = transport.has(filename)
303
[('call', 'has', (filename,))],
305
self.assertTrue(result)
308
class TestRemote(tests.TestCaseWithMemoryTransport):
310
def get_branch_format(self):
311
reference_bzrdir_format = bzrdir.format_registry.get('default')()
312
return reference_bzrdir_format.get_branch_format()
314
def get_repo_format(self):
315
reference_bzrdir_format = bzrdir.format_registry.get('default')()
316
return reference_bzrdir_format.repository_format
318
def disable_verb(self, verb):
319
"""Disable a verb for one test."""
320
request_handlers = smart.request.request_handlers
321
orig_method = request_handlers.get(verb)
322
request_handlers.remove(verb)
324
request_handlers.register(verb, orig_method)
325
self.addCleanup(restoreVerb)
328
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
329
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
331
def assertRemotePath(self, expected, client_base, transport_base):
332
"""Assert that the result of
333
SmartClientMedium.remote_path_from_transport is the expected value for
334
a given client_base and transport_base.
336
client_medium = medium.SmartClientMedium(client_base)
337
transport = get_transport(transport_base)
338
result = client_medium.remote_path_from_transport(transport)
339
self.assertEqual(expected, result)
341
def test_remote_path_from_transport(self):
342
"""SmartClientMedium.remote_path_from_transport calculates a URL for
343
the given transport relative to the root of the client base URL.
345
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
346
self.assertRemotePath(
347
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
349
def assertRemotePathHTTP(self, expected, transport_base, relpath):
350
"""Assert that the result of
351
HttpTransportBase.remote_path_from_transport is the expected value for
352
a given transport_base and relpath of that transport. (Note that
353
HttpTransportBase is a subclass of SmartClientMedium)
355
base_transport = get_transport(transport_base)
356
client_medium = base_transport.get_smart_medium()
357
cloned_transport = base_transport.clone(relpath)
358
result = client_medium.remote_path_from_transport(cloned_transport)
359
self.assertEqual(expected, result)
361
def test_remote_path_from_transport_http(self):
362
"""Remote paths for HTTP transports are calculated differently to other
363
transports. They are just relative to the client base, not the root
364
directory of the host.
366
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
367
self.assertRemotePathHTTP(
368
'../xyz/', scheme + '//host/path', '../xyz/')
369
self.assertRemotePathHTTP(
370
'xyz/', scheme + '//host/path', 'xyz/')
373
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
374
"""Tests for the behaviour of client_medium.remote_is_at_least."""
376
def test_initially_unlimited(self):
377
"""A fresh medium assumes that the remote side supports all
380
client_medium = medium.SmartClientMedium('dummy base')
381
self.assertFalse(client_medium._is_remote_before((99, 99)))
383
def test__remember_remote_is_before(self):
384
"""Calling _remember_remote_is_before ratchets down the known remote
387
client_medium = medium.SmartClientMedium('dummy base')
388
# Mark the remote side as being less than 1.6. The remote side may
390
client_medium._remember_remote_is_before((1, 6))
391
self.assertTrue(client_medium._is_remote_before((1, 6)))
392
self.assertFalse(client_medium._is_remote_before((1, 5)))
393
# Calling _remember_remote_is_before again with a lower value works.
394
client_medium._remember_remote_is_before((1, 5))
395
self.assertTrue(client_medium._is_remote_before((1, 5)))
396
# You cannot call _remember_remote_is_before with a larger value.
398
AssertionError, client_medium._remember_remote_is_before, (1, 9))
401
class TestBzrDirCloningMetaDir(TestRemote):
403
def test_backwards_compat(self):
404
self.setup_smart_server_with_call_log()
405
a_dir = self.make_bzrdir('.')
406
self.reset_smart_call_log()
407
verb = 'BzrDir.cloning_metadir'
408
self.disable_verb(verb)
409
format = a_dir.cloning_metadir()
410
call_count = len([call for call in self.hpss_calls if
411
call.call.method == verb])
412
self.assertEqual(1, call_count)
414
def test_branch_reference(self):
415
transport = self.get_transport('quack')
416
referenced = self.make_branch('referenced')
417
expected = referenced.bzrdir.cloning_metadir()
418
client = FakeClient(transport.base)
419
client.add_expected_call(
420
'BzrDir.cloning_metadir', ('quack/', 'False'),
421
'error', ('BranchReference',)),
422
client.add_expected_call(
423
'BzrDir.open_branchV2', ('quack/',),
424
'success', ('ref', self.get_url('referenced'))),
425
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
427
result = a_bzrdir.cloning_metadir()
428
# We should have got a control dir matching the referenced branch.
429
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
430
self.assertEqual(expected._repository_format, result._repository_format)
431
self.assertEqual(expected._branch_format, result._branch_format)
432
client.finished_test()
434
def test_current_server(self):
435
transport = self.get_transport('.')
436
transport = transport.clone('quack')
437
self.make_bzrdir('quack')
438
client = FakeClient(transport.base)
439
reference_bzrdir_format = bzrdir.format_registry.get('default')()
440
control_name = reference_bzrdir_format.network_name()
441
client.add_expected_call(
442
'BzrDir.cloning_metadir', ('quack/', 'False'),
443
'success', (control_name, '', ('branch', ''))),
444
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
446
result = a_bzrdir.cloning_metadir()
447
# We should have got a reference control dir with default branch and
448
# repository formats.
449
# This pokes a little, just to be sure.
450
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
451
self.assertEqual(None, result._repository_format)
452
self.assertEqual(None, result._branch_format)
453
client.finished_test()
456
class TestBzrDirOpenBranch(TestRemote):
458
def test_backwards_compat(self):
459
self.setup_smart_server_with_call_log()
460
self.make_branch('.')
461
a_dir = BzrDir.open(self.get_url('.'))
462
self.reset_smart_call_log()
463
verb = 'BzrDir.open_branchV2'
464
self.disable_verb(verb)
465
format = a_dir.open_branch()
466
call_count = len([call for call in self.hpss_calls if
467
call.call.method == verb])
468
self.assertEqual(1, call_count)
470
def test_branch_present(self):
471
reference_format = self.get_repo_format()
472
network_name = reference_format.network_name()
473
branch_network_name = self.get_branch_format().network_name()
474
transport = MemoryTransport()
475
transport.mkdir('quack')
476
transport = transport.clone('quack')
477
client = FakeClient(transport.base)
478
client.add_expected_call(
479
'BzrDir.open_branchV2', ('quack/',),
480
'success', ('branch', branch_network_name))
481
client.add_expected_call(
482
'BzrDir.find_repositoryV3', ('quack/',),
483
'success', ('ok', '', 'no', 'no', 'no', network_name))
484
client.add_expected_call(
485
'Branch.get_stacked_on_url', ('quack/',),
486
'error', ('NotStacked',))
487
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
489
result = bzrdir.open_branch()
490
self.assertIsInstance(result, RemoteBranch)
491
self.assertEqual(bzrdir, result.bzrdir)
492
client.finished_test()
494
def test_branch_missing(self):
495
transport = MemoryTransport()
496
transport.mkdir('quack')
497
transport = transport.clone('quack')
498
client = FakeClient(transport.base)
499
client.add_error_response('nobranch')
500
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
502
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
504
[('call', 'BzrDir.open_branchV2', ('quack/',))],
507
def test__get_tree_branch(self):
508
# _get_tree_branch is a form of open_branch, but it should only ask for
509
# branch opening, not any other network requests.
512
calls.append("Called")
514
transport = MemoryTransport()
515
# no requests on the network - catches other api calls being made.
516
client = FakeClient(transport.base)
517
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
519
# patch the open_branch call to record that it was called.
520
bzrdir.open_branch = open_branch
521
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
522
self.assertEqual(["Called"], calls)
523
self.assertEqual([], client._calls)
525
def test_url_quoting_of_path(self):
526
# Relpaths on the wire should not be URL-escaped. So "~" should be
527
# transmitted as "~", not "%7E".
528
transport = RemoteTCPTransport('bzr://localhost/~hello/')
529
client = FakeClient(transport.base)
530
reference_format = self.get_repo_format()
531
network_name = reference_format.network_name()
532
branch_network_name = self.get_branch_format().network_name()
533
client.add_expected_call(
534
'BzrDir.open_branchV2', ('~hello/',),
535
'success', ('branch', branch_network_name))
536
client.add_expected_call(
537
'BzrDir.find_repositoryV3', ('~hello/',),
538
'success', ('ok', '', 'no', 'no', 'no', network_name))
539
client.add_expected_call(
540
'Branch.get_stacked_on_url', ('~hello/',),
541
'error', ('NotStacked',))
542
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
544
result = bzrdir.open_branch()
545
client.finished_test()
547
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
548
reference_format = self.get_repo_format()
549
network_name = reference_format.network_name()
550
transport = MemoryTransport()
551
transport.mkdir('quack')
552
transport = transport.clone('quack')
554
rich_response = 'yes'
558
subtree_response = 'yes'
560
subtree_response = 'no'
561
client = FakeClient(transport.base)
562
client.add_success_response(
563
'ok', '', rich_response, subtree_response, external_lookup,
565
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
567
result = bzrdir.open_repository()
569
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
571
self.assertIsInstance(result, RemoteRepository)
572
self.assertEqual(bzrdir, result.bzrdir)
573
self.assertEqual(rich_root, result._format.rich_root_data)
574
self.assertEqual(subtrees, result._format.supports_tree_reference)
576
def test_open_repository_sets_format_attributes(self):
577
self.check_open_repository(True, True)
578
self.check_open_repository(False, True)
579
self.check_open_repository(True, False)
580
self.check_open_repository(False, False)
581
self.check_open_repository(False, False, 'yes')
583
def test_old_server(self):
584
"""RemoteBzrDirFormat should fail to probe if the server version is too
587
self.assertRaises(errors.NotBranchError,
588
RemoteBzrDirFormat.probe_transport, OldServerTransport())
591
class TestBzrDirCreateBranch(TestRemote):
593
def test_backwards_compat(self):
594
self.setup_smart_server_with_call_log()
595
repo = self.make_repository('.')
596
self.reset_smart_call_log()
597
self.disable_verb('BzrDir.create_branch')
598
branch = repo.bzrdir.create_branch()
599
create_branch_call_count = len([call for call in self.hpss_calls if
600
call.call.method == 'BzrDir.create_branch'])
601
self.assertEqual(1, create_branch_call_count)
603
def test_current_server(self):
604
transport = self.get_transport('.')
605
transport = transport.clone('quack')
606
self.make_repository('quack')
607
client = FakeClient(transport.base)
608
reference_bzrdir_format = bzrdir.format_registry.get('default')()
609
reference_format = reference_bzrdir_format.get_branch_format()
610
network_name = reference_format.network_name()
611
reference_repo_fmt = reference_bzrdir_format.repository_format
612
reference_repo_name = reference_repo_fmt.network_name()
613
client.add_expected_call(
614
'BzrDir.create_branch', ('quack/', network_name),
615
'success', ('ok', network_name, '', 'no', 'no', 'yes',
616
reference_repo_name))
617
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
619
branch = a_bzrdir.create_branch()
620
# We should have got a remote branch
621
self.assertIsInstance(branch, remote.RemoteBranch)
622
# its format should have the settings from the response
623
format = branch._format
624
self.assertEqual(network_name, format.network_name())
627
class TestBzrDirCreateRepository(TestRemote):
629
def test_backwards_compat(self):
630
self.setup_smart_server_with_call_log()
631
bzrdir = self.make_bzrdir('.')
632
self.reset_smart_call_log()
633
self.disable_verb('BzrDir.create_repository')
634
repo = bzrdir.create_repository()
635
create_repo_call_count = len([call for call in self.hpss_calls if
636
call.call.method == 'BzrDir.create_repository'])
637
self.assertEqual(1, create_repo_call_count)
639
def test_current_server(self):
640
transport = self.get_transport('.')
641
transport = transport.clone('quack')
642
self.make_bzrdir('quack')
643
client = FakeClient(transport.base)
644
reference_bzrdir_format = bzrdir.format_registry.get('default')()
645
reference_format = reference_bzrdir_format.repository_format
646
network_name = reference_format.network_name()
647
client.add_expected_call(
648
'BzrDir.create_repository', ('quack/',
649
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
650
'success', ('ok', 'no', 'no', 'no', network_name))
651
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
653
repo = a_bzrdir.create_repository()
654
# We should have got a remote repository
655
self.assertIsInstance(repo, remote.RemoteRepository)
656
# its format should have the settings from the response
657
format = repo._format
658
self.assertFalse(format.rich_root_data)
659
self.assertFalse(format.supports_tree_reference)
660
self.assertFalse(format.supports_external_lookups)
661
self.assertEqual(network_name, format.network_name())
664
class TestBzrDirOpenRepository(TestRemote):
666
def test_backwards_compat_1_2_3(self):
667
# fallback all the way to the first version.
668
reference_format = self.get_repo_format()
669
network_name = reference_format.network_name()
670
client = FakeClient('bzr://example.com/')
671
client.add_unknown_method_response('BzrDir.find_repositoryV3')
672
client.add_unknown_method_response('BzrDir.find_repositoryV2')
673
client.add_success_response('ok', '', 'no', 'no')
674
# A real repository instance will be created to determine the network
676
client.add_success_response_with_body(
677
"Bazaar-NG meta directory, format 1\n", 'ok')
678
client.add_success_response_with_body(
679
reference_format.get_format_string(), 'ok')
680
# PackRepository wants to do a stat
681
client.add_success_response('stat', '0', '65535')
682
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
684
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
686
repo = bzrdir.open_repository()
688
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
689
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
690
('call', 'BzrDir.find_repository', ('quack/',)),
691
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
692
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
693
('call', 'stat', ('/quack/.bzr/repository',)),
696
self.assertEqual(network_name, repo._format.network_name())
698
def test_backwards_compat_2(self):
699
# fallback to find_repositoryV2
700
reference_format = self.get_repo_format()
701
network_name = reference_format.network_name()
702
client = FakeClient('bzr://example.com/')
703
client.add_unknown_method_response('BzrDir.find_repositoryV3')
704
client.add_success_response('ok', '', 'no', 'no', 'no')
705
# A real repository instance will be created to determine the network
707
client.add_success_response_with_body(
708
"Bazaar-NG meta directory, format 1\n", 'ok')
709
client.add_success_response_with_body(
710
reference_format.get_format_string(), 'ok')
711
# PackRepository wants to do a stat
712
client.add_success_response('stat', '0', '65535')
713
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
715
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
717
repo = bzrdir.open_repository()
719
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
720
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
721
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
722
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
723
('call', 'stat', ('/quack/.bzr/repository',)),
726
self.assertEqual(network_name, repo._format.network_name())
728
def test_current_server(self):
729
reference_format = self.get_repo_format()
730
network_name = reference_format.network_name()
731
transport = MemoryTransport()
732
transport.mkdir('quack')
733
transport = transport.clone('quack')
734
client = FakeClient(transport.base)
735
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
736
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
738
repo = bzrdir.open_repository()
740
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
742
self.assertEqual(network_name, repo._format.network_name())
745
class OldSmartClient(object):
746
"""A fake smart client for test_old_version that just returns a version one
747
response to the 'hello' (query version) command.
750
def get_request(self):
751
input_file = StringIO('ok\x011\n')
752
output_file = StringIO()
753
client_medium = medium.SmartSimplePipesClientMedium(
754
input_file, output_file)
755
return medium.SmartClientStreamMediumRequest(client_medium)
757
def protocol_version(self):
761
class OldServerTransport(object):
762
"""A fake transport for test_old_server that reports it's smart server
763
protocol version as version one.
769
def get_smart_client(self):
770
return OldSmartClient()
773
class RemoteBranchTestCase(TestRemote):
775
def make_remote_branch(self, transport, client):
776
"""Make a RemoteBranch using 'client' as its _SmartClient.
778
A RemoteBzrDir and RemoteRepository will also be created to fill out
779
the RemoteBranch, albeit with stub values for some of their attributes.
781
# we do not want bzrdir to make any remote calls, so use False as its
782
# _client. If it tries to make a remote call, this will fail
784
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
786
repo = RemoteRepository(bzrdir, None, _client=client)
787
branch_format = self.get_branch_format()
788
format = RemoteBranchFormat(network_name=branch_format.network_name())
789
return RemoteBranch(bzrdir, repo, _client=client, format=format)
792
class TestBranchGetParent(RemoteBranchTestCase):
794
def test_no_parent(self):
795
# in an empty branch we decode the response properly
796
transport = MemoryTransport()
797
client = FakeClient(transport.base)
798
client.add_expected_call(
799
'Branch.get_stacked_on_url', ('quack/',),
800
'error', ('NotStacked',))
801
client.add_expected_call(
802
'Branch.get_parent', ('quack/',),
804
transport.mkdir('quack')
805
transport = transport.clone('quack')
806
branch = self.make_remote_branch(transport, client)
807
result = branch.get_parent()
808
client.finished_test()
809
self.assertEqual(None, result)
811
def test_parent_relative(self):
812
transport = MemoryTransport()
813
client = FakeClient(transport.base)
814
client.add_expected_call(
815
'Branch.get_stacked_on_url', ('kwaak/',),
816
'error', ('NotStacked',))
817
client.add_expected_call(
818
'Branch.get_parent', ('kwaak/',),
819
'success', ('../foo/',))
820
transport.mkdir('kwaak')
821
transport = transport.clone('kwaak')
822
branch = self.make_remote_branch(transport, client)
823
result = branch.get_parent()
824
self.assertEqual(transport.clone('../foo').base, result)
826
def test_parent_absolute(self):
827
transport = MemoryTransport()
828
client = FakeClient(transport.base)
829
client.add_expected_call(
830
'Branch.get_stacked_on_url', ('kwaak/',),
831
'error', ('NotStacked',))
832
client.add_expected_call(
833
'Branch.get_parent', ('kwaak/',),
834
'success', ('http://foo/',))
835
transport.mkdir('kwaak')
836
transport = transport.clone('kwaak')
837
branch = self.make_remote_branch(transport, client)
838
result = branch.get_parent()
839
self.assertEqual('http://foo/', result)
842
class TestBranchGetTagsBytes(RemoteBranchTestCase):
844
def test_backwards_compat(self):
845
self.setup_smart_server_with_call_log()
846
branch = self.make_branch('.')
847
self.reset_smart_call_log()
848
verb = 'Branch.get_tags_bytes'
849
self.disable_verb(verb)
850
branch.tags.get_tag_dict()
851
call_count = len([call for call in self.hpss_calls if
852
call.call.method == verb])
853
self.assertEqual(1, call_count)
855
def test_trivial(self):
856
transport = MemoryTransport()
857
client = FakeClient(transport.base)
858
client.add_expected_call(
859
'Branch.get_stacked_on_url', ('quack/',),
860
'error', ('NotStacked',))
861
client.add_expected_call(
862
'Branch.get_tags_bytes', ('quack/',),
864
transport.mkdir('quack')
865
transport = transport.clone('quack')
866
branch = self.make_remote_branch(transport, client)
867
result = branch.tags.get_tag_dict()
868
client.finished_test()
869
self.assertEqual({}, result)
872
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
874
def test_empty_branch(self):
875
# in an empty branch we decode the response properly
876
transport = MemoryTransport()
877
client = FakeClient(transport.base)
878
client.add_expected_call(
879
'Branch.get_stacked_on_url', ('quack/',),
880
'error', ('NotStacked',))
881
client.add_expected_call(
882
'Branch.last_revision_info', ('quack/',),
883
'success', ('ok', '0', 'null:'))
884
transport.mkdir('quack')
885
transport = transport.clone('quack')
886
branch = self.make_remote_branch(transport, client)
887
result = branch.last_revision_info()
888
client.finished_test()
889
self.assertEqual((0, NULL_REVISION), result)
891
def test_non_empty_branch(self):
892
# in a non-empty branch we also decode the response properly
893
revid = u'\xc8'.encode('utf8')
894
transport = MemoryTransport()
895
client = FakeClient(transport.base)
896
client.add_expected_call(
897
'Branch.get_stacked_on_url', ('kwaak/',),
898
'error', ('NotStacked',))
899
client.add_expected_call(
900
'Branch.last_revision_info', ('kwaak/',),
901
'success', ('ok', '2', revid))
902
transport.mkdir('kwaak')
903
transport = transport.clone('kwaak')
904
branch = self.make_remote_branch(transport, client)
905
result = branch.last_revision_info()
906
self.assertEqual((2, revid), result)
909
class TestBranch_get_stacked_on_url(TestRemote):
910
"""Test Branch._get_stacked_on_url rpc"""
912
def test_get_stacked_on_invalid_url(self):
913
# test that asking for a stacked on url the server can't access works.
914
# This isn't perfect, but then as we're in the same process there
915
# really isn't anything we can do to be 100% sure that the server
916
# doesn't just open in - this test probably needs to be rewritten using
917
# a spawn()ed server.
918
stacked_branch = self.make_branch('stacked', format='1.9')
919
memory_branch = self.make_branch('base', format='1.9')
920
vfs_url = self.get_vfs_only_url('base')
921
stacked_branch.set_stacked_on_url(vfs_url)
922
transport = stacked_branch.bzrdir.root_transport
923
client = FakeClient(transport.base)
924
client.add_expected_call(
925
'Branch.get_stacked_on_url', ('stacked/',),
926
'success', ('ok', vfs_url))
927
# XXX: Multiple calls are bad, this second call documents what is
929
client.add_expected_call(
930
'Branch.get_stacked_on_url', ('stacked/',),
931
'success', ('ok', vfs_url))
932
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
934
repo_fmt = remote.RemoteRepositoryFormat()
935
repo_fmt._custom_format = stacked_branch.repository._format
936
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
938
result = branch.get_stacked_on_url()
939
self.assertEqual(vfs_url, result)
941
def test_backwards_compatible(self):
942
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
943
base_branch = self.make_branch('base', format='1.6')
944
stacked_branch = self.make_branch('stacked', format='1.6')
945
stacked_branch.set_stacked_on_url('../base')
946
client = FakeClient(self.get_url())
947
branch_network_name = self.get_branch_format().network_name()
948
client.add_expected_call(
949
'BzrDir.open_branchV2', ('stacked/',),
950
'success', ('branch', branch_network_name))
951
client.add_expected_call(
952
'BzrDir.find_repositoryV3', ('stacked/',),
953
'success', ('ok', '', 'no', 'no', 'yes',
954
stacked_branch.repository._format.network_name()))
955
# called twice, once from constructor and then again by us
956
client.add_expected_call(
957
'Branch.get_stacked_on_url', ('stacked/',),
958
'unknown', ('Branch.get_stacked_on_url',))
959
client.add_expected_call(
960
'Branch.get_stacked_on_url', ('stacked/',),
961
'unknown', ('Branch.get_stacked_on_url',))
962
# this will also do vfs access, but that goes direct to the transport
963
# and isn't seen by the FakeClient.
964
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
965
remote.RemoteBzrDirFormat(), _client=client)
966
branch = bzrdir.open_branch()
967
result = branch.get_stacked_on_url()
968
self.assertEqual('../base', result)
969
client.finished_test()
970
# it's in the fallback list both for the RemoteRepository and its vfs
972
self.assertEqual(1, len(branch.repository._fallback_repositories))
974
len(branch.repository._real_repository._fallback_repositories))
976
def test_get_stacked_on_real_branch(self):
977
base_branch = self.make_branch('base', format='1.6')
978
stacked_branch = self.make_branch('stacked', format='1.6')
979
stacked_branch.set_stacked_on_url('../base')
980
reference_format = self.get_repo_format()
981
network_name = reference_format.network_name()
982
client = FakeClient(self.get_url())
983
branch_network_name = self.get_branch_format().network_name()
984
client.add_expected_call(
985
'BzrDir.open_branchV2', ('stacked/',),
986
'success', ('branch', branch_network_name))
987
client.add_expected_call(
988
'BzrDir.find_repositoryV3', ('stacked/',),
989
'success', ('ok', '', 'no', 'no', 'yes', network_name))
990
# called twice, once from constructor and then again by us
991
client.add_expected_call(
992
'Branch.get_stacked_on_url', ('stacked/',),
993
'success', ('ok', '../base'))
994
client.add_expected_call(
995
'Branch.get_stacked_on_url', ('stacked/',),
996
'success', ('ok', '../base'))
997
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
998
remote.RemoteBzrDirFormat(), _client=client)
999
branch = bzrdir.open_branch()
1000
result = branch.get_stacked_on_url()
1001
self.assertEqual('../base', result)
1002
client.finished_test()
1003
# it's in the fallback list both for the RemoteRepository.
1004
self.assertEqual(1, len(branch.repository._fallback_repositories))
1005
# And we haven't had to construct a real repository.
1006
self.assertEqual(None, branch.repository._real_repository)
1009
class TestBranchSetLastRevision(RemoteBranchTestCase):
1011
def test_set_empty(self):
1012
# set_revision_history([]) is translated to calling
1013
# Branch.set_last_revision(path, '') on the wire.
1014
transport = MemoryTransport()
1015
transport.mkdir('branch')
1016
transport = transport.clone('branch')
1018
client = FakeClient(transport.base)
1019
client.add_expected_call(
1020
'Branch.get_stacked_on_url', ('branch/',),
1021
'error', ('NotStacked',))
1022
client.add_expected_call(
1023
'Branch.lock_write', ('branch/', '', ''),
1024
'success', ('ok', 'branch token', 'repo token'))
1025
client.add_expected_call(
1026
'Branch.last_revision_info',
1028
'success', ('ok', '0', 'null:'))
1029
client.add_expected_call(
1030
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1032
client.add_expected_call(
1033
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1035
branch = self.make_remote_branch(transport, client)
1036
# This is a hack to work around the problem that RemoteBranch currently
1037
# unnecessarily invokes _ensure_real upon a call to lock_write.
1038
branch._ensure_real = lambda: None
1040
result = branch.set_revision_history([])
1042
self.assertEqual(None, result)
1043
client.finished_test()
1045
def test_set_nonempty(self):
1046
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1047
# Branch.set_last_revision(path, rev-idN) on the wire.
1048
transport = MemoryTransport()
1049
transport.mkdir('branch')
1050
transport = transport.clone('branch')
1052
client = FakeClient(transport.base)
1053
client.add_expected_call(
1054
'Branch.get_stacked_on_url', ('branch/',),
1055
'error', ('NotStacked',))
1056
client.add_expected_call(
1057
'Branch.lock_write', ('branch/', '', ''),
1058
'success', ('ok', 'branch token', 'repo token'))
1059
client.add_expected_call(
1060
'Branch.last_revision_info',
1062
'success', ('ok', '0', 'null:'))
1064
encoded_body = bz2.compress('\n'.join(lines))
1065
client.add_success_response_with_body(encoded_body, 'ok')
1066
client.add_expected_call(
1067
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1069
client.add_expected_call(
1070
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1072
branch = self.make_remote_branch(transport, client)
1073
# This is a hack to work around the problem that RemoteBranch currently
1074
# unnecessarily invokes _ensure_real upon a call to lock_write.
1075
branch._ensure_real = lambda: None
1076
# Lock the branch, reset the record of remote calls.
1078
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1080
self.assertEqual(None, result)
1081
client.finished_test()
1083
def test_no_such_revision(self):
1084
transport = MemoryTransport()
1085
transport.mkdir('branch')
1086
transport = transport.clone('branch')
1087
# A response of 'NoSuchRevision' is translated into an exception.
1088
client = FakeClient(transport.base)
1089
client.add_expected_call(
1090
'Branch.get_stacked_on_url', ('branch/',),
1091
'error', ('NotStacked',))
1092
client.add_expected_call(
1093
'Branch.lock_write', ('branch/', '', ''),
1094
'success', ('ok', 'branch token', 'repo token'))
1095
client.add_expected_call(
1096
'Branch.last_revision_info',
1098
'success', ('ok', '0', 'null:'))
1099
# get_graph calls to construct the revision history, for the set_rh
1102
encoded_body = bz2.compress('\n'.join(lines))
1103
client.add_success_response_with_body(encoded_body, 'ok')
1104
client.add_expected_call(
1105
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1106
'error', ('NoSuchRevision', 'rev-id'))
1107
client.add_expected_call(
1108
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1111
branch = self.make_remote_branch(transport, client)
1114
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1116
client.finished_test()
1118
def test_tip_change_rejected(self):
1119
"""TipChangeRejected responses cause a TipChangeRejected exception to
1122
transport = MemoryTransport()
1123
transport.mkdir('branch')
1124
transport = transport.clone('branch')
1125
client = FakeClient(transport.base)
1126
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1127
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1128
client.add_expected_call(
1129
'Branch.get_stacked_on_url', ('branch/',),
1130
'error', ('NotStacked',))
1131
client.add_expected_call(
1132
'Branch.lock_write', ('branch/', '', ''),
1133
'success', ('ok', 'branch token', 'repo token'))
1134
client.add_expected_call(
1135
'Branch.last_revision_info',
1137
'success', ('ok', '0', 'null:'))
1139
encoded_body = bz2.compress('\n'.join(lines))
1140
client.add_success_response_with_body(encoded_body, 'ok')
1141
client.add_expected_call(
1142
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1143
'error', ('TipChangeRejected', rejection_msg_utf8))
1144
client.add_expected_call(
1145
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1147
branch = self.make_remote_branch(transport, client)
1148
branch._ensure_real = lambda: None
1150
# The 'TipChangeRejected' error response triggered by calling
1151
# set_revision_history causes a TipChangeRejected exception.
1152
err = self.assertRaises(
1153
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1154
# The UTF-8 message from the response has been decoded into a unicode
1156
self.assertIsInstance(err.msg, unicode)
1157
self.assertEqual(rejection_msg_unicode, err.msg)
1159
client.finished_test()
1162
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1164
def test_set_last_revision_info(self):
1165
# set_last_revision_info(num, 'rev-id') is translated to calling
1166
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1167
transport = MemoryTransport()
1168
transport.mkdir('branch')
1169
transport = transport.clone('branch')
1170
client = FakeClient(transport.base)
1171
# get_stacked_on_url
1172
client.add_error_response('NotStacked')
1174
client.add_success_response('ok', 'branch token', 'repo token')
1175
# query the current revision
1176
client.add_success_response('ok', '0', 'null:')
1178
client.add_success_response('ok')
1180
client.add_success_response('ok')
1182
branch = self.make_remote_branch(transport, client)
1183
# Lock the branch, reset the record of remote calls.
1186
result = branch.set_last_revision_info(1234, 'a-revision-id')
1188
[('call', 'Branch.last_revision_info', ('branch/',)),
1189
('call', 'Branch.set_last_revision_info',
1190
('branch/', 'branch token', 'repo token',
1191
'1234', 'a-revision-id'))],
1193
self.assertEqual(None, result)
1195
def test_no_such_revision(self):
1196
# A response of 'NoSuchRevision' is translated into an exception.
1197
transport = MemoryTransport()
1198
transport.mkdir('branch')
1199
transport = transport.clone('branch')
1200
client = FakeClient(transport.base)
1201
# get_stacked_on_url
1202
client.add_error_response('NotStacked')
1204
client.add_success_response('ok', 'branch token', 'repo token')
1206
client.add_error_response('NoSuchRevision', 'revid')
1208
client.add_success_response('ok')
1210
branch = self.make_remote_branch(transport, client)
1211
# Lock the branch, reset the record of remote calls.
1216
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1219
def lock_remote_branch(self, branch):
1220
"""Trick a RemoteBranch into thinking it is locked."""
1221
branch._lock_mode = 'w'
1222
branch._lock_count = 2
1223
branch._lock_token = 'branch token'
1224
branch._repo_lock_token = 'repo token'
1225
branch.repository._lock_mode = 'w'
1226
branch.repository._lock_count = 2
1227
branch.repository._lock_token = 'repo token'
1229
def test_backwards_compatibility(self):
1230
"""If the server does not support the Branch.set_last_revision_info
1231
verb (which is new in 1.4), then the client falls back to VFS methods.
1233
# This test is a little messy. Unlike most tests in this file, it
1234
# doesn't purely test what a Remote* object sends over the wire, and
1235
# how it reacts to responses from the wire. It instead relies partly
1236
# on asserting that the RemoteBranch will call
1237
# self._real_branch.set_last_revision_info(...).
1239
# First, set up our RemoteBranch with a FakeClient that raises
1240
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1241
transport = MemoryTransport()
1242
transport.mkdir('branch')
1243
transport = transport.clone('branch')
1244
client = FakeClient(transport.base)
1245
client.add_expected_call(
1246
'Branch.get_stacked_on_url', ('branch/',),
1247
'error', ('NotStacked',))
1248
client.add_expected_call(
1249
'Branch.last_revision_info',
1251
'success', ('ok', '0', 'null:'))
1252
client.add_expected_call(
1253
'Branch.set_last_revision_info',
1254
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1255
'unknown', 'Branch.set_last_revision_info')
1257
branch = self.make_remote_branch(transport, client)
1258
class StubRealBranch(object):
1261
def set_last_revision_info(self, revno, revision_id):
1263
('set_last_revision_info', revno, revision_id))
1264
def _clear_cached_state(self):
1266
real_branch = StubRealBranch()
1267
branch._real_branch = real_branch
1268
self.lock_remote_branch(branch)
1270
# Call set_last_revision_info, and verify it behaved as expected.
1271
result = branch.set_last_revision_info(1234, 'a-revision-id')
1273
[('set_last_revision_info', 1234, 'a-revision-id')],
1275
client.finished_test()
1277
def test_unexpected_error(self):
1278
# If the server sends an error the client doesn't understand, it gets
1279
# turned into an UnknownErrorFromSmartServer, which is presented as a
1280
# non-internal error to the user.
1281
transport = MemoryTransport()
1282
transport.mkdir('branch')
1283
transport = transport.clone('branch')
1284
client = FakeClient(transport.base)
1285
# get_stacked_on_url
1286
client.add_error_response('NotStacked')
1288
client.add_success_response('ok', 'branch token', 'repo token')
1290
client.add_error_response('UnexpectedError')
1292
client.add_success_response('ok')
1294
branch = self.make_remote_branch(transport, client)
1295
# Lock the branch, reset the record of remote calls.
1299
err = self.assertRaises(
1300
errors.UnknownErrorFromSmartServer,
1301
branch.set_last_revision_info, 123, 'revid')
1302
self.assertEqual(('UnexpectedError',), err.error_tuple)
1305
def test_tip_change_rejected(self):
1306
"""TipChangeRejected responses cause a TipChangeRejected exception to
1309
transport = MemoryTransport()
1310
transport.mkdir('branch')
1311
transport = transport.clone('branch')
1312
client = FakeClient(transport.base)
1313
# get_stacked_on_url
1314
client.add_error_response('NotStacked')
1316
client.add_success_response('ok', 'branch token', 'repo token')
1318
client.add_error_response('TipChangeRejected', 'rejection message')
1320
client.add_success_response('ok')
1322
branch = self.make_remote_branch(transport, client)
1323
# Lock the branch, reset the record of remote calls.
1325
self.addCleanup(branch.unlock)
1328
# The 'TipChangeRejected' error response triggered by calling
1329
# set_last_revision_info causes a TipChangeRejected exception.
1330
err = self.assertRaises(
1331
errors.TipChangeRejected,
1332
branch.set_last_revision_info, 123, 'revid')
1333
self.assertEqual('rejection message', err.msg)
1336
class TestBranchGetSetConfig(RemoteBranchTestCase):
1338
def test_get_branch_conf(self):
1339
# in an empty branch we decode the response properly
1340
client = FakeClient()
1341
client.add_expected_call(
1342
'Branch.get_stacked_on_url', ('memory:///',),
1343
'error', ('NotStacked',),)
1344
client.add_success_response_with_body('# config file body', 'ok')
1345
transport = MemoryTransport()
1346
branch = self.make_remote_branch(transport, client)
1347
config = branch.get_config()
1348
config.has_explicit_nickname()
1350
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1351
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1354
def test_get_multi_line_branch_conf(self):
1355
# Make sure that multiple-line branch.conf files are supported
1357
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1358
client = FakeClient()
1359
client.add_expected_call(
1360
'Branch.get_stacked_on_url', ('memory:///',),
1361
'error', ('NotStacked',),)
1362
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1363
transport = MemoryTransport()
1364
branch = self.make_remote_branch(transport, client)
1365
config = branch.get_config()
1366
self.assertEqual(u'2', config.get_user_option('b'))
1368
def test_set_option(self):
1369
client = FakeClient()
1370
client.add_expected_call(
1371
'Branch.get_stacked_on_url', ('memory:///',),
1372
'error', ('NotStacked',),)
1373
client.add_expected_call(
1374
'Branch.lock_write', ('memory:///', '', ''),
1375
'success', ('ok', 'branch token', 'repo token'))
1376
client.add_expected_call(
1377
'Branch.set_config_option', ('memory:///', 'branch token',
1378
'repo token', 'foo', 'bar', ''),
1380
client.add_expected_call(
1381
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1383
transport = MemoryTransport()
1384
branch = self.make_remote_branch(transport, client)
1386
config = branch._get_config()
1387
config.set_option('foo', 'bar')
1389
client.finished_test()
1391
def test_backwards_compat_set_option(self):
1392
self.setup_smart_server_with_call_log()
1393
branch = self.make_branch('.')
1394
verb = 'Branch.set_config_option'
1395
self.disable_verb(verb)
1397
self.addCleanup(branch.unlock)
1398
self.reset_smart_call_log()
1399
branch._get_config().set_option('value', 'name')
1400
self.assertLength(10, self.hpss_calls)
1401
self.assertEqual('value', branch._get_config().get_option('name'))
1404
class TestBranchLockWrite(RemoteBranchTestCase):
1406
def test_lock_write_unlockable(self):
1407
transport = MemoryTransport()
1408
client = FakeClient(transport.base)
1409
client.add_expected_call(
1410
'Branch.get_stacked_on_url', ('quack/',),
1411
'error', ('NotStacked',),)
1412
client.add_expected_call(
1413
'Branch.lock_write', ('quack/', '', ''),
1414
'error', ('UnlockableTransport',))
1415
transport.mkdir('quack')
1416
transport = transport.clone('quack')
1417
branch = self.make_remote_branch(transport, client)
1418
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1419
client.finished_test()
1422
class TestTransportIsReadonly(tests.TestCase):
1424
def test_true(self):
1425
client = FakeClient()
1426
client.add_success_response('yes')
1427
transport = RemoteTransport('bzr://example.com/', medium=False,
1429
self.assertEqual(True, transport.is_readonly())
1431
[('call', 'Transport.is_readonly', ())],
1434
def test_false(self):
1435
client = FakeClient()
1436
client.add_success_response('no')
1437
transport = RemoteTransport('bzr://example.com/', medium=False,
1439
self.assertEqual(False, transport.is_readonly())
1441
[('call', 'Transport.is_readonly', ())],
1444
def test_error_from_old_server(self):
1445
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1447
Clients should treat it as a "no" response, because is_readonly is only
1448
advisory anyway (a transport could be read-write, but then the
1449
underlying filesystem could be readonly anyway).
1451
client = FakeClient()
1452
client.add_unknown_method_response('Transport.is_readonly')
1453
transport = RemoteTransport('bzr://example.com/', medium=False,
1455
self.assertEqual(False, transport.is_readonly())
1457
[('call', 'Transport.is_readonly', ())],
1461
class TestTransportMkdir(tests.TestCase):
1463
def test_permissiondenied(self):
1464
client = FakeClient()
1465
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1466
transport = RemoteTransport('bzr://example.com/', medium=False,
1468
exc = self.assertRaises(
1469
errors.PermissionDenied, transport.mkdir, 'client path')
1470
expected_error = errors.PermissionDenied('/client path', 'extra')
1471
self.assertEqual(expected_error, exc)
1474
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1476
def test_defaults_to_getuser(self):
1477
t = RemoteSSHTransport('bzr+ssh://example.com')
1478
self.assertIs(getpass.getuser(), t._get_credentials()[0])
1480
def test_uses_authentication_config(self):
1481
conf = config.AuthenticationConfig()
1482
conf._get_config().update(
1483
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1486
t = RemoteSSHTransport('bzr+ssh://example.com')
1487
self.assertEqual('bar', t._get_credentials()[0])
1490
class TestRemoteRepository(TestRemote):
1491
"""Base for testing RemoteRepository protocol usage.
1493
These tests contain frozen requests and responses. We want any changes to
1494
what is sent or expected to be require a thoughtful update to these tests
1495
because they might break compatibility with different-versioned servers.
1498
def setup_fake_client_and_repository(self, transport_path):
1499
"""Create the fake client and repository for testing with.
1501
There's no real server here; we just have canned responses sent
1504
:param transport_path: Path below the root of the MemoryTransport
1505
where the repository will be created.
1507
transport = MemoryTransport()
1508
transport.mkdir(transport_path)
1509
client = FakeClient(transport.base)
1510
transport = transport.clone(transport_path)
1511
# we do not want bzrdir to make any remote calls
1512
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1514
repo = RemoteRepository(bzrdir, None, _client=client)
1518
class TestRepositoryFormat(TestRemoteRepository):
1520
def test_fast_delta(self):
1521
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1522
true_format = RemoteRepositoryFormat()
1523
true_format._network_name = true_name
1524
self.assertEqual(True, true_format.fast_deltas)
1525
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1526
false_format = RemoteRepositoryFormat()
1527
false_format._network_name = false_name
1528
self.assertEqual(False, false_format.fast_deltas)
1531
class TestRepositoryGatherStats(TestRemoteRepository):
1533
def test_revid_none(self):
1534
# ('ok',), body with revisions and size
1535
transport_path = 'quack'
1536
repo, client = self.setup_fake_client_and_repository(transport_path)
1537
client.add_success_response_with_body(
1538
'revisions: 2\nsize: 18\n', 'ok')
1539
result = repo.gather_stats(None)
1541
[('call_expecting_body', 'Repository.gather_stats',
1542
('quack/','','no'))],
1544
self.assertEqual({'revisions': 2, 'size': 18}, result)
1546
def test_revid_no_committers(self):
1547
# ('ok',), body without committers
1548
body = ('firstrev: 123456.300 3600\n'
1549
'latestrev: 654231.400 0\n'
1552
transport_path = 'quick'
1553
revid = u'\xc8'.encode('utf8')
1554
repo, client = self.setup_fake_client_and_repository(transport_path)
1555
client.add_success_response_with_body(body, 'ok')
1556
result = repo.gather_stats(revid)
1558
[('call_expecting_body', 'Repository.gather_stats',
1559
('quick/', revid, 'no'))],
1561
self.assertEqual({'revisions': 2, 'size': 18,
1562
'firstrev': (123456.300, 3600),
1563
'latestrev': (654231.400, 0),},
1566
def test_revid_with_committers(self):
1567
# ('ok',), body with committers
1568
body = ('committers: 128\n'
1569
'firstrev: 123456.300 3600\n'
1570
'latestrev: 654231.400 0\n'
1573
transport_path = 'buick'
1574
revid = u'\xc8'.encode('utf8')
1575
repo, client = self.setup_fake_client_and_repository(transport_path)
1576
client.add_success_response_with_body(body, 'ok')
1577
result = repo.gather_stats(revid, True)
1579
[('call_expecting_body', 'Repository.gather_stats',
1580
('buick/', revid, 'yes'))],
1582
self.assertEqual({'revisions': 2, 'size': 18,
1584
'firstrev': (123456.300, 3600),
1585
'latestrev': (654231.400, 0),},
1589
class TestRepositoryGetGraph(TestRemoteRepository):
1591
def test_get_graph(self):
1592
# get_graph returns a graph with a custom parents provider.
1593
transport_path = 'quack'
1594
repo, client = self.setup_fake_client_and_repository(transport_path)
1595
graph = repo.get_graph()
1596
self.assertNotEqual(graph._parents_provider, repo)
1599
class TestRepositoryGetParentMap(TestRemoteRepository):
1601
def test_get_parent_map_caching(self):
1602
# get_parent_map returns from cache until unlock()
1603
# setup a reponse with two revisions
1604
r1 = u'\u0e33'.encode('utf8')
1605
r2 = u'\u0dab'.encode('utf8')
1606
lines = [' '.join([r2, r1]), r1]
1607
encoded_body = bz2.compress('\n'.join(lines))
1609
transport_path = 'quack'
1610
repo, client = self.setup_fake_client_and_repository(transport_path)
1611
client.add_success_response_with_body(encoded_body, 'ok')
1612
client.add_success_response_with_body(encoded_body, 'ok')
1614
graph = repo.get_graph()
1615
parents = graph.get_parent_map([r2])
1616
self.assertEqual({r2: (r1,)}, parents)
1617
# locking and unlocking deeper should not reset
1620
parents = graph.get_parent_map([r1])
1621
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1623
[('call_with_body_bytes_expecting_body',
1624
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1628
# now we call again, and it should use the second response.
1630
graph = repo.get_graph()
1631
parents = graph.get_parent_map([r1])
1632
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1634
[('call_with_body_bytes_expecting_body',
1635
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1637
('call_with_body_bytes_expecting_body',
1638
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1644
def test_get_parent_map_reconnects_if_unknown_method(self):
1645
transport_path = 'quack'
1646
rev_id = 'revision-id'
1647
repo, client = self.setup_fake_client_and_repository(transport_path)
1648
client.add_unknown_method_response('Repository.get_parent_map')
1649
client.add_success_response_with_body(rev_id, 'ok')
1650
self.assertFalse(client._medium._is_remote_before((1, 2)))
1651
parents = repo.get_parent_map([rev_id])
1653
[('call_with_body_bytes_expecting_body',
1654
'Repository.get_parent_map', ('quack/', 'include-missing:',
1656
('disconnect medium',),
1657
('call_expecting_body', 'Repository.get_revision_graph',
1660
# The medium is now marked as being connected to an older server
1661
self.assertTrue(client._medium._is_remote_before((1, 2)))
1662
self.assertEqual({rev_id: ('null:',)}, parents)
1664
def test_get_parent_map_fallback_parentless_node(self):
1665
"""get_parent_map falls back to get_revision_graph on old servers. The
1666
results from get_revision_graph are tweaked to match the get_parent_map
1669
Specifically, a {key: ()} result from get_revision_graph means "no
1670
parents" for that key, which in get_parent_map results should be
1671
represented as {key: ('null:',)}.
1673
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1675
rev_id = 'revision-id'
1676
transport_path = 'quack'
1677
repo, client = self.setup_fake_client_and_repository(transport_path)
1678
client.add_success_response_with_body(rev_id, 'ok')
1679
client._medium._remember_remote_is_before((1, 2))
1680
parents = repo.get_parent_map([rev_id])
1682
[('call_expecting_body', 'Repository.get_revision_graph',
1685
self.assertEqual({rev_id: ('null:',)}, parents)
1687
def test_get_parent_map_unexpected_response(self):
1688
repo, client = self.setup_fake_client_and_repository('path')
1689
client.add_success_response('something unexpected!')
1691
errors.UnexpectedSmartServerResponse,
1692
repo.get_parent_map, ['a-revision-id'])
1694
def test_get_parent_map_negative_caches_missing_keys(self):
1695
self.setup_smart_server_with_call_log()
1696
repo = self.make_repository('foo')
1697
self.assertIsInstance(repo, RemoteRepository)
1699
self.addCleanup(repo.unlock)
1700
self.reset_smart_call_log()
1701
graph = repo.get_graph()
1702
self.assertEqual({},
1703
graph.get_parent_map(['some-missing', 'other-missing']))
1704
self.assertLength(1, self.hpss_calls)
1705
# No call if we repeat this
1706
self.reset_smart_call_log()
1707
graph = repo.get_graph()
1708
self.assertEqual({},
1709
graph.get_parent_map(['some-missing', 'other-missing']))
1710
self.assertLength(0, self.hpss_calls)
1711
# Asking for more unknown keys makes a request.
1712
self.reset_smart_call_log()
1713
graph = repo.get_graph()
1714
self.assertEqual({},
1715
graph.get_parent_map(['some-missing', 'other-missing',
1717
self.assertLength(1, self.hpss_calls)
1719
def disableExtraResults(self):
1720
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1721
SmartServerRepositoryGetParentMap.no_extra_results = True
1723
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1724
self.addCleanup(reset_values)
1726
def test_null_cached_missing_and_stop_key(self):
1727
self.setup_smart_server_with_call_log()
1728
# Make a branch with a single revision.
1729
builder = self.make_branch_builder('foo')
1730
builder.start_series()
1731
builder.build_snapshot('first', None, [
1732
('add', ('', 'root-id', 'directory', ''))])
1733
builder.finish_series()
1734
branch = builder.get_branch()
1735
repo = branch.repository
1736
self.assertIsInstance(repo, RemoteRepository)
1737
# Stop the server from sending extra results.
1738
self.disableExtraResults()
1740
self.addCleanup(repo.unlock)
1741
self.reset_smart_call_log()
1742
graph = repo.get_graph()
1743
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1744
# 'first' it will be a candidate for the stop_keys of subsequent
1745
# requests, and because 'null:' was queried but not returned it will be
1746
# cached as missing.
1747
self.assertEqual({'first': ('null:',)},
1748
graph.get_parent_map(['first', 'null:']))
1749
# Now query for another key. This request will pass along a recipe of
1750
# start and stop keys describing the already cached results, and this
1751
# recipe's revision count must be correct (or else it will trigger an
1752
# error from the server).
1753
self.assertEqual({}, graph.get_parent_map(['another-key']))
1754
# This assertion guards against disableExtraResults silently failing to
1755
# work, thus invalidating the test.
1756
self.assertLength(2, self.hpss_calls)
1758
def test_get_parent_map_gets_ghosts_from_result(self):
1759
# asking for a revision should negatively cache close ghosts in its
1761
self.setup_smart_server_with_call_log()
1762
tree = self.make_branch_and_memory_tree('foo')
1765
builder = treebuilder.TreeBuilder()
1766
builder.start_tree(tree)
1768
builder.finish_tree()
1769
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1770
rev_id = tree.commit('')
1774
self.addCleanup(tree.unlock)
1775
repo = tree.branch.repository
1776
self.assertIsInstance(repo, RemoteRepository)
1778
repo.get_parent_map([rev_id])
1779
self.reset_smart_call_log()
1780
# Now asking for rev_id's ghost parent should not make calls
1781
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1782
self.assertLength(0, self.hpss_calls)
1785
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1787
def test_allows_new_revisions(self):
1788
"""get_parent_map's results can be updated by commit."""
1789
smart_server = server.SmartTCPServer_for_testing()
1790
smart_server.setUp()
1791
self.addCleanup(smart_server.tearDown)
1792
self.make_branch('branch')
1793
branch = Branch.open(smart_server.get_url() + '/branch')
1794
tree = branch.create_checkout('tree', lightweight=True)
1796
self.addCleanup(tree.unlock)
1797
graph = tree.branch.repository.get_graph()
1798
# This provides an opportunity for the missing rev-id to be cached.
1799
self.assertEqual({}, graph.get_parent_map(['rev1']))
1800
tree.commit('message', rev_id='rev1')
1801
graph = tree.branch.repository.get_graph()
1802
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1805
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1807
def test_null_revision(self):
1808
# a null revision has the predictable result {}, we should have no wire
1809
# traffic when calling it with this argument
1810
transport_path = 'empty'
1811
repo, client = self.setup_fake_client_and_repository(transport_path)
1812
client.add_success_response('notused')
1813
# actual RemoteRepository.get_revision_graph is gone, but there's an
1814
# equivalent private method for testing
1815
result = repo._get_revision_graph(NULL_REVISION)
1816
self.assertEqual([], client._calls)
1817
self.assertEqual({}, result)
1819
def test_none_revision(self):
1820
# with none we want the entire graph
1821
r1 = u'\u0e33'.encode('utf8')
1822
r2 = u'\u0dab'.encode('utf8')
1823
lines = [' '.join([r2, r1]), r1]
1824
encoded_body = '\n'.join(lines)
1826
transport_path = 'sinhala'
1827
repo, client = self.setup_fake_client_and_repository(transport_path)
1828
client.add_success_response_with_body(encoded_body, 'ok')
1829
# actual RemoteRepository.get_revision_graph is gone, but there's an
1830
# equivalent private method for testing
1831
result = repo._get_revision_graph(None)
1833
[('call_expecting_body', 'Repository.get_revision_graph',
1836
self.assertEqual({r1: (), r2: (r1, )}, result)
1838
def test_specific_revision(self):
1839
# with a specific revision we want the graph for that
1840
# with none we want the entire graph
1841
r11 = u'\u0e33'.encode('utf8')
1842
r12 = u'\xc9'.encode('utf8')
1843
r2 = u'\u0dab'.encode('utf8')
1844
lines = [' '.join([r2, r11, r12]), r11, r12]
1845
encoded_body = '\n'.join(lines)
1847
transport_path = 'sinhala'
1848
repo, client = self.setup_fake_client_and_repository(transport_path)
1849
client.add_success_response_with_body(encoded_body, 'ok')
1850
result = repo._get_revision_graph(r2)
1852
[('call_expecting_body', 'Repository.get_revision_graph',
1855
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1857
def test_no_such_revision(self):
1859
transport_path = 'sinhala'
1860
repo, client = self.setup_fake_client_and_repository(transport_path)
1861
client.add_error_response('nosuchrevision', revid)
1862
# also check that the right revision is reported in the error
1863
self.assertRaises(errors.NoSuchRevision,
1864
repo._get_revision_graph, revid)
1866
[('call_expecting_body', 'Repository.get_revision_graph',
1867
('sinhala/', revid))],
1870
def test_unexpected_error(self):
1872
transport_path = 'sinhala'
1873
repo, client = self.setup_fake_client_and_repository(transport_path)
1874
client.add_error_response('AnUnexpectedError')
1875
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1876
repo._get_revision_graph, revid)
1877
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1880
class TestRepositoryIsShared(TestRemoteRepository):
1882
def test_is_shared(self):
1883
# ('yes', ) for Repository.is_shared -> 'True'.
1884
transport_path = 'quack'
1885
repo, client = self.setup_fake_client_and_repository(transport_path)
1886
client.add_success_response('yes')
1887
result = repo.is_shared()
1889
[('call', 'Repository.is_shared', ('quack/',))],
1891
self.assertEqual(True, result)
1893
def test_is_not_shared(self):
1894
# ('no', ) for Repository.is_shared -> 'False'.
1895
transport_path = 'qwack'
1896
repo, client = self.setup_fake_client_and_repository(transport_path)
1897
client.add_success_response('no')
1898
result = repo.is_shared()
1900
[('call', 'Repository.is_shared', ('qwack/',))],
1902
self.assertEqual(False, result)
1905
class TestRepositoryLockWrite(TestRemoteRepository):
1907
def test_lock_write(self):
1908
transport_path = 'quack'
1909
repo, client = self.setup_fake_client_and_repository(transport_path)
1910
client.add_success_response('ok', 'a token')
1911
result = repo.lock_write()
1913
[('call', 'Repository.lock_write', ('quack/', ''))],
1915
self.assertEqual('a token', result)
1917
def test_lock_write_already_locked(self):
1918
transport_path = 'quack'
1919
repo, client = self.setup_fake_client_and_repository(transport_path)
1920
client.add_error_response('LockContention')
1921
self.assertRaises(errors.LockContention, repo.lock_write)
1923
[('call', 'Repository.lock_write', ('quack/', ''))],
1926
def test_lock_write_unlockable(self):
1927
transport_path = 'quack'
1928
repo, client = self.setup_fake_client_and_repository(transport_path)
1929
client.add_error_response('UnlockableTransport')
1930
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1932
[('call', 'Repository.lock_write', ('quack/', ''))],
1936
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1938
def test_backwards_compat(self):
1939
self.setup_smart_server_with_call_log()
1940
repo = self.make_repository('.')
1941
self.reset_smart_call_log()
1942
verb = 'Repository.set_make_working_trees'
1943
self.disable_verb(verb)
1944
repo.set_make_working_trees(True)
1945
call_count = len([call for call in self.hpss_calls if
1946
call.call.method == verb])
1947
self.assertEqual(1, call_count)
1949
def test_current(self):
1950
transport_path = 'quack'
1951
repo, client = self.setup_fake_client_and_repository(transport_path)
1952
client.add_expected_call(
1953
'Repository.set_make_working_trees', ('quack/', 'True'),
1955
client.add_expected_call(
1956
'Repository.set_make_working_trees', ('quack/', 'False'),
1958
repo.set_make_working_trees(True)
1959
repo.set_make_working_trees(False)
1962
class TestRepositoryUnlock(TestRemoteRepository):
1964
def test_unlock(self):
1965
transport_path = 'quack'
1966
repo, client = self.setup_fake_client_and_repository(transport_path)
1967
client.add_success_response('ok', 'a token')
1968
client.add_success_response('ok')
1972
[('call', 'Repository.lock_write', ('quack/', '')),
1973
('call', 'Repository.unlock', ('quack/', 'a token'))],
1976
def test_unlock_wrong_token(self):
1977
# If somehow the token is wrong, unlock will raise TokenMismatch.
1978
transport_path = 'quack'
1979
repo, client = self.setup_fake_client_and_repository(transport_path)
1980
client.add_success_response('ok', 'a token')
1981
client.add_error_response('TokenMismatch')
1983
self.assertRaises(errors.TokenMismatch, repo.unlock)
1986
class TestRepositoryHasRevision(TestRemoteRepository):
1988
def test_none(self):
1989
# repo.has_revision(None) should not cause any traffic.
1990
transport_path = 'quack'
1991
repo, client = self.setup_fake_client_and_repository(transport_path)
1993
# The null revision is always there, so has_revision(None) == True.
1994
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1996
# The remote repo shouldn't be accessed.
1997
self.assertEqual([], client._calls)
2000
class TestRepositoryInsertStream(TestRemoteRepository):
2002
def test_unlocked_repo(self):
2003
transport_path = 'quack'
2004
repo, client = self.setup_fake_client_and_repository(transport_path)
2005
client.add_expected_call(
2006
'Repository.insert_stream', ('quack/', ''),
2008
client.add_expected_call(
2009
'Repository.insert_stream', ('quack/', ''),
2011
sink = repo._get_sink()
2012
fmt = repository.RepositoryFormat.get_default_format()
2013
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2014
self.assertEqual([], resume_tokens)
2015
self.assertEqual(set(), missing_keys)
2016
client.finished_test()
2018
def test_locked_repo_with_no_lock_token(self):
2019
transport_path = 'quack'
2020
repo, client = self.setup_fake_client_and_repository(transport_path)
2021
client.add_expected_call(
2022
'Repository.lock_write', ('quack/', ''),
2023
'success', ('ok', ''))
2024
client.add_expected_call(
2025
'Repository.insert_stream', ('quack/', ''),
2027
client.add_expected_call(
2028
'Repository.insert_stream', ('quack/', ''),
2031
sink = repo._get_sink()
2032
fmt = repository.RepositoryFormat.get_default_format()
2033
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2034
self.assertEqual([], resume_tokens)
2035
self.assertEqual(set(), missing_keys)
2036
client.finished_test()
2038
def test_locked_repo_with_lock_token(self):
2039
transport_path = 'quack'
2040
repo, client = self.setup_fake_client_and_repository(transport_path)
2041
client.add_expected_call(
2042
'Repository.lock_write', ('quack/', ''),
2043
'success', ('ok', 'a token'))
2044
client.add_expected_call(
2045
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2047
client.add_expected_call(
2048
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2051
sink = repo._get_sink()
2052
fmt = repository.RepositoryFormat.get_default_format()
2053
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2054
self.assertEqual([], resume_tokens)
2055
self.assertEqual(set(), missing_keys)
2056
client.finished_test()
2059
class TestRepositoryTarball(TestRemoteRepository):
2061
# This is a canned tarball reponse we can validate against
2063
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2064
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2065
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2066
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2067
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2068
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2069
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2070
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2071
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2072
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2073
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2074
'nWQ7QH/F3JFOFCQ0aSPfA='
2077
def test_repository_tarball(self):
2078
# Test that Repository.tarball generates the right operations
2079
transport_path = 'repo'
2080
expected_calls = [('call_expecting_body', 'Repository.tarball',
2081
('repo/', 'bz2',),),
2083
repo, client = self.setup_fake_client_and_repository(transport_path)
2084
client.add_success_response_with_body(self.tarball_content, 'ok')
2085
# Now actually ask for the tarball
2086
tarball_file = repo._get_tarball('bz2')
2088
self.assertEqual(expected_calls, client._calls)
2089
self.assertEqual(self.tarball_content, tarball_file.read())
2091
tarball_file.close()
2094
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2095
"""RemoteRepository.copy_content_into optimizations"""
2097
def test_copy_content_remote_to_local(self):
2098
self.transport_server = server.SmartTCPServer_for_testing
2099
src_repo = self.make_repository('repo1')
2100
src_repo = repository.Repository.open(self.get_url('repo1'))
2101
# At the moment the tarball-based copy_content_into can't write back
2102
# into a smart server. It would be good if it could upload the
2103
# tarball; once that works we'd have to create repositories of
2104
# different formats. -- mbp 20070410
2105
dest_url = self.get_vfs_only_url('repo2')
2106
dest_bzrdir = BzrDir.create(dest_url)
2107
dest_repo = dest_bzrdir.create_repository()
2108
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2109
self.assertTrue(isinstance(src_repo, RemoteRepository))
2110
src_repo.copy_content_into(dest_repo)
2113
class _StubRealPackRepository(object):
2115
def __init__(self, calls):
2117
self._pack_collection = _StubPackCollection(calls)
2119
def is_in_write_group(self):
2122
def refresh_data(self):
2123
self.calls.append(('pack collection reload_pack_names',))
2126
class _StubPackCollection(object):
2128
def __init__(self, calls):
2132
self.calls.append(('pack collection autopack',))
2135
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2136
"""Tests for RemoteRepository.autopack implementation."""
2139
"""When the server returns 'ok' and there's no _real_repository, then
2140
nothing else happens: the autopack method is done.
2142
transport_path = 'quack'
2143
repo, client = self.setup_fake_client_and_repository(transport_path)
2144
client.add_expected_call(
2145
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2147
client.finished_test()
2149
def test_ok_with_real_repo(self):
2150
"""When the server returns 'ok' and there is a _real_repository, then
2151
the _real_repository's reload_pack_name's method will be called.
2153
transport_path = 'quack'
2154
repo, client = self.setup_fake_client_and_repository(transport_path)
2155
client.add_expected_call(
2156
'PackRepository.autopack', ('quack/',),
2158
repo._real_repository = _StubRealPackRepository(client._calls)
2161
[('call', 'PackRepository.autopack', ('quack/',)),
2162
('pack collection reload_pack_names',)],
2165
def test_backwards_compatibility(self):
2166
"""If the server does not recognise the PackRepository.autopack verb,
2167
fallback to the real_repository's implementation.
2169
transport_path = 'quack'
2170
repo, client = self.setup_fake_client_and_repository(transport_path)
2171
client.add_unknown_method_response('PackRepository.autopack')
2172
def stub_ensure_real():
2173
client._calls.append(('_ensure_real',))
2174
repo._real_repository = _StubRealPackRepository(client._calls)
2175
repo._ensure_real = stub_ensure_real
2178
[('call', 'PackRepository.autopack', ('quack/',)),
2180
('pack collection autopack',)],
2184
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2185
"""Base class for unit tests for bzrlib.remote._translate_error."""
2187
def translateTuple(self, error_tuple, **context):
2188
"""Call _translate_error with an ErrorFromSmartServer built from the
2191
:param error_tuple: A tuple of a smart server response, as would be
2192
passed to an ErrorFromSmartServer.
2193
:kwargs context: context items to call _translate_error with.
2195
:returns: The error raised by _translate_error.
2197
# Raise the ErrorFromSmartServer before passing it as an argument,
2198
# because _translate_error may need to re-raise it with a bare 'raise'
2200
server_error = errors.ErrorFromSmartServer(error_tuple)
2201
translated_error = self.translateErrorFromSmartServer(
2202
server_error, **context)
2203
return translated_error
2205
def translateErrorFromSmartServer(self, error_object, **context):
2206
"""Like translateTuple, but takes an already constructed
2207
ErrorFromSmartServer rather than a tuple.
2211
except errors.ErrorFromSmartServer, server_error:
2212
translated_error = self.assertRaises(
2213
errors.BzrError, remote._translate_error, server_error,
2215
return translated_error
2218
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2219
"""Unit tests for bzrlib.remote._translate_error.
2221
Given an ErrorFromSmartServer (which has an error tuple from a smart
2222
server) and some context, _translate_error raises more specific errors from
2225
This test case covers the cases where _translate_error succeeds in
2226
translating an ErrorFromSmartServer to something better. See
2227
TestErrorTranslationRobustness for other cases.
2230
def test_NoSuchRevision(self):
2231
branch = self.make_branch('')
2233
translated_error = self.translateTuple(
2234
('NoSuchRevision', revid), branch=branch)
2235
expected_error = errors.NoSuchRevision(branch, revid)
2236
self.assertEqual(expected_error, translated_error)
2238
def test_nosuchrevision(self):
2239
repository = self.make_repository('')
2241
translated_error = self.translateTuple(
2242
('nosuchrevision', revid), repository=repository)
2243
expected_error = errors.NoSuchRevision(repository, revid)
2244
self.assertEqual(expected_error, translated_error)
2246
def test_nobranch(self):
2247
bzrdir = self.make_bzrdir('')
2248
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2249
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2250
self.assertEqual(expected_error, translated_error)
2252
def test_LockContention(self):
2253
translated_error = self.translateTuple(('LockContention',))
2254
expected_error = errors.LockContention('(remote lock)')
2255
self.assertEqual(expected_error, translated_error)
2257
def test_UnlockableTransport(self):
2258
bzrdir = self.make_bzrdir('')
2259
translated_error = self.translateTuple(
2260
('UnlockableTransport',), bzrdir=bzrdir)
2261
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2262
self.assertEqual(expected_error, translated_error)
2264
def test_LockFailed(self):
2265
lock = 'str() of a server lock'
2266
why = 'str() of why'
2267
translated_error = self.translateTuple(('LockFailed', lock, why))
2268
expected_error = errors.LockFailed(lock, why)
2269
self.assertEqual(expected_error, translated_error)
2271
def test_TokenMismatch(self):
2272
token = 'a lock token'
2273
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2274
expected_error = errors.TokenMismatch(token, '(remote token)')
2275
self.assertEqual(expected_error, translated_error)
2277
def test_Diverged(self):
2278
branch = self.make_branch('a')
2279
other_branch = self.make_branch('b')
2280
translated_error = self.translateTuple(
2281
('Diverged',), branch=branch, other_branch=other_branch)
2282
expected_error = errors.DivergedBranches(branch, other_branch)
2283
self.assertEqual(expected_error, translated_error)
2285
def test_ReadError_no_args(self):
2287
translated_error = self.translateTuple(('ReadError',), path=path)
2288
expected_error = errors.ReadError(path)
2289
self.assertEqual(expected_error, translated_error)
2291
def test_ReadError(self):
2293
translated_error = self.translateTuple(('ReadError', path))
2294
expected_error = errors.ReadError(path)
2295
self.assertEqual(expected_error, translated_error)
2297
def test_PermissionDenied_no_args(self):
2299
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2300
expected_error = errors.PermissionDenied(path)
2301
self.assertEqual(expected_error, translated_error)
2303
def test_PermissionDenied_one_arg(self):
2305
translated_error = self.translateTuple(('PermissionDenied', path))
2306
expected_error = errors.PermissionDenied(path)
2307
self.assertEqual(expected_error, translated_error)
2309
def test_PermissionDenied_one_arg_and_context(self):
2310
"""Given a choice between a path from the local context and a path on
2311
the wire, _translate_error prefers the path from the local context.
2313
local_path = 'local path'
2314
remote_path = 'remote path'
2315
translated_error = self.translateTuple(
2316
('PermissionDenied', remote_path), path=local_path)
2317
expected_error = errors.PermissionDenied(local_path)
2318
self.assertEqual(expected_error, translated_error)
2320
def test_PermissionDenied_two_args(self):
2322
extra = 'a string with extra info'
2323
translated_error = self.translateTuple(
2324
('PermissionDenied', path, extra))
2325
expected_error = errors.PermissionDenied(path, extra)
2326
self.assertEqual(expected_error, translated_error)
2329
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2330
"""Unit tests for bzrlib.remote._translate_error's robustness.
2332
TestErrorTranslationSuccess is for cases where _translate_error can
2333
translate successfully. This class about how _translate_err behaves when
2334
it fails to translate: it re-raises the original error.
2337
def test_unrecognised_server_error(self):
2338
"""If the error code from the server is not recognised, the original
2339
ErrorFromSmartServer is propagated unmodified.
2341
error_tuple = ('An unknown error tuple',)
2342
server_error = errors.ErrorFromSmartServer(error_tuple)
2343
translated_error = self.translateErrorFromSmartServer(server_error)
2344
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2345
self.assertEqual(expected_error, translated_error)
2347
def test_context_missing_a_key(self):
2348
"""In case of a bug in the client, or perhaps an unexpected response
2349
from a server, _translate_error returns the original error tuple from
2350
the server and mutters a warning.
2352
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2353
# in the context dict. So let's give it an empty context dict instead
2354
# to exercise its error recovery.
2356
error_tuple = ('NoSuchRevision', 'revid')
2357
server_error = errors.ErrorFromSmartServer(error_tuple)
2358
translated_error = self.translateErrorFromSmartServer(server_error)
2359
self.assertEqual(server_error, translated_error)
2360
# In addition to re-raising ErrorFromSmartServer, some debug info has
2361
# been muttered to the log file for developer to look at.
2362
self.assertContainsRe(
2363
self._get_log(keep_log_file=True),
2364
"Missing key 'branch' in context")
2366
def test_path_missing(self):
2367
"""Some translations (PermissionDenied, ReadError) can determine the
2368
'path' variable from either the wire or the local context. If neither
2369
has it, then an error is raised.
2371
error_tuple = ('ReadError',)
2372
server_error = errors.ErrorFromSmartServer(error_tuple)
2373
translated_error = self.translateErrorFromSmartServer(server_error)
2374
self.assertEqual(server_error, translated_error)
2375
# In addition to re-raising ErrorFromSmartServer, some debug info has
2376
# been muttered to the log file for developer to look at.
2377
self.assertContainsRe(
2378
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2381
class TestStacking(tests.TestCaseWithTransport):
2382
"""Tests for operations on stacked remote repositories.
2384
The underlying format type must support stacking.
2387
def test_access_stacked_remote(self):
2388
# based on <http://launchpad.net/bugs/261315>
2389
# make a branch stacked on another repository containing an empty
2390
# revision, then open it over hpss - we should be able to see that
2392
base_transport = self.get_transport()
2393
base_builder = self.make_branch_builder('base', format='1.9')
2394
base_builder.start_series()
2395
base_revid = base_builder.build_snapshot('rev-id', None,
2396
[('add', ('', None, 'directory', None))],
2398
base_builder.finish_series()
2399
stacked_branch = self.make_branch('stacked', format='1.9')
2400
stacked_branch.set_stacked_on_url('../base')
2401
# start a server looking at this
2402
smart_server = server.SmartTCPServer_for_testing()
2403
smart_server.setUp()
2404
self.addCleanup(smart_server.tearDown)
2405
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2406
# can get its branch and repository
2407
remote_branch = remote_bzrdir.open_branch()
2408
remote_repo = remote_branch.repository
2409
remote_repo.lock_read()
2411
# it should have an appropriate fallback repository, which should also
2412
# be a RemoteRepository
2413
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2414
self.assertIsInstance(remote_repo._fallback_repositories[0],
2416
# and it has the revision committed to the underlying repository;
2417
# these have varying implementations so we try several of them
2418
self.assertTrue(remote_repo.has_revisions([base_revid]))
2419
self.assertTrue(remote_repo.has_revision(base_revid))
2420
self.assertEqual(remote_repo.get_revision(base_revid).message,
2423
remote_repo.unlock()
2425
def prepare_stacked_remote_branch(self):
2426
"""Get stacked_upon and stacked branches with content in each."""
2427
self.setup_smart_server_with_call_log()
2428
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2429
tree1.commit('rev1', rev_id='rev1')
2430
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2431
).open_workingtree()
2432
tree2.commit('local changes make me feel good.')
2433
branch2 = Branch.open(self.get_url('tree2'))
2435
self.addCleanup(branch2.unlock)
2436
return tree1.branch, branch2
2438
def test_stacked_get_parent_map(self):
2439
# the public implementation of get_parent_map obeys stacking
2440
_, branch = self.prepare_stacked_remote_branch()
2441
repo = branch.repository
2442
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2444
def test_unstacked_get_parent_map(self):
2445
# _unstacked_provider.get_parent_map ignores stacking
2446
_, branch = self.prepare_stacked_remote_branch()
2447
provider = branch.repository._unstacked_provider
2448
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2450
def fetch_stream_to_rev_order(self, stream):
2452
for kind, substream in stream:
2453
if not kind == 'revisions':
2456
for content in substream:
2457
result.append(content.key[-1])
2460
def get_ordered_revs(self, format, order):
2461
"""Get a list of the revisions in a stream to format format.
2463
:param format: The format of the target.
2464
:param order: the order that target should have requested.
2465
:result: The revision ids in the stream, in the order seen,
2466
the topological order of revisions in the source.
2468
unordered_format = bzrdir.format_registry.get(format)()
2469
target_repository_format = unordered_format.repository_format
2471
self.assertEqual(order, target_repository_format._fetch_order)
2472
trunk, stacked = self.prepare_stacked_remote_branch()
2473
source = stacked.repository._get_source(target_repository_format)
2474
tip = stacked.last_revision()
2475
revs = stacked.repository.get_ancestry(tip)
2476
search = graph.PendingAncestryResult([tip], stacked.repository)
2477
self.reset_smart_call_log()
2478
stream = source.get_stream(search)
2481
# We trust that if a revision is in the stream the rest of the new
2482
# content for it is too, as per our main fetch tests; here we are
2483
# checking that the revisions are actually included at all, and their
2485
return self.fetch_stream_to_rev_order(stream), revs
2487
def test_stacked_get_stream_unordered(self):
2488
# Repository._get_source.get_stream() from a stacked repository with
2489
# unordered yields the full data from both stacked and stacked upon
2491
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2492
self.assertEqual(set(expected_revs), set(rev_ord))
2493
# Getting unordered results should have made a streaming data request
2494
# from the server, then one from the backing branch.
2495
self.assertLength(2, self.hpss_calls)
2497
def test_stacked_get_stream_topological(self):
2498
# Repository._get_source.get_stream() from a stacked repository with
2499
# topological sorting yields the full data from both stacked and
2500
# stacked upon sources in topological order.
2501
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2502
self.assertEqual(expected_revs, rev_ord)
2503
# Getting topological sort requires VFS calls still
2504
self.assertLength(12, self.hpss_calls)
2506
def test_stacked_get_stream_groupcompress(self):
2507
# Repository._get_source.get_stream() from a stacked repository with
2508
# groupcompress sorting yields the full data from both stacked and
2509
# stacked upon sources in groupcompress order.
2510
raise tests.TestSkipped('No groupcompress ordered format available')
2511
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2512
self.assertEqual(expected_revs, reversed(rev_ord))
2513
# Getting unordered results should have made a streaming data request
2514
# from the backing branch, and one from the stacked on branch.
2515
self.assertLength(2, self.hpss_calls)
2518
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2521
super(TestRemoteBranchEffort, self).setUp()
2522
# Create a smart server that publishes whatever the backing VFS server
2524
self.smart_server = server.SmartTCPServer_for_testing()
2525
self.smart_server.setUp(self.get_server())
2526
self.addCleanup(self.smart_server.tearDown)
2527
# Log all HPSS calls into self.hpss_calls.
2528
_SmartClient.hooks.install_named_hook(
2529
'call', self.capture_hpss_call, None)
2530
self.hpss_calls = []
2532
def capture_hpss_call(self, params):
2533
self.hpss_calls.append(params.method)
2535
def test_copy_content_into_avoids_revision_history(self):
2536
local = self.make_branch('local')
2537
remote_backing_tree = self.make_branch_and_tree('remote')
2538
remote_backing_tree.commit("Commit.")
2539
remote_branch_url = self.smart_server.get_url() + 'remote'
2540
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2541
local.repository.fetch(remote_branch.repository)
2542
self.hpss_calls = []
2543
remote_branch.copy_content_into(local)
2544
self.assertFalse('Branch.revision_history' in self.hpss_calls)