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 RemoteBzrDirTestCase(TestRemote):
775
def make_remote_bzrdir(self, transport, client):
776
"""Make a RemotebzrDir using 'client' as the _client."""
777
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
781
class RemoteBranchTestCase(RemoteBzrDirTestCase):
783
def make_remote_branch(self, transport, client):
784
"""Make a RemoteBranch using 'client' as its _SmartClient.
786
A RemoteBzrDir and RemoteRepository will also be created to fill out
787
the RemoteBranch, albeit with stub values for some of their attributes.
789
# we do not want bzrdir to make any remote calls, so use False as its
790
# _client. If it tries to make a remote call, this will fail
792
bzrdir = self.make_remote_bzrdir(transport, False)
793
repo = RemoteRepository(bzrdir, None, _client=client)
794
branch_format = self.get_branch_format()
795
format = RemoteBranchFormat(network_name=branch_format.network_name())
796
return RemoteBranch(bzrdir, repo, _client=client, format=format)
799
class TestBranchGetParent(RemoteBranchTestCase):
801
def test_no_parent(self):
802
# in an empty branch we decode the response properly
803
transport = MemoryTransport()
804
client = FakeClient(transport.base)
805
client.add_expected_call(
806
'Branch.get_stacked_on_url', ('quack/',),
807
'error', ('NotStacked',))
808
client.add_expected_call(
809
'Branch.get_parent', ('quack/',),
811
transport.mkdir('quack')
812
transport = transport.clone('quack')
813
branch = self.make_remote_branch(transport, client)
814
result = branch.get_parent()
815
client.finished_test()
816
self.assertEqual(None, result)
818
def test_parent_relative(self):
819
transport = MemoryTransport()
820
client = FakeClient(transport.base)
821
client.add_expected_call(
822
'Branch.get_stacked_on_url', ('kwaak/',),
823
'error', ('NotStacked',))
824
client.add_expected_call(
825
'Branch.get_parent', ('kwaak/',),
826
'success', ('../foo/',))
827
transport.mkdir('kwaak')
828
transport = transport.clone('kwaak')
829
branch = self.make_remote_branch(transport, client)
830
result = branch.get_parent()
831
self.assertEqual(transport.clone('../foo').base, result)
833
def test_parent_absolute(self):
834
transport = MemoryTransport()
835
client = FakeClient(transport.base)
836
client.add_expected_call(
837
'Branch.get_stacked_on_url', ('kwaak/',),
838
'error', ('NotStacked',))
839
client.add_expected_call(
840
'Branch.get_parent', ('kwaak/',),
841
'success', ('http://foo/',))
842
transport.mkdir('kwaak')
843
transport = transport.clone('kwaak')
844
branch = self.make_remote_branch(transport, client)
845
result = branch.get_parent()
846
self.assertEqual('http://foo/', result)
849
class TestBranchGetTagsBytes(RemoteBranchTestCase):
851
def test_backwards_compat(self):
852
self.setup_smart_server_with_call_log()
853
branch = self.make_branch('.')
854
self.reset_smart_call_log()
855
verb = 'Branch.get_tags_bytes'
856
self.disable_verb(verb)
857
branch.tags.get_tag_dict()
858
call_count = len([call for call in self.hpss_calls if
859
call.call.method == verb])
860
self.assertEqual(1, call_count)
862
def test_trivial(self):
863
transport = MemoryTransport()
864
client = FakeClient(transport.base)
865
client.add_expected_call(
866
'Branch.get_stacked_on_url', ('quack/',),
867
'error', ('NotStacked',))
868
client.add_expected_call(
869
'Branch.get_tags_bytes', ('quack/',),
871
transport.mkdir('quack')
872
transport = transport.clone('quack')
873
branch = self.make_remote_branch(transport, client)
874
result = branch.tags.get_tag_dict()
875
client.finished_test()
876
self.assertEqual({}, result)
879
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
881
def test_empty_branch(self):
882
# in an empty branch we decode the response properly
883
transport = MemoryTransport()
884
client = FakeClient(transport.base)
885
client.add_expected_call(
886
'Branch.get_stacked_on_url', ('quack/',),
887
'error', ('NotStacked',))
888
client.add_expected_call(
889
'Branch.last_revision_info', ('quack/',),
890
'success', ('ok', '0', 'null:'))
891
transport.mkdir('quack')
892
transport = transport.clone('quack')
893
branch = self.make_remote_branch(transport, client)
894
result = branch.last_revision_info()
895
client.finished_test()
896
self.assertEqual((0, NULL_REVISION), result)
898
def test_non_empty_branch(self):
899
# in a non-empty branch we also decode the response properly
900
revid = u'\xc8'.encode('utf8')
901
transport = MemoryTransport()
902
client = FakeClient(transport.base)
903
client.add_expected_call(
904
'Branch.get_stacked_on_url', ('kwaak/',),
905
'error', ('NotStacked',))
906
client.add_expected_call(
907
'Branch.last_revision_info', ('kwaak/',),
908
'success', ('ok', '2', revid))
909
transport.mkdir('kwaak')
910
transport = transport.clone('kwaak')
911
branch = self.make_remote_branch(transport, client)
912
result = branch.last_revision_info()
913
self.assertEqual((2, revid), result)
916
class TestBranch_get_stacked_on_url(TestRemote):
917
"""Test Branch._get_stacked_on_url rpc"""
919
def test_get_stacked_on_invalid_url(self):
920
# test that asking for a stacked on url the server can't access works.
921
# This isn't perfect, but then as we're in the same process there
922
# really isn't anything we can do to be 100% sure that the server
923
# doesn't just open in - this test probably needs to be rewritten using
924
# a spawn()ed server.
925
stacked_branch = self.make_branch('stacked', format='1.9')
926
memory_branch = self.make_branch('base', format='1.9')
927
vfs_url = self.get_vfs_only_url('base')
928
stacked_branch.set_stacked_on_url(vfs_url)
929
transport = stacked_branch.bzrdir.root_transport
930
client = FakeClient(transport.base)
931
client.add_expected_call(
932
'Branch.get_stacked_on_url', ('stacked/',),
933
'success', ('ok', vfs_url))
934
# XXX: Multiple calls are bad, this second call documents what is
936
client.add_expected_call(
937
'Branch.get_stacked_on_url', ('stacked/',),
938
'success', ('ok', vfs_url))
939
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
941
repo_fmt = remote.RemoteRepositoryFormat()
942
repo_fmt._custom_format = stacked_branch.repository._format
943
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
945
result = branch.get_stacked_on_url()
946
self.assertEqual(vfs_url, result)
948
def test_backwards_compatible(self):
949
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
950
base_branch = self.make_branch('base', format='1.6')
951
stacked_branch = self.make_branch('stacked', format='1.6')
952
stacked_branch.set_stacked_on_url('../base')
953
client = FakeClient(self.get_url())
954
branch_network_name = self.get_branch_format().network_name()
955
client.add_expected_call(
956
'BzrDir.open_branchV2', ('stacked/',),
957
'success', ('branch', branch_network_name))
958
client.add_expected_call(
959
'BzrDir.find_repositoryV3', ('stacked/',),
960
'success', ('ok', '', 'no', 'no', 'yes',
961
stacked_branch.repository._format.network_name()))
962
# called twice, once from constructor and then again by us
963
client.add_expected_call(
964
'Branch.get_stacked_on_url', ('stacked/',),
965
'unknown', ('Branch.get_stacked_on_url',))
966
client.add_expected_call(
967
'Branch.get_stacked_on_url', ('stacked/',),
968
'unknown', ('Branch.get_stacked_on_url',))
969
# this will also do vfs access, but that goes direct to the transport
970
# and isn't seen by the FakeClient.
971
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
972
remote.RemoteBzrDirFormat(), _client=client)
973
branch = bzrdir.open_branch()
974
result = branch.get_stacked_on_url()
975
self.assertEqual('../base', result)
976
client.finished_test()
977
# it's in the fallback list both for the RemoteRepository and its vfs
979
self.assertEqual(1, len(branch.repository._fallback_repositories))
981
len(branch.repository._real_repository._fallback_repositories))
983
def test_get_stacked_on_real_branch(self):
984
base_branch = self.make_branch('base', format='1.6')
985
stacked_branch = self.make_branch('stacked', format='1.6')
986
stacked_branch.set_stacked_on_url('../base')
987
reference_format = self.get_repo_format()
988
network_name = reference_format.network_name()
989
client = FakeClient(self.get_url())
990
branch_network_name = self.get_branch_format().network_name()
991
client.add_expected_call(
992
'BzrDir.open_branchV2', ('stacked/',),
993
'success', ('branch', branch_network_name))
994
client.add_expected_call(
995
'BzrDir.find_repositoryV3', ('stacked/',),
996
'success', ('ok', '', 'no', 'no', 'yes', network_name))
997
# called twice, once from constructor and then again by us
998
client.add_expected_call(
999
'Branch.get_stacked_on_url', ('stacked/',),
1000
'success', ('ok', '../base'))
1001
client.add_expected_call(
1002
'Branch.get_stacked_on_url', ('stacked/',),
1003
'success', ('ok', '../base'))
1004
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1005
remote.RemoteBzrDirFormat(), _client=client)
1006
branch = bzrdir.open_branch()
1007
result = branch.get_stacked_on_url()
1008
self.assertEqual('../base', result)
1009
client.finished_test()
1010
# it's in the fallback list both for the RemoteRepository.
1011
self.assertEqual(1, len(branch.repository._fallback_repositories))
1012
# And we haven't had to construct a real repository.
1013
self.assertEqual(None, branch.repository._real_repository)
1016
class TestBranchSetLastRevision(RemoteBranchTestCase):
1018
def test_set_empty(self):
1019
# set_revision_history([]) is translated to calling
1020
# Branch.set_last_revision(path, '') on the wire.
1021
transport = MemoryTransport()
1022
transport.mkdir('branch')
1023
transport = transport.clone('branch')
1025
client = FakeClient(transport.base)
1026
client.add_expected_call(
1027
'Branch.get_stacked_on_url', ('branch/',),
1028
'error', ('NotStacked',))
1029
client.add_expected_call(
1030
'Branch.lock_write', ('branch/', '', ''),
1031
'success', ('ok', 'branch token', 'repo token'))
1032
client.add_expected_call(
1033
'Branch.last_revision_info',
1035
'success', ('ok', '0', 'null:'))
1036
client.add_expected_call(
1037
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1039
client.add_expected_call(
1040
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1042
branch = self.make_remote_branch(transport, client)
1043
# This is a hack to work around the problem that RemoteBranch currently
1044
# unnecessarily invokes _ensure_real upon a call to lock_write.
1045
branch._ensure_real = lambda: None
1047
result = branch.set_revision_history([])
1049
self.assertEqual(None, result)
1050
client.finished_test()
1052
def test_set_nonempty(self):
1053
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1054
# Branch.set_last_revision(path, rev-idN) on the wire.
1055
transport = MemoryTransport()
1056
transport.mkdir('branch')
1057
transport = transport.clone('branch')
1059
client = FakeClient(transport.base)
1060
client.add_expected_call(
1061
'Branch.get_stacked_on_url', ('branch/',),
1062
'error', ('NotStacked',))
1063
client.add_expected_call(
1064
'Branch.lock_write', ('branch/', '', ''),
1065
'success', ('ok', 'branch token', 'repo token'))
1066
client.add_expected_call(
1067
'Branch.last_revision_info',
1069
'success', ('ok', '0', 'null:'))
1071
encoded_body = bz2.compress('\n'.join(lines))
1072
client.add_success_response_with_body(encoded_body, 'ok')
1073
client.add_expected_call(
1074
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1076
client.add_expected_call(
1077
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1079
branch = self.make_remote_branch(transport, client)
1080
# This is a hack to work around the problem that RemoteBranch currently
1081
# unnecessarily invokes _ensure_real upon a call to lock_write.
1082
branch._ensure_real = lambda: None
1083
# Lock the branch, reset the record of remote calls.
1085
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1087
self.assertEqual(None, result)
1088
client.finished_test()
1090
def test_no_such_revision(self):
1091
transport = MemoryTransport()
1092
transport.mkdir('branch')
1093
transport = transport.clone('branch')
1094
# A response of 'NoSuchRevision' is translated into an exception.
1095
client = FakeClient(transport.base)
1096
client.add_expected_call(
1097
'Branch.get_stacked_on_url', ('branch/',),
1098
'error', ('NotStacked',))
1099
client.add_expected_call(
1100
'Branch.lock_write', ('branch/', '', ''),
1101
'success', ('ok', 'branch token', 'repo token'))
1102
client.add_expected_call(
1103
'Branch.last_revision_info',
1105
'success', ('ok', '0', 'null:'))
1106
# get_graph calls to construct the revision history, for the set_rh
1109
encoded_body = bz2.compress('\n'.join(lines))
1110
client.add_success_response_with_body(encoded_body, 'ok')
1111
client.add_expected_call(
1112
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1113
'error', ('NoSuchRevision', 'rev-id'))
1114
client.add_expected_call(
1115
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1118
branch = self.make_remote_branch(transport, client)
1121
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1123
client.finished_test()
1125
def test_tip_change_rejected(self):
1126
"""TipChangeRejected responses cause a TipChangeRejected exception to
1129
transport = MemoryTransport()
1130
transport.mkdir('branch')
1131
transport = transport.clone('branch')
1132
client = FakeClient(transport.base)
1133
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1134
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1135
client.add_expected_call(
1136
'Branch.get_stacked_on_url', ('branch/',),
1137
'error', ('NotStacked',))
1138
client.add_expected_call(
1139
'Branch.lock_write', ('branch/', '', ''),
1140
'success', ('ok', 'branch token', 'repo token'))
1141
client.add_expected_call(
1142
'Branch.last_revision_info',
1144
'success', ('ok', '0', 'null:'))
1146
encoded_body = bz2.compress('\n'.join(lines))
1147
client.add_success_response_with_body(encoded_body, 'ok')
1148
client.add_expected_call(
1149
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1150
'error', ('TipChangeRejected', rejection_msg_utf8))
1151
client.add_expected_call(
1152
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1154
branch = self.make_remote_branch(transport, client)
1155
branch._ensure_real = lambda: None
1157
# The 'TipChangeRejected' error response triggered by calling
1158
# set_revision_history causes a TipChangeRejected exception.
1159
err = self.assertRaises(
1160
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1161
# The UTF-8 message from the response has been decoded into a unicode
1163
self.assertIsInstance(err.msg, unicode)
1164
self.assertEqual(rejection_msg_unicode, err.msg)
1166
client.finished_test()
1169
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1171
def test_set_last_revision_info(self):
1172
# set_last_revision_info(num, 'rev-id') is translated to calling
1173
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1174
transport = MemoryTransport()
1175
transport.mkdir('branch')
1176
transport = transport.clone('branch')
1177
client = FakeClient(transport.base)
1178
# get_stacked_on_url
1179
client.add_error_response('NotStacked')
1181
client.add_success_response('ok', 'branch token', 'repo token')
1182
# query the current revision
1183
client.add_success_response('ok', '0', 'null:')
1185
client.add_success_response('ok')
1187
client.add_success_response('ok')
1189
branch = self.make_remote_branch(transport, client)
1190
# Lock the branch, reset the record of remote calls.
1193
result = branch.set_last_revision_info(1234, 'a-revision-id')
1195
[('call', 'Branch.last_revision_info', ('branch/',)),
1196
('call', 'Branch.set_last_revision_info',
1197
('branch/', 'branch token', 'repo token',
1198
'1234', 'a-revision-id'))],
1200
self.assertEqual(None, result)
1202
def test_no_such_revision(self):
1203
# A response of 'NoSuchRevision' is translated into an exception.
1204
transport = MemoryTransport()
1205
transport.mkdir('branch')
1206
transport = transport.clone('branch')
1207
client = FakeClient(transport.base)
1208
# get_stacked_on_url
1209
client.add_error_response('NotStacked')
1211
client.add_success_response('ok', 'branch token', 'repo token')
1213
client.add_error_response('NoSuchRevision', 'revid')
1215
client.add_success_response('ok')
1217
branch = self.make_remote_branch(transport, client)
1218
# Lock the branch, reset the record of remote calls.
1223
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1226
def lock_remote_branch(self, branch):
1227
"""Trick a RemoteBranch into thinking it is locked."""
1228
branch._lock_mode = 'w'
1229
branch._lock_count = 2
1230
branch._lock_token = 'branch token'
1231
branch._repo_lock_token = 'repo token'
1232
branch.repository._lock_mode = 'w'
1233
branch.repository._lock_count = 2
1234
branch.repository._lock_token = 'repo token'
1236
def test_backwards_compatibility(self):
1237
"""If the server does not support the Branch.set_last_revision_info
1238
verb (which is new in 1.4), then the client falls back to VFS methods.
1240
# This test is a little messy. Unlike most tests in this file, it
1241
# doesn't purely test what a Remote* object sends over the wire, and
1242
# how it reacts to responses from the wire. It instead relies partly
1243
# on asserting that the RemoteBranch will call
1244
# self._real_branch.set_last_revision_info(...).
1246
# First, set up our RemoteBranch with a FakeClient that raises
1247
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1248
transport = MemoryTransport()
1249
transport.mkdir('branch')
1250
transport = transport.clone('branch')
1251
client = FakeClient(transport.base)
1252
client.add_expected_call(
1253
'Branch.get_stacked_on_url', ('branch/',),
1254
'error', ('NotStacked',))
1255
client.add_expected_call(
1256
'Branch.last_revision_info',
1258
'success', ('ok', '0', 'null:'))
1259
client.add_expected_call(
1260
'Branch.set_last_revision_info',
1261
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1262
'unknown', 'Branch.set_last_revision_info')
1264
branch = self.make_remote_branch(transport, client)
1265
class StubRealBranch(object):
1268
def set_last_revision_info(self, revno, revision_id):
1270
('set_last_revision_info', revno, revision_id))
1271
def _clear_cached_state(self):
1273
real_branch = StubRealBranch()
1274
branch._real_branch = real_branch
1275
self.lock_remote_branch(branch)
1277
# Call set_last_revision_info, and verify it behaved as expected.
1278
result = branch.set_last_revision_info(1234, 'a-revision-id')
1280
[('set_last_revision_info', 1234, 'a-revision-id')],
1282
client.finished_test()
1284
def test_unexpected_error(self):
1285
# If the server sends an error the client doesn't understand, it gets
1286
# turned into an UnknownErrorFromSmartServer, which is presented as a
1287
# non-internal error to the user.
1288
transport = MemoryTransport()
1289
transport.mkdir('branch')
1290
transport = transport.clone('branch')
1291
client = FakeClient(transport.base)
1292
# get_stacked_on_url
1293
client.add_error_response('NotStacked')
1295
client.add_success_response('ok', 'branch token', 'repo token')
1297
client.add_error_response('UnexpectedError')
1299
client.add_success_response('ok')
1301
branch = self.make_remote_branch(transport, client)
1302
# Lock the branch, reset the record of remote calls.
1306
err = self.assertRaises(
1307
errors.UnknownErrorFromSmartServer,
1308
branch.set_last_revision_info, 123, 'revid')
1309
self.assertEqual(('UnexpectedError',), err.error_tuple)
1312
def test_tip_change_rejected(self):
1313
"""TipChangeRejected responses cause a TipChangeRejected exception to
1316
transport = MemoryTransport()
1317
transport.mkdir('branch')
1318
transport = transport.clone('branch')
1319
client = FakeClient(transport.base)
1320
# get_stacked_on_url
1321
client.add_error_response('NotStacked')
1323
client.add_success_response('ok', 'branch token', 'repo token')
1325
client.add_error_response('TipChangeRejected', 'rejection message')
1327
client.add_success_response('ok')
1329
branch = self.make_remote_branch(transport, client)
1330
# Lock the branch, reset the record of remote calls.
1332
self.addCleanup(branch.unlock)
1335
# The 'TipChangeRejected' error response triggered by calling
1336
# set_last_revision_info causes a TipChangeRejected exception.
1337
err = self.assertRaises(
1338
errors.TipChangeRejected,
1339
branch.set_last_revision_info, 123, 'revid')
1340
self.assertEqual('rejection message', err.msg)
1343
class TestBranchGetSetConfig(RemoteBranchTestCase):
1345
def test_get_branch_conf(self):
1346
# in an empty branch we decode the response properly
1347
client = FakeClient()
1348
client.add_expected_call(
1349
'Branch.get_stacked_on_url', ('memory:///',),
1350
'error', ('NotStacked',),)
1351
client.add_success_response_with_body('# config file body', 'ok')
1352
transport = MemoryTransport()
1353
branch = self.make_remote_branch(transport, client)
1354
config = branch.get_config()
1355
config.has_explicit_nickname()
1357
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1358
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1361
def test_get_multi_line_branch_conf(self):
1362
# Make sure that multiple-line branch.conf files are supported
1364
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1365
client = FakeClient()
1366
client.add_expected_call(
1367
'Branch.get_stacked_on_url', ('memory:///',),
1368
'error', ('NotStacked',),)
1369
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1370
transport = MemoryTransport()
1371
branch = self.make_remote_branch(transport, client)
1372
config = branch.get_config()
1373
self.assertEqual(u'2', config.get_user_option('b'))
1375
def test_set_option(self):
1376
client = FakeClient()
1377
client.add_expected_call(
1378
'Branch.get_stacked_on_url', ('memory:///',),
1379
'error', ('NotStacked',),)
1380
client.add_expected_call(
1381
'Branch.lock_write', ('memory:///', '', ''),
1382
'success', ('ok', 'branch token', 'repo token'))
1383
client.add_expected_call(
1384
'Branch.set_config_option', ('memory:///', 'branch token',
1385
'repo token', 'foo', 'bar', ''),
1387
client.add_expected_call(
1388
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1390
transport = MemoryTransport()
1391
branch = self.make_remote_branch(transport, client)
1393
config = branch._get_config()
1394
config.set_option('foo', 'bar')
1396
client.finished_test()
1398
def test_backwards_compat_set_option(self):
1399
self.setup_smart_server_with_call_log()
1400
branch = self.make_branch('.')
1401
verb = 'Branch.set_config_option'
1402
self.disable_verb(verb)
1404
self.addCleanup(branch.unlock)
1405
self.reset_smart_call_log()
1406
branch._get_config().set_option('value', 'name')
1407
self.assertLength(10, self.hpss_calls)
1408
self.assertEqual('value', branch._get_config().get_option('name'))
1411
class TestBranchLockWrite(RemoteBranchTestCase):
1413
def test_lock_write_unlockable(self):
1414
transport = MemoryTransport()
1415
client = FakeClient(transport.base)
1416
client.add_expected_call(
1417
'Branch.get_stacked_on_url', ('quack/',),
1418
'error', ('NotStacked',),)
1419
client.add_expected_call(
1420
'Branch.lock_write', ('quack/', '', ''),
1421
'error', ('UnlockableTransport',))
1422
transport.mkdir('quack')
1423
transport = transport.clone('quack')
1424
branch = self.make_remote_branch(transport, client)
1425
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1426
client.finished_test()
1429
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1431
def test__get_config(self):
1432
client = FakeClient()
1433
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1434
transport = MemoryTransport()
1435
bzrdir = self.make_remote_bzrdir(transport, client)
1436
config = bzrdir.get_config()
1437
self.assertEqual('/', config.get_default_stack_on())
1439
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1442
def test_set_option_uses_vfs(self):
1443
self.setup_smart_server_with_call_log()
1444
bzrdir = self.make_bzrdir('.')
1445
verb = 'BzrDir.get_config_file'
1446
# self.disable_verb(verb)
1447
self.reset_smart_call_log()
1448
config = bzrdir.get_config()
1449
config.set_default_stack_on('/')
1450
self.assertLength(3, self.hpss_calls)
1452
def test_backwards_compat_get_option(self):
1453
self.setup_smart_server_with_call_log()
1454
bzrdir = self.make_bzrdir('.')
1455
verb = 'BzrDir.get_config_file'
1456
self.reset_smart_call_log()
1457
self.assertEqual(None,
1458
bzrdir._get_config().get_option('default_stack_on'))
1459
self.assertLength(3, self.hpss_calls)
1462
class TestTransportIsReadonly(tests.TestCase):
1464
def test_true(self):
1465
client = FakeClient()
1466
client.add_success_response('yes')
1467
transport = RemoteTransport('bzr://example.com/', medium=False,
1469
self.assertEqual(True, transport.is_readonly())
1471
[('call', 'Transport.is_readonly', ())],
1474
def test_false(self):
1475
client = FakeClient()
1476
client.add_success_response('no')
1477
transport = RemoteTransport('bzr://example.com/', medium=False,
1479
self.assertEqual(False, transport.is_readonly())
1481
[('call', 'Transport.is_readonly', ())],
1484
def test_error_from_old_server(self):
1485
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1487
Clients should treat it as a "no" response, because is_readonly is only
1488
advisory anyway (a transport could be read-write, but then the
1489
underlying filesystem could be readonly anyway).
1491
client = FakeClient()
1492
client.add_unknown_method_response('Transport.is_readonly')
1493
transport = RemoteTransport('bzr://example.com/', medium=False,
1495
self.assertEqual(False, transport.is_readonly())
1497
[('call', 'Transport.is_readonly', ())],
1501
class TestTransportMkdir(tests.TestCase):
1503
def test_permissiondenied(self):
1504
client = FakeClient()
1505
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1506
transport = RemoteTransport('bzr://example.com/', medium=False,
1508
exc = self.assertRaises(
1509
errors.PermissionDenied, transport.mkdir, 'client path')
1510
expected_error = errors.PermissionDenied('/client path', 'extra')
1511
self.assertEqual(expected_error, exc)
1514
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1516
def test_defaults_to_getuser(self):
1517
t = RemoteSSHTransport('bzr+ssh://example.com')
1518
self.assertIs(getpass.getuser(), t._get_credentials()[0])
1520
def test_uses_authentication_config(self):
1521
conf = config.AuthenticationConfig()
1522
conf._get_config().update(
1523
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1526
t = RemoteSSHTransport('bzr+ssh://example.com')
1527
self.assertEqual('bar', t._get_credentials()[0])
1530
class TestRemoteRepository(TestRemote):
1531
"""Base for testing RemoteRepository protocol usage.
1533
These tests contain frozen requests and responses. We want any changes to
1534
what is sent or expected to be require a thoughtful update to these tests
1535
because they might break compatibility with different-versioned servers.
1538
def setup_fake_client_and_repository(self, transport_path):
1539
"""Create the fake client and repository for testing with.
1541
There's no real server here; we just have canned responses sent
1544
:param transport_path: Path below the root of the MemoryTransport
1545
where the repository will be created.
1547
transport = MemoryTransport()
1548
transport.mkdir(transport_path)
1549
client = FakeClient(transport.base)
1550
transport = transport.clone(transport_path)
1551
# we do not want bzrdir to make any remote calls
1552
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1554
repo = RemoteRepository(bzrdir, None, _client=client)
1558
class TestRepositoryFormat(TestRemoteRepository):
1560
def test_fast_delta(self):
1561
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1562
true_format = RemoteRepositoryFormat()
1563
true_format._network_name = true_name
1564
self.assertEqual(True, true_format.fast_deltas)
1565
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1566
false_format = RemoteRepositoryFormat()
1567
false_format._network_name = false_name
1568
self.assertEqual(False, false_format.fast_deltas)
1571
class TestRepositoryGatherStats(TestRemoteRepository):
1573
def test_revid_none(self):
1574
# ('ok',), body with revisions and size
1575
transport_path = 'quack'
1576
repo, client = self.setup_fake_client_and_repository(transport_path)
1577
client.add_success_response_with_body(
1578
'revisions: 2\nsize: 18\n', 'ok')
1579
result = repo.gather_stats(None)
1581
[('call_expecting_body', 'Repository.gather_stats',
1582
('quack/','','no'))],
1584
self.assertEqual({'revisions': 2, 'size': 18}, result)
1586
def test_revid_no_committers(self):
1587
# ('ok',), body without committers
1588
body = ('firstrev: 123456.300 3600\n'
1589
'latestrev: 654231.400 0\n'
1592
transport_path = 'quick'
1593
revid = u'\xc8'.encode('utf8')
1594
repo, client = self.setup_fake_client_and_repository(transport_path)
1595
client.add_success_response_with_body(body, 'ok')
1596
result = repo.gather_stats(revid)
1598
[('call_expecting_body', 'Repository.gather_stats',
1599
('quick/', revid, 'no'))],
1601
self.assertEqual({'revisions': 2, 'size': 18,
1602
'firstrev': (123456.300, 3600),
1603
'latestrev': (654231.400, 0),},
1606
def test_revid_with_committers(self):
1607
# ('ok',), body with committers
1608
body = ('committers: 128\n'
1609
'firstrev: 123456.300 3600\n'
1610
'latestrev: 654231.400 0\n'
1613
transport_path = 'buick'
1614
revid = u'\xc8'.encode('utf8')
1615
repo, client = self.setup_fake_client_and_repository(transport_path)
1616
client.add_success_response_with_body(body, 'ok')
1617
result = repo.gather_stats(revid, True)
1619
[('call_expecting_body', 'Repository.gather_stats',
1620
('buick/', revid, 'yes'))],
1622
self.assertEqual({'revisions': 2, 'size': 18,
1624
'firstrev': (123456.300, 3600),
1625
'latestrev': (654231.400, 0),},
1629
class TestRepositoryGetGraph(TestRemoteRepository):
1631
def test_get_graph(self):
1632
# get_graph returns a graph with a custom parents provider.
1633
transport_path = 'quack'
1634
repo, client = self.setup_fake_client_and_repository(transport_path)
1635
graph = repo.get_graph()
1636
self.assertNotEqual(graph._parents_provider, repo)
1639
class TestRepositoryGetParentMap(TestRemoteRepository):
1641
def test_get_parent_map_caching(self):
1642
# get_parent_map returns from cache until unlock()
1643
# setup a reponse with two revisions
1644
r1 = u'\u0e33'.encode('utf8')
1645
r2 = u'\u0dab'.encode('utf8')
1646
lines = [' '.join([r2, r1]), r1]
1647
encoded_body = bz2.compress('\n'.join(lines))
1649
transport_path = 'quack'
1650
repo, client = self.setup_fake_client_and_repository(transport_path)
1651
client.add_success_response_with_body(encoded_body, 'ok')
1652
client.add_success_response_with_body(encoded_body, 'ok')
1654
graph = repo.get_graph()
1655
parents = graph.get_parent_map([r2])
1656
self.assertEqual({r2: (r1,)}, parents)
1657
# locking and unlocking deeper should not reset
1660
parents = graph.get_parent_map([r1])
1661
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1663
[('call_with_body_bytes_expecting_body',
1664
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1668
# now we call again, and it should use the second response.
1670
graph = repo.get_graph()
1671
parents = graph.get_parent_map([r1])
1672
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1674
[('call_with_body_bytes_expecting_body',
1675
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1677
('call_with_body_bytes_expecting_body',
1678
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1684
def test_get_parent_map_reconnects_if_unknown_method(self):
1685
transport_path = 'quack'
1686
rev_id = 'revision-id'
1687
repo, client = self.setup_fake_client_and_repository(transport_path)
1688
client.add_unknown_method_response('Repository.get_parent_map')
1689
client.add_success_response_with_body(rev_id, 'ok')
1690
self.assertFalse(client._medium._is_remote_before((1, 2)))
1691
parents = repo.get_parent_map([rev_id])
1693
[('call_with_body_bytes_expecting_body',
1694
'Repository.get_parent_map', ('quack/', 'include-missing:',
1696
('disconnect medium',),
1697
('call_expecting_body', 'Repository.get_revision_graph',
1700
# The medium is now marked as being connected to an older server
1701
self.assertTrue(client._medium._is_remote_before((1, 2)))
1702
self.assertEqual({rev_id: ('null:',)}, parents)
1704
def test_get_parent_map_fallback_parentless_node(self):
1705
"""get_parent_map falls back to get_revision_graph on old servers. The
1706
results from get_revision_graph are tweaked to match the get_parent_map
1709
Specifically, a {key: ()} result from get_revision_graph means "no
1710
parents" for that key, which in get_parent_map results should be
1711
represented as {key: ('null:',)}.
1713
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1715
rev_id = 'revision-id'
1716
transport_path = 'quack'
1717
repo, client = self.setup_fake_client_and_repository(transport_path)
1718
client.add_success_response_with_body(rev_id, 'ok')
1719
client._medium._remember_remote_is_before((1, 2))
1720
parents = repo.get_parent_map([rev_id])
1722
[('call_expecting_body', 'Repository.get_revision_graph',
1725
self.assertEqual({rev_id: ('null:',)}, parents)
1727
def test_get_parent_map_unexpected_response(self):
1728
repo, client = self.setup_fake_client_and_repository('path')
1729
client.add_success_response('something unexpected!')
1731
errors.UnexpectedSmartServerResponse,
1732
repo.get_parent_map, ['a-revision-id'])
1734
def test_get_parent_map_negative_caches_missing_keys(self):
1735
self.setup_smart_server_with_call_log()
1736
repo = self.make_repository('foo')
1737
self.assertIsInstance(repo, RemoteRepository)
1739
self.addCleanup(repo.unlock)
1740
self.reset_smart_call_log()
1741
graph = repo.get_graph()
1742
self.assertEqual({},
1743
graph.get_parent_map(['some-missing', 'other-missing']))
1744
self.assertLength(1, self.hpss_calls)
1745
# No call if we repeat this
1746
self.reset_smart_call_log()
1747
graph = repo.get_graph()
1748
self.assertEqual({},
1749
graph.get_parent_map(['some-missing', 'other-missing']))
1750
self.assertLength(0, self.hpss_calls)
1751
# Asking for more unknown keys makes a request.
1752
self.reset_smart_call_log()
1753
graph = repo.get_graph()
1754
self.assertEqual({},
1755
graph.get_parent_map(['some-missing', 'other-missing',
1757
self.assertLength(1, self.hpss_calls)
1759
def disableExtraResults(self):
1760
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1761
SmartServerRepositoryGetParentMap.no_extra_results = True
1763
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1764
self.addCleanup(reset_values)
1766
def test_null_cached_missing_and_stop_key(self):
1767
self.setup_smart_server_with_call_log()
1768
# Make a branch with a single revision.
1769
builder = self.make_branch_builder('foo')
1770
builder.start_series()
1771
builder.build_snapshot('first', None, [
1772
('add', ('', 'root-id', 'directory', ''))])
1773
builder.finish_series()
1774
branch = builder.get_branch()
1775
repo = branch.repository
1776
self.assertIsInstance(repo, RemoteRepository)
1777
# Stop the server from sending extra results.
1778
self.disableExtraResults()
1780
self.addCleanup(repo.unlock)
1781
self.reset_smart_call_log()
1782
graph = repo.get_graph()
1783
# Query for 'first' and 'null:'. Because 'null:' is a parent of
1784
# 'first' it will be a candidate for the stop_keys of subsequent
1785
# requests, and because 'null:' was queried but not returned it will be
1786
# cached as missing.
1787
self.assertEqual({'first': ('null:',)},
1788
graph.get_parent_map(['first', 'null:']))
1789
# Now query for another key. This request will pass along a recipe of
1790
# start and stop keys describing the already cached results, and this
1791
# recipe's revision count must be correct (or else it will trigger an
1792
# error from the server).
1793
self.assertEqual({}, graph.get_parent_map(['another-key']))
1794
# This assertion guards against disableExtraResults silently failing to
1795
# work, thus invalidating the test.
1796
self.assertLength(2, self.hpss_calls)
1798
def test_get_parent_map_gets_ghosts_from_result(self):
1799
# asking for a revision should negatively cache close ghosts in its
1801
self.setup_smart_server_with_call_log()
1802
tree = self.make_branch_and_memory_tree('foo')
1805
builder = treebuilder.TreeBuilder()
1806
builder.start_tree(tree)
1808
builder.finish_tree()
1809
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1810
rev_id = tree.commit('')
1814
self.addCleanup(tree.unlock)
1815
repo = tree.branch.repository
1816
self.assertIsInstance(repo, RemoteRepository)
1818
repo.get_parent_map([rev_id])
1819
self.reset_smart_call_log()
1820
# Now asking for rev_id's ghost parent should not make calls
1821
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1822
self.assertLength(0, self.hpss_calls)
1825
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1827
def test_allows_new_revisions(self):
1828
"""get_parent_map's results can be updated by commit."""
1829
smart_server = server.SmartTCPServer_for_testing()
1830
smart_server.setUp()
1831
self.addCleanup(smart_server.tearDown)
1832
self.make_branch('branch')
1833
branch = Branch.open(smart_server.get_url() + '/branch')
1834
tree = branch.create_checkout('tree', lightweight=True)
1836
self.addCleanup(tree.unlock)
1837
graph = tree.branch.repository.get_graph()
1838
# This provides an opportunity for the missing rev-id to be cached.
1839
self.assertEqual({}, graph.get_parent_map(['rev1']))
1840
tree.commit('message', rev_id='rev1')
1841
graph = tree.branch.repository.get_graph()
1842
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1845
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1847
def test_null_revision(self):
1848
# a null revision has the predictable result {}, we should have no wire
1849
# traffic when calling it with this argument
1850
transport_path = 'empty'
1851
repo, client = self.setup_fake_client_and_repository(transport_path)
1852
client.add_success_response('notused')
1853
# actual RemoteRepository.get_revision_graph is gone, but there's an
1854
# equivalent private method for testing
1855
result = repo._get_revision_graph(NULL_REVISION)
1856
self.assertEqual([], client._calls)
1857
self.assertEqual({}, result)
1859
def test_none_revision(self):
1860
# with none we want the entire graph
1861
r1 = u'\u0e33'.encode('utf8')
1862
r2 = u'\u0dab'.encode('utf8')
1863
lines = [' '.join([r2, r1]), r1]
1864
encoded_body = '\n'.join(lines)
1866
transport_path = 'sinhala'
1867
repo, client = self.setup_fake_client_and_repository(transport_path)
1868
client.add_success_response_with_body(encoded_body, 'ok')
1869
# actual RemoteRepository.get_revision_graph is gone, but there's an
1870
# equivalent private method for testing
1871
result = repo._get_revision_graph(None)
1873
[('call_expecting_body', 'Repository.get_revision_graph',
1876
self.assertEqual({r1: (), r2: (r1, )}, result)
1878
def test_specific_revision(self):
1879
# with a specific revision we want the graph for that
1880
# with none we want the entire graph
1881
r11 = u'\u0e33'.encode('utf8')
1882
r12 = u'\xc9'.encode('utf8')
1883
r2 = u'\u0dab'.encode('utf8')
1884
lines = [' '.join([r2, r11, r12]), r11, r12]
1885
encoded_body = '\n'.join(lines)
1887
transport_path = 'sinhala'
1888
repo, client = self.setup_fake_client_and_repository(transport_path)
1889
client.add_success_response_with_body(encoded_body, 'ok')
1890
result = repo._get_revision_graph(r2)
1892
[('call_expecting_body', 'Repository.get_revision_graph',
1895
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1897
def test_no_such_revision(self):
1899
transport_path = 'sinhala'
1900
repo, client = self.setup_fake_client_and_repository(transport_path)
1901
client.add_error_response('nosuchrevision', revid)
1902
# also check that the right revision is reported in the error
1903
self.assertRaises(errors.NoSuchRevision,
1904
repo._get_revision_graph, revid)
1906
[('call_expecting_body', 'Repository.get_revision_graph',
1907
('sinhala/', revid))],
1910
def test_unexpected_error(self):
1912
transport_path = 'sinhala'
1913
repo, client = self.setup_fake_client_and_repository(transport_path)
1914
client.add_error_response('AnUnexpectedError')
1915
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1916
repo._get_revision_graph, revid)
1917
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1920
class TestRepositoryIsShared(TestRemoteRepository):
1922
def test_is_shared(self):
1923
# ('yes', ) for Repository.is_shared -> 'True'.
1924
transport_path = 'quack'
1925
repo, client = self.setup_fake_client_and_repository(transport_path)
1926
client.add_success_response('yes')
1927
result = repo.is_shared()
1929
[('call', 'Repository.is_shared', ('quack/',))],
1931
self.assertEqual(True, result)
1933
def test_is_not_shared(self):
1934
# ('no', ) for Repository.is_shared -> 'False'.
1935
transport_path = 'qwack'
1936
repo, client = self.setup_fake_client_and_repository(transport_path)
1937
client.add_success_response('no')
1938
result = repo.is_shared()
1940
[('call', 'Repository.is_shared', ('qwack/',))],
1942
self.assertEqual(False, result)
1945
class TestRepositoryLockWrite(TestRemoteRepository):
1947
def test_lock_write(self):
1948
transport_path = 'quack'
1949
repo, client = self.setup_fake_client_and_repository(transport_path)
1950
client.add_success_response('ok', 'a token')
1951
result = repo.lock_write()
1953
[('call', 'Repository.lock_write', ('quack/', ''))],
1955
self.assertEqual('a token', result)
1957
def test_lock_write_already_locked(self):
1958
transport_path = 'quack'
1959
repo, client = self.setup_fake_client_and_repository(transport_path)
1960
client.add_error_response('LockContention')
1961
self.assertRaises(errors.LockContention, repo.lock_write)
1963
[('call', 'Repository.lock_write', ('quack/', ''))],
1966
def test_lock_write_unlockable(self):
1967
transport_path = 'quack'
1968
repo, client = self.setup_fake_client_and_repository(transport_path)
1969
client.add_error_response('UnlockableTransport')
1970
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1972
[('call', 'Repository.lock_write', ('quack/', ''))],
1976
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1978
def test_backwards_compat(self):
1979
self.setup_smart_server_with_call_log()
1980
repo = self.make_repository('.')
1981
self.reset_smart_call_log()
1982
verb = 'Repository.set_make_working_trees'
1983
self.disable_verb(verb)
1984
repo.set_make_working_trees(True)
1985
call_count = len([call for call in self.hpss_calls if
1986
call.call.method == verb])
1987
self.assertEqual(1, call_count)
1989
def test_current(self):
1990
transport_path = 'quack'
1991
repo, client = self.setup_fake_client_and_repository(transport_path)
1992
client.add_expected_call(
1993
'Repository.set_make_working_trees', ('quack/', 'True'),
1995
client.add_expected_call(
1996
'Repository.set_make_working_trees', ('quack/', 'False'),
1998
repo.set_make_working_trees(True)
1999
repo.set_make_working_trees(False)
2002
class TestRepositoryUnlock(TestRemoteRepository):
2004
def test_unlock(self):
2005
transport_path = 'quack'
2006
repo, client = self.setup_fake_client_and_repository(transport_path)
2007
client.add_success_response('ok', 'a token')
2008
client.add_success_response('ok')
2012
[('call', 'Repository.lock_write', ('quack/', '')),
2013
('call', 'Repository.unlock', ('quack/', 'a token'))],
2016
def test_unlock_wrong_token(self):
2017
# If somehow the token is wrong, unlock will raise TokenMismatch.
2018
transport_path = 'quack'
2019
repo, client = self.setup_fake_client_and_repository(transport_path)
2020
client.add_success_response('ok', 'a token')
2021
client.add_error_response('TokenMismatch')
2023
self.assertRaises(errors.TokenMismatch, repo.unlock)
2026
class TestRepositoryHasRevision(TestRemoteRepository):
2028
def test_none(self):
2029
# repo.has_revision(None) should not cause any traffic.
2030
transport_path = 'quack'
2031
repo, client = self.setup_fake_client_and_repository(transport_path)
2033
# The null revision is always there, so has_revision(None) == True.
2034
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2036
# The remote repo shouldn't be accessed.
2037
self.assertEqual([], client._calls)
2040
class TestRepositoryInsertStream(TestRemoteRepository):
2042
def test_unlocked_repo(self):
2043
transport_path = 'quack'
2044
repo, client = self.setup_fake_client_and_repository(transport_path)
2045
client.add_expected_call(
2046
'Repository.insert_stream', ('quack/', ''),
2048
client.add_expected_call(
2049
'Repository.insert_stream', ('quack/', ''),
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()
2058
def test_locked_repo_with_no_lock_token(self):
2059
transport_path = 'quack'
2060
repo, client = self.setup_fake_client_and_repository(transport_path)
2061
client.add_expected_call(
2062
'Repository.lock_write', ('quack/', ''),
2063
'success', ('ok', ''))
2064
client.add_expected_call(
2065
'Repository.insert_stream', ('quack/', ''),
2067
client.add_expected_call(
2068
'Repository.insert_stream', ('quack/', ''),
2071
sink = repo._get_sink()
2072
fmt = repository.RepositoryFormat.get_default_format()
2073
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2074
self.assertEqual([], resume_tokens)
2075
self.assertEqual(set(), missing_keys)
2076
client.finished_test()
2078
def test_locked_repo_with_lock_token(self):
2079
transport_path = 'quack'
2080
repo, client = self.setup_fake_client_and_repository(transport_path)
2081
client.add_expected_call(
2082
'Repository.lock_write', ('quack/', ''),
2083
'success', ('ok', 'a token'))
2084
client.add_expected_call(
2085
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2087
client.add_expected_call(
2088
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2091
sink = repo._get_sink()
2092
fmt = repository.RepositoryFormat.get_default_format()
2093
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2094
self.assertEqual([], resume_tokens)
2095
self.assertEqual(set(), missing_keys)
2096
client.finished_test()
2099
class TestRepositoryTarball(TestRemoteRepository):
2101
# This is a canned tarball reponse we can validate against
2103
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2104
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2105
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2106
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2107
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2108
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2109
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2110
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2111
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2112
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2113
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2114
'nWQ7QH/F3JFOFCQ0aSPfA='
2117
def test_repository_tarball(self):
2118
# Test that Repository.tarball generates the right operations
2119
transport_path = 'repo'
2120
expected_calls = [('call_expecting_body', 'Repository.tarball',
2121
('repo/', 'bz2',),),
2123
repo, client = self.setup_fake_client_and_repository(transport_path)
2124
client.add_success_response_with_body(self.tarball_content, 'ok')
2125
# Now actually ask for the tarball
2126
tarball_file = repo._get_tarball('bz2')
2128
self.assertEqual(expected_calls, client._calls)
2129
self.assertEqual(self.tarball_content, tarball_file.read())
2131
tarball_file.close()
2134
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2135
"""RemoteRepository.copy_content_into optimizations"""
2137
def test_copy_content_remote_to_local(self):
2138
self.transport_server = server.SmartTCPServer_for_testing
2139
src_repo = self.make_repository('repo1')
2140
src_repo = repository.Repository.open(self.get_url('repo1'))
2141
# At the moment the tarball-based copy_content_into can't write back
2142
# into a smart server. It would be good if it could upload the
2143
# tarball; once that works we'd have to create repositories of
2144
# different formats. -- mbp 20070410
2145
dest_url = self.get_vfs_only_url('repo2')
2146
dest_bzrdir = BzrDir.create(dest_url)
2147
dest_repo = dest_bzrdir.create_repository()
2148
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2149
self.assertTrue(isinstance(src_repo, RemoteRepository))
2150
src_repo.copy_content_into(dest_repo)
2153
class _StubRealPackRepository(object):
2155
def __init__(self, calls):
2157
self._pack_collection = _StubPackCollection(calls)
2159
def is_in_write_group(self):
2162
def refresh_data(self):
2163
self.calls.append(('pack collection reload_pack_names',))
2166
class _StubPackCollection(object):
2168
def __init__(self, calls):
2172
self.calls.append(('pack collection autopack',))
2175
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2176
"""Tests for RemoteRepository.autopack implementation."""
2179
"""When the server returns 'ok' and there's no _real_repository, then
2180
nothing else happens: the autopack method is done.
2182
transport_path = 'quack'
2183
repo, client = self.setup_fake_client_and_repository(transport_path)
2184
client.add_expected_call(
2185
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2187
client.finished_test()
2189
def test_ok_with_real_repo(self):
2190
"""When the server returns 'ok' and there is a _real_repository, then
2191
the _real_repository's reload_pack_name's method will be called.
2193
transport_path = 'quack'
2194
repo, client = self.setup_fake_client_and_repository(transport_path)
2195
client.add_expected_call(
2196
'PackRepository.autopack', ('quack/',),
2198
repo._real_repository = _StubRealPackRepository(client._calls)
2201
[('call', 'PackRepository.autopack', ('quack/',)),
2202
('pack collection reload_pack_names',)],
2205
def test_backwards_compatibility(self):
2206
"""If the server does not recognise the PackRepository.autopack verb,
2207
fallback to the real_repository's implementation.
2209
transport_path = 'quack'
2210
repo, client = self.setup_fake_client_and_repository(transport_path)
2211
client.add_unknown_method_response('PackRepository.autopack')
2212
def stub_ensure_real():
2213
client._calls.append(('_ensure_real',))
2214
repo._real_repository = _StubRealPackRepository(client._calls)
2215
repo._ensure_real = stub_ensure_real
2218
[('call', 'PackRepository.autopack', ('quack/',)),
2220
('pack collection autopack',)],
2224
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2225
"""Base class for unit tests for bzrlib.remote._translate_error."""
2227
def translateTuple(self, error_tuple, **context):
2228
"""Call _translate_error with an ErrorFromSmartServer built from the
2231
:param error_tuple: A tuple of a smart server response, as would be
2232
passed to an ErrorFromSmartServer.
2233
:kwargs context: context items to call _translate_error with.
2235
:returns: The error raised by _translate_error.
2237
# Raise the ErrorFromSmartServer before passing it as an argument,
2238
# because _translate_error may need to re-raise it with a bare 'raise'
2240
server_error = errors.ErrorFromSmartServer(error_tuple)
2241
translated_error = self.translateErrorFromSmartServer(
2242
server_error, **context)
2243
return translated_error
2245
def translateErrorFromSmartServer(self, error_object, **context):
2246
"""Like translateTuple, but takes an already constructed
2247
ErrorFromSmartServer rather than a tuple.
2251
except errors.ErrorFromSmartServer, server_error:
2252
translated_error = self.assertRaises(
2253
errors.BzrError, remote._translate_error, server_error,
2255
return translated_error
2258
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2259
"""Unit tests for bzrlib.remote._translate_error.
2261
Given an ErrorFromSmartServer (which has an error tuple from a smart
2262
server) and some context, _translate_error raises more specific errors from
2265
This test case covers the cases where _translate_error succeeds in
2266
translating an ErrorFromSmartServer to something better. See
2267
TestErrorTranslationRobustness for other cases.
2270
def test_NoSuchRevision(self):
2271
branch = self.make_branch('')
2273
translated_error = self.translateTuple(
2274
('NoSuchRevision', revid), branch=branch)
2275
expected_error = errors.NoSuchRevision(branch, revid)
2276
self.assertEqual(expected_error, translated_error)
2278
def test_nosuchrevision(self):
2279
repository = self.make_repository('')
2281
translated_error = self.translateTuple(
2282
('nosuchrevision', revid), repository=repository)
2283
expected_error = errors.NoSuchRevision(repository, revid)
2284
self.assertEqual(expected_error, translated_error)
2286
def test_nobranch(self):
2287
bzrdir = self.make_bzrdir('')
2288
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2289
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2290
self.assertEqual(expected_error, translated_error)
2292
def test_LockContention(self):
2293
translated_error = self.translateTuple(('LockContention',))
2294
expected_error = errors.LockContention('(remote lock)')
2295
self.assertEqual(expected_error, translated_error)
2297
def test_UnlockableTransport(self):
2298
bzrdir = self.make_bzrdir('')
2299
translated_error = self.translateTuple(
2300
('UnlockableTransport',), bzrdir=bzrdir)
2301
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2302
self.assertEqual(expected_error, translated_error)
2304
def test_LockFailed(self):
2305
lock = 'str() of a server lock'
2306
why = 'str() of why'
2307
translated_error = self.translateTuple(('LockFailed', lock, why))
2308
expected_error = errors.LockFailed(lock, why)
2309
self.assertEqual(expected_error, translated_error)
2311
def test_TokenMismatch(self):
2312
token = 'a lock token'
2313
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2314
expected_error = errors.TokenMismatch(token, '(remote token)')
2315
self.assertEqual(expected_error, translated_error)
2317
def test_Diverged(self):
2318
branch = self.make_branch('a')
2319
other_branch = self.make_branch('b')
2320
translated_error = self.translateTuple(
2321
('Diverged',), branch=branch, other_branch=other_branch)
2322
expected_error = errors.DivergedBranches(branch, other_branch)
2323
self.assertEqual(expected_error, translated_error)
2325
def test_ReadError_no_args(self):
2327
translated_error = self.translateTuple(('ReadError',), path=path)
2328
expected_error = errors.ReadError(path)
2329
self.assertEqual(expected_error, translated_error)
2331
def test_ReadError(self):
2333
translated_error = self.translateTuple(('ReadError', path))
2334
expected_error = errors.ReadError(path)
2335
self.assertEqual(expected_error, translated_error)
2337
def test_PermissionDenied_no_args(self):
2339
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2340
expected_error = errors.PermissionDenied(path)
2341
self.assertEqual(expected_error, translated_error)
2343
def test_PermissionDenied_one_arg(self):
2345
translated_error = self.translateTuple(('PermissionDenied', path))
2346
expected_error = errors.PermissionDenied(path)
2347
self.assertEqual(expected_error, translated_error)
2349
def test_PermissionDenied_one_arg_and_context(self):
2350
"""Given a choice between a path from the local context and a path on
2351
the wire, _translate_error prefers the path from the local context.
2353
local_path = 'local path'
2354
remote_path = 'remote path'
2355
translated_error = self.translateTuple(
2356
('PermissionDenied', remote_path), path=local_path)
2357
expected_error = errors.PermissionDenied(local_path)
2358
self.assertEqual(expected_error, translated_error)
2360
def test_PermissionDenied_two_args(self):
2362
extra = 'a string with extra info'
2363
translated_error = self.translateTuple(
2364
('PermissionDenied', path, extra))
2365
expected_error = errors.PermissionDenied(path, extra)
2366
self.assertEqual(expected_error, translated_error)
2369
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2370
"""Unit tests for bzrlib.remote._translate_error's robustness.
2372
TestErrorTranslationSuccess is for cases where _translate_error can
2373
translate successfully. This class about how _translate_err behaves when
2374
it fails to translate: it re-raises the original error.
2377
def test_unrecognised_server_error(self):
2378
"""If the error code from the server is not recognised, the original
2379
ErrorFromSmartServer is propagated unmodified.
2381
error_tuple = ('An unknown error tuple',)
2382
server_error = errors.ErrorFromSmartServer(error_tuple)
2383
translated_error = self.translateErrorFromSmartServer(server_error)
2384
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2385
self.assertEqual(expected_error, translated_error)
2387
def test_context_missing_a_key(self):
2388
"""In case of a bug in the client, or perhaps an unexpected response
2389
from a server, _translate_error returns the original error tuple from
2390
the server and mutters a warning.
2392
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2393
# in the context dict. So let's give it an empty context dict instead
2394
# to exercise its error recovery.
2396
error_tuple = ('NoSuchRevision', 'revid')
2397
server_error = errors.ErrorFromSmartServer(error_tuple)
2398
translated_error = self.translateErrorFromSmartServer(server_error)
2399
self.assertEqual(server_error, translated_error)
2400
# In addition to re-raising ErrorFromSmartServer, some debug info has
2401
# been muttered to the log file for developer to look at.
2402
self.assertContainsRe(
2403
self._get_log(keep_log_file=True),
2404
"Missing key 'branch' in context")
2406
def test_path_missing(self):
2407
"""Some translations (PermissionDenied, ReadError) can determine the
2408
'path' variable from either the wire or the local context. If neither
2409
has it, then an error is raised.
2411
error_tuple = ('ReadError',)
2412
server_error = errors.ErrorFromSmartServer(error_tuple)
2413
translated_error = self.translateErrorFromSmartServer(server_error)
2414
self.assertEqual(server_error, translated_error)
2415
# In addition to re-raising ErrorFromSmartServer, some debug info has
2416
# been muttered to the log file for developer to look at.
2417
self.assertContainsRe(
2418
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2421
class TestStacking(tests.TestCaseWithTransport):
2422
"""Tests for operations on stacked remote repositories.
2424
The underlying format type must support stacking.
2427
def test_access_stacked_remote(self):
2428
# based on <http://launchpad.net/bugs/261315>
2429
# make a branch stacked on another repository containing an empty
2430
# revision, then open it over hpss - we should be able to see that
2432
base_transport = self.get_transport()
2433
base_builder = self.make_branch_builder('base', format='1.9')
2434
base_builder.start_series()
2435
base_revid = base_builder.build_snapshot('rev-id', None,
2436
[('add', ('', None, 'directory', None))],
2438
base_builder.finish_series()
2439
stacked_branch = self.make_branch('stacked', format='1.9')
2440
stacked_branch.set_stacked_on_url('../base')
2441
# start a server looking at this
2442
smart_server = server.SmartTCPServer_for_testing()
2443
smart_server.setUp()
2444
self.addCleanup(smart_server.tearDown)
2445
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2446
# can get its branch and repository
2447
remote_branch = remote_bzrdir.open_branch()
2448
remote_repo = remote_branch.repository
2449
remote_repo.lock_read()
2451
# it should have an appropriate fallback repository, which should also
2452
# be a RemoteRepository
2453
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2454
self.assertIsInstance(remote_repo._fallback_repositories[0],
2456
# and it has the revision committed to the underlying repository;
2457
# these have varying implementations so we try several of them
2458
self.assertTrue(remote_repo.has_revisions([base_revid]))
2459
self.assertTrue(remote_repo.has_revision(base_revid))
2460
self.assertEqual(remote_repo.get_revision(base_revid).message,
2463
remote_repo.unlock()
2465
def prepare_stacked_remote_branch(self):
2466
"""Get stacked_upon and stacked branches with content in each."""
2467
self.setup_smart_server_with_call_log()
2468
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2469
tree1.commit('rev1', rev_id='rev1')
2470
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2471
).open_workingtree()
2472
tree2.commit('local changes make me feel good.')
2473
branch2 = Branch.open(self.get_url('tree2'))
2475
self.addCleanup(branch2.unlock)
2476
return tree1.branch, branch2
2478
def test_stacked_get_parent_map(self):
2479
# the public implementation of get_parent_map obeys stacking
2480
_, branch = self.prepare_stacked_remote_branch()
2481
repo = branch.repository
2482
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2484
def test_unstacked_get_parent_map(self):
2485
# _unstacked_provider.get_parent_map ignores stacking
2486
_, branch = self.prepare_stacked_remote_branch()
2487
provider = branch.repository._unstacked_provider
2488
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2490
def fetch_stream_to_rev_order(self, stream):
2492
for kind, substream in stream:
2493
if not kind == 'revisions':
2496
for content in substream:
2497
result.append(content.key[-1])
2500
def get_ordered_revs(self, format, order):
2501
"""Get a list of the revisions in a stream to format format.
2503
:param format: The format of the target.
2504
:param order: the order that target should have requested.
2505
:result: The revision ids in the stream, in the order seen,
2506
the topological order of revisions in the source.
2508
unordered_format = bzrdir.format_registry.get(format)()
2509
target_repository_format = unordered_format.repository_format
2511
self.assertEqual(order, target_repository_format._fetch_order)
2512
trunk, stacked = self.prepare_stacked_remote_branch()
2513
source = stacked.repository._get_source(target_repository_format)
2514
tip = stacked.last_revision()
2515
revs = stacked.repository.get_ancestry(tip)
2516
search = graph.PendingAncestryResult([tip], stacked.repository)
2517
self.reset_smart_call_log()
2518
stream = source.get_stream(search)
2521
# We trust that if a revision is in the stream the rest of the new
2522
# content for it is too, as per our main fetch tests; here we are
2523
# checking that the revisions are actually included at all, and their
2525
return self.fetch_stream_to_rev_order(stream), revs
2527
def test_stacked_get_stream_unordered(self):
2528
# Repository._get_source.get_stream() from a stacked repository with
2529
# unordered yields the full data from both stacked and stacked upon
2531
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2532
self.assertEqual(set(expected_revs), set(rev_ord))
2533
# Getting unordered results should have made a streaming data request
2534
# from the server, then one from the backing branch.
2535
self.assertLength(2, self.hpss_calls)
2537
def test_stacked_get_stream_topological(self):
2538
# Repository._get_source.get_stream() from a stacked repository with
2539
# topological sorting yields the full data from both stacked and
2540
# stacked upon sources in topological order.
2541
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2542
self.assertEqual(expected_revs, rev_ord)
2543
# Getting topological sort requires VFS calls still
2544
self.assertLength(12, self.hpss_calls)
2546
def test_stacked_get_stream_groupcompress(self):
2547
# Repository._get_source.get_stream() from a stacked repository with
2548
# groupcompress sorting yields the full data from both stacked and
2549
# stacked upon sources in groupcompress order.
2550
raise tests.TestSkipped('No groupcompress ordered format available')
2551
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2552
self.assertEqual(expected_revs, reversed(rev_ord))
2553
# Getting unordered results should have made a streaming data request
2554
# from the backing branch, and one from the stacked on branch.
2555
self.assertLength(2, self.hpss_calls)
2558
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2561
super(TestRemoteBranchEffort, self).setUp()
2562
# Create a smart server that publishes whatever the backing VFS server
2564
self.smart_server = server.SmartTCPServer_for_testing()
2565
self.smart_server.setUp(self.get_server())
2566
self.addCleanup(self.smart_server.tearDown)
2567
# Log all HPSS calls into self.hpss_calls.
2568
_SmartClient.hooks.install_named_hook(
2569
'call', self.capture_hpss_call, None)
2570
self.hpss_calls = []
2572
def capture_hpss_call(self, params):
2573
self.hpss_calls.append(params.method)
2575
def test_copy_content_into_avoids_revision_history(self):
2576
local = self.make_branch('local')
2577
remote_backing_tree = self.make_branch_and_tree('remote')
2578
remote_backing_tree.commit("Commit.")
2579
remote_branch_url = self.smart_server.get_url() + 'remote'
2580
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2581
local.repository.fetch(remote_branch.repository)
2582
self.hpss_calls = []
2583
remote_branch.copy_content_into(local)
2584
self.assertFalse('Branch.revision_history' in self.hpss_calls)