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
42
from bzrlib.branch import Branch
43
from bzrlib.bzrdir import BzrDir, BzrDirFormat
44
from bzrlib.remote import (
50
RemoteRepositoryFormat,
52
from bzrlib.repofmt import pack_repo
53
from bzrlib.revision import NULL_REVISION
54
from bzrlib.smart import server, medium
55
from bzrlib.smart.client import _SmartClient
56
from bzrlib.tests import (
58
split_suite_by_condition,
61
from bzrlib.transport import get_transport, http
62
from bzrlib.transport.memory import MemoryTransport
63
from bzrlib.transport.remote import (
69
def load_tests(standard_tests, module, loader):
70
to_adapt, result = split_suite_by_condition(
71
standard_tests, condition_isinstance(BasicRemoteObjectTests))
72
smart_server_version_scenarios = [
74
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
76
{'transport_server': server.SmartTCPServer_for_testing})]
77
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
80
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
83
super(BasicRemoteObjectTests, self).setUp()
84
self.transport = self.get_transport()
85
# make a branch that can be opened over the smart transport
86
self.local_wt = BzrDir.create_standalone_workingtree('.')
89
self.transport.disconnect()
90
tests.TestCaseWithTransport.tearDown(self)
92
def test_create_remote_bzrdir(self):
93
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
94
self.assertIsInstance(b, BzrDir)
96
def test_open_remote_branch(self):
97
# open a standalone branch in the working directory
98
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
99
branch = b.open_branch()
100
self.assertIsInstance(branch, Branch)
102
def test_remote_repository(self):
103
b = BzrDir.open_from_transport(self.transport)
104
repo = b.open_repository()
105
revid = u'\xc823123123'.encode('utf8')
106
self.assertFalse(repo.has_revision(revid))
107
self.local_wt.commit(message='test commit', rev_id=revid)
108
self.assertTrue(repo.has_revision(revid))
110
def test_remote_branch_revision_history(self):
111
b = BzrDir.open_from_transport(self.transport).open_branch()
112
self.assertEqual([], b.revision_history())
113
r1 = self.local_wt.commit('1st commit')
114
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
115
self.assertEqual([r1, r2], b.revision_history())
117
def test_find_correct_format(self):
118
"""Should open a RemoteBzrDir over a RemoteTransport"""
119
fmt = BzrDirFormat.find_format(self.transport)
120
self.assertTrue(RemoteBzrDirFormat
121
in BzrDirFormat._control_server_formats)
122
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
124
def test_open_detected_smart_format(self):
125
fmt = BzrDirFormat.find_format(self.transport)
126
d = fmt.open(self.transport)
127
self.assertIsInstance(d, BzrDir)
129
def test_remote_branch_repr(self):
130
b = BzrDir.open_from_transport(self.transport).open_branch()
131
self.assertStartsWith(str(b), 'RemoteBranch(')
133
def test_remote_branch_format_supports_stacking(self):
135
self.make_branch('unstackable', format='pack-0.92')
136
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
137
self.assertFalse(b._format.supports_stacking())
138
self.make_branch('stackable', format='1.9')
139
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
140
self.assertTrue(b._format.supports_stacking())
142
def test_remote_repo_format_supports_external_references(self):
144
bd = self.make_bzrdir('unstackable', format='pack-0.92')
145
r = bd.create_repository()
146
self.assertFalse(r._format.supports_external_lookups)
147
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
148
self.assertFalse(r._format.supports_external_lookups)
149
bd = self.make_bzrdir('stackable', format='1.9')
150
r = bd.create_repository()
151
self.assertTrue(r._format.supports_external_lookups)
152
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
153
self.assertTrue(r._format.supports_external_lookups)
156
class FakeProtocol(object):
157
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
159
def __init__(self, body, fake_client):
161
self._body_buffer = None
162
self._fake_client = fake_client
164
def read_body_bytes(self, count=-1):
165
if self._body_buffer is None:
166
self._body_buffer = StringIO(self.body)
167
bytes = self._body_buffer.read(count)
168
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
169
self._fake_client.expecting_body = False
172
def cancel_read_body(self):
173
self._fake_client.expecting_body = False
175
def read_streamed_body(self):
179
class FakeClient(_SmartClient):
180
"""Lookalike for _SmartClient allowing testing."""
182
def __init__(self, fake_medium_base='fake base'):
183
"""Create a FakeClient."""
186
self.expecting_body = False
187
# if non-None, this is the list of expected calls, with only the
188
# method name and arguments included. the body might be hard to
189
# compute so is not included. If a call is None, that call can
191
self._expected_calls = None
192
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
194
def add_expected_call(self, call_name, call_args, response_type,
195
response_args, response_body=None):
196
if self._expected_calls is None:
197
self._expected_calls = []
198
self._expected_calls.append((call_name, call_args))
199
self.responses.append((response_type, response_args, response_body))
201
def add_success_response(self, *args):
202
self.responses.append(('success', args, None))
204
def add_success_response_with_body(self, body, *args):
205
self.responses.append(('success', args, body))
206
if self._expected_calls is not None:
207
self._expected_calls.append(None)
209
def add_error_response(self, *args):
210
self.responses.append(('error', args))
212
def add_unknown_method_response(self, verb):
213
self.responses.append(('unknown', verb))
215
def finished_test(self):
216
if self._expected_calls:
217
raise AssertionError("%r finished but was still expecting %r"
218
% (self, self._expected_calls[0]))
220
def _get_next_response(self):
222
response_tuple = self.responses.pop(0)
223
except IndexError, e:
224
raise AssertionError("%r didn't expect any more calls"
226
if response_tuple[0] == 'unknown':
227
raise errors.UnknownSmartMethod(response_tuple[1])
228
elif response_tuple[0] == 'error':
229
raise errors.ErrorFromSmartServer(response_tuple[1])
230
return response_tuple
232
def _check_call(self, method, args):
233
if self._expected_calls is None:
234
# the test should be updated to say what it expects
237
next_call = self._expected_calls.pop(0)
239
raise AssertionError("%r didn't expect any more calls "
241
% (self, method, args,))
242
if next_call is None:
244
if method != next_call[0] or args != next_call[1]:
245
raise AssertionError("%r expected %r%r "
247
% (self, next_call[0], next_call[1], method, args,))
249
def call(self, method, *args):
250
self._check_call(method, args)
251
self._calls.append(('call', method, args))
252
return self._get_next_response()[1]
254
def call_expecting_body(self, method, *args):
255
self._check_call(method, args)
256
self._calls.append(('call_expecting_body', method, args))
257
result = self._get_next_response()
258
self.expecting_body = True
259
return result[1], FakeProtocol(result[2], self)
261
def call_with_body_bytes_expecting_body(self, method, args, body):
262
self._check_call(method, args)
263
self._calls.append(('call_with_body_bytes_expecting_body', method,
265
result = self._get_next_response()
266
self.expecting_body = True
267
return result[1], FakeProtocol(result[2], self)
269
def call_with_body_stream(self, args, stream):
270
# Explicitly consume the stream before checking for an error, because
271
# that's what happens a real medium.
272
stream = list(stream)
273
self._check_call(args[0], args[1:])
274
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
275
result = self._get_next_response()
276
# The second value returned from call_with_body_stream is supposed to
277
# be a response_handler object, but so far no tests depend on that.
278
response_handler = None
279
return result[1], response_handler
282
class FakeMedium(medium.SmartClientMedium):
284
def __init__(self, client_calls, base):
285
medium.SmartClientMedium.__init__(self, base)
286
self._client_calls = client_calls
288
def disconnect(self):
289
self._client_calls.append(('disconnect medium',))
292
class TestVfsHas(tests.TestCase):
294
def test_unicode_path(self):
295
client = FakeClient('/')
296
client.add_success_response('yes',)
297
transport = RemoteTransport('bzr://localhost/', _client=client)
298
filename = u'/hell\u00d8'.encode('utf8')
299
result = transport.has(filename)
301
[('call', 'has', (filename,))],
303
self.assertTrue(result)
306
class TestRemote(tests.TestCaseWithMemoryTransport):
308
def get_branch_format(self):
309
reference_bzrdir_format = bzrdir.format_registry.get('default')()
310
return reference_bzrdir_format.get_branch_format()
312
def get_repo_format(self):
313
reference_bzrdir_format = bzrdir.format_registry.get('default')()
314
return reference_bzrdir_format.repository_format
316
def disable_verb(self, verb):
317
"""Disable a verb for one test."""
318
request_handlers = smart.request.request_handlers
319
orig_method = request_handlers.get(verb)
320
request_handlers.remove(verb)
322
request_handlers.register(verb, orig_method)
323
self.addCleanup(restoreVerb)
326
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
327
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
329
def assertRemotePath(self, expected, client_base, transport_base):
330
"""Assert that the result of
331
SmartClientMedium.remote_path_from_transport is the expected value for
332
a given client_base and transport_base.
334
client_medium = medium.SmartClientMedium(client_base)
335
transport = get_transport(transport_base)
336
result = client_medium.remote_path_from_transport(transport)
337
self.assertEqual(expected, result)
339
def test_remote_path_from_transport(self):
340
"""SmartClientMedium.remote_path_from_transport calculates a URL for
341
the given transport relative to the root of the client base URL.
343
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
344
self.assertRemotePath(
345
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
347
def assertRemotePathHTTP(self, expected, transport_base, relpath):
348
"""Assert that the result of
349
HttpTransportBase.remote_path_from_transport is the expected value for
350
a given transport_base and relpath of that transport. (Note that
351
HttpTransportBase is a subclass of SmartClientMedium)
353
base_transport = get_transport(transport_base)
354
client_medium = base_transport.get_smart_medium()
355
cloned_transport = base_transport.clone(relpath)
356
result = client_medium.remote_path_from_transport(cloned_transport)
357
self.assertEqual(expected, result)
359
def test_remote_path_from_transport_http(self):
360
"""Remote paths for HTTP transports are calculated differently to other
361
transports. They are just relative to the client base, not the root
362
directory of the host.
364
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
365
self.assertRemotePathHTTP(
366
'../xyz/', scheme + '//host/path', '../xyz/')
367
self.assertRemotePathHTTP(
368
'xyz/', scheme + '//host/path', 'xyz/')
371
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
372
"""Tests for the behaviour of client_medium.remote_is_at_least."""
374
def test_initially_unlimited(self):
375
"""A fresh medium assumes that the remote side supports all
378
client_medium = medium.SmartClientMedium('dummy base')
379
self.assertFalse(client_medium._is_remote_before((99, 99)))
381
def test__remember_remote_is_before(self):
382
"""Calling _remember_remote_is_before ratchets down the known remote
385
client_medium = medium.SmartClientMedium('dummy base')
386
# Mark the remote side as being less than 1.6. The remote side may
388
client_medium._remember_remote_is_before((1, 6))
389
self.assertTrue(client_medium._is_remote_before((1, 6)))
390
self.assertFalse(client_medium._is_remote_before((1, 5)))
391
# Calling _remember_remote_is_before again with a lower value works.
392
client_medium._remember_remote_is_before((1, 5))
393
self.assertTrue(client_medium._is_remote_before((1, 5)))
394
# You cannot call _remember_remote_is_before with a larger value.
396
AssertionError, client_medium._remember_remote_is_before, (1, 9))
399
class TestBzrDirCloningMetaDir(TestRemote):
401
def test_backwards_compat(self):
402
self.setup_smart_server_with_call_log()
403
a_dir = self.make_bzrdir('.')
404
self.reset_smart_call_log()
405
verb = 'BzrDir.cloning_metadir'
406
self.disable_verb(verb)
407
format = a_dir.cloning_metadir()
408
call_count = len([call for call in self.hpss_calls if
409
call.call.method == verb])
410
self.assertEqual(1, call_count)
412
def test_branch_reference(self):
413
transport = self.get_transport('quack')
414
referenced = self.make_branch('referenced')
415
expected = referenced.bzrdir.cloning_metadir()
416
client = FakeClient(transport.base)
417
client.add_expected_call(
418
'BzrDir.cloning_metadir', ('quack/', 'False'),
419
'error', ('BranchReference',)),
420
client.add_expected_call(
421
'BzrDir.open_branchV2', ('quack/',),
422
'success', ('ref', self.get_url('referenced'))),
423
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
425
result = a_bzrdir.cloning_metadir()
426
# We should have got a control dir matching the referenced branch.
427
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
428
self.assertEqual(expected._repository_format, result._repository_format)
429
self.assertEqual(expected._branch_format, result._branch_format)
430
client.finished_test()
432
def test_current_server(self):
433
transport = self.get_transport('.')
434
transport = transport.clone('quack')
435
self.make_bzrdir('quack')
436
client = FakeClient(transport.base)
437
reference_bzrdir_format = bzrdir.format_registry.get('default')()
438
control_name = reference_bzrdir_format.network_name()
439
client.add_expected_call(
440
'BzrDir.cloning_metadir', ('quack/', 'False'),
441
'success', (control_name, '', ('branch', ''))),
442
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
444
result = a_bzrdir.cloning_metadir()
445
# We should have got a reference control dir with default branch and
446
# repository formats.
447
# This pokes a little, just to be sure.
448
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
449
self.assertEqual(None, result._repository_format)
450
self.assertEqual(None, result._branch_format)
451
client.finished_test()
454
class TestBzrDirOpenBranch(TestRemote):
456
def test_backwards_compat(self):
457
self.setup_smart_server_with_call_log()
458
self.make_branch('.')
459
a_dir = BzrDir.open(self.get_url('.'))
460
self.reset_smart_call_log()
461
verb = 'BzrDir.open_branchV2'
462
self.disable_verb(verb)
463
format = a_dir.open_branch()
464
call_count = len([call for call in self.hpss_calls if
465
call.call.method == verb])
466
self.assertEqual(1, call_count)
468
def test_branch_present(self):
469
reference_format = self.get_repo_format()
470
network_name = reference_format.network_name()
471
branch_network_name = self.get_branch_format().network_name()
472
transport = MemoryTransport()
473
transport.mkdir('quack')
474
transport = transport.clone('quack')
475
client = FakeClient(transport.base)
476
client.add_expected_call(
477
'BzrDir.open_branchV2', ('quack/',),
478
'success', ('branch', branch_network_name))
479
client.add_expected_call(
480
'BzrDir.find_repositoryV3', ('quack/',),
481
'success', ('ok', '', 'no', 'no', 'no', network_name))
482
client.add_expected_call(
483
'Branch.get_stacked_on_url', ('quack/',),
484
'error', ('NotStacked',))
485
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
487
result = bzrdir.open_branch()
488
self.assertIsInstance(result, RemoteBranch)
489
self.assertEqual(bzrdir, result.bzrdir)
490
client.finished_test()
492
def test_branch_missing(self):
493
transport = MemoryTransport()
494
transport.mkdir('quack')
495
transport = transport.clone('quack')
496
client = FakeClient(transport.base)
497
client.add_error_response('nobranch')
498
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
500
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
502
[('call', 'BzrDir.open_branchV2', ('quack/',))],
505
def test__get_tree_branch(self):
506
# _get_tree_branch is a form of open_branch, but it should only ask for
507
# branch opening, not any other network requests.
510
calls.append("Called")
512
transport = MemoryTransport()
513
# no requests on the network - catches other api calls being made.
514
client = FakeClient(transport.base)
515
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
517
# patch the open_branch call to record that it was called.
518
bzrdir.open_branch = open_branch
519
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
520
self.assertEqual(["Called"], calls)
521
self.assertEqual([], client._calls)
523
def test_url_quoting_of_path(self):
524
# Relpaths on the wire should not be URL-escaped. So "~" should be
525
# transmitted as "~", not "%7E".
526
transport = RemoteTCPTransport('bzr://localhost/~hello/')
527
client = FakeClient(transport.base)
528
reference_format = self.get_repo_format()
529
network_name = reference_format.network_name()
530
branch_network_name = self.get_branch_format().network_name()
531
client.add_expected_call(
532
'BzrDir.open_branchV2', ('~hello/',),
533
'success', ('branch', branch_network_name))
534
client.add_expected_call(
535
'BzrDir.find_repositoryV3', ('~hello/',),
536
'success', ('ok', '', 'no', 'no', 'no', network_name))
537
client.add_expected_call(
538
'Branch.get_stacked_on_url', ('~hello/',),
539
'error', ('NotStacked',))
540
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
542
result = bzrdir.open_branch()
543
client.finished_test()
545
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
546
reference_format = self.get_repo_format()
547
network_name = reference_format.network_name()
548
transport = MemoryTransport()
549
transport.mkdir('quack')
550
transport = transport.clone('quack')
552
rich_response = 'yes'
556
subtree_response = 'yes'
558
subtree_response = 'no'
559
client = FakeClient(transport.base)
560
client.add_success_response(
561
'ok', '', rich_response, subtree_response, external_lookup,
563
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
565
result = bzrdir.open_repository()
567
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
569
self.assertIsInstance(result, RemoteRepository)
570
self.assertEqual(bzrdir, result.bzrdir)
571
self.assertEqual(rich_root, result._format.rich_root_data)
572
self.assertEqual(subtrees, result._format.supports_tree_reference)
574
def test_open_repository_sets_format_attributes(self):
575
self.check_open_repository(True, True)
576
self.check_open_repository(False, True)
577
self.check_open_repository(True, False)
578
self.check_open_repository(False, False)
579
self.check_open_repository(False, False, 'yes')
581
def test_old_server(self):
582
"""RemoteBzrDirFormat should fail to probe if the server version is too
585
self.assertRaises(errors.NotBranchError,
586
RemoteBzrDirFormat.probe_transport, OldServerTransport())
589
class TestBzrDirCreateBranch(TestRemote):
591
def test_backwards_compat(self):
592
self.setup_smart_server_with_call_log()
593
repo = self.make_repository('.')
594
self.reset_smart_call_log()
595
self.disable_verb('BzrDir.create_branch')
596
branch = repo.bzrdir.create_branch()
597
create_branch_call_count = len([call for call in self.hpss_calls if
598
call.call.method == 'BzrDir.create_branch'])
599
self.assertEqual(1, create_branch_call_count)
601
def test_current_server(self):
602
transport = self.get_transport('.')
603
transport = transport.clone('quack')
604
self.make_repository('quack')
605
client = FakeClient(transport.base)
606
reference_bzrdir_format = bzrdir.format_registry.get('default')()
607
reference_format = reference_bzrdir_format.get_branch_format()
608
network_name = reference_format.network_name()
609
reference_repo_fmt = reference_bzrdir_format.repository_format
610
reference_repo_name = reference_repo_fmt.network_name()
611
client.add_expected_call(
612
'BzrDir.create_branch', ('quack/', network_name),
613
'success', ('ok', network_name, '', 'no', 'no', 'yes',
614
reference_repo_name))
615
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
617
branch = a_bzrdir.create_branch()
618
# We should have got a remote branch
619
self.assertIsInstance(branch, remote.RemoteBranch)
620
# its format should have the settings from the response
621
format = branch._format
622
self.assertEqual(network_name, format.network_name())
625
class TestBzrDirCreateRepository(TestRemote):
627
def test_backwards_compat(self):
628
self.setup_smart_server_with_call_log()
629
bzrdir = self.make_bzrdir('.')
630
self.reset_smart_call_log()
631
self.disable_verb('BzrDir.create_repository')
632
repo = bzrdir.create_repository()
633
create_repo_call_count = len([call for call in self.hpss_calls if
634
call.call.method == 'BzrDir.create_repository'])
635
self.assertEqual(1, create_repo_call_count)
637
def test_current_server(self):
638
transport = self.get_transport('.')
639
transport = transport.clone('quack')
640
self.make_bzrdir('quack')
641
client = FakeClient(transport.base)
642
reference_bzrdir_format = bzrdir.format_registry.get('default')()
643
reference_format = reference_bzrdir_format.repository_format
644
network_name = reference_format.network_name()
645
client.add_expected_call(
646
'BzrDir.create_repository', ('quack/',
647
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
648
'success', ('ok', 'no', 'no', 'no', network_name))
649
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
651
repo = a_bzrdir.create_repository()
652
# We should have got a remote repository
653
self.assertIsInstance(repo, remote.RemoteRepository)
654
# its format should have the settings from the response
655
format = repo._format
656
self.assertFalse(format.rich_root_data)
657
self.assertFalse(format.supports_tree_reference)
658
self.assertFalse(format.supports_external_lookups)
659
self.assertEqual(network_name, format.network_name())
662
class TestBzrDirOpenRepository(TestRemote):
664
def test_backwards_compat_1_2_3(self):
665
# fallback all the way to the first version.
666
reference_format = self.get_repo_format()
667
network_name = reference_format.network_name()
668
client = FakeClient('bzr://example.com/')
669
client.add_unknown_method_response('BzrDir.find_repositoryV3')
670
client.add_unknown_method_response('BzrDir.find_repositoryV2')
671
client.add_success_response('ok', '', 'no', 'no')
672
# A real repository instance will be created to determine the network
674
client.add_success_response_with_body(
675
"Bazaar-NG meta directory, format 1\n", 'ok')
676
client.add_success_response_with_body(
677
reference_format.get_format_string(), 'ok')
678
# PackRepository wants to do a stat
679
client.add_success_response('stat', '0', '65535')
680
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
682
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
684
repo = bzrdir.open_repository()
686
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
687
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
688
('call', 'BzrDir.find_repository', ('quack/',)),
689
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
690
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
691
('call', 'stat', ('/quack/.bzr/repository',)),
694
self.assertEqual(network_name, repo._format.network_name())
696
def test_backwards_compat_2(self):
697
# fallback to find_repositoryV2
698
reference_format = self.get_repo_format()
699
network_name = reference_format.network_name()
700
client = FakeClient('bzr://example.com/')
701
client.add_unknown_method_response('BzrDir.find_repositoryV3')
702
client.add_success_response('ok', '', 'no', 'no', 'no')
703
# A real repository instance will be created to determine the network
705
client.add_success_response_with_body(
706
"Bazaar-NG meta directory, format 1\n", 'ok')
707
client.add_success_response_with_body(
708
reference_format.get_format_string(), 'ok')
709
# PackRepository wants to do a stat
710
client.add_success_response('stat', '0', '65535')
711
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
713
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
715
repo = bzrdir.open_repository()
717
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
718
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
719
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
720
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
721
('call', 'stat', ('/quack/.bzr/repository',)),
724
self.assertEqual(network_name, repo._format.network_name())
726
def test_current_server(self):
727
reference_format = self.get_repo_format()
728
network_name = reference_format.network_name()
729
transport = MemoryTransport()
730
transport.mkdir('quack')
731
transport = transport.clone('quack')
732
client = FakeClient(transport.base)
733
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
734
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
736
repo = bzrdir.open_repository()
738
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
740
self.assertEqual(network_name, repo._format.network_name())
743
class OldSmartClient(object):
744
"""A fake smart client for test_old_version that just returns a version one
745
response to the 'hello' (query version) command.
748
def get_request(self):
749
input_file = StringIO('ok\x011\n')
750
output_file = StringIO()
751
client_medium = medium.SmartSimplePipesClientMedium(
752
input_file, output_file)
753
return medium.SmartClientStreamMediumRequest(client_medium)
755
def protocol_version(self):
759
class OldServerTransport(object):
760
"""A fake transport for test_old_server that reports it's smart server
761
protocol version as version one.
767
def get_smart_client(self):
768
return OldSmartClient()
771
class RemoteBranchTestCase(TestRemote):
773
def make_remote_branch(self, transport, client):
774
"""Make a RemoteBranch using 'client' as its _SmartClient.
776
A RemoteBzrDir and RemoteRepository will also be created to fill out
777
the RemoteBranch, albeit with stub values for some of their attributes.
779
# we do not want bzrdir to make any remote calls, so use False as its
780
# _client. If it tries to make a remote call, this will fail
782
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
784
repo = RemoteRepository(bzrdir, None, _client=client)
785
branch_format = self.get_branch_format()
786
format = RemoteBranchFormat(network_name=branch_format.network_name())
787
return RemoteBranch(bzrdir, repo, _client=client, format=format)
790
class TestBranchGetParent(RemoteBranchTestCase):
792
def test_no_parent(self):
793
# in an empty branch we decode the response properly
794
transport = MemoryTransport()
795
client = FakeClient(transport.base)
796
client.add_expected_call(
797
'Branch.get_stacked_on_url', ('quack/',),
798
'error', ('NotStacked',))
799
client.add_expected_call(
800
'Branch.get_parent', ('quack/',),
802
transport.mkdir('quack')
803
transport = transport.clone('quack')
804
branch = self.make_remote_branch(transport, client)
805
result = branch.get_parent()
806
client.finished_test()
807
self.assertEqual(None, result)
809
def test_parent_relative(self):
810
transport = MemoryTransport()
811
client = FakeClient(transport.base)
812
client.add_expected_call(
813
'Branch.get_stacked_on_url', ('kwaak/',),
814
'error', ('NotStacked',))
815
client.add_expected_call(
816
'Branch.get_parent', ('kwaak/',),
817
'success', ('../foo/',))
818
transport.mkdir('kwaak')
819
transport = transport.clone('kwaak')
820
branch = self.make_remote_branch(transport, client)
821
result = branch.get_parent()
822
self.assertEqual(transport.clone('../foo').base, result)
824
def test_parent_absolute(self):
825
transport = MemoryTransport()
826
client = FakeClient(transport.base)
827
client.add_expected_call(
828
'Branch.get_stacked_on_url', ('kwaak/',),
829
'error', ('NotStacked',))
830
client.add_expected_call(
831
'Branch.get_parent', ('kwaak/',),
832
'success', ('http://foo/',))
833
transport.mkdir('kwaak')
834
transport = transport.clone('kwaak')
835
branch = self.make_remote_branch(transport, client)
836
result = branch.get_parent()
837
self.assertEqual('http://foo/', result)
840
class TestBranchGetTagsBytes(RemoteBranchTestCase):
842
def test_backwards_compat(self):
843
self.setup_smart_server_with_call_log()
844
branch = self.make_branch('.')
845
self.reset_smart_call_log()
846
verb = 'Branch.get_tags_bytes'
847
self.disable_verb(verb)
848
branch.tags.get_tag_dict()
849
call_count = len([call for call in self.hpss_calls if
850
call.call.method == verb])
851
self.assertEqual(1, call_count)
853
def test_trivial(self):
854
transport = MemoryTransport()
855
client = FakeClient(transport.base)
856
client.add_expected_call(
857
'Branch.get_stacked_on_url', ('quack/',),
858
'error', ('NotStacked',))
859
client.add_expected_call(
860
'Branch.get_tags_bytes', ('quack/',),
862
transport.mkdir('quack')
863
transport = transport.clone('quack')
864
branch = self.make_remote_branch(transport, client)
865
result = branch.tags.get_tag_dict()
866
client.finished_test()
867
self.assertEqual({}, result)
870
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
872
def test_empty_branch(self):
873
# in an empty branch we decode the response properly
874
transport = MemoryTransport()
875
client = FakeClient(transport.base)
876
client.add_expected_call(
877
'Branch.get_stacked_on_url', ('quack/',),
878
'error', ('NotStacked',))
879
client.add_expected_call(
880
'Branch.last_revision_info', ('quack/',),
881
'success', ('ok', '0', 'null:'))
882
transport.mkdir('quack')
883
transport = transport.clone('quack')
884
branch = self.make_remote_branch(transport, client)
885
result = branch.last_revision_info()
886
client.finished_test()
887
self.assertEqual((0, NULL_REVISION), result)
889
def test_non_empty_branch(self):
890
# in a non-empty branch we also decode the response properly
891
revid = u'\xc8'.encode('utf8')
892
transport = MemoryTransport()
893
client = FakeClient(transport.base)
894
client.add_expected_call(
895
'Branch.get_stacked_on_url', ('kwaak/',),
896
'error', ('NotStacked',))
897
client.add_expected_call(
898
'Branch.last_revision_info', ('kwaak/',),
899
'success', ('ok', '2', revid))
900
transport.mkdir('kwaak')
901
transport = transport.clone('kwaak')
902
branch = self.make_remote_branch(transport, client)
903
result = branch.last_revision_info()
904
self.assertEqual((2, revid), result)
907
class TestBranch_get_stacked_on_url(TestRemote):
908
"""Test Branch._get_stacked_on_url rpc"""
910
def test_get_stacked_on_invalid_url(self):
911
# test that asking for a stacked on url the server can't access works.
912
# This isn't perfect, but then as we're in the same process there
913
# really isn't anything we can do to be 100% sure that the server
914
# doesn't just open in - this test probably needs to be rewritten using
915
# a spawn()ed server.
916
stacked_branch = self.make_branch('stacked', format='1.9')
917
memory_branch = self.make_branch('base', format='1.9')
918
vfs_url = self.get_vfs_only_url('base')
919
stacked_branch.set_stacked_on_url(vfs_url)
920
transport = stacked_branch.bzrdir.root_transport
921
client = FakeClient(transport.base)
922
client.add_expected_call(
923
'Branch.get_stacked_on_url', ('stacked/',),
924
'success', ('ok', vfs_url))
925
# XXX: Multiple calls are bad, this second call documents what is
927
client.add_expected_call(
928
'Branch.get_stacked_on_url', ('stacked/',),
929
'success', ('ok', vfs_url))
930
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
932
repo_fmt = remote.RemoteRepositoryFormat()
933
repo_fmt._custom_format = stacked_branch.repository._format
934
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
936
result = branch.get_stacked_on_url()
937
self.assertEqual(vfs_url, result)
939
def test_backwards_compatible(self):
940
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
941
base_branch = self.make_branch('base', format='1.6')
942
stacked_branch = self.make_branch('stacked', format='1.6')
943
stacked_branch.set_stacked_on_url('../base')
944
client = FakeClient(self.get_url())
945
branch_network_name = self.get_branch_format().network_name()
946
client.add_expected_call(
947
'BzrDir.open_branchV2', ('stacked/',),
948
'success', ('branch', branch_network_name))
949
client.add_expected_call(
950
'BzrDir.find_repositoryV3', ('stacked/',),
951
'success', ('ok', '', 'no', 'no', 'yes',
952
stacked_branch.repository._format.network_name()))
953
# called twice, once from constructor and then again by us
954
client.add_expected_call(
955
'Branch.get_stacked_on_url', ('stacked/',),
956
'unknown', ('Branch.get_stacked_on_url',))
957
client.add_expected_call(
958
'Branch.get_stacked_on_url', ('stacked/',),
959
'unknown', ('Branch.get_stacked_on_url',))
960
# this will also do vfs access, but that goes direct to the transport
961
# and isn't seen by the FakeClient.
962
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
963
remote.RemoteBzrDirFormat(), _client=client)
964
branch = bzrdir.open_branch()
965
result = branch.get_stacked_on_url()
966
self.assertEqual('../base', result)
967
client.finished_test()
968
# it's in the fallback list both for the RemoteRepository and its vfs
970
self.assertEqual(1, len(branch.repository._fallback_repositories))
972
len(branch.repository._real_repository._fallback_repositories))
974
def test_get_stacked_on_real_branch(self):
975
base_branch = self.make_branch('base', format='1.6')
976
stacked_branch = self.make_branch('stacked', format='1.6')
977
stacked_branch.set_stacked_on_url('../base')
978
reference_format = self.get_repo_format()
979
network_name = reference_format.network_name()
980
client = FakeClient(self.get_url())
981
branch_network_name = self.get_branch_format().network_name()
982
client.add_expected_call(
983
'BzrDir.open_branchV2', ('stacked/',),
984
'success', ('branch', branch_network_name))
985
client.add_expected_call(
986
'BzrDir.find_repositoryV3', ('stacked/',),
987
'success', ('ok', '', 'no', 'no', 'yes', network_name))
988
# called twice, once from constructor and then again by us
989
client.add_expected_call(
990
'Branch.get_stacked_on_url', ('stacked/',),
991
'success', ('ok', '../base'))
992
client.add_expected_call(
993
'Branch.get_stacked_on_url', ('stacked/',),
994
'success', ('ok', '../base'))
995
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
996
remote.RemoteBzrDirFormat(), _client=client)
997
branch = bzrdir.open_branch()
998
result = branch.get_stacked_on_url()
999
self.assertEqual('../base', result)
1000
client.finished_test()
1001
# it's in the fallback list both for the RemoteRepository and its vfs
1003
self.assertEqual(1, len(branch.repository._fallback_repositories))
1005
len(branch.repository._real_repository._fallback_repositories))
1008
class TestBranchSetLastRevision(RemoteBranchTestCase):
1010
def test_set_empty(self):
1011
# set_revision_history([]) is translated to calling
1012
# Branch.set_last_revision(path, '') on the wire.
1013
transport = MemoryTransport()
1014
transport.mkdir('branch')
1015
transport = transport.clone('branch')
1017
client = FakeClient(transport.base)
1018
client.add_expected_call(
1019
'Branch.get_stacked_on_url', ('branch/',),
1020
'error', ('NotStacked',))
1021
client.add_expected_call(
1022
'Branch.lock_write', ('branch/', '', ''),
1023
'success', ('ok', 'branch token', 'repo token'))
1024
client.add_expected_call(
1025
'Branch.last_revision_info',
1027
'success', ('ok', '0', 'null:'))
1028
client.add_expected_call(
1029
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1031
client.add_expected_call(
1032
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1034
branch = self.make_remote_branch(transport, client)
1035
# This is a hack to work around the problem that RemoteBranch currently
1036
# unnecessarily invokes _ensure_real upon a call to lock_write.
1037
branch._ensure_real = lambda: None
1039
result = branch.set_revision_history([])
1041
self.assertEqual(None, result)
1042
client.finished_test()
1044
def test_set_nonempty(self):
1045
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1046
# Branch.set_last_revision(path, rev-idN) on the wire.
1047
transport = MemoryTransport()
1048
transport.mkdir('branch')
1049
transport = transport.clone('branch')
1051
client = FakeClient(transport.base)
1052
client.add_expected_call(
1053
'Branch.get_stacked_on_url', ('branch/',),
1054
'error', ('NotStacked',))
1055
client.add_expected_call(
1056
'Branch.lock_write', ('branch/', '', ''),
1057
'success', ('ok', 'branch token', 'repo token'))
1058
client.add_expected_call(
1059
'Branch.last_revision_info',
1061
'success', ('ok', '0', 'null:'))
1063
encoded_body = bz2.compress('\n'.join(lines))
1064
client.add_success_response_with_body(encoded_body, 'ok')
1065
client.add_expected_call(
1066
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1068
client.add_expected_call(
1069
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1071
branch = self.make_remote_branch(transport, client)
1072
# This is a hack to work around the problem that RemoteBranch currently
1073
# unnecessarily invokes _ensure_real upon a call to lock_write.
1074
branch._ensure_real = lambda: None
1075
# Lock the branch, reset the record of remote calls.
1077
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1079
self.assertEqual(None, result)
1080
client.finished_test()
1082
def test_no_such_revision(self):
1083
transport = MemoryTransport()
1084
transport.mkdir('branch')
1085
transport = transport.clone('branch')
1086
# A response of 'NoSuchRevision' is translated into an exception.
1087
client = FakeClient(transport.base)
1088
client.add_expected_call(
1089
'Branch.get_stacked_on_url', ('branch/',),
1090
'error', ('NotStacked',))
1091
client.add_expected_call(
1092
'Branch.lock_write', ('branch/', '', ''),
1093
'success', ('ok', 'branch token', 'repo token'))
1094
client.add_expected_call(
1095
'Branch.last_revision_info',
1097
'success', ('ok', '0', 'null:'))
1098
# get_graph calls to construct the revision history, for the set_rh
1101
encoded_body = bz2.compress('\n'.join(lines))
1102
client.add_success_response_with_body(encoded_body, 'ok')
1103
client.add_expected_call(
1104
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1105
'error', ('NoSuchRevision', 'rev-id'))
1106
client.add_expected_call(
1107
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1110
branch = self.make_remote_branch(transport, client)
1113
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1115
client.finished_test()
1117
def test_tip_change_rejected(self):
1118
"""TipChangeRejected responses cause a TipChangeRejected exception to
1121
transport = MemoryTransport()
1122
transport.mkdir('branch')
1123
transport = transport.clone('branch')
1124
client = FakeClient(transport.base)
1125
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1126
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1127
client.add_expected_call(
1128
'Branch.get_stacked_on_url', ('branch/',),
1129
'error', ('NotStacked',))
1130
client.add_expected_call(
1131
'Branch.lock_write', ('branch/', '', ''),
1132
'success', ('ok', 'branch token', 'repo token'))
1133
client.add_expected_call(
1134
'Branch.last_revision_info',
1136
'success', ('ok', '0', 'null:'))
1138
encoded_body = bz2.compress('\n'.join(lines))
1139
client.add_success_response_with_body(encoded_body, 'ok')
1140
client.add_expected_call(
1141
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1142
'error', ('TipChangeRejected', rejection_msg_utf8))
1143
client.add_expected_call(
1144
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1146
branch = self.make_remote_branch(transport, client)
1147
branch._ensure_real = lambda: None
1149
# The 'TipChangeRejected' error response triggered by calling
1150
# set_revision_history causes a TipChangeRejected exception.
1151
err = self.assertRaises(
1152
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1153
# The UTF-8 message from the response has been decoded into a unicode
1155
self.assertIsInstance(err.msg, unicode)
1156
self.assertEqual(rejection_msg_unicode, err.msg)
1158
client.finished_test()
1161
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1163
def test_set_last_revision_info(self):
1164
# set_last_revision_info(num, 'rev-id') is translated to calling
1165
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1166
transport = MemoryTransport()
1167
transport.mkdir('branch')
1168
transport = transport.clone('branch')
1169
client = FakeClient(transport.base)
1170
# get_stacked_on_url
1171
client.add_error_response('NotStacked')
1173
client.add_success_response('ok', 'branch token', 'repo token')
1174
# query the current revision
1175
client.add_success_response('ok', '0', 'null:')
1177
client.add_success_response('ok')
1179
client.add_success_response('ok')
1181
branch = self.make_remote_branch(transport, client)
1182
# Lock the branch, reset the record of remote calls.
1185
result = branch.set_last_revision_info(1234, 'a-revision-id')
1187
[('call', 'Branch.last_revision_info', ('branch/',)),
1188
('call', 'Branch.set_last_revision_info',
1189
('branch/', 'branch token', 'repo token',
1190
'1234', 'a-revision-id'))],
1192
self.assertEqual(None, result)
1194
def test_no_such_revision(self):
1195
# A response of 'NoSuchRevision' is translated into an exception.
1196
transport = MemoryTransport()
1197
transport.mkdir('branch')
1198
transport = transport.clone('branch')
1199
client = FakeClient(transport.base)
1200
# get_stacked_on_url
1201
client.add_error_response('NotStacked')
1203
client.add_success_response('ok', 'branch token', 'repo token')
1205
client.add_error_response('NoSuchRevision', 'revid')
1207
client.add_success_response('ok')
1209
branch = self.make_remote_branch(transport, client)
1210
# Lock the branch, reset the record of remote calls.
1215
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1218
def lock_remote_branch(self, branch):
1219
"""Trick a RemoteBranch into thinking it is locked."""
1220
branch._lock_mode = 'w'
1221
branch._lock_count = 2
1222
branch._lock_token = 'branch token'
1223
branch._repo_lock_token = 'repo token'
1224
branch.repository._lock_mode = 'w'
1225
branch.repository._lock_count = 2
1226
branch.repository._lock_token = 'repo token'
1228
def test_backwards_compatibility(self):
1229
"""If the server does not support the Branch.set_last_revision_info
1230
verb (which is new in 1.4), then the client falls back to VFS methods.
1232
# This test is a little messy. Unlike most tests in this file, it
1233
# doesn't purely test what a Remote* object sends over the wire, and
1234
# how it reacts to responses from the wire. It instead relies partly
1235
# on asserting that the RemoteBranch will call
1236
# self._real_branch.set_last_revision_info(...).
1238
# First, set up our RemoteBranch with a FakeClient that raises
1239
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1240
transport = MemoryTransport()
1241
transport.mkdir('branch')
1242
transport = transport.clone('branch')
1243
client = FakeClient(transport.base)
1244
client.add_expected_call(
1245
'Branch.get_stacked_on_url', ('branch/',),
1246
'error', ('NotStacked',))
1247
client.add_expected_call(
1248
'Branch.last_revision_info',
1250
'success', ('ok', '0', 'null:'))
1251
client.add_expected_call(
1252
'Branch.set_last_revision_info',
1253
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1254
'unknown', 'Branch.set_last_revision_info')
1256
branch = self.make_remote_branch(transport, client)
1257
class StubRealBranch(object):
1260
def set_last_revision_info(self, revno, revision_id):
1262
('set_last_revision_info', revno, revision_id))
1263
def _clear_cached_state(self):
1265
real_branch = StubRealBranch()
1266
branch._real_branch = real_branch
1267
self.lock_remote_branch(branch)
1269
# Call set_last_revision_info, and verify it behaved as expected.
1270
result = branch.set_last_revision_info(1234, 'a-revision-id')
1272
[('set_last_revision_info', 1234, 'a-revision-id')],
1274
client.finished_test()
1276
def test_unexpected_error(self):
1277
# If the server sends an error the client doesn't understand, it gets
1278
# turned into an UnknownErrorFromSmartServer, which is presented as a
1279
# non-internal error to the user.
1280
transport = MemoryTransport()
1281
transport.mkdir('branch')
1282
transport = transport.clone('branch')
1283
client = FakeClient(transport.base)
1284
# get_stacked_on_url
1285
client.add_error_response('NotStacked')
1287
client.add_success_response('ok', 'branch token', 'repo token')
1289
client.add_error_response('UnexpectedError')
1291
client.add_success_response('ok')
1293
branch = self.make_remote_branch(transport, client)
1294
# Lock the branch, reset the record of remote calls.
1298
err = self.assertRaises(
1299
errors.UnknownErrorFromSmartServer,
1300
branch.set_last_revision_info, 123, 'revid')
1301
self.assertEqual(('UnexpectedError',), err.error_tuple)
1304
def test_tip_change_rejected(self):
1305
"""TipChangeRejected responses cause a TipChangeRejected exception to
1308
transport = MemoryTransport()
1309
transport.mkdir('branch')
1310
transport = transport.clone('branch')
1311
client = FakeClient(transport.base)
1312
# get_stacked_on_url
1313
client.add_error_response('NotStacked')
1315
client.add_success_response('ok', 'branch token', 'repo token')
1317
client.add_error_response('TipChangeRejected', 'rejection message')
1319
client.add_success_response('ok')
1321
branch = self.make_remote_branch(transport, client)
1322
# Lock the branch, reset the record of remote calls.
1324
self.addCleanup(branch.unlock)
1327
# The 'TipChangeRejected' error response triggered by calling
1328
# set_last_revision_info causes a TipChangeRejected exception.
1329
err = self.assertRaises(
1330
errors.TipChangeRejected,
1331
branch.set_last_revision_info, 123, 'revid')
1332
self.assertEqual('rejection message', err.msg)
1335
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1336
"""Getting the branch configuration should use an abstract method not vfs.
1339
def test_get_branch_conf(self):
1340
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1341
## # We should see that branch.get_config() does a single rpc to get the
1342
## # remote configuration file, abstracting away where that is stored on
1343
## # the server. However at the moment it always falls back to using the
1344
## # vfs, and this would need some changes in config.py.
1346
## # in an empty branch we decode the response properly
1347
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1348
## # we need to make a real branch because the remote_branch.control_files
1349
## # will trigger _ensure_real.
1350
## branch = self.make_branch('quack')
1351
## transport = branch.bzrdir.root_transport
1352
## # we do not want bzrdir to make any remote calls
1353
## bzrdir = RemoteBzrDir(transport, _client=False)
1354
## branch = RemoteBranch(bzrdir, None, _client=client)
1355
## config = branch.get_config()
1356
## self.assertEqual(
1357
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1361
class TestBranchLockWrite(RemoteBranchTestCase):
1363
def test_lock_write_unlockable(self):
1364
transport = MemoryTransport()
1365
client = FakeClient(transport.base)
1366
client.add_expected_call(
1367
'Branch.get_stacked_on_url', ('quack/',),
1368
'error', ('NotStacked',),)
1369
client.add_expected_call(
1370
'Branch.lock_write', ('quack/', '', ''),
1371
'error', ('UnlockableTransport',))
1372
transport.mkdir('quack')
1373
transport = transport.clone('quack')
1374
branch = self.make_remote_branch(transport, client)
1375
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1376
client.finished_test()
1379
class TestTransportIsReadonly(tests.TestCase):
1381
def test_true(self):
1382
client = FakeClient()
1383
client.add_success_response('yes')
1384
transport = RemoteTransport('bzr://example.com/', medium=False,
1386
self.assertEqual(True, transport.is_readonly())
1388
[('call', 'Transport.is_readonly', ())],
1391
def test_false(self):
1392
client = FakeClient()
1393
client.add_success_response('no')
1394
transport = RemoteTransport('bzr://example.com/', medium=False,
1396
self.assertEqual(False, transport.is_readonly())
1398
[('call', 'Transport.is_readonly', ())],
1401
def test_error_from_old_server(self):
1402
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1404
Clients should treat it as a "no" response, because is_readonly is only
1405
advisory anyway (a transport could be read-write, but then the
1406
underlying filesystem could be readonly anyway).
1408
client = FakeClient()
1409
client.add_unknown_method_response('Transport.is_readonly')
1410
transport = RemoteTransport('bzr://example.com/', medium=False,
1412
self.assertEqual(False, transport.is_readonly())
1414
[('call', 'Transport.is_readonly', ())],
1418
class TestTransportMkdir(tests.TestCase):
1420
def test_permissiondenied(self):
1421
client = FakeClient()
1422
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1423
transport = RemoteTransport('bzr://example.com/', medium=False,
1425
exc = self.assertRaises(
1426
errors.PermissionDenied, transport.mkdir, 'client path')
1427
expected_error = errors.PermissionDenied('/client path', 'extra')
1428
self.assertEqual(expected_error, exc)
1431
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1433
def test_defaults_to_none(self):
1434
t = RemoteSSHTransport('bzr+ssh://example.com')
1435
self.assertIs(None, t._get_credentials()[0])
1437
def test_uses_authentication_config(self):
1438
conf = config.AuthenticationConfig()
1439
conf._get_config().update(
1440
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1443
t = RemoteSSHTransport('bzr+ssh://example.com')
1444
self.assertEqual('bar', t._get_credentials()[0])
1447
class TestRemoteRepository(TestRemote):
1448
"""Base for testing RemoteRepository protocol usage.
1450
These tests contain frozen requests and responses. We want any changes to
1451
what is sent or expected to be require a thoughtful update to these tests
1452
because they might break compatibility with different-versioned servers.
1455
def setup_fake_client_and_repository(self, transport_path):
1456
"""Create the fake client and repository for testing with.
1458
There's no real server here; we just have canned responses sent
1461
:param transport_path: Path below the root of the MemoryTransport
1462
where the repository will be created.
1464
transport = MemoryTransport()
1465
transport.mkdir(transport_path)
1466
client = FakeClient(transport.base)
1467
transport = transport.clone(transport_path)
1468
# we do not want bzrdir to make any remote calls
1469
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1471
repo = RemoteRepository(bzrdir, None, _client=client)
1475
class TestRepositoryFormat(TestRemoteRepository):
1477
def test_fast_delta(self):
1478
true_name = pack_repo.RepositoryFormatPackDevelopment2().network_name()
1479
true_format = RemoteRepositoryFormat()
1480
true_format._network_name = true_name
1481
self.assertEqual(True, true_format.fast_deltas)
1482
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1483
false_format = RemoteRepositoryFormat()
1484
false_format._network_name = false_name
1485
self.assertEqual(False, false_format.fast_deltas)
1488
class TestRepositoryGatherStats(TestRemoteRepository):
1490
def test_revid_none(self):
1491
# ('ok',), body with revisions and size
1492
transport_path = 'quack'
1493
repo, client = self.setup_fake_client_and_repository(transport_path)
1494
client.add_success_response_with_body(
1495
'revisions: 2\nsize: 18\n', 'ok')
1496
result = repo.gather_stats(None)
1498
[('call_expecting_body', 'Repository.gather_stats',
1499
('quack/','','no'))],
1501
self.assertEqual({'revisions': 2, 'size': 18}, result)
1503
def test_revid_no_committers(self):
1504
# ('ok',), body without committers
1505
body = ('firstrev: 123456.300 3600\n'
1506
'latestrev: 654231.400 0\n'
1509
transport_path = 'quick'
1510
revid = u'\xc8'.encode('utf8')
1511
repo, client = self.setup_fake_client_and_repository(transport_path)
1512
client.add_success_response_with_body(body, 'ok')
1513
result = repo.gather_stats(revid)
1515
[('call_expecting_body', 'Repository.gather_stats',
1516
('quick/', revid, 'no'))],
1518
self.assertEqual({'revisions': 2, 'size': 18,
1519
'firstrev': (123456.300, 3600),
1520
'latestrev': (654231.400, 0),},
1523
def test_revid_with_committers(self):
1524
# ('ok',), body with committers
1525
body = ('committers: 128\n'
1526
'firstrev: 123456.300 3600\n'
1527
'latestrev: 654231.400 0\n'
1530
transport_path = 'buick'
1531
revid = u'\xc8'.encode('utf8')
1532
repo, client = self.setup_fake_client_and_repository(transport_path)
1533
client.add_success_response_with_body(body, 'ok')
1534
result = repo.gather_stats(revid, True)
1536
[('call_expecting_body', 'Repository.gather_stats',
1537
('buick/', revid, 'yes'))],
1539
self.assertEqual({'revisions': 2, 'size': 18,
1541
'firstrev': (123456.300, 3600),
1542
'latestrev': (654231.400, 0),},
1546
class TestRepositoryGetGraph(TestRemoteRepository):
1548
def test_get_graph(self):
1549
# get_graph returns a graph with a custom parents provider.
1550
transport_path = 'quack'
1551
repo, client = self.setup_fake_client_and_repository(transport_path)
1552
graph = repo.get_graph()
1553
self.assertNotEqual(graph._parents_provider, repo)
1556
class TestRepositoryGetParentMap(TestRemoteRepository):
1558
def test_get_parent_map_caching(self):
1559
# get_parent_map returns from cache until unlock()
1560
# setup a reponse with two revisions
1561
r1 = u'\u0e33'.encode('utf8')
1562
r2 = u'\u0dab'.encode('utf8')
1563
lines = [' '.join([r2, r1]), r1]
1564
encoded_body = bz2.compress('\n'.join(lines))
1566
transport_path = 'quack'
1567
repo, client = self.setup_fake_client_and_repository(transport_path)
1568
client.add_success_response_with_body(encoded_body, 'ok')
1569
client.add_success_response_with_body(encoded_body, 'ok')
1571
graph = repo.get_graph()
1572
parents = graph.get_parent_map([r2])
1573
self.assertEqual({r2: (r1,)}, parents)
1574
# locking and unlocking deeper should not reset
1577
parents = graph.get_parent_map([r1])
1578
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1580
[('call_with_body_bytes_expecting_body',
1581
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1585
# now we call again, and it should use the second response.
1587
graph = repo.get_graph()
1588
parents = graph.get_parent_map([r1])
1589
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1591
[('call_with_body_bytes_expecting_body',
1592
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1594
('call_with_body_bytes_expecting_body',
1595
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1601
def test_get_parent_map_reconnects_if_unknown_method(self):
1602
transport_path = 'quack'
1603
rev_id = 'revision-id'
1604
repo, client = self.setup_fake_client_and_repository(transport_path)
1605
client.add_unknown_method_response('Repository.get_parent_map')
1606
client.add_success_response_with_body(rev_id, 'ok')
1607
self.assertFalse(client._medium._is_remote_before((1, 2)))
1608
parents = repo.get_parent_map([rev_id])
1610
[('call_with_body_bytes_expecting_body',
1611
'Repository.get_parent_map', ('quack/', 'include-missing:',
1613
('disconnect medium',),
1614
('call_expecting_body', 'Repository.get_revision_graph',
1617
# The medium is now marked as being connected to an older server
1618
self.assertTrue(client._medium._is_remote_before((1, 2)))
1619
self.assertEqual({rev_id: ('null:',)}, parents)
1621
def test_get_parent_map_fallback_parentless_node(self):
1622
"""get_parent_map falls back to get_revision_graph on old servers. The
1623
results from get_revision_graph are tweaked to match the get_parent_map
1626
Specifically, a {key: ()} result from get_revision_graph means "no
1627
parents" for that key, which in get_parent_map results should be
1628
represented as {key: ('null:',)}.
1630
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1632
rev_id = 'revision-id'
1633
transport_path = 'quack'
1634
repo, client = self.setup_fake_client_and_repository(transport_path)
1635
client.add_success_response_with_body(rev_id, 'ok')
1636
client._medium._remember_remote_is_before((1, 2))
1637
parents = repo.get_parent_map([rev_id])
1639
[('call_expecting_body', 'Repository.get_revision_graph',
1642
self.assertEqual({rev_id: ('null:',)}, parents)
1644
def test_get_parent_map_unexpected_response(self):
1645
repo, client = self.setup_fake_client_and_repository('path')
1646
client.add_success_response('something unexpected!')
1648
errors.UnexpectedSmartServerResponse,
1649
repo.get_parent_map, ['a-revision-id'])
1651
def test_get_parent_map_negative_caches_missing_keys(self):
1652
self.setup_smart_server_with_call_log()
1653
repo = self.make_repository('foo')
1654
self.assertIsInstance(repo, RemoteRepository)
1656
self.addCleanup(repo.unlock)
1657
self.reset_smart_call_log()
1658
graph = repo.get_graph()
1659
self.assertEqual({},
1660
graph.get_parent_map(['some-missing', 'other-missing']))
1661
self.assertLength(1, self.hpss_calls)
1662
# No call if we repeat this
1663
self.reset_smart_call_log()
1664
graph = repo.get_graph()
1665
self.assertEqual({},
1666
graph.get_parent_map(['some-missing', 'other-missing']))
1667
self.assertLength(0, self.hpss_calls)
1668
# Asking for more unknown keys makes a request.
1669
self.reset_smart_call_log()
1670
graph = repo.get_graph()
1671
self.assertEqual({},
1672
graph.get_parent_map(['some-missing', 'other-missing',
1674
self.assertLength(1, self.hpss_calls)
1676
def test_get_parent_map_gets_ghosts_from_result(self):
1677
# asking for a revision should negatively cache close ghosts in its
1679
self.setup_smart_server_with_call_log()
1680
tree = self.make_branch_and_memory_tree('foo')
1683
builder = treebuilder.TreeBuilder()
1684
builder.start_tree(tree)
1686
builder.finish_tree()
1687
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1688
rev_id = tree.commit('')
1692
self.addCleanup(tree.unlock)
1693
repo = tree.branch.repository
1694
self.assertIsInstance(repo, RemoteRepository)
1696
repo.get_parent_map([rev_id])
1697
self.reset_smart_call_log()
1698
# Now asking for rev_id's ghost parent should not make calls
1699
self.assertEqual({}, repo.get_parent_map(['non-existant']))
1700
self.assertLength(0, self.hpss_calls)
1703
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1705
def test_allows_new_revisions(self):
1706
"""get_parent_map's results can be updated by commit."""
1707
smart_server = server.SmartTCPServer_for_testing()
1708
smart_server.setUp()
1709
self.addCleanup(smart_server.tearDown)
1710
self.make_branch('branch')
1711
branch = Branch.open(smart_server.get_url() + '/branch')
1712
tree = branch.create_checkout('tree', lightweight=True)
1714
self.addCleanup(tree.unlock)
1715
graph = tree.branch.repository.get_graph()
1716
# This provides an opportunity for the missing rev-id to be cached.
1717
self.assertEqual({}, graph.get_parent_map(['rev1']))
1718
tree.commit('message', rev_id='rev1')
1719
graph = tree.branch.repository.get_graph()
1720
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1723
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1725
def test_null_revision(self):
1726
# a null revision has the predictable result {}, we should have no wire
1727
# traffic when calling it with this argument
1728
transport_path = 'empty'
1729
repo, client = self.setup_fake_client_and_repository(transport_path)
1730
client.add_success_response('notused')
1731
# actual RemoteRepository.get_revision_graph is gone, but there's an
1732
# equivalent private method for testing
1733
result = repo._get_revision_graph(NULL_REVISION)
1734
self.assertEqual([], client._calls)
1735
self.assertEqual({}, result)
1737
def test_none_revision(self):
1738
# with none we want the entire graph
1739
r1 = u'\u0e33'.encode('utf8')
1740
r2 = u'\u0dab'.encode('utf8')
1741
lines = [' '.join([r2, r1]), r1]
1742
encoded_body = '\n'.join(lines)
1744
transport_path = 'sinhala'
1745
repo, client = self.setup_fake_client_and_repository(transport_path)
1746
client.add_success_response_with_body(encoded_body, 'ok')
1747
# actual RemoteRepository.get_revision_graph is gone, but there's an
1748
# equivalent private method for testing
1749
result = repo._get_revision_graph(None)
1751
[('call_expecting_body', 'Repository.get_revision_graph',
1754
self.assertEqual({r1: (), r2: (r1, )}, result)
1756
def test_specific_revision(self):
1757
# with a specific revision we want the graph for that
1758
# with none we want the entire graph
1759
r11 = u'\u0e33'.encode('utf8')
1760
r12 = u'\xc9'.encode('utf8')
1761
r2 = u'\u0dab'.encode('utf8')
1762
lines = [' '.join([r2, r11, r12]), r11, r12]
1763
encoded_body = '\n'.join(lines)
1765
transport_path = 'sinhala'
1766
repo, client = self.setup_fake_client_and_repository(transport_path)
1767
client.add_success_response_with_body(encoded_body, 'ok')
1768
result = repo._get_revision_graph(r2)
1770
[('call_expecting_body', 'Repository.get_revision_graph',
1773
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1775
def test_no_such_revision(self):
1777
transport_path = 'sinhala'
1778
repo, client = self.setup_fake_client_and_repository(transport_path)
1779
client.add_error_response('nosuchrevision', revid)
1780
# also check that the right revision is reported in the error
1781
self.assertRaises(errors.NoSuchRevision,
1782
repo._get_revision_graph, revid)
1784
[('call_expecting_body', 'Repository.get_revision_graph',
1785
('sinhala/', revid))],
1788
def test_unexpected_error(self):
1790
transport_path = 'sinhala'
1791
repo, client = self.setup_fake_client_and_repository(transport_path)
1792
client.add_error_response('AnUnexpectedError')
1793
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1794
repo._get_revision_graph, revid)
1795
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1798
class TestRepositoryIsShared(TestRemoteRepository):
1800
def test_is_shared(self):
1801
# ('yes', ) for Repository.is_shared -> 'True'.
1802
transport_path = 'quack'
1803
repo, client = self.setup_fake_client_and_repository(transport_path)
1804
client.add_success_response('yes')
1805
result = repo.is_shared()
1807
[('call', 'Repository.is_shared', ('quack/',))],
1809
self.assertEqual(True, result)
1811
def test_is_not_shared(self):
1812
# ('no', ) for Repository.is_shared -> 'False'.
1813
transport_path = 'qwack'
1814
repo, client = self.setup_fake_client_and_repository(transport_path)
1815
client.add_success_response('no')
1816
result = repo.is_shared()
1818
[('call', 'Repository.is_shared', ('qwack/',))],
1820
self.assertEqual(False, result)
1823
class TestRepositoryLockWrite(TestRemoteRepository):
1825
def test_lock_write(self):
1826
transport_path = 'quack'
1827
repo, client = self.setup_fake_client_and_repository(transport_path)
1828
client.add_success_response('ok', 'a token')
1829
result = repo.lock_write()
1831
[('call', 'Repository.lock_write', ('quack/', ''))],
1833
self.assertEqual('a token', result)
1835
def test_lock_write_already_locked(self):
1836
transport_path = 'quack'
1837
repo, client = self.setup_fake_client_and_repository(transport_path)
1838
client.add_error_response('LockContention')
1839
self.assertRaises(errors.LockContention, repo.lock_write)
1841
[('call', 'Repository.lock_write', ('quack/', ''))],
1844
def test_lock_write_unlockable(self):
1845
transport_path = 'quack'
1846
repo, client = self.setup_fake_client_and_repository(transport_path)
1847
client.add_error_response('UnlockableTransport')
1848
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1850
[('call', 'Repository.lock_write', ('quack/', ''))],
1854
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1856
def test_backwards_compat(self):
1857
self.setup_smart_server_with_call_log()
1858
repo = self.make_repository('.')
1859
self.reset_smart_call_log()
1860
verb = 'Repository.set_make_working_trees'
1861
self.disable_verb(verb)
1862
repo.set_make_working_trees(True)
1863
call_count = len([call for call in self.hpss_calls if
1864
call.call.method == verb])
1865
self.assertEqual(1, call_count)
1867
def test_current(self):
1868
transport_path = 'quack'
1869
repo, client = self.setup_fake_client_and_repository(transport_path)
1870
client.add_expected_call(
1871
'Repository.set_make_working_trees', ('quack/', 'True'),
1873
client.add_expected_call(
1874
'Repository.set_make_working_trees', ('quack/', 'False'),
1876
repo.set_make_working_trees(True)
1877
repo.set_make_working_trees(False)
1880
class TestRepositoryUnlock(TestRemoteRepository):
1882
def test_unlock(self):
1883
transport_path = 'quack'
1884
repo, client = self.setup_fake_client_and_repository(transport_path)
1885
client.add_success_response('ok', 'a token')
1886
client.add_success_response('ok')
1890
[('call', 'Repository.lock_write', ('quack/', '')),
1891
('call', 'Repository.unlock', ('quack/', 'a token'))],
1894
def test_unlock_wrong_token(self):
1895
# If somehow the token is wrong, unlock will raise TokenMismatch.
1896
transport_path = 'quack'
1897
repo, client = self.setup_fake_client_and_repository(transport_path)
1898
client.add_success_response('ok', 'a token')
1899
client.add_error_response('TokenMismatch')
1901
self.assertRaises(errors.TokenMismatch, repo.unlock)
1904
class TestRepositoryHasRevision(TestRemoteRepository):
1906
def test_none(self):
1907
# repo.has_revision(None) should not cause any traffic.
1908
transport_path = 'quack'
1909
repo, client = self.setup_fake_client_and_repository(transport_path)
1911
# The null revision is always there, so has_revision(None) == True.
1912
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1914
# The remote repo shouldn't be accessed.
1915
self.assertEqual([], client._calls)
1918
class TestRepositoryInsertStream(TestRemoteRepository):
1920
def test_unlocked_repo(self):
1921
transport_path = 'quack'
1922
repo, client = self.setup_fake_client_and_repository(transport_path)
1923
client.add_expected_call(
1924
'Repository.insert_stream', ('quack/', ''),
1926
client.add_expected_call(
1927
'Repository.insert_stream', ('quack/', ''),
1929
sink = repo._get_sink()
1930
fmt = repository.RepositoryFormat.get_default_format()
1931
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1932
self.assertEqual([], resume_tokens)
1933
self.assertEqual(set(), missing_keys)
1934
client.finished_test()
1936
def test_locked_repo_with_no_lock_token(self):
1937
transport_path = 'quack'
1938
repo, client = self.setup_fake_client_and_repository(transport_path)
1939
client.add_expected_call(
1940
'Repository.lock_write', ('quack/', ''),
1941
'success', ('ok', ''))
1942
client.add_expected_call(
1943
'Repository.insert_stream', ('quack/', ''),
1945
client.add_expected_call(
1946
'Repository.insert_stream', ('quack/', ''),
1949
sink = repo._get_sink()
1950
fmt = repository.RepositoryFormat.get_default_format()
1951
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1952
self.assertEqual([], resume_tokens)
1953
self.assertEqual(set(), missing_keys)
1954
client.finished_test()
1956
def test_locked_repo_with_lock_token(self):
1957
transport_path = 'quack'
1958
repo, client = self.setup_fake_client_and_repository(transport_path)
1959
client.add_expected_call(
1960
'Repository.lock_write', ('quack/', ''),
1961
'success', ('ok', 'a token'))
1962
client.add_expected_call(
1963
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
1965
client.add_expected_call(
1966
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
1969
sink = repo._get_sink()
1970
fmt = repository.RepositoryFormat.get_default_format()
1971
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1972
self.assertEqual([], resume_tokens)
1973
self.assertEqual(set(), missing_keys)
1974
client.finished_test()
1977
class TestRepositoryTarball(TestRemoteRepository):
1979
# This is a canned tarball reponse we can validate against
1981
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1982
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1983
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1984
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1985
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1986
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1987
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1988
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1989
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1990
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1991
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1992
'nWQ7QH/F3JFOFCQ0aSPfA='
1995
def test_repository_tarball(self):
1996
# Test that Repository.tarball generates the right operations
1997
transport_path = 'repo'
1998
expected_calls = [('call_expecting_body', 'Repository.tarball',
1999
('repo/', 'bz2',),),
2001
repo, client = self.setup_fake_client_and_repository(transport_path)
2002
client.add_success_response_with_body(self.tarball_content, 'ok')
2003
# Now actually ask for the tarball
2004
tarball_file = repo._get_tarball('bz2')
2006
self.assertEqual(expected_calls, client._calls)
2007
self.assertEqual(self.tarball_content, tarball_file.read())
2009
tarball_file.close()
2012
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2013
"""RemoteRepository.copy_content_into optimizations"""
2015
def test_copy_content_remote_to_local(self):
2016
self.transport_server = server.SmartTCPServer_for_testing
2017
src_repo = self.make_repository('repo1')
2018
src_repo = repository.Repository.open(self.get_url('repo1'))
2019
# At the moment the tarball-based copy_content_into can't write back
2020
# into a smart server. It would be good if it could upload the
2021
# tarball; once that works we'd have to create repositories of
2022
# different formats. -- mbp 20070410
2023
dest_url = self.get_vfs_only_url('repo2')
2024
dest_bzrdir = BzrDir.create(dest_url)
2025
dest_repo = dest_bzrdir.create_repository()
2026
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2027
self.assertTrue(isinstance(src_repo, RemoteRepository))
2028
src_repo.copy_content_into(dest_repo)
2031
class _StubRealPackRepository(object):
2033
def __init__(self, calls):
2035
self._pack_collection = _StubPackCollection(calls)
2037
def is_in_write_group(self):
2040
def refresh_data(self):
2041
self.calls.append(('pack collection reload_pack_names',))
2044
class _StubPackCollection(object):
2046
def __init__(self, calls):
2050
self.calls.append(('pack collection autopack',))
2053
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2054
"""Tests for RemoteRepository.autopack implementation."""
2057
"""When the server returns 'ok' and there's no _real_repository, then
2058
nothing else happens: the autopack method is done.
2060
transport_path = 'quack'
2061
repo, client = self.setup_fake_client_and_repository(transport_path)
2062
client.add_expected_call(
2063
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2065
client.finished_test()
2067
def test_ok_with_real_repo(self):
2068
"""When the server returns 'ok' and there is a _real_repository, then
2069
the _real_repository's reload_pack_name's method will be called.
2071
transport_path = 'quack'
2072
repo, client = self.setup_fake_client_and_repository(transport_path)
2073
client.add_expected_call(
2074
'PackRepository.autopack', ('quack/',),
2076
repo._real_repository = _StubRealPackRepository(client._calls)
2079
[('call', 'PackRepository.autopack', ('quack/',)),
2080
('pack collection reload_pack_names',)],
2083
def test_backwards_compatibility(self):
2084
"""If the server does not recognise the PackRepository.autopack verb,
2085
fallback to the real_repository's implementation.
2087
transport_path = 'quack'
2088
repo, client = self.setup_fake_client_and_repository(transport_path)
2089
client.add_unknown_method_response('PackRepository.autopack')
2090
def stub_ensure_real():
2091
client._calls.append(('_ensure_real',))
2092
repo._real_repository = _StubRealPackRepository(client._calls)
2093
repo._ensure_real = stub_ensure_real
2096
[('call', 'PackRepository.autopack', ('quack/',)),
2098
('pack collection autopack',)],
2102
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2103
"""Base class for unit tests for bzrlib.remote._translate_error."""
2105
def translateTuple(self, error_tuple, **context):
2106
"""Call _translate_error with an ErrorFromSmartServer built from the
2109
:param error_tuple: A tuple of a smart server response, as would be
2110
passed to an ErrorFromSmartServer.
2111
:kwargs context: context items to call _translate_error with.
2113
:returns: The error raised by _translate_error.
2115
# Raise the ErrorFromSmartServer before passing it as an argument,
2116
# because _translate_error may need to re-raise it with a bare 'raise'
2118
server_error = errors.ErrorFromSmartServer(error_tuple)
2119
translated_error = self.translateErrorFromSmartServer(
2120
server_error, **context)
2121
return translated_error
2123
def translateErrorFromSmartServer(self, error_object, **context):
2124
"""Like translateTuple, but takes an already constructed
2125
ErrorFromSmartServer rather than a tuple.
2129
except errors.ErrorFromSmartServer, server_error:
2130
translated_error = self.assertRaises(
2131
errors.BzrError, remote._translate_error, server_error,
2133
return translated_error
2136
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2137
"""Unit tests for bzrlib.remote._translate_error.
2139
Given an ErrorFromSmartServer (which has an error tuple from a smart
2140
server) and some context, _translate_error raises more specific errors from
2143
This test case covers the cases where _translate_error succeeds in
2144
translating an ErrorFromSmartServer to something better. See
2145
TestErrorTranslationRobustness for other cases.
2148
def test_NoSuchRevision(self):
2149
branch = self.make_branch('')
2151
translated_error = self.translateTuple(
2152
('NoSuchRevision', revid), branch=branch)
2153
expected_error = errors.NoSuchRevision(branch, revid)
2154
self.assertEqual(expected_error, translated_error)
2156
def test_nosuchrevision(self):
2157
repository = self.make_repository('')
2159
translated_error = self.translateTuple(
2160
('nosuchrevision', revid), repository=repository)
2161
expected_error = errors.NoSuchRevision(repository, revid)
2162
self.assertEqual(expected_error, translated_error)
2164
def test_nobranch(self):
2165
bzrdir = self.make_bzrdir('')
2166
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2167
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2168
self.assertEqual(expected_error, translated_error)
2170
def test_LockContention(self):
2171
translated_error = self.translateTuple(('LockContention',))
2172
expected_error = errors.LockContention('(remote lock)')
2173
self.assertEqual(expected_error, translated_error)
2175
def test_UnlockableTransport(self):
2176
bzrdir = self.make_bzrdir('')
2177
translated_error = self.translateTuple(
2178
('UnlockableTransport',), bzrdir=bzrdir)
2179
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2180
self.assertEqual(expected_error, translated_error)
2182
def test_LockFailed(self):
2183
lock = 'str() of a server lock'
2184
why = 'str() of why'
2185
translated_error = self.translateTuple(('LockFailed', lock, why))
2186
expected_error = errors.LockFailed(lock, why)
2187
self.assertEqual(expected_error, translated_error)
2189
def test_TokenMismatch(self):
2190
token = 'a lock token'
2191
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2192
expected_error = errors.TokenMismatch(token, '(remote token)')
2193
self.assertEqual(expected_error, translated_error)
2195
def test_Diverged(self):
2196
branch = self.make_branch('a')
2197
other_branch = self.make_branch('b')
2198
translated_error = self.translateTuple(
2199
('Diverged',), branch=branch, other_branch=other_branch)
2200
expected_error = errors.DivergedBranches(branch, other_branch)
2201
self.assertEqual(expected_error, translated_error)
2203
def test_ReadError_no_args(self):
2205
translated_error = self.translateTuple(('ReadError',), path=path)
2206
expected_error = errors.ReadError(path)
2207
self.assertEqual(expected_error, translated_error)
2209
def test_ReadError(self):
2211
translated_error = self.translateTuple(('ReadError', path))
2212
expected_error = errors.ReadError(path)
2213
self.assertEqual(expected_error, translated_error)
2215
def test_PermissionDenied_no_args(self):
2217
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2218
expected_error = errors.PermissionDenied(path)
2219
self.assertEqual(expected_error, translated_error)
2221
def test_PermissionDenied_one_arg(self):
2223
translated_error = self.translateTuple(('PermissionDenied', path))
2224
expected_error = errors.PermissionDenied(path)
2225
self.assertEqual(expected_error, translated_error)
2227
def test_PermissionDenied_one_arg_and_context(self):
2228
"""Given a choice between a path from the local context and a path on
2229
the wire, _translate_error prefers the path from the local context.
2231
local_path = 'local path'
2232
remote_path = 'remote path'
2233
translated_error = self.translateTuple(
2234
('PermissionDenied', remote_path), path=local_path)
2235
expected_error = errors.PermissionDenied(local_path)
2236
self.assertEqual(expected_error, translated_error)
2238
def test_PermissionDenied_two_args(self):
2240
extra = 'a string with extra info'
2241
translated_error = self.translateTuple(
2242
('PermissionDenied', path, extra))
2243
expected_error = errors.PermissionDenied(path, extra)
2244
self.assertEqual(expected_error, translated_error)
2247
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2248
"""Unit tests for bzrlib.remote._translate_error's robustness.
2250
TestErrorTranslationSuccess is for cases where _translate_error can
2251
translate successfully. This class about how _translate_err behaves when
2252
it fails to translate: it re-raises the original error.
2255
def test_unrecognised_server_error(self):
2256
"""If the error code from the server is not recognised, the original
2257
ErrorFromSmartServer is propagated unmodified.
2259
error_tuple = ('An unknown error tuple',)
2260
server_error = errors.ErrorFromSmartServer(error_tuple)
2261
translated_error = self.translateErrorFromSmartServer(server_error)
2262
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2263
self.assertEqual(expected_error, translated_error)
2265
def test_context_missing_a_key(self):
2266
"""In case of a bug in the client, or perhaps an unexpected response
2267
from a server, _translate_error returns the original error tuple from
2268
the server and mutters a warning.
2270
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2271
# in the context dict. So let's give it an empty context dict instead
2272
# to exercise its error recovery.
2274
error_tuple = ('NoSuchRevision', 'revid')
2275
server_error = errors.ErrorFromSmartServer(error_tuple)
2276
translated_error = self.translateErrorFromSmartServer(server_error)
2277
self.assertEqual(server_error, translated_error)
2278
# In addition to re-raising ErrorFromSmartServer, some debug info has
2279
# been muttered to the log file for developer to look at.
2280
self.assertContainsRe(
2281
self._get_log(keep_log_file=True),
2282
"Missing key 'branch' in context")
2284
def test_path_missing(self):
2285
"""Some translations (PermissionDenied, ReadError) can determine the
2286
'path' variable from either the wire or the local context. If neither
2287
has it, then an error is raised.
2289
error_tuple = ('ReadError',)
2290
server_error = errors.ErrorFromSmartServer(error_tuple)
2291
translated_error = self.translateErrorFromSmartServer(server_error)
2292
self.assertEqual(server_error, translated_error)
2293
# In addition to re-raising ErrorFromSmartServer, some debug info has
2294
# been muttered to the log file for developer to look at.
2295
self.assertContainsRe(
2296
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2299
class TestStacking(tests.TestCaseWithTransport):
2300
"""Tests for operations on stacked remote repositories.
2302
The underlying format type must support stacking.
2305
def test_access_stacked_remote(self):
2306
# based on <http://launchpad.net/bugs/261315>
2307
# make a branch stacked on another repository containing an empty
2308
# revision, then open it over hpss - we should be able to see that
2310
base_transport = self.get_transport()
2311
base_builder = self.make_branch_builder('base', format='1.9')
2312
base_builder.start_series()
2313
base_revid = base_builder.build_snapshot('rev-id', None,
2314
[('add', ('', None, 'directory', None))],
2316
base_builder.finish_series()
2317
stacked_branch = self.make_branch('stacked', format='1.9')
2318
stacked_branch.set_stacked_on_url('../base')
2319
# start a server looking at this
2320
smart_server = server.SmartTCPServer_for_testing()
2321
smart_server.setUp()
2322
self.addCleanup(smart_server.tearDown)
2323
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2324
# can get its branch and repository
2325
remote_branch = remote_bzrdir.open_branch()
2326
remote_repo = remote_branch.repository
2327
remote_repo.lock_read()
2329
# it should have an appropriate fallback repository, which should also
2330
# be a RemoteRepository
2331
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2332
self.assertIsInstance(remote_repo._fallback_repositories[0],
2334
# and it has the revision committed to the underlying repository;
2335
# these have varying implementations so we try several of them
2336
self.assertTrue(remote_repo.has_revisions([base_revid]))
2337
self.assertTrue(remote_repo.has_revision(base_revid))
2338
self.assertEqual(remote_repo.get_revision(base_revid).message,
2341
remote_repo.unlock()
2343
def prepare_stacked_remote_branch(self):
2344
"""Get stacked_upon and stacked branches with content in each."""
2345
self.setup_smart_server_with_call_log()
2346
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2347
tree1.commit('rev1', rev_id='rev1')
2348
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2349
).open_workingtree()
2350
tree2.commit('local changes make me feel good.')
2351
branch2 = Branch.open(self.get_url('tree2'))
2353
self.addCleanup(branch2.unlock)
2354
return tree1.branch, branch2
2356
def test_stacked_get_parent_map(self):
2357
# the public implementation of get_parent_map obeys stacking
2358
_, branch = self.prepare_stacked_remote_branch()
2359
repo = branch.repository
2360
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2362
def test_unstacked_get_parent_map(self):
2363
# _unstacked_provider.get_parent_map ignores stacking
2364
_, branch = self.prepare_stacked_remote_branch()
2365
provider = branch.repository._unstacked_provider
2366
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2368
def fetch_stream_to_rev_order(self, stream):
2370
for kind, substream in stream:
2371
if not kind == 'revisions':
2374
for content in substream:
2375
result.append(content.key[-1])
2378
def get_ordered_revs(self, format, order):
2379
"""Get a list of the revisions in a stream to format format.
2381
:param format: The format of the target.
2382
:param order: the order that target should have requested.
2383
:result: The revision ids in the stream, in the order seen,
2384
the topological order of revisions in the source.
2386
unordered_format = bzrdir.format_registry.get(format)()
2387
target_repository_format = unordered_format.repository_format
2389
self.assertEqual(order, target_repository_format._fetch_order)
2390
trunk, stacked = self.prepare_stacked_remote_branch()
2391
source = stacked.repository._get_source(target_repository_format)
2392
tip = stacked.last_revision()
2393
revs = stacked.repository.get_ancestry(tip)
2394
search = graph.PendingAncestryResult([tip], stacked.repository)
2395
self.reset_smart_call_log()
2396
stream = source.get_stream(search)
2399
# We trust that if a revision is in the stream the rest of the new
2400
# content for it is too, as per our main fetch tests; here we are
2401
# checking that the revisions are actually included at all, and their
2403
return self.fetch_stream_to_rev_order(stream), revs
2405
def test_stacked_get_stream_unordered(self):
2406
# Repository._get_source.get_stream() from a stacked repository with
2407
# unordered yields the full data from both stacked and stacked upon
2409
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2410
self.assertEqual(set(expected_revs), set(rev_ord))
2411
# Getting unordered results should have made a streaming data request
2412
# from the server, then one from the backing branch.
2413
self.assertLength(2, self.hpss_calls)
2415
def test_stacked_get_stream_topological(self):
2416
# Repository._get_source.get_stream() from a stacked repository with
2417
# topological sorting yields the full data from both stacked and
2418
# stacked upon sources in topological order.
2419
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2420
self.assertEqual(expected_revs, rev_ord)
2421
# Getting topological sort requires VFS calls still
2422
self.assertLength(12, self.hpss_calls)
2424
def test_stacked_get_stream_groupcompress(self):
2425
# Repository._get_source.get_stream() from a stacked repository with
2426
# groupcompress sorting yields the full data from both stacked and
2427
# stacked upon sources in groupcompress order.
2428
raise tests.TestSkipped('No groupcompress ordered format available')
2429
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2430
self.assertEqual(expected_revs, reversed(rev_ord))
2431
# Getting unordered results should have made a streaming data request
2432
# from the backing branch, and one from the stacked on branch.
2433
self.assertLength(2, self.hpss_calls)
2436
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2439
super(TestRemoteBranchEffort, self).setUp()
2440
# Create a smart server that publishes whatever the backing VFS server
2442
self.smart_server = server.SmartTCPServer_for_testing()
2443
self.smart_server.setUp(self.get_server())
2444
self.addCleanup(self.smart_server.tearDown)
2445
# Log all HPSS calls into self.hpss_calls.
2446
_SmartClient.hooks.install_named_hook(
2447
'call', self.capture_hpss_call, None)
2448
self.hpss_calls = []
2450
def capture_hpss_call(self, params):
2451
self.hpss_calls.append(params.method)
2453
def test_copy_content_into_avoids_revision_history(self):
2454
local = self.make_branch('local')
2455
remote_backing_tree = self.make_branch_and_tree('remote')
2456
remote_backing_tree.commit("Commit.")
2457
remote_branch_url = self.smart_server.get_url() + 'remote'
2458
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2459
local.repository.fetch(remote_branch.repository)
2460
self.hpss_calls = []
2461
remote_branch.copy_content_into(local)
2462
self.assertFalse('Branch.revision_history' in self.hpss_calls)