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
45
from bzrlib.branch import Branch
46
from bzrlib.bzrdir import BzrDir, BzrDirFormat
47
from bzrlib.remote import (
53
RemoteRepositoryFormat,
55
from bzrlib.repofmt import groupcompress_repo, pack_repo
56
from bzrlib.revision import NULL_REVISION
57
from bzrlib.smart import server, medium
58
from bzrlib.smart.client import _SmartClient
59
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
60
from bzrlib.tests import (
62
split_suite_by_condition,
65
from bzrlib.transport import get_transport
66
from bzrlib.transport.memory import MemoryTransport
67
from bzrlib.transport.remote import (
73
def load_tests(standard_tests, module, loader):
74
to_adapt, result = split_suite_by_condition(
75
standard_tests, condition_isinstance(BasicRemoteObjectTests))
76
smart_server_version_scenarios = [
78
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
80
{'transport_server': server.SmartTCPServer_for_testing})]
81
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
84
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
87
super(BasicRemoteObjectTests, self).setUp()
88
self.transport = self.get_transport()
89
# make a branch that can be opened over the smart transport
90
self.local_wt = BzrDir.create_standalone_workingtree('.')
93
self.transport.disconnect()
94
tests.TestCaseWithTransport.tearDown(self)
96
def test_create_remote_bzrdir(self):
97
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
98
self.assertIsInstance(b, BzrDir)
100
def test_open_remote_branch(self):
101
# open a standalone branch in the working directory
102
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
103
branch = b.open_branch()
104
self.assertIsInstance(branch, Branch)
106
def test_remote_repository(self):
107
b = BzrDir.open_from_transport(self.transport)
108
repo = b.open_repository()
109
revid = u'\xc823123123'.encode('utf8')
110
self.assertFalse(repo.has_revision(revid))
111
self.local_wt.commit(message='test commit', rev_id=revid)
112
self.assertTrue(repo.has_revision(revid))
114
def test_remote_branch_revision_history(self):
115
b = BzrDir.open_from_transport(self.transport).open_branch()
116
self.assertEqual([], b.revision_history())
117
r1 = self.local_wt.commit('1st commit')
118
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
119
self.assertEqual([r1, r2], b.revision_history())
121
def test_find_correct_format(self):
122
"""Should open a RemoteBzrDir over a RemoteTransport"""
123
fmt = BzrDirFormat.find_format(self.transport)
124
self.assertTrue(RemoteBzrDirFormat
125
in BzrDirFormat._control_server_formats)
126
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
128
def test_open_detected_smart_format(self):
129
fmt = BzrDirFormat.find_format(self.transport)
130
d = fmt.open(self.transport)
131
self.assertIsInstance(d, BzrDir)
133
def test_remote_branch_repr(self):
134
b = BzrDir.open_from_transport(self.transport).open_branch()
135
self.assertStartsWith(str(b), 'RemoteBranch(')
137
def test_remote_branch_format_supports_stacking(self):
139
self.make_branch('unstackable', format='pack-0.92')
140
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
141
self.assertFalse(b._format.supports_stacking())
142
self.make_branch('stackable', format='1.9')
143
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
144
self.assertTrue(b._format.supports_stacking())
146
def test_remote_repo_format_supports_external_references(self):
148
bd = self.make_bzrdir('unstackable', format='pack-0.92')
149
r = bd.create_repository()
150
self.assertFalse(r._format.supports_external_lookups)
151
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
152
self.assertFalse(r._format.supports_external_lookups)
153
bd = self.make_bzrdir('stackable', format='1.9')
154
r = bd.create_repository()
155
self.assertTrue(r._format.supports_external_lookups)
156
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
157
self.assertTrue(r._format.supports_external_lookups)
159
def test_remote_branch_set_append_revisions_only(self):
160
# Make a format 1.9 branch, which supports append_revisions_only
161
branch = self.make_branch('branch', format='1.9')
162
config = branch.get_config()
163
branch.set_append_revisions_only(True)
165
'True', config.get_user_option('append_revisions_only'))
166
branch.set_append_revisions_only(False)
168
'False', config.get_user_option('append_revisions_only'))
170
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
171
branch = self.make_branch('branch', format='knit')
172
config = branch.get_config()
174
errors.UpgradeRequired, branch.set_append_revisions_only, True)
177
class FakeProtocol(object):
178
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
180
def __init__(self, body, fake_client):
182
self._body_buffer = None
183
self._fake_client = fake_client
185
def read_body_bytes(self, count=-1):
186
if self._body_buffer is None:
187
self._body_buffer = StringIO(self.body)
188
bytes = self._body_buffer.read(count)
189
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
190
self._fake_client.expecting_body = False
193
def cancel_read_body(self):
194
self._fake_client.expecting_body = False
196
def read_streamed_body(self):
200
class FakeClient(_SmartClient):
201
"""Lookalike for _SmartClient allowing testing."""
203
def __init__(self, fake_medium_base='fake base'):
204
"""Create a FakeClient."""
207
self.expecting_body = False
208
# if non-None, this is the list of expected calls, with only the
209
# method name and arguments included. the body might be hard to
210
# compute so is not included. If a call is None, that call can
212
self._expected_calls = None
213
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
215
def add_expected_call(self, call_name, call_args, response_type,
216
response_args, response_body=None):
217
if self._expected_calls is None:
218
self._expected_calls = []
219
self._expected_calls.append((call_name, call_args))
220
self.responses.append((response_type, response_args, response_body))
222
def add_success_response(self, *args):
223
self.responses.append(('success', args, None))
225
def add_success_response_with_body(self, body, *args):
226
self.responses.append(('success', args, body))
227
if self._expected_calls is not None:
228
self._expected_calls.append(None)
230
def add_error_response(self, *args):
231
self.responses.append(('error', args))
233
def add_unknown_method_response(self, verb):
234
self.responses.append(('unknown', verb))
236
def finished_test(self):
237
if self._expected_calls:
238
raise AssertionError("%r finished but was still expecting %r"
239
% (self, self._expected_calls[0]))
241
def _get_next_response(self):
243
response_tuple = self.responses.pop(0)
244
except IndexError, e:
245
raise AssertionError("%r didn't expect any more calls"
247
if response_tuple[0] == 'unknown':
248
raise errors.UnknownSmartMethod(response_tuple[1])
249
elif response_tuple[0] == 'error':
250
raise errors.ErrorFromSmartServer(response_tuple[1])
251
return response_tuple
253
def _check_call(self, method, args):
254
if self._expected_calls is None:
255
# the test should be updated to say what it expects
258
next_call = self._expected_calls.pop(0)
260
raise AssertionError("%r didn't expect any more calls "
262
% (self, method, args,))
263
if next_call is None:
265
if method != next_call[0] or args != next_call[1]:
266
raise AssertionError("%r expected %r%r "
268
% (self, next_call[0], next_call[1], method, args,))
270
def call(self, method, *args):
271
self._check_call(method, args)
272
self._calls.append(('call', method, args))
273
return self._get_next_response()[1]
275
def call_expecting_body(self, method, *args):
276
self._check_call(method, args)
277
self._calls.append(('call_expecting_body', method, args))
278
result = self._get_next_response()
279
self.expecting_body = True
280
return result[1], FakeProtocol(result[2], self)
282
def call_with_body_bytes(self, method, args, body):
283
self._check_call(method, args)
284
self._calls.append(('call_with_body_bytes', method, args, body))
285
result = self._get_next_response()
286
return result[1], FakeProtocol(result[2], self)
288
def call_with_body_bytes_expecting_body(self, method, args, body):
289
self._check_call(method, args)
290
self._calls.append(('call_with_body_bytes_expecting_body', method,
292
result = self._get_next_response()
293
self.expecting_body = True
294
return result[1], FakeProtocol(result[2], self)
296
def call_with_body_stream(self, args, stream):
297
# Explicitly consume the stream before checking for an error, because
298
# that's what happens a real medium.
299
stream = list(stream)
300
self._check_call(args[0], args[1:])
301
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
302
result = self._get_next_response()
303
# The second value returned from call_with_body_stream is supposed to
304
# be a response_handler object, but so far no tests depend on that.
305
response_handler = None
306
return result[1], response_handler
309
class FakeMedium(medium.SmartClientMedium):
311
def __init__(self, client_calls, base):
312
medium.SmartClientMedium.__init__(self, base)
313
self._client_calls = client_calls
315
def disconnect(self):
316
self._client_calls.append(('disconnect medium',))
319
class TestVfsHas(tests.TestCase):
321
def test_unicode_path(self):
322
client = FakeClient('/')
323
client.add_success_response('yes',)
324
transport = RemoteTransport('bzr://localhost/', _client=client)
325
filename = u'/hell\u00d8'.encode('utf8')
326
result = transport.has(filename)
328
[('call', 'has', (filename,))],
330
self.assertTrue(result)
333
class TestRemote(tests.TestCaseWithMemoryTransport):
335
def get_branch_format(self):
336
reference_bzrdir_format = bzrdir.format_registry.get('default')()
337
return reference_bzrdir_format.get_branch_format()
339
def get_repo_format(self):
340
reference_bzrdir_format = bzrdir.format_registry.get('default')()
341
return reference_bzrdir_format.repository_format
343
def assertFinished(self, fake_client):
344
"""Assert that all of a FakeClient's expected calls have occurred."""
345
fake_client.finished_test()
348
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
349
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
351
def assertRemotePath(self, expected, client_base, transport_base):
352
"""Assert that the result of
353
SmartClientMedium.remote_path_from_transport is the expected value for
354
a given client_base and transport_base.
356
client_medium = medium.SmartClientMedium(client_base)
357
transport = get_transport(transport_base)
358
result = client_medium.remote_path_from_transport(transport)
359
self.assertEqual(expected, result)
361
def test_remote_path_from_transport(self):
362
"""SmartClientMedium.remote_path_from_transport calculates a URL for
363
the given transport relative to the root of the client base URL.
365
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
366
self.assertRemotePath(
367
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
369
def assertRemotePathHTTP(self, expected, transport_base, relpath):
370
"""Assert that the result of
371
HttpTransportBase.remote_path_from_transport is the expected value for
372
a given transport_base and relpath of that transport. (Note that
373
HttpTransportBase is a subclass of SmartClientMedium)
375
base_transport = get_transport(transport_base)
376
client_medium = base_transport.get_smart_medium()
377
cloned_transport = base_transport.clone(relpath)
378
result = client_medium.remote_path_from_transport(cloned_transport)
379
self.assertEqual(expected, result)
381
def test_remote_path_from_transport_http(self):
382
"""Remote paths for HTTP transports are calculated differently to other
383
transports. They are just relative to the client base, not the root
384
directory of the host.
386
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
387
self.assertRemotePathHTTP(
388
'../xyz/', scheme + '//host/path', '../xyz/')
389
self.assertRemotePathHTTP(
390
'xyz/', scheme + '//host/path', 'xyz/')
393
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
394
"""Tests for the behaviour of client_medium.remote_is_at_least."""
396
def test_initially_unlimited(self):
397
"""A fresh medium assumes that the remote side supports all
400
client_medium = medium.SmartClientMedium('dummy base')
401
self.assertFalse(client_medium._is_remote_before((99, 99)))
403
def test__remember_remote_is_before(self):
404
"""Calling _remember_remote_is_before ratchets down the known remote
407
client_medium = medium.SmartClientMedium('dummy base')
408
# Mark the remote side as being less than 1.6. The remote side may
410
client_medium._remember_remote_is_before((1, 6))
411
self.assertTrue(client_medium._is_remote_before((1, 6)))
412
self.assertFalse(client_medium._is_remote_before((1, 5)))
413
# Calling _remember_remote_is_before again with a lower value works.
414
client_medium._remember_remote_is_before((1, 5))
415
self.assertTrue(client_medium._is_remote_before((1, 5)))
416
# You cannot call _remember_remote_is_before with a larger value.
418
AssertionError, client_medium._remember_remote_is_before, (1, 9))
421
class TestBzrDirCloningMetaDir(TestRemote):
423
def test_backwards_compat(self):
424
self.setup_smart_server_with_call_log()
425
a_dir = self.make_bzrdir('.')
426
self.reset_smart_call_log()
427
verb = 'BzrDir.cloning_metadir'
428
self.disable_verb(verb)
429
format = a_dir.cloning_metadir()
430
call_count = len([call for call in self.hpss_calls if
431
call.call.method == verb])
432
self.assertEqual(1, call_count)
434
def test_branch_reference(self):
435
transport = self.get_transport('quack')
436
referenced = self.make_branch('referenced')
437
expected = referenced.bzrdir.cloning_metadir()
438
client = FakeClient(transport.base)
439
client.add_expected_call(
440
'BzrDir.cloning_metadir', ('quack/', 'False'),
441
'error', ('BranchReference',)),
442
client.add_expected_call(
443
'BzrDir.open_branchV2', ('quack/',),
444
'success', ('ref', self.get_url('referenced'))),
445
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
447
result = a_bzrdir.cloning_metadir()
448
# We should have got a control dir matching the referenced branch.
449
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
450
self.assertEqual(expected._repository_format, result._repository_format)
451
self.assertEqual(expected._branch_format, result._branch_format)
452
self.assertFinished(client)
454
def test_current_server(self):
455
transport = self.get_transport('.')
456
transport = transport.clone('quack')
457
self.make_bzrdir('quack')
458
client = FakeClient(transport.base)
459
reference_bzrdir_format = bzrdir.format_registry.get('default')()
460
control_name = reference_bzrdir_format.network_name()
461
client.add_expected_call(
462
'BzrDir.cloning_metadir', ('quack/', 'False'),
463
'success', (control_name, '', ('branch', ''))),
464
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
466
result = a_bzrdir.cloning_metadir()
467
# We should have got a reference control dir with default branch and
468
# repository formats.
469
# This pokes a little, just to be sure.
470
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
471
self.assertEqual(None, result._repository_format)
472
self.assertEqual(None, result._branch_format)
473
self.assertFinished(client)
476
class TestBzrDirOpen(TestRemote):
478
def make_fake_client_and_transport(self, path='quack'):
479
transport = MemoryTransport()
480
transport.mkdir(path)
481
transport = transport.clone(path)
482
client = FakeClient(transport.base)
483
return client, transport
485
def test_absent(self):
486
client, transport = self.make_fake_client_and_transport()
487
client.add_expected_call(
488
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
489
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
490
remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
491
self.assertFinished(client)
493
def test_present_without_workingtree(self):
494
client, transport = self.make_fake_client_and_transport()
495
client.add_expected_call(
496
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
497
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
498
_client=client, _force_probe=True)
499
self.assertIsInstance(bd, RemoteBzrDir)
500
self.assertFalse(bd.has_workingtree())
501
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
502
self.assertFinished(client)
504
def test_present_with_workingtree(self):
505
client, transport = self.make_fake_client_and_transport()
506
client.add_expected_call(
507
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
508
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
509
_client=client, _force_probe=True)
510
self.assertIsInstance(bd, RemoteBzrDir)
511
self.assertTrue(bd.has_workingtree())
512
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
513
self.assertFinished(client)
515
def test_backwards_compat(self):
516
client, transport = self.make_fake_client_and_transport()
517
client.add_expected_call(
518
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
519
client.add_expected_call(
520
'BzrDir.open', ('quack/',), 'success', ('yes',))
521
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
522
_client=client, _force_probe=True)
523
self.assertIsInstance(bd, RemoteBzrDir)
524
self.assertFinished(client)
527
class TestBzrDirOpenBranch(TestRemote):
529
def test_backwards_compat(self):
530
self.setup_smart_server_with_call_log()
531
self.make_branch('.')
532
a_dir = BzrDir.open(self.get_url('.'))
533
self.reset_smart_call_log()
534
verb = 'BzrDir.open_branchV2'
535
self.disable_verb(verb)
536
format = a_dir.open_branch()
537
call_count = len([call for call in self.hpss_calls if
538
call.call.method == verb])
539
self.assertEqual(1, call_count)
541
def test_branch_present(self):
542
reference_format = self.get_repo_format()
543
network_name = reference_format.network_name()
544
branch_network_name = self.get_branch_format().network_name()
545
transport = MemoryTransport()
546
transport.mkdir('quack')
547
transport = transport.clone('quack')
548
client = FakeClient(transport.base)
549
client.add_expected_call(
550
'BzrDir.open_branchV2', ('quack/',),
551
'success', ('branch', branch_network_name))
552
client.add_expected_call(
553
'BzrDir.find_repositoryV3', ('quack/',),
554
'success', ('ok', '', 'no', 'no', 'no', network_name))
555
client.add_expected_call(
556
'Branch.get_stacked_on_url', ('quack/',),
557
'error', ('NotStacked',))
558
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
560
result = bzrdir.open_branch()
561
self.assertIsInstance(result, RemoteBranch)
562
self.assertEqual(bzrdir, result.bzrdir)
563
self.assertFinished(client)
565
def test_branch_missing(self):
566
transport = MemoryTransport()
567
transport.mkdir('quack')
568
transport = transport.clone('quack')
569
client = FakeClient(transport.base)
570
client.add_error_response('nobranch')
571
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
573
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
575
[('call', 'BzrDir.open_branchV2', ('quack/',))],
578
def test__get_tree_branch(self):
579
# _get_tree_branch is a form of open_branch, but it should only ask for
580
# branch opening, not any other network requests.
583
calls.append("Called")
585
transport = MemoryTransport()
586
# no requests on the network - catches other api calls being made.
587
client = FakeClient(transport.base)
588
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
590
# patch the open_branch call to record that it was called.
591
bzrdir.open_branch = open_branch
592
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
593
self.assertEqual(["Called"], calls)
594
self.assertEqual([], client._calls)
596
def test_url_quoting_of_path(self):
597
# Relpaths on the wire should not be URL-escaped. So "~" should be
598
# transmitted as "~", not "%7E".
599
transport = RemoteTCPTransport('bzr://localhost/~hello/')
600
client = FakeClient(transport.base)
601
reference_format = self.get_repo_format()
602
network_name = reference_format.network_name()
603
branch_network_name = self.get_branch_format().network_name()
604
client.add_expected_call(
605
'BzrDir.open_branchV2', ('~hello/',),
606
'success', ('branch', branch_network_name))
607
client.add_expected_call(
608
'BzrDir.find_repositoryV3', ('~hello/',),
609
'success', ('ok', '', 'no', 'no', 'no', network_name))
610
client.add_expected_call(
611
'Branch.get_stacked_on_url', ('~hello/',),
612
'error', ('NotStacked',))
613
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
615
result = bzrdir.open_branch()
616
self.assertFinished(client)
618
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
619
reference_format = self.get_repo_format()
620
network_name = reference_format.network_name()
621
transport = MemoryTransport()
622
transport.mkdir('quack')
623
transport = transport.clone('quack')
625
rich_response = 'yes'
629
subtree_response = 'yes'
631
subtree_response = 'no'
632
client = FakeClient(transport.base)
633
client.add_success_response(
634
'ok', '', rich_response, subtree_response, external_lookup,
636
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
638
result = bzrdir.open_repository()
640
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
642
self.assertIsInstance(result, RemoteRepository)
643
self.assertEqual(bzrdir, result.bzrdir)
644
self.assertEqual(rich_root, result._format.rich_root_data)
645
self.assertEqual(subtrees, result._format.supports_tree_reference)
647
def test_open_repository_sets_format_attributes(self):
648
self.check_open_repository(True, True)
649
self.check_open_repository(False, True)
650
self.check_open_repository(True, False)
651
self.check_open_repository(False, False)
652
self.check_open_repository(False, False, 'yes')
654
def test_old_server(self):
655
"""RemoteBzrDirFormat should fail to probe if the server version is too
658
self.assertRaises(errors.NotBranchError,
659
RemoteBzrDirFormat.probe_transport, OldServerTransport())
662
class TestBzrDirCreateBranch(TestRemote):
664
def test_backwards_compat(self):
665
self.setup_smart_server_with_call_log()
666
repo = self.make_repository('.')
667
self.reset_smart_call_log()
668
self.disable_verb('BzrDir.create_branch')
669
branch = repo.bzrdir.create_branch()
670
create_branch_call_count = len([call for call in self.hpss_calls if
671
call.call.method == 'BzrDir.create_branch'])
672
self.assertEqual(1, create_branch_call_count)
674
def test_current_server(self):
675
transport = self.get_transport('.')
676
transport = transport.clone('quack')
677
self.make_repository('quack')
678
client = FakeClient(transport.base)
679
reference_bzrdir_format = bzrdir.format_registry.get('default')()
680
reference_format = reference_bzrdir_format.get_branch_format()
681
network_name = reference_format.network_name()
682
reference_repo_fmt = reference_bzrdir_format.repository_format
683
reference_repo_name = reference_repo_fmt.network_name()
684
client.add_expected_call(
685
'BzrDir.create_branch', ('quack/', network_name),
686
'success', ('ok', network_name, '', 'no', 'no', 'yes',
687
reference_repo_name))
688
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
690
branch = a_bzrdir.create_branch()
691
# We should have got a remote branch
692
self.assertIsInstance(branch, remote.RemoteBranch)
693
# its format should have the settings from the response
694
format = branch._format
695
self.assertEqual(network_name, format.network_name())
698
class TestBzrDirCreateRepository(TestRemote):
700
def test_backwards_compat(self):
701
self.setup_smart_server_with_call_log()
702
bzrdir = self.make_bzrdir('.')
703
self.reset_smart_call_log()
704
self.disable_verb('BzrDir.create_repository')
705
repo = bzrdir.create_repository()
706
create_repo_call_count = len([call for call in self.hpss_calls if
707
call.call.method == 'BzrDir.create_repository'])
708
self.assertEqual(1, create_repo_call_count)
710
def test_current_server(self):
711
transport = self.get_transport('.')
712
transport = transport.clone('quack')
713
self.make_bzrdir('quack')
714
client = FakeClient(transport.base)
715
reference_bzrdir_format = bzrdir.format_registry.get('default')()
716
reference_format = reference_bzrdir_format.repository_format
717
network_name = reference_format.network_name()
718
client.add_expected_call(
719
'BzrDir.create_repository', ('quack/',
720
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
722
'success', ('ok', 'yes', 'yes', 'yes', network_name))
723
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
725
repo = a_bzrdir.create_repository()
726
# We should have got a remote repository
727
self.assertIsInstance(repo, remote.RemoteRepository)
728
# its format should have the settings from the response
729
format = repo._format
730
self.assertTrue(format.rich_root_data)
731
self.assertTrue(format.supports_tree_reference)
732
self.assertTrue(format.supports_external_lookups)
733
self.assertEqual(network_name, format.network_name())
736
class TestBzrDirOpenRepository(TestRemote):
738
def test_backwards_compat_1_2_3(self):
739
# fallback all the way to the first version.
740
reference_format = self.get_repo_format()
741
network_name = reference_format.network_name()
742
server_url = 'bzr://example.com/'
743
self.permit_url(server_url)
744
client = FakeClient(server_url)
745
client.add_unknown_method_response('BzrDir.find_repositoryV3')
746
client.add_unknown_method_response('BzrDir.find_repositoryV2')
747
client.add_success_response('ok', '', 'no', 'no')
748
# A real repository instance will be created to determine the network
750
client.add_success_response_with_body(
751
"Bazaar-NG meta directory, format 1\n", 'ok')
752
client.add_success_response_with_body(
753
reference_format.get_format_string(), 'ok')
754
# PackRepository wants to do a stat
755
client.add_success_response('stat', '0', '65535')
756
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
758
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
760
repo = bzrdir.open_repository()
762
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
763
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
764
('call', 'BzrDir.find_repository', ('quack/',)),
765
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
766
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
767
('call', 'stat', ('/quack/.bzr/repository',)),
770
self.assertEqual(network_name, repo._format.network_name())
772
def test_backwards_compat_2(self):
773
# fallback to find_repositoryV2
774
reference_format = self.get_repo_format()
775
network_name = reference_format.network_name()
776
server_url = 'bzr://example.com/'
777
self.permit_url(server_url)
778
client = FakeClient(server_url)
779
client.add_unknown_method_response('BzrDir.find_repositoryV3')
780
client.add_success_response('ok', '', 'no', 'no', 'no')
781
# A real repository instance will be created to determine the network
783
client.add_success_response_with_body(
784
"Bazaar-NG meta directory, format 1\n", 'ok')
785
client.add_success_response_with_body(
786
reference_format.get_format_string(), 'ok')
787
# PackRepository wants to do a stat
788
client.add_success_response('stat', '0', '65535')
789
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
791
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
793
repo = bzrdir.open_repository()
795
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
796
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
797
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
798
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
799
('call', 'stat', ('/quack/.bzr/repository',)),
802
self.assertEqual(network_name, repo._format.network_name())
804
def test_current_server(self):
805
reference_format = self.get_repo_format()
806
network_name = reference_format.network_name()
807
transport = MemoryTransport()
808
transport.mkdir('quack')
809
transport = transport.clone('quack')
810
client = FakeClient(transport.base)
811
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
812
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
814
repo = bzrdir.open_repository()
816
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
818
self.assertEqual(network_name, repo._format.network_name())
821
class TestBzrDirFormatInitializeEx(TestRemote):
823
def test_success(self):
824
"""Simple test for typical successful call."""
825
fmt = bzrdir.RemoteBzrDirFormat()
826
default_format_name = BzrDirFormat.get_default_format().network_name()
827
transport = self.get_transport()
828
client = FakeClient(transport.base)
829
client.add_expected_call(
830
'BzrDirFormat.initialize_ex_1.16',
831
(default_format_name, 'path', 'False', 'False', 'False', '',
832
'', '', '', 'False'),
834
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
835
'bzrdir fmt', 'False', '', '', 'repo lock token'))
836
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
837
# it's currently hard to test that without supplying a real remote
838
# transport connected to a real server.
839
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
840
transport, False, False, False, None, None, None, None, False)
841
self.assertFinished(client)
843
def test_error(self):
844
"""Error responses are translated, e.g. 'PermissionDenied' raises the
845
corresponding error from the client.
847
fmt = bzrdir.RemoteBzrDirFormat()
848
default_format_name = BzrDirFormat.get_default_format().network_name()
849
transport = self.get_transport()
850
client = FakeClient(transport.base)
851
client.add_expected_call(
852
'BzrDirFormat.initialize_ex_1.16',
853
(default_format_name, 'path', 'False', 'False', 'False', '',
854
'', '', '', 'False'),
856
('PermissionDenied', 'path', 'extra info'))
857
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
858
# it's currently hard to test that without supplying a real remote
859
# transport connected to a real server.
860
err = self.assertRaises(errors.PermissionDenied,
861
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
862
False, False, False, None, None, None, None, False)
863
self.assertEqual('path', err.path)
864
self.assertEqual(': extra info', err.extra)
865
self.assertFinished(client)
867
def test_error_from_real_server(self):
868
"""Integration test for error translation."""
869
transport = self.make_smart_server('foo')
870
transport = transport.clone('no-such-path')
871
fmt = bzrdir.RemoteBzrDirFormat()
872
err = self.assertRaises(errors.NoSuchFile,
873
fmt.initialize_on_transport_ex, transport, create_prefix=False)
876
class OldSmartClient(object):
877
"""A fake smart client for test_old_version that just returns a version one
878
response to the 'hello' (query version) command.
881
def get_request(self):
882
input_file = StringIO('ok\x011\n')
883
output_file = StringIO()
884
client_medium = medium.SmartSimplePipesClientMedium(
885
input_file, output_file)
886
return medium.SmartClientStreamMediumRequest(client_medium)
888
def protocol_version(self):
892
class OldServerTransport(object):
893
"""A fake transport for test_old_server that reports it's smart server
894
protocol version as version one.
900
def get_smart_client(self):
901
return OldSmartClient()
904
class RemoteBzrDirTestCase(TestRemote):
906
def make_remote_bzrdir(self, transport, client):
907
"""Make a RemotebzrDir using 'client' as the _client."""
908
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
912
class RemoteBranchTestCase(RemoteBzrDirTestCase):
914
def lock_remote_branch(self, branch):
915
"""Trick a RemoteBranch into thinking it is locked."""
916
branch._lock_mode = 'w'
917
branch._lock_count = 2
918
branch._lock_token = 'branch token'
919
branch._repo_lock_token = 'repo token'
920
branch.repository._lock_mode = 'w'
921
branch.repository._lock_count = 2
922
branch.repository._lock_token = 'repo token'
924
def make_remote_branch(self, transport, client):
925
"""Make a RemoteBranch using 'client' as its _SmartClient.
927
A RemoteBzrDir and RemoteRepository will also be created to fill out
928
the RemoteBranch, albeit with stub values for some of their attributes.
930
# we do not want bzrdir to make any remote calls, so use False as its
931
# _client. If it tries to make a remote call, this will fail
933
bzrdir = self.make_remote_bzrdir(transport, False)
934
repo = RemoteRepository(bzrdir, None, _client=client)
935
branch_format = self.get_branch_format()
936
format = RemoteBranchFormat(network_name=branch_format.network_name())
937
return RemoteBranch(bzrdir, repo, _client=client, format=format)
940
class TestBranchGetParent(RemoteBranchTestCase):
942
def test_no_parent(self):
943
# in an empty branch we decode the response properly
944
transport = MemoryTransport()
945
client = FakeClient(transport.base)
946
client.add_expected_call(
947
'Branch.get_stacked_on_url', ('quack/',),
948
'error', ('NotStacked',))
949
client.add_expected_call(
950
'Branch.get_parent', ('quack/',),
952
transport.mkdir('quack')
953
transport = transport.clone('quack')
954
branch = self.make_remote_branch(transport, client)
955
result = branch.get_parent()
956
self.assertFinished(client)
957
self.assertEqual(None, result)
959
def test_parent_relative(self):
960
transport = MemoryTransport()
961
client = FakeClient(transport.base)
962
client.add_expected_call(
963
'Branch.get_stacked_on_url', ('kwaak/',),
964
'error', ('NotStacked',))
965
client.add_expected_call(
966
'Branch.get_parent', ('kwaak/',),
967
'success', ('../foo/',))
968
transport.mkdir('kwaak')
969
transport = transport.clone('kwaak')
970
branch = self.make_remote_branch(transport, client)
971
result = branch.get_parent()
972
self.assertEqual(transport.clone('../foo').base, result)
974
def test_parent_absolute(self):
975
transport = MemoryTransport()
976
client = FakeClient(transport.base)
977
client.add_expected_call(
978
'Branch.get_stacked_on_url', ('kwaak/',),
979
'error', ('NotStacked',))
980
client.add_expected_call(
981
'Branch.get_parent', ('kwaak/',),
982
'success', ('http://foo/',))
983
transport.mkdir('kwaak')
984
transport = transport.clone('kwaak')
985
branch = self.make_remote_branch(transport, client)
986
result = branch.get_parent()
987
self.assertEqual('http://foo/', result)
988
self.assertFinished(client)
991
class TestBranchSetParentLocation(RemoteBranchTestCase):
993
def test_no_parent(self):
994
# We call the verb when setting parent to None
995
transport = MemoryTransport()
996
client = FakeClient(transport.base)
997
client.add_expected_call(
998
'Branch.get_stacked_on_url', ('quack/',),
999
'error', ('NotStacked',))
1000
client.add_expected_call(
1001
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1003
transport.mkdir('quack')
1004
transport = transport.clone('quack')
1005
branch = self.make_remote_branch(transport, client)
1006
branch._lock_token = 'b'
1007
branch._repo_lock_token = 'r'
1008
branch._set_parent_location(None)
1009
self.assertFinished(client)
1011
def test_parent(self):
1012
transport = MemoryTransport()
1013
client = FakeClient(transport.base)
1014
client.add_expected_call(
1015
'Branch.get_stacked_on_url', ('kwaak/',),
1016
'error', ('NotStacked',))
1017
client.add_expected_call(
1018
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1020
transport.mkdir('kwaak')
1021
transport = transport.clone('kwaak')
1022
branch = self.make_remote_branch(transport, client)
1023
branch._lock_token = 'b'
1024
branch._repo_lock_token = 'r'
1025
branch._set_parent_location('foo')
1026
self.assertFinished(client)
1028
def test_backwards_compat(self):
1029
self.setup_smart_server_with_call_log()
1030
branch = self.make_branch('.')
1031
self.reset_smart_call_log()
1032
verb = 'Branch.set_parent_location'
1033
self.disable_verb(verb)
1034
branch.set_parent('http://foo/')
1035
self.assertLength(12, self.hpss_calls)
1038
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1040
def test_backwards_compat(self):
1041
self.setup_smart_server_with_call_log()
1042
branch = self.make_branch('.')
1043
self.reset_smart_call_log()
1044
verb = 'Branch.get_tags_bytes'
1045
self.disable_verb(verb)
1046
branch.tags.get_tag_dict()
1047
call_count = len([call for call in self.hpss_calls if
1048
call.call.method == verb])
1049
self.assertEqual(1, call_count)
1051
def test_trivial(self):
1052
transport = MemoryTransport()
1053
client = FakeClient(transport.base)
1054
client.add_expected_call(
1055
'Branch.get_stacked_on_url', ('quack/',),
1056
'error', ('NotStacked',))
1057
client.add_expected_call(
1058
'Branch.get_tags_bytes', ('quack/',),
1060
transport.mkdir('quack')
1061
transport = transport.clone('quack')
1062
branch = self.make_remote_branch(transport, client)
1063
result = branch.tags.get_tag_dict()
1064
self.assertFinished(client)
1065
self.assertEqual({}, result)
1068
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1070
def test_trivial(self):
1071
transport = MemoryTransport()
1072
client = FakeClient(transport.base)
1073
client.add_expected_call(
1074
'Branch.get_stacked_on_url', ('quack/',),
1075
'error', ('NotStacked',))
1076
client.add_expected_call(
1077
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1079
transport.mkdir('quack')
1080
transport = transport.clone('quack')
1081
branch = self.make_remote_branch(transport, client)
1082
self.lock_remote_branch(branch)
1083
branch._set_tags_bytes('tags bytes')
1084
self.assertFinished(client)
1085
self.assertEqual('tags bytes', client._calls[-1][-1])
1087
def test_backwards_compatible(self):
1088
transport = MemoryTransport()
1089
client = FakeClient(transport.base)
1090
client.add_expected_call(
1091
'Branch.get_stacked_on_url', ('quack/',),
1092
'error', ('NotStacked',))
1093
client.add_expected_call(
1094
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1095
'unknown', ('Branch.set_tags_bytes',))
1096
transport.mkdir('quack')
1097
transport = transport.clone('quack')
1098
branch = self.make_remote_branch(transport, client)
1099
self.lock_remote_branch(branch)
1100
class StubRealBranch(object):
1103
def _set_tags_bytes(self, bytes):
1104
self.calls.append(('set_tags_bytes', bytes))
1105
real_branch = StubRealBranch()
1106
branch._real_branch = real_branch
1107
branch._set_tags_bytes('tags bytes')
1108
# Call a second time, to exercise the 'remote version already inferred'
1110
branch._set_tags_bytes('tags bytes')
1111
self.assertFinished(client)
1113
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1116
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1118
def test_empty_branch(self):
1119
# in an empty branch we decode the response properly
1120
transport = MemoryTransport()
1121
client = FakeClient(transport.base)
1122
client.add_expected_call(
1123
'Branch.get_stacked_on_url', ('quack/',),
1124
'error', ('NotStacked',))
1125
client.add_expected_call(
1126
'Branch.last_revision_info', ('quack/',),
1127
'success', ('ok', '0', 'null:'))
1128
transport.mkdir('quack')
1129
transport = transport.clone('quack')
1130
branch = self.make_remote_branch(transport, client)
1131
result = branch.last_revision_info()
1132
self.assertFinished(client)
1133
self.assertEqual((0, NULL_REVISION), result)
1135
def test_non_empty_branch(self):
1136
# in a non-empty branch we also decode the response properly
1137
revid = u'\xc8'.encode('utf8')
1138
transport = MemoryTransport()
1139
client = FakeClient(transport.base)
1140
client.add_expected_call(
1141
'Branch.get_stacked_on_url', ('kwaak/',),
1142
'error', ('NotStacked',))
1143
client.add_expected_call(
1144
'Branch.last_revision_info', ('kwaak/',),
1145
'success', ('ok', '2', revid))
1146
transport.mkdir('kwaak')
1147
transport = transport.clone('kwaak')
1148
branch = self.make_remote_branch(transport, client)
1149
result = branch.last_revision_info()
1150
self.assertEqual((2, revid), result)
1153
class TestBranch_get_stacked_on_url(TestRemote):
1154
"""Test Branch._get_stacked_on_url rpc"""
1156
def test_get_stacked_on_invalid_url(self):
1157
# test that asking for a stacked on url the server can't access works.
1158
# This isn't perfect, but then as we're in the same process there
1159
# really isn't anything we can do to be 100% sure that the server
1160
# doesn't just open in - this test probably needs to be rewritten using
1161
# a spawn()ed server.
1162
stacked_branch = self.make_branch('stacked', format='1.9')
1163
memory_branch = self.make_branch('base', format='1.9')
1164
vfs_url = self.get_vfs_only_url('base')
1165
stacked_branch.set_stacked_on_url(vfs_url)
1166
transport = stacked_branch.bzrdir.root_transport
1167
client = FakeClient(transport.base)
1168
client.add_expected_call(
1169
'Branch.get_stacked_on_url', ('stacked/',),
1170
'success', ('ok', vfs_url))
1171
# XXX: Multiple calls are bad, this second call documents what is
1173
client.add_expected_call(
1174
'Branch.get_stacked_on_url', ('stacked/',),
1175
'success', ('ok', vfs_url))
1176
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1178
repo_fmt = remote.RemoteRepositoryFormat()
1179
repo_fmt._custom_format = stacked_branch.repository._format
1180
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1182
result = branch.get_stacked_on_url()
1183
self.assertEqual(vfs_url, result)
1185
def test_backwards_compatible(self):
1186
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1187
base_branch = self.make_branch('base', format='1.6')
1188
stacked_branch = self.make_branch('stacked', format='1.6')
1189
stacked_branch.set_stacked_on_url('../base')
1190
client = FakeClient(self.get_url())
1191
branch_network_name = self.get_branch_format().network_name()
1192
client.add_expected_call(
1193
'BzrDir.open_branchV2', ('stacked/',),
1194
'success', ('branch', branch_network_name))
1195
client.add_expected_call(
1196
'BzrDir.find_repositoryV3', ('stacked/',),
1197
'success', ('ok', '', 'no', 'no', 'yes',
1198
stacked_branch.repository._format.network_name()))
1199
# called twice, once from constructor and then again by us
1200
client.add_expected_call(
1201
'Branch.get_stacked_on_url', ('stacked/',),
1202
'unknown', ('Branch.get_stacked_on_url',))
1203
client.add_expected_call(
1204
'Branch.get_stacked_on_url', ('stacked/',),
1205
'unknown', ('Branch.get_stacked_on_url',))
1206
# this will also do vfs access, but that goes direct to the transport
1207
# and isn't seen by the FakeClient.
1208
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1209
remote.RemoteBzrDirFormat(), _client=client)
1210
branch = bzrdir.open_branch()
1211
result = branch.get_stacked_on_url()
1212
self.assertEqual('../base', result)
1213
self.assertFinished(client)
1214
# it's in the fallback list both for the RemoteRepository and its vfs
1216
self.assertEqual(1, len(branch.repository._fallback_repositories))
1218
len(branch.repository._real_repository._fallback_repositories))
1220
def test_get_stacked_on_real_branch(self):
1221
base_branch = self.make_branch('base', format='1.6')
1222
stacked_branch = self.make_branch('stacked', format='1.6')
1223
stacked_branch.set_stacked_on_url('../base')
1224
reference_format = self.get_repo_format()
1225
network_name = reference_format.network_name()
1226
client = FakeClient(self.get_url())
1227
branch_network_name = self.get_branch_format().network_name()
1228
client.add_expected_call(
1229
'BzrDir.open_branchV2', ('stacked/',),
1230
'success', ('branch', branch_network_name))
1231
client.add_expected_call(
1232
'BzrDir.find_repositoryV3', ('stacked/',),
1233
'success', ('ok', '', 'no', 'no', 'yes', network_name))
1234
# called twice, once from constructor and then again by us
1235
client.add_expected_call(
1236
'Branch.get_stacked_on_url', ('stacked/',),
1237
'success', ('ok', '../base'))
1238
client.add_expected_call(
1239
'Branch.get_stacked_on_url', ('stacked/',),
1240
'success', ('ok', '../base'))
1241
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1242
remote.RemoteBzrDirFormat(), _client=client)
1243
branch = bzrdir.open_branch()
1244
result = branch.get_stacked_on_url()
1245
self.assertEqual('../base', result)
1246
self.assertFinished(client)
1247
# it's in the fallback list both for the RemoteRepository.
1248
self.assertEqual(1, len(branch.repository._fallback_repositories))
1249
# And we haven't had to construct a real repository.
1250
self.assertEqual(None, branch.repository._real_repository)
1253
class TestBranchSetLastRevision(RemoteBranchTestCase):
1255
def test_set_empty(self):
1256
# set_revision_history([]) is translated to calling
1257
# Branch.set_last_revision(path, '') on the wire.
1258
transport = MemoryTransport()
1259
transport.mkdir('branch')
1260
transport = transport.clone('branch')
1262
client = FakeClient(transport.base)
1263
client.add_expected_call(
1264
'Branch.get_stacked_on_url', ('branch/',),
1265
'error', ('NotStacked',))
1266
client.add_expected_call(
1267
'Branch.lock_write', ('branch/', '', ''),
1268
'success', ('ok', 'branch token', 'repo token'))
1269
client.add_expected_call(
1270
'Branch.last_revision_info',
1272
'success', ('ok', '0', 'null:'))
1273
client.add_expected_call(
1274
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1276
client.add_expected_call(
1277
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1279
branch = self.make_remote_branch(transport, client)
1280
# This is a hack to work around the problem that RemoteBranch currently
1281
# unnecessarily invokes _ensure_real upon a call to lock_write.
1282
branch._ensure_real = lambda: None
1284
result = branch.set_revision_history([])
1286
self.assertEqual(None, result)
1287
self.assertFinished(client)
1289
def test_set_nonempty(self):
1290
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1291
# Branch.set_last_revision(path, rev-idN) on the wire.
1292
transport = MemoryTransport()
1293
transport.mkdir('branch')
1294
transport = transport.clone('branch')
1296
client = FakeClient(transport.base)
1297
client.add_expected_call(
1298
'Branch.get_stacked_on_url', ('branch/',),
1299
'error', ('NotStacked',))
1300
client.add_expected_call(
1301
'Branch.lock_write', ('branch/', '', ''),
1302
'success', ('ok', 'branch token', 'repo token'))
1303
client.add_expected_call(
1304
'Branch.last_revision_info',
1306
'success', ('ok', '0', 'null:'))
1308
encoded_body = bz2.compress('\n'.join(lines))
1309
client.add_success_response_with_body(encoded_body, 'ok')
1310
client.add_expected_call(
1311
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1313
client.add_expected_call(
1314
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1316
branch = self.make_remote_branch(transport, client)
1317
# This is a hack to work around the problem that RemoteBranch currently
1318
# unnecessarily invokes _ensure_real upon a call to lock_write.
1319
branch._ensure_real = lambda: None
1320
# Lock the branch, reset the record of remote calls.
1322
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1324
self.assertEqual(None, result)
1325
self.assertFinished(client)
1327
def test_no_such_revision(self):
1328
transport = MemoryTransport()
1329
transport.mkdir('branch')
1330
transport = transport.clone('branch')
1331
# A response of 'NoSuchRevision' is translated into an exception.
1332
client = FakeClient(transport.base)
1333
client.add_expected_call(
1334
'Branch.get_stacked_on_url', ('branch/',),
1335
'error', ('NotStacked',))
1336
client.add_expected_call(
1337
'Branch.lock_write', ('branch/', '', ''),
1338
'success', ('ok', 'branch token', 'repo token'))
1339
client.add_expected_call(
1340
'Branch.last_revision_info',
1342
'success', ('ok', '0', 'null:'))
1343
# get_graph calls to construct the revision history, for the set_rh
1346
encoded_body = bz2.compress('\n'.join(lines))
1347
client.add_success_response_with_body(encoded_body, 'ok')
1348
client.add_expected_call(
1349
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1350
'error', ('NoSuchRevision', 'rev-id'))
1351
client.add_expected_call(
1352
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1355
branch = self.make_remote_branch(transport, client)
1358
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1360
self.assertFinished(client)
1362
def test_tip_change_rejected(self):
1363
"""TipChangeRejected responses cause a TipChangeRejected exception to
1366
transport = MemoryTransport()
1367
transport.mkdir('branch')
1368
transport = transport.clone('branch')
1369
client = FakeClient(transport.base)
1370
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1371
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1372
client.add_expected_call(
1373
'Branch.get_stacked_on_url', ('branch/',),
1374
'error', ('NotStacked',))
1375
client.add_expected_call(
1376
'Branch.lock_write', ('branch/', '', ''),
1377
'success', ('ok', 'branch token', 'repo token'))
1378
client.add_expected_call(
1379
'Branch.last_revision_info',
1381
'success', ('ok', '0', 'null:'))
1383
encoded_body = bz2.compress('\n'.join(lines))
1384
client.add_success_response_with_body(encoded_body, 'ok')
1385
client.add_expected_call(
1386
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1387
'error', ('TipChangeRejected', rejection_msg_utf8))
1388
client.add_expected_call(
1389
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1391
branch = self.make_remote_branch(transport, client)
1392
branch._ensure_real = lambda: None
1394
# The 'TipChangeRejected' error response triggered by calling
1395
# set_revision_history causes a TipChangeRejected exception.
1396
err = self.assertRaises(
1397
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1398
# The UTF-8 message from the response has been decoded into a unicode
1400
self.assertIsInstance(err.msg, unicode)
1401
self.assertEqual(rejection_msg_unicode, err.msg)
1403
self.assertFinished(client)
1406
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1408
def test_set_last_revision_info(self):
1409
# set_last_revision_info(num, 'rev-id') is translated to calling
1410
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1411
transport = MemoryTransport()
1412
transport.mkdir('branch')
1413
transport = transport.clone('branch')
1414
client = FakeClient(transport.base)
1415
# get_stacked_on_url
1416
client.add_error_response('NotStacked')
1418
client.add_success_response('ok', 'branch token', 'repo token')
1419
# query the current revision
1420
client.add_success_response('ok', '0', 'null:')
1422
client.add_success_response('ok')
1424
client.add_success_response('ok')
1426
branch = self.make_remote_branch(transport, client)
1427
# Lock the branch, reset the record of remote calls.
1430
result = branch.set_last_revision_info(1234, 'a-revision-id')
1432
[('call', 'Branch.last_revision_info', ('branch/',)),
1433
('call', 'Branch.set_last_revision_info',
1434
('branch/', 'branch token', 'repo token',
1435
'1234', 'a-revision-id'))],
1437
self.assertEqual(None, result)
1439
def test_no_such_revision(self):
1440
# A response of 'NoSuchRevision' is translated into an exception.
1441
transport = MemoryTransport()
1442
transport.mkdir('branch')
1443
transport = transport.clone('branch')
1444
client = FakeClient(transport.base)
1445
# get_stacked_on_url
1446
client.add_error_response('NotStacked')
1448
client.add_success_response('ok', 'branch token', 'repo token')
1450
client.add_error_response('NoSuchRevision', 'revid')
1452
client.add_success_response('ok')
1454
branch = self.make_remote_branch(transport, client)
1455
# Lock the branch, reset the record of remote calls.
1460
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1463
def test_backwards_compatibility(self):
1464
"""If the server does not support the Branch.set_last_revision_info
1465
verb (which is new in 1.4), then the client falls back to VFS methods.
1467
# This test is a little messy. Unlike most tests in this file, it
1468
# doesn't purely test what a Remote* object sends over the wire, and
1469
# how it reacts to responses from the wire. It instead relies partly
1470
# on asserting that the RemoteBranch will call
1471
# self._real_branch.set_last_revision_info(...).
1473
# First, set up our RemoteBranch with a FakeClient that raises
1474
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1475
transport = MemoryTransport()
1476
transport.mkdir('branch')
1477
transport = transport.clone('branch')
1478
client = FakeClient(transport.base)
1479
client.add_expected_call(
1480
'Branch.get_stacked_on_url', ('branch/',),
1481
'error', ('NotStacked',))
1482
client.add_expected_call(
1483
'Branch.last_revision_info',
1485
'success', ('ok', '0', 'null:'))
1486
client.add_expected_call(
1487
'Branch.set_last_revision_info',
1488
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1489
'unknown', 'Branch.set_last_revision_info')
1491
branch = self.make_remote_branch(transport, client)
1492
class StubRealBranch(object):
1495
def set_last_revision_info(self, revno, revision_id):
1497
('set_last_revision_info', revno, revision_id))
1498
def _clear_cached_state(self):
1500
real_branch = StubRealBranch()
1501
branch._real_branch = real_branch
1502
self.lock_remote_branch(branch)
1504
# Call set_last_revision_info, and verify it behaved as expected.
1505
result = branch.set_last_revision_info(1234, 'a-revision-id')
1507
[('set_last_revision_info', 1234, 'a-revision-id')],
1509
self.assertFinished(client)
1511
def test_unexpected_error(self):
1512
# If the server sends an error the client doesn't understand, it gets
1513
# turned into an UnknownErrorFromSmartServer, which is presented as a
1514
# non-internal error to the user.
1515
transport = MemoryTransport()
1516
transport.mkdir('branch')
1517
transport = transport.clone('branch')
1518
client = FakeClient(transport.base)
1519
# get_stacked_on_url
1520
client.add_error_response('NotStacked')
1522
client.add_success_response('ok', 'branch token', 'repo token')
1524
client.add_error_response('UnexpectedError')
1526
client.add_success_response('ok')
1528
branch = self.make_remote_branch(transport, client)
1529
# Lock the branch, reset the record of remote calls.
1533
err = self.assertRaises(
1534
errors.UnknownErrorFromSmartServer,
1535
branch.set_last_revision_info, 123, 'revid')
1536
self.assertEqual(('UnexpectedError',), err.error_tuple)
1539
def test_tip_change_rejected(self):
1540
"""TipChangeRejected responses cause a TipChangeRejected exception to
1543
transport = MemoryTransport()
1544
transport.mkdir('branch')
1545
transport = transport.clone('branch')
1546
client = FakeClient(transport.base)
1547
# get_stacked_on_url
1548
client.add_error_response('NotStacked')
1550
client.add_success_response('ok', 'branch token', 'repo token')
1552
client.add_error_response('TipChangeRejected', 'rejection message')
1554
client.add_success_response('ok')
1556
branch = self.make_remote_branch(transport, client)
1557
# Lock the branch, reset the record of remote calls.
1559
self.addCleanup(branch.unlock)
1562
# The 'TipChangeRejected' error response triggered by calling
1563
# set_last_revision_info causes a TipChangeRejected exception.
1564
err = self.assertRaises(
1565
errors.TipChangeRejected,
1566
branch.set_last_revision_info, 123, 'revid')
1567
self.assertEqual('rejection message', err.msg)
1570
class TestBranchGetSetConfig(RemoteBranchTestCase):
1572
def test_get_branch_conf(self):
1573
# in an empty branch we decode the response properly
1574
client = FakeClient()
1575
client.add_expected_call(
1576
'Branch.get_stacked_on_url', ('memory:///',),
1577
'error', ('NotStacked',),)
1578
client.add_success_response_with_body('# config file body', 'ok')
1579
transport = MemoryTransport()
1580
branch = self.make_remote_branch(transport, client)
1581
config = branch.get_config()
1582
config.has_explicit_nickname()
1584
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1585
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1588
def test_get_multi_line_branch_conf(self):
1589
# Make sure that multiple-line branch.conf files are supported
1591
# https://bugs.edge.launchpad.net/bzr/+bug/354075
1592
client = FakeClient()
1593
client.add_expected_call(
1594
'Branch.get_stacked_on_url', ('memory:///',),
1595
'error', ('NotStacked',),)
1596
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1597
transport = MemoryTransport()
1598
branch = self.make_remote_branch(transport, client)
1599
config = branch.get_config()
1600
self.assertEqual(u'2', config.get_user_option('b'))
1602
def test_set_option(self):
1603
client = FakeClient()
1604
client.add_expected_call(
1605
'Branch.get_stacked_on_url', ('memory:///',),
1606
'error', ('NotStacked',),)
1607
client.add_expected_call(
1608
'Branch.lock_write', ('memory:///', '', ''),
1609
'success', ('ok', 'branch token', 'repo token'))
1610
client.add_expected_call(
1611
'Branch.set_config_option', ('memory:///', 'branch token',
1612
'repo token', 'foo', 'bar', ''),
1614
client.add_expected_call(
1615
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1617
transport = MemoryTransport()
1618
branch = self.make_remote_branch(transport, client)
1620
config = branch._get_config()
1621
config.set_option('foo', 'bar')
1623
self.assertFinished(client)
1625
def test_backwards_compat_set_option(self):
1626
self.setup_smart_server_with_call_log()
1627
branch = self.make_branch('.')
1628
verb = 'Branch.set_config_option'
1629
self.disable_verb(verb)
1631
self.addCleanup(branch.unlock)
1632
self.reset_smart_call_log()
1633
branch._get_config().set_option('value', 'name')
1634
self.assertLength(10, self.hpss_calls)
1635
self.assertEqual('value', branch._get_config().get_option('name'))
1638
class TestBranchLockWrite(RemoteBranchTestCase):
1640
def test_lock_write_unlockable(self):
1641
transport = MemoryTransport()
1642
client = FakeClient(transport.base)
1643
client.add_expected_call(
1644
'Branch.get_stacked_on_url', ('quack/',),
1645
'error', ('NotStacked',),)
1646
client.add_expected_call(
1647
'Branch.lock_write', ('quack/', '', ''),
1648
'error', ('UnlockableTransport',))
1649
transport.mkdir('quack')
1650
transport = transport.clone('quack')
1651
branch = self.make_remote_branch(transport, client)
1652
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1653
self.assertFinished(client)
1656
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1658
def test__get_config(self):
1659
client = FakeClient()
1660
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1661
transport = MemoryTransport()
1662
bzrdir = self.make_remote_bzrdir(transport, client)
1663
config = bzrdir.get_config()
1664
self.assertEqual('/', config.get_default_stack_on())
1666
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1669
def test_set_option_uses_vfs(self):
1670
self.setup_smart_server_with_call_log()
1671
bzrdir = self.make_bzrdir('.')
1672
self.reset_smart_call_log()
1673
config = bzrdir.get_config()
1674
config.set_default_stack_on('/')
1675
self.assertLength(3, self.hpss_calls)
1677
def test_backwards_compat_get_option(self):
1678
self.setup_smart_server_with_call_log()
1679
bzrdir = self.make_bzrdir('.')
1680
verb = 'BzrDir.get_config_file'
1681
self.disable_verb(verb)
1682
self.reset_smart_call_log()
1683
self.assertEqual(None,
1684
bzrdir._get_config().get_option('default_stack_on'))
1685
self.assertLength(3, self.hpss_calls)
1688
class TestTransportIsReadonly(tests.TestCase):
1690
def test_true(self):
1691
client = FakeClient()
1692
client.add_success_response('yes')
1693
transport = RemoteTransport('bzr://example.com/', medium=False,
1695
self.assertEqual(True, transport.is_readonly())
1697
[('call', 'Transport.is_readonly', ())],
1700
def test_false(self):
1701
client = FakeClient()
1702
client.add_success_response('no')
1703
transport = RemoteTransport('bzr://example.com/', medium=False,
1705
self.assertEqual(False, transport.is_readonly())
1707
[('call', 'Transport.is_readonly', ())],
1710
def test_error_from_old_server(self):
1711
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1713
Clients should treat it as a "no" response, because is_readonly is only
1714
advisory anyway (a transport could be read-write, but then the
1715
underlying filesystem could be readonly anyway).
1717
client = FakeClient()
1718
client.add_unknown_method_response('Transport.is_readonly')
1719
transport = RemoteTransport('bzr://example.com/', medium=False,
1721
self.assertEqual(False, transport.is_readonly())
1723
[('call', 'Transport.is_readonly', ())],
1727
class TestTransportMkdir(tests.TestCase):
1729
def test_permissiondenied(self):
1730
client = FakeClient()
1731
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1732
transport = RemoteTransport('bzr://example.com/', medium=False,
1734
exc = self.assertRaises(
1735
errors.PermissionDenied, transport.mkdir, 'client path')
1736
expected_error = errors.PermissionDenied('/client path', 'extra')
1737
self.assertEqual(expected_error, exc)
1740
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1742
def test_defaults_to_none(self):
1743
t = RemoteSSHTransport('bzr+ssh://example.com')
1744
self.assertIs(None, t._get_credentials()[0])
1746
def test_uses_authentication_config(self):
1747
conf = config.AuthenticationConfig()
1748
conf._get_config().update(
1749
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1752
t = RemoteSSHTransport('bzr+ssh://example.com')
1753
self.assertEqual('bar', t._get_credentials()[0])
1756
class TestRemoteRepository(TestRemote):
1757
"""Base for testing RemoteRepository protocol usage.
1759
These tests contain frozen requests and responses. We want any changes to
1760
what is sent or expected to be require a thoughtful update to these tests
1761
because they might break compatibility with different-versioned servers.
1764
def setup_fake_client_and_repository(self, transport_path):
1765
"""Create the fake client and repository for testing with.
1767
There's no real server here; we just have canned responses sent
1770
:param transport_path: Path below the root of the MemoryTransport
1771
where the repository will be created.
1773
transport = MemoryTransport()
1774
transport.mkdir(transport_path)
1775
client = FakeClient(transport.base)
1776
transport = transport.clone(transport_path)
1777
# we do not want bzrdir to make any remote calls
1778
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1780
repo = RemoteRepository(bzrdir, None, _client=client)
1784
def remoted_description(format):
1785
return 'Remote: ' + format.get_format_description()
1788
class TestBranchFormat(tests.TestCase):
1790
def test_get_format_description(self):
1791
remote_format = RemoteBranchFormat()
1792
real_format = branch.BranchFormat.get_default_format()
1793
remote_format._network_name = real_format.network_name()
1794
self.assertEqual(remoted_description(real_format),
1795
remote_format.get_format_description())
1798
class TestRepositoryFormat(TestRemoteRepository):
1800
def test_fast_delta(self):
1801
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1802
true_format = RemoteRepositoryFormat()
1803
true_format._network_name = true_name
1804
self.assertEqual(True, true_format.fast_deltas)
1805
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1806
false_format = RemoteRepositoryFormat()
1807
false_format._network_name = false_name
1808
self.assertEqual(False, false_format.fast_deltas)
1810
def test_get_format_description(self):
1811
remote_repo_format = RemoteRepositoryFormat()
1812
real_format = repository.RepositoryFormat.get_default_format()
1813
remote_repo_format._network_name = real_format.network_name()
1814
self.assertEqual(remoted_description(real_format),
1815
remote_repo_format.get_format_description())
1818
class TestRepositoryGatherStats(TestRemoteRepository):
1820
def test_revid_none(self):
1821
# ('ok',), body with revisions and size
1822
transport_path = 'quack'
1823
repo, client = self.setup_fake_client_and_repository(transport_path)
1824
client.add_success_response_with_body(
1825
'revisions: 2\nsize: 18\n', 'ok')
1826
result = repo.gather_stats(None)
1828
[('call_expecting_body', 'Repository.gather_stats',
1829
('quack/','','no'))],
1831
self.assertEqual({'revisions': 2, 'size': 18}, result)
1833
def test_revid_no_committers(self):
1834
# ('ok',), body without committers
1835
body = ('firstrev: 123456.300 3600\n'
1836
'latestrev: 654231.400 0\n'
1839
transport_path = 'quick'
1840
revid = u'\xc8'.encode('utf8')
1841
repo, client = self.setup_fake_client_and_repository(transport_path)
1842
client.add_success_response_with_body(body, 'ok')
1843
result = repo.gather_stats(revid)
1845
[('call_expecting_body', 'Repository.gather_stats',
1846
('quick/', revid, 'no'))],
1848
self.assertEqual({'revisions': 2, 'size': 18,
1849
'firstrev': (123456.300, 3600),
1850
'latestrev': (654231.400, 0),},
1853
def test_revid_with_committers(self):
1854
# ('ok',), body with committers
1855
body = ('committers: 128\n'
1856
'firstrev: 123456.300 3600\n'
1857
'latestrev: 654231.400 0\n'
1860
transport_path = 'buick'
1861
revid = u'\xc8'.encode('utf8')
1862
repo, client = self.setup_fake_client_and_repository(transport_path)
1863
client.add_success_response_with_body(body, 'ok')
1864
result = repo.gather_stats(revid, True)
1866
[('call_expecting_body', 'Repository.gather_stats',
1867
('buick/', revid, 'yes'))],
1869
self.assertEqual({'revisions': 2, 'size': 18,
1871
'firstrev': (123456.300, 3600),
1872
'latestrev': (654231.400, 0),},
1876
class TestRepositoryGetGraph(TestRemoteRepository):
1878
def test_get_graph(self):
1879
# get_graph returns a graph with a custom parents provider.
1880
transport_path = 'quack'
1881
repo, client = self.setup_fake_client_and_repository(transport_path)
1882
graph = repo.get_graph()
1883
self.assertNotEqual(graph._parents_provider, repo)
1886
class TestRepositoryGetParentMap(TestRemoteRepository):
1888
def test_get_parent_map_caching(self):
1889
# get_parent_map returns from cache until unlock()
1890
# setup a reponse with two revisions
1891
r1 = u'\u0e33'.encode('utf8')
1892
r2 = u'\u0dab'.encode('utf8')
1893
lines = [' '.join([r2, r1]), r1]
1894
encoded_body = bz2.compress('\n'.join(lines))
1896
transport_path = 'quack'
1897
repo, client = self.setup_fake_client_and_repository(transport_path)
1898
client.add_success_response_with_body(encoded_body, 'ok')
1899
client.add_success_response_with_body(encoded_body, 'ok')
1901
graph = repo.get_graph()
1902
parents = graph.get_parent_map([r2])
1903
self.assertEqual({r2: (r1,)}, parents)
1904
# locking and unlocking deeper should not reset
1907
parents = graph.get_parent_map([r1])
1908
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1910
[('call_with_body_bytes_expecting_body',
1911
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1915
# now we call again, and it should use the second response.
1917
graph = repo.get_graph()
1918
parents = graph.get_parent_map([r1])
1919
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1921
[('call_with_body_bytes_expecting_body',
1922
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1924
('call_with_body_bytes_expecting_body',
1925
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1931
def test_get_parent_map_reconnects_if_unknown_method(self):
1932
transport_path = 'quack'
1933
rev_id = 'revision-id'
1934
repo, client = self.setup_fake_client_and_repository(transport_path)
1935
client.add_unknown_method_response('Repository.get_parent_map')
1936
client.add_success_response_with_body(rev_id, 'ok')
1937
self.assertFalse(client._medium._is_remote_before((1, 2)))
1938
parents = repo.get_parent_map([rev_id])
1940
[('call_with_body_bytes_expecting_body',
1941
'Repository.get_parent_map', ('quack/', 'include-missing:',
1943
('disconnect medium',),
1944
('call_expecting_body', 'Repository.get_revision_graph',
1947
# The medium is now marked as being connected to an older server
1948
self.assertTrue(client._medium._is_remote_before((1, 2)))
1949
self.assertEqual({rev_id: ('null:',)}, parents)
1951
def test_get_parent_map_fallback_parentless_node(self):
1952
"""get_parent_map falls back to get_revision_graph on old servers. The
1953
results from get_revision_graph are tweaked to match the get_parent_map
1956
Specifically, a {key: ()} result from get_revision_graph means "no
1957
parents" for that key, which in get_parent_map results should be
1958
represented as {key: ('null:',)}.
1960
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1962
rev_id = 'revision-id'
1963
transport_path = 'quack'
1964
repo, client = self.setup_fake_client_and_repository(transport_path)
1965
client.add_success_response_with_body(rev_id, 'ok')
1966
client._medium._remember_remote_is_before((1, 2))
1967
parents = repo.get_parent_map([rev_id])
1969
[('call_expecting_body', 'Repository.get_revision_graph',
1972
self.assertEqual({rev_id: ('null:',)}, parents)
1974
def test_get_parent_map_unexpected_response(self):
1975
repo, client = self.setup_fake_client_and_repository('path')
1976
client.add_success_response('something unexpected!')
1978
errors.UnexpectedSmartServerResponse,
1979
repo.get_parent_map, ['a-revision-id'])
1981
def test_get_parent_map_negative_caches_missing_keys(self):
1982
self.setup_smart_server_with_call_log()
1983
repo = self.make_repository('foo')
1984
self.assertIsInstance(repo, RemoteRepository)
1986
self.addCleanup(repo.unlock)
1987
self.reset_smart_call_log()
1988
graph = repo.get_graph()
1989
self.assertEqual({},
1990
graph.get_parent_map(['some-missing', 'other-missing']))
1991
self.assertLength(1, self.hpss_calls)
1992
# No call if we repeat this
1993
self.reset_smart_call_log()
1994
graph = repo.get_graph()
1995
self.assertEqual({},
1996
graph.get_parent_map(['some-missing', 'other-missing']))
1997
self.assertLength(0, self.hpss_calls)
1998
# Asking for more unknown keys makes a request.
1999
self.reset_smart_call_log()
2000
graph = repo.get_graph()
2001
self.assertEqual({},
2002
graph.get_parent_map(['some-missing', 'other-missing',
2004
self.assertLength(1, self.hpss_calls)
2006
def disableExtraResults(self):
2007
old_flag = SmartServerRepositoryGetParentMap.no_extra_results
2008
SmartServerRepositoryGetParentMap.no_extra_results = True
2010
SmartServerRepositoryGetParentMap.no_extra_results = old_flag
2011
self.addCleanup(reset_values)
2013
def test_null_cached_missing_and_stop_key(self):
2014
self.setup_smart_server_with_call_log()
2015
# Make a branch with a single revision.
2016
builder = self.make_branch_builder('foo')
2017
builder.start_series()
2018
builder.build_snapshot('first', None, [
2019
('add', ('', 'root-id', 'directory', ''))])
2020
builder.finish_series()
2021
branch = builder.get_branch()
2022
repo = branch.repository
2023
self.assertIsInstance(repo, RemoteRepository)
2024
# Stop the server from sending extra results.
2025
self.disableExtraResults()
2027
self.addCleanup(repo.unlock)
2028
self.reset_smart_call_log()
2029
graph = repo.get_graph()
2030
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2031
# 'first' it will be a candidate for the stop_keys of subsequent
2032
# requests, and because 'null:' was queried but not returned it will be
2033
# cached as missing.
2034
self.assertEqual({'first': ('null:',)},
2035
graph.get_parent_map(['first', 'null:']))
2036
# Now query for another key. This request will pass along a recipe of
2037
# start and stop keys describing the already cached results, and this
2038
# recipe's revision count must be correct (or else it will trigger an
2039
# error from the server).
2040
self.assertEqual({}, graph.get_parent_map(['another-key']))
2041
# This assertion guards against disableExtraResults silently failing to
2042
# work, thus invalidating the test.
2043
self.assertLength(2, self.hpss_calls)
2045
def test_get_parent_map_gets_ghosts_from_result(self):
2046
# asking for a revision should negatively cache close ghosts in its
2048
self.setup_smart_server_with_call_log()
2049
tree = self.make_branch_and_memory_tree('foo')
2052
builder = treebuilder.TreeBuilder()
2053
builder.start_tree(tree)
2055
builder.finish_tree()
2056
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2057
rev_id = tree.commit('')
2061
self.addCleanup(tree.unlock)
2062
repo = tree.branch.repository
2063
self.assertIsInstance(repo, RemoteRepository)
2065
repo.get_parent_map([rev_id])
2066
self.reset_smart_call_log()
2067
# Now asking for rev_id's ghost parent should not make calls
2068
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2069
self.assertLength(0, self.hpss_calls)
2072
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2074
def test_allows_new_revisions(self):
2075
"""get_parent_map's results can be updated by commit."""
2076
smart_server = server.SmartTCPServer_for_testing()
2077
self.start_server(smart_server)
2078
self.make_branch('branch')
2079
branch = Branch.open(smart_server.get_url() + '/branch')
2080
tree = branch.create_checkout('tree', lightweight=True)
2082
self.addCleanup(tree.unlock)
2083
graph = tree.branch.repository.get_graph()
2084
# This provides an opportunity for the missing rev-id to be cached.
2085
self.assertEqual({}, graph.get_parent_map(['rev1']))
2086
tree.commit('message', rev_id='rev1')
2087
graph = tree.branch.repository.get_graph()
2088
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2091
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2093
def test_null_revision(self):
2094
# a null revision has the predictable result {}, we should have no wire
2095
# traffic when calling it with this argument
2096
transport_path = 'empty'
2097
repo, client = self.setup_fake_client_and_repository(transport_path)
2098
client.add_success_response('notused')
2099
# actual RemoteRepository.get_revision_graph is gone, but there's an
2100
# equivalent private method for testing
2101
result = repo._get_revision_graph(NULL_REVISION)
2102
self.assertEqual([], client._calls)
2103
self.assertEqual({}, result)
2105
def test_none_revision(self):
2106
# with none we want the entire graph
2107
r1 = u'\u0e33'.encode('utf8')
2108
r2 = u'\u0dab'.encode('utf8')
2109
lines = [' '.join([r2, r1]), r1]
2110
encoded_body = '\n'.join(lines)
2112
transport_path = 'sinhala'
2113
repo, client = self.setup_fake_client_and_repository(transport_path)
2114
client.add_success_response_with_body(encoded_body, 'ok')
2115
# actual RemoteRepository.get_revision_graph is gone, but there's an
2116
# equivalent private method for testing
2117
result = repo._get_revision_graph(None)
2119
[('call_expecting_body', 'Repository.get_revision_graph',
2122
self.assertEqual({r1: (), r2: (r1, )}, result)
2124
def test_specific_revision(self):
2125
# with a specific revision we want the graph for that
2126
# with none we want the entire graph
2127
r11 = u'\u0e33'.encode('utf8')
2128
r12 = u'\xc9'.encode('utf8')
2129
r2 = u'\u0dab'.encode('utf8')
2130
lines = [' '.join([r2, r11, r12]), r11, r12]
2131
encoded_body = '\n'.join(lines)
2133
transport_path = 'sinhala'
2134
repo, client = self.setup_fake_client_and_repository(transport_path)
2135
client.add_success_response_with_body(encoded_body, 'ok')
2136
result = repo._get_revision_graph(r2)
2138
[('call_expecting_body', 'Repository.get_revision_graph',
2141
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2143
def test_no_such_revision(self):
2145
transport_path = 'sinhala'
2146
repo, client = self.setup_fake_client_and_repository(transport_path)
2147
client.add_error_response('nosuchrevision', revid)
2148
# also check that the right revision is reported in the error
2149
self.assertRaises(errors.NoSuchRevision,
2150
repo._get_revision_graph, revid)
2152
[('call_expecting_body', 'Repository.get_revision_graph',
2153
('sinhala/', revid))],
2156
def test_unexpected_error(self):
2158
transport_path = 'sinhala'
2159
repo, client = self.setup_fake_client_and_repository(transport_path)
2160
client.add_error_response('AnUnexpectedError')
2161
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2162
repo._get_revision_graph, revid)
2163
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2166
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2169
repo, client = self.setup_fake_client_and_repository('quack')
2170
client.add_expected_call(
2171
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2172
'success', ('ok', 'rev-five'))
2173
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2174
self.assertEqual((True, 'rev-five'), result)
2175
self.assertFinished(client)
2177
def test_history_incomplete(self):
2178
repo, client = self.setup_fake_client_and_repository('quack')
2179
client.add_expected_call(
2180
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2181
'success', ('history-incomplete', 10, 'rev-ten'))
2182
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2183
self.assertEqual((False, (10, 'rev-ten')), result)
2184
self.assertFinished(client)
2186
def test_history_incomplete_with_fallback(self):
2187
"""A 'history-incomplete' response causes the fallback repository to be
2188
queried too, if one is set.
2190
# Make a repo with a fallback repo, both using a FakeClient.
2191
format = remote.response_tuple_to_repo_format(
2192
('yes', 'no', 'yes', 'fake-network-name'))
2193
repo, client = self.setup_fake_client_and_repository('quack')
2194
repo._format = format
2195
fallback_repo, ignored = self.setup_fake_client_and_repository(
2197
fallback_repo._client = client
2198
repo.add_fallback_repository(fallback_repo)
2199
# First the client should ask the primary repo
2200
client.add_expected_call(
2201
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2202
'success', ('history-incomplete', 2, 'rev-two'))
2203
# Then it should ask the fallback, using revno/revid from the
2204
# history-incomplete response as the known revno/revid.
2205
client.add_expected_call(
2206
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2207
'success', ('ok', 'rev-one'))
2208
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2209
self.assertEqual((True, 'rev-one'), result)
2210
self.assertFinished(client)
2212
def test_nosuchrevision(self):
2213
# 'nosuchrevision' is returned when the known-revid is not found in the
2214
# remote repo. The client translates that response to NoSuchRevision.
2215
repo, client = self.setup_fake_client_and_repository('quack')
2216
client.add_expected_call(
2217
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2218
'error', ('nosuchrevision', 'rev-foo'))
2220
errors.NoSuchRevision,
2221
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2222
self.assertFinished(client)
2224
def test_branch_fallback_locking(self):
2225
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2226
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2227
will be invoked, which will fail if the repo is unlocked.
2229
self.setup_smart_server_with_call_log()
2230
tree = self.make_branch_and_memory_tree('.')
2232
rev1 = tree.commit('First')
2233
rev2 = tree.commit('Second')
2235
branch = tree.branch
2236
self.assertFalse(branch.is_locked())
2237
self.reset_smart_call_log()
2238
verb = 'Repository.get_rev_id_for_revno'
2239
self.disable_verb(verb)
2240
self.assertEqual(rev1, branch.get_rev_id(1))
2241
self.assertLength(1, [call for call in self.hpss_calls if
2242
call.call.method == verb])
2245
class TestRepositoryIsShared(TestRemoteRepository):
2247
def test_is_shared(self):
2248
# ('yes', ) for Repository.is_shared -> 'True'.
2249
transport_path = 'quack'
2250
repo, client = self.setup_fake_client_and_repository(transport_path)
2251
client.add_success_response('yes')
2252
result = repo.is_shared()
2254
[('call', 'Repository.is_shared', ('quack/',))],
2256
self.assertEqual(True, result)
2258
def test_is_not_shared(self):
2259
# ('no', ) for Repository.is_shared -> 'False'.
2260
transport_path = 'qwack'
2261
repo, client = self.setup_fake_client_and_repository(transport_path)
2262
client.add_success_response('no')
2263
result = repo.is_shared()
2265
[('call', 'Repository.is_shared', ('qwack/',))],
2267
self.assertEqual(False, result)
2270
class TestRepositoryLockWrite(TestRemoteRepository):
2272
def test_lock_write(self):
2273
transport_path = 'quack'
2274
repo, client = self.setup_fake_client_and_repository(transport_path)
2275
client.add_success_response('ok', 'a token')
2276
result = repo.lock_write()
2278
[('call', 'Repository.lock_write', ('quack/', ''))],
2280
self.assertEqual('a token', result)
2282
def test_lock_write_already_locked(self):
2283
transport_path = 'quack'
2284
repo, client = self.setup_fake_client_and_repository(transport_path)
2285
client.add_error_response('LockContention')
2286
self.assertRaises(errors.LockContention, repo.lock_write)
2288
[('call', 'Repository.lock_write', ('quack/', ''))],
2291
def test_lock_write_unlockable(self):
2292
transport_path = 'quack'
2293
repo, client = self.setup_fake_client_and_repository(transport_path)
2294
client.add_error_response('UnlockableTransport')
2295
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2297
[('call', 'Repository.lock_write', ('quack/', ''))],
2301
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2303
def test_backwards_compat(self):
2304
self.setup_smart_server_with_call_log()
2305
repo = self.make_repository('.')
2306
self.reset_smart_call_log()
2307
verb = 'Repository.set_make_working_trees'
2308
self.disable_verb(verb)
2309
repo.set_make_working_trees(True)
2310
call_count = len([call for call in self.hpss_calls if
2311
call.call.method == verb])
2312
self.assertEqual(1, call_count)
2314
def test_current(self):
2315
transport_path = 'quack'
2316
repo, client = self.setup_fake_client_and_repository(transport_path)
2317
client.add_expected_call(
2318
'Repository.set_make_working_trees', ('quack/', 'True'),
2320
client.add_expected_call(
2321
'Repository.set_make_working_trees', ('quack/', 'False'),
2323
repo.set_make_working_trees(True)
2324
repo.set_make_working_trees(False)
2327
class TestRepositoryUnlock(TestRemoteRepository):
2329
def test_unlock(self):
2330
transport_path = 'quack'
2331
repo, client = self.setup_fake_client_and_repository(transport_path)
2332
client.add_success_response('ok', 'a token')
2333
client.add_success_response('ok')
2337
[('call', 'Repository.lock_write', ('quack/', '')),
2338
('call', 'Repository.unlock', ('quack/', 'a token'))],
2341
def test_unlock_wrong_token(self):
2342
# If somehow the token is wrong, unlock will raise TokenMismatch.
2343
transport_path = 'quack'
2344
repo, client = self.setup_fake_client_and_repository(transport_path)
2345
client.add_success_response('ok', 'a token')
2346
client.add_error_response('TokenMismatch')
2348
self.assertRaises(errors.TokenMismatch, repo.unlock)
2351
class TestRepositoryHasRevision(TestRemoteRepository):
2353
def test_none(self):
2354
# repo.has_revision(None) should not cause any traffic.
2355
transport_path = 'quack'
2356
repo, client = self.setup_fake_client_and_repository(transport_path)
2358
# The null revision is always there, so has_revision(None) == True.
2359
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2361
# The remote repo shouldn't be accessed.
2362
self.assertEqual([], client._calls)
2365
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2366
"""Base class for Repository.insert_stream and .insert_stream_1.19
2370
def checkInsertEmptyStream(self, repo, client):
2371
"""Insert an empty stream, checking the result.
2373
This checks that there are no resume_tokens or missing_keys, and that
2374
the client is finished.
2376
sink = repo._get_sink()
2377
fmt = repository.RepositoryFormat.get_default_format()
2378
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2379
self.assertEqual([], resume_tokens)
2380
self.assertEqual(set(), missing_keys)
2381
self.assertFinished(client)
2384
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2385
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2388
This test case is very similar to TestRepositoryInsertStream_1_19.
2392
TestRemoteRepository.setUp(self)
2393
self.disable_verb('Repository.insert_stream_1.19')
2395
def test_unlocked_repo(self):
2396
transport_path = 'quack'
2397
repo, client = self.setup_fake_client_and_repository(transport_path)
2398
client.add_expected_call(
2399
'Repository.insert_stream_1.19', ('quack/', ''),
2400
'unknown', ('Repository.insert_stream_1.19',))
2401
client.add_expected_call(
2402
'Repository.insert_stream', ('quack/', ''),
2404
client.add_expected_call(
2405
'Repository.insert_stream', ('quack/', ''),
2407
self.checkInsertEmptyStream(repo, client)
2409
def test_locked_repo_with_no_lock_token(self):
2410
transport_path = 'quack'
2411
repo, client = self.setup_fake_client_and_repository(transport_path)
2412
client.add_expected_call(
2413
'Repository.lock_write', ('quack/', ''),
2414
'success', ('ok', ''))
2415
client.add_expected_call(
2416
'Repository.insert_stream_1.19', ('quack/', ''),
2417
'unknown', ('Repository.insert_stream_1.19',))
2418
client.add_expected_call(
2419
'Repository.insert_stream', ('quack/', ''),
2421
client.add_expected_call(
2422
'Repository.insert_stream', ('quack/', ''),
2425
self.checkInsertEmptyStream(repo, client)
2427
def test_locked_repo_with_lock_token(self):
2428
transport_path = 'quack'
2429
repo, client = self.setup_fake_client_and_repository(transport_path)
2430
client.add_expected_call(
2431
'Repository.lock_write', ('quack/', ''),
2432
'success', ('ok', 'a token'))
2433
client.add_expected_call(
2434
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2435
'unknown', ('Repository.insert_stream_1.19',))
2436
client.add_expected_call(
2437
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2439
client.add_expected_call(
2440
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2443
self.checkInsertEmptyStream(repo, client)
2445
def test_stream_with_inventory_deltas(self):
2446
"""'inventory-deltas' substreams cannot be sent to the
2447
Repository.insert_stream verb, because not all servers that implement
2448
that verb will accept them. So when one is encountered the RemoteSink
2449
immediately stops using that verb and falls back to VFS insert_stream.
2451
transport_path = 'quack'
2452
repo, client = self.setup_fake_client_and_repository(transport_path)
2453
client.add_expected_call(
2454
'Repository.insert_stream_1.19', ('quack/', ''),
2455
'unknown', ('Repository.insert_stream_1.19',))
2456
client.add_expected_call(
2457
'Repository.insert_stream', ('quack/', ''),
2459
client.add_expected_call(
2460
'Repository.insert_stream', ('quack/', ''),
2462
# Create a fake real repository for insert_stream to fall back on, so
2463
# that we can directly see the records the RemoteSink passes to the
2468
def insert_stream(self, stream, src_format, resume_tokens):
2469
for substream_kind, substream in stream:
2470
self.records.append(
2471
(substream_kind, [record.key for record in substream]))
2472
return ['fake tokens'], ['fake missing keys']
2473
fake_real_sink = FakeRealSink()
2474
class FakeRealRepository:
2475
def _get_sink(self):
2476
return fake_real_sink
2477
def is_in_write_group(self):
2479
def refresh_data(self):
2481
repo._real_repository = FakeRealRepository()
2482
sink = repo._get_sink()
2483
fmt = repository.RepositoryFormat.get_default_format()
2484
stream = self.make_stream_with_inv_deltas(fmt)
2485
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2486
# Every record from the first inventory delta should have been sent to
2488
expected_records = [
2489
('inventory-deltas', [('rev2',), ('rev3',)]),
2490
('texts', [('some-rev', 'some-file')])]
2491
self.assertEqual(expected_records, fake_real_sink.records)
2492
# The return values from the real sink's insert_stream are propagated
2493
# back to the original caller.
2494
self.assertEqual(['fake tokens'], resume_tokens)
2495
self.assertEqual(['fake missing keys'], missing_keys)
2496
self.assertFinished(client)
2498
def make_stream_with_inv_deltas(self, fmt):
2499
"""Make a simple stream with an inventory delta followed by more
2500
records and more substreams to test that all records and substreams
2501
from that point on are used.
2503
This sends, in order:
2504
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2506
* texts substream: (some-rev, some-file)
2508
# Define a stream using generators so that it isn't rewindable.
2509
inv = inventory.Inventory(revision_id='rev1')
2510
inv.root.revision = 'rev1'
2511
def stream_with_inv_delta():
2512
yield ('inventories', inventories_substream())
2513
yield ('inventory-deltas', inventory_delta_substream())
2515
versionedfile.FulltextContentFactory(
2516
('some-rev', 'some-file'), (), None, 'content')])
2517
def inventories_substream():
2518
# An empty inventory fulltext. This will be streamed normally.
2519
text = fmt._serializer.write_inventory_to_string(inv)
2520
yield versionedfile.FulltextContentFactory(
2521
('rev1',), (), None, text)
2522
def inventory_delta_substream():
2523
# An inventory delta. This can't be streamed via this verb, so it
2524
# will trigger a fallback to VFS insert_stream.
2525
entry = inv.make_entry(
2526
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2527
entry.revision = 'ghost'
2528
delta = [(None, 'newdir', 'newdir-id', entry)]
2529
serializer = inventory_delta.InventoryDeltaSerializer(
2530
versioned_root=True, tree_references=False)
2531
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2532
yield versionedfile.ChunkedContentFactory(
2533
('rev2',), (('rev1',)), None, lines)
2535
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2536
yield versionedfile.ChunkedContentFactory(
2537
('rev3',), (('rev1',)), None, lines)
2538
return stream_with_inv_delta()
2541
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2543
def test_unlocked_repo(self):
2544
transport_path = 'quack'
2545
repo, client = self.setup_fake_client_and_repository(transport_path)
2546
client.add_expected_call(
2547
'Repository.insert_stream_1.19', ('quack/', ''),
2549
client.add_expected_call(
2550
'Repository.insert_stream_1.19', ('quack/', ''),
2552
self.checkInsertEmptyStream(repo, client)
2554
def test_locked_repo_with_no_lock_token(self):
2555
transport_path = 'quack'
2556
repo, client = self.setup_fake_client_and_repository(transport_path)
2557
client.add_expected_call(
2558
'Repository.lock_write', ('quack/', ''),
2559
'success', ('ok', ''))
2560
client.add_expected_call(
2561
'Repository.insert_stream_1.19', ('quack/', ''),
2563
client.add_expected_call(
2564
'Repository.insert_stream_1.19', ('quack/', ''),
2567
self.checkInsertEmptyStream(repo, client)
2569
def test_locked_repo_with_lock_token(self):
2570
transport_path = 'quack'
2571
repo, client = self.setup_fake_client_and_repository(transport_path)
2572
client.add_expected_call(
2573
'Repository.lock_write', ('quack/', ''),
2574
'success', ('ok', 'a token'))
2575
client.add_expected_call(
2576
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2578
client.add_expected_call(
2579
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2582
self.checkInsertEmptyStream(repo, client)
2585
class TestRepositoryTarball(TestRemoteRepository):
2587
# This is a canned tarball reponse we can validate against
2589
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2590
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2591
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2592
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2593
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2594
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2595
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2596
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2597
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2598
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2599
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2600
'nWQ7QH/F3JFOFCQ0aSPfA='
2603
def test_repository_tarball(self):
2604
# Test that Repository.tarball generates the right operations
2605
transport_path = 'repo'
2606
expected_calls = [('call_expecting_body', 'Repository.tarball',
2607
('repo/', 'bz2',),),
2609
repo, client = self.setup_fake_client_and_repository(transport_path)
2610
client.add_success_response_with_body(self.tarball_content, 'ok')
2611
# Now actually ask for the tarball
2612
tarball_file = repo._get_tarball('bz2')
2614
self.assertEqual(expected_calls, client._calls)
2615
self.assertEqual(self.tarball_content, tarball_file.read())
2617
tarball_file.close()
2620
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2621
"""RemoteRepository.copy_content_into optimizations"""
2623
def test_copy_content_remote_to_local(self):
2624
self.transport_server = server.SmartTCPServer_for_testing
2625
src_repo = self.make_repository('repo1')
2626
src_repo = repository.Repository.open(self.get_url('repo1'))
2627
# At the moment the tarball-based copy_content_into can't write back
2628
# into a smart server. It would be good if it could upload the
2629
# tarball; once that works we'd have to create repositories of
2630
# different formats. -- mbp 20070410
2631
dest_url = self.get_vfs_only_url('repo2')
2632
dest_bzrdir = BzrDir.create(dest_url)
2633
dest_repo = dest_bzrdir.create_repository()
2634
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2635
self.assertTrue(isinstance(src_repo, RemoteRepository))
2636
src_repo.copy_content_into(dest_repo)
2639
class _StubRealPackRepository(object):
2641
def __init__(self, calls):
2643
self._pack_collection = _StubPackCollection(calls)
2645
def is_in_write_group(self):
2648
def refresh_data(self):
2649
self.calls.append(('pack collection reload_pack_names',))
2652
class _StubPackCollection(object):
2654
def __init__(self, calls):
2658
self.calls.append(('pack collection autopack',))
2661
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2662
"""Tests for RemoteRepository.autopack implementation."""
2665
"""When the server returns 'ok' and there's no _real_repository, then
2666
nothing else happens: the autopack method is done.
2668
transport_path = 'quack'
2669
repo, client = self.setup_fake_client_and_repository(transport_path)
2670
client.add_expected_call(
2671
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2673
self.assertFinished(client)
2675
def test_ok_with_real_repo(self):
2676
"""When the server returns 'ok' and there is a _real_repository, then
2677
the _real_repository's reload_pack_name's method will be called.
2679
transport_path = 'quack'
2680
repo, client = self.setup_fake_client_and_repository(transport_path)
2681
client.add_expected_call(
2682
'PackRepository.autopack', ('quack/',),
2684
repo._real_repository = _StubRealPackRepository(client._calls)
2687
[('call', 'PackRepository.autopack', ('quack/',)),
2688
('pack collection reload_pack_names',)],
2691
def test_backwards_compatibility(self):
2692
"""If the server does not recognise the PackRepository.autopack verb,
2693
fallback to the real_repository's implementation.
2695
transport_path = 'quack'
2696
repo, client = self.setup_fake_client_and_repository(transport_path)
2697
client.add_unknown_method_response('PackRepository.autopack')
2698
def stub_ensure_real():
2699
client._calls.append(('_ensure_real',))
2700
repo._real_repository = _StubRealPackRepository(client._calls)
2701
repo._ensure_real = stub_ensure_real
2704
[('call', 'PackRepository.autopack', ('quack/',)),
2706
('pack collection autopack',)],
2710
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2711
"""Base class for unit tests for bzrlib.remote._translate_error."""
2713
def translateTuple(self, error_tuple, **context):
2714
"""Call _translate_error with an ErrorFromSmartServer built from the
2717
:param error_tuple: A tuple of a smart server response, as would be
2718
passed to an ErrorFromSmartServer.
2719
:kwargs context: context items to call _translate_error with.
2721
:returns: The error raised by _translate_error.
2723
# Raise the ErrorFromSmartServer before passing it as an argument,
2724
# because _translate_error may need to re-raise it with a bare 'raise'
2726
server_error = errors.ErrorFromSmartServer(error_tuple)
2727
translated_error = self.translateErrorFromSmartServer(
2728
server_error, **context)
2729
return translated_error
2731
def translateErrorFromSmartServer(self, error_object, **context):
2732
"""Like translateTuple, but takes an already constructed
2733
ErrorFromSmartServer rather than a tuple.
2737
except errors.ErrorFromSmartServer, server_error:
2738
translated_error = self.assertRaises(
2739
errors.BzrError, remote._translate_error, server_error,
2741
return translated_error
2744
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2745
"""Unit tests for bzrlib.remote._translate_error.
2747
Given an ErrorFromSmartServer (which has an error tuple from a smart
2748
server) and some context, _translate_error raises more specific errors from
2751
This test case covers the cases where _translate_error succeeds in
2752
translating an ErrorFromSmartServer to something better. See
2753
TestErrorTranslationRobustness for other cases.
2756
def test_NoSuchRevision(self):
2757
branch = self.make_branch('')
2759
translated_error = self.translateTuple(
2760
('NoSuchRevision', revid), branch=branch)
2761
expected_error = errors.NoSuchRevision(branch, revid)
2762
self.assertEqual(expected_error, translated_error)
2764
def test_nosuchrevision(self):
2765
repository = self.make_repository('')
2767
translated_error = self.translateTuple(
2768
('nosuchrevision', revid), repository=repository)
2769
expected_error = errors.NoSuchRevision(repository, revid)
2770
self.assertEqual(expected_error, translated_error)
2772
def test_nobranch(self):
2773
bzrdir = self.make_bzrdir('')
2774
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2775
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2776
self.assertEqual(expected_error, translated_error)
2778
def test_LockContention(self):
2779
translated_error = self.translateTuple(('LockContention',))
2780
expected_error = errors.LockContention('(remote lock)')
2781
self.assertEqual(expected_error, translated_error)
2783
def test_UnlockableTransport(self):
2784
bzrdir = self.make_bzrdir('')
2785
translated_error = self.translateTuple(
2786
('UnlockableTransport',), bzrdir=bzrdir)
2787
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2788
self.assertEqual(expected_error, translated_error)
2790
def test_LockFailed(self):
2791
lock = 'str() of a server lock'
2792
why = 'str() of why'
2793
translated_error = self.translateTuple(('LockFailed', lock, why))
2794
expected_error = errors.LockFailed(lock, why)
2795
self.assertEqual(expected_error, translated_error)
2797
def test_TokenMismatch(self):
2798
token = 'a lock token'
2799
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2800
expected_error = errors.TokenMismatch(token, '(remote token)')
2801
self.assertEqual(expected_error, translated_error)
2803
def test_Diverged(self):
2804
branch = self.make_branch('a')
2805
other_branch = self.make_branch('b')
2806
translated_error = self.translateTuple(
2807
('Diverged',), branch=branch, other_branch=other_branch)
2808
expected_error = errors.DivergedBranches(branch, other_branch)
2809
self.assertEqual(expected_error, translated_error)
2811
def test_ReadError_no_args(self):
2813
translated_error = self.translateTuple(('ReadError',), path=path)
2814
expected_error = errors.ReadError(path)
2815
self.assertEqual(expected_error, translated_error)
2817
def test_ReadError(self):
2819
translated_error = self.translateTuple(('ReadError', path))
2820
expected_error = errors.ReadError(path)
2821
self.assertEqual(expected_error, translated_error)
2823
def test_IncompatibleRepositories(self):
2824
translated_error = self.translateTuple(('IncompatibleRepositories',
2825
"repo1", "repo2", "details here"))
2826
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2828
self.assertEqual(expected_error, translated_error)
2830
def test_PermissionDenied_no_args(self):
2832
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2833
expected_error = errors.PermissionDenied(path)
2834
self.assertEqual(expected_error, translated_error)
2836
def test_PermissionDenied_one_arg(self):
2838
translated_error = self.translateTuple(('PermissionDenied', path))
2839
expected_error = errors.PermissionDenied(path)
2840
self.assertEqual(expected_error, translated_error)
2842
def test_PermissionDenied_one_arg_and_context(self):
2843
"""Given a choice between a path from the local context and a path on
2844
the wire, _translate_error prefers the path from the local context.
2846
local_path = 'local path'
2847
remote_path = 'remote path'
2848
translated_error = self.translateTuple(
2849
('PermissionDenied', remote_path), path=local_path)
2850
expected_error = errors.PermissionDenied(local_path)
2851
self.assertEqual(expected_error, translated_error)
2853
def test_PermissionDenied_two_args(self):
2855
extra = 'a string with extra info'
2856
translated_error = self.translateTuple(
2857
('PermissionDenied', path, extra))
2858
expected_error = errors.PermissionDenied(path, extra)
2859
self.assertEqual(expected_error, translated_error)
2862
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2863
"""Unit tests for bzrlib.remote._translate_error's robustness.
2865
TestErrorTranslationSuccess is for cases where _translate_error can
2866
translate successfully. This class about how _translate_err behaves when
2867
it fails to translate: it re-raises the original error.
2870
def test_unrecognised_server_error(self):
2871
"""If the error code from the server is not recognised, the original
2872
ErrorFromSmartServer is propagated unmodified.
2874
error_tuple = ('An unknown error tuple',)
2875
server_error = errors.ErrorFromSmartServer(error_tuple)
2876
translated_error = self.translateErrorFromSmartServer(server_error)
2877
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2878
self.assertEqual(expected_error, translated_error)
2880
def test_context_missing_a_key(self):
2881
"""In case of a bug in the client, or perhaps an unexpected response
2882
from a server, _translate_error returns the original error tuple from
2883
the server and mutters a warning.
2885
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2886
# in the context dict. So let's give it an empty context dict instead
2887
# to exercise its error recovery.
2889
error_tuple = ('NoSuchRevision', 'revid')
2890
server_error = errors.ErrorFromSmartServer(error_tuple)
2891
translated_error = self.translateErrorFromSmartServer(server_error)
2892
self.assertEqual(server_error, translated_error)
2893
# In addition to re-raising ErrorFromSmartServer, some debug info has
2894
# been muttered to the log file for developer to look at.
2895
self.assertContainsRe(
2897
"Missing key 'branch' in context")
2899
def test_path_missing(self):
2900
"""Some translations (PermissionDenied, ReadError) can determine the
2901
'path' variable from either the wire or the local context. If neither
2902
has it, then an error is raised.
2904
error_tuple = ('ReadError',)
2905
server_error = errors.ErrorFromSmartServer(error_tuple)
2906
translated_error = self.translateErrorFromSmartServer(server_error)
2907
self.assertEqual(server_error, translated_error)
2908
# In addition to re-raising ErrorFromSmartServer, some debug info has
2909
# been muttered to the log file for developer to look at.
2910
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
2913
class TestStacking(tests.TestCaseWithTransport):
2914
"""Tests for operations on stacked remote repositories.
2916
The underlying format type must support stacking.
2919
def test_access_stacked_remote(self):
2920
# based on <http://launchpad.net/bugs/261315>
2921
# make a branch stacked on another repository containing an empty
2922
# revision, then open it over hpss - we should be able to see that
2924
base_transport = self.get_transport()
2925
base_builder = self.make_branch_builder('base', format='1.9')
2926
base_builder.start_series()
2927
base_revid = base_builder.build_snapshot('rev-id', None,
2928
[('add', ('', None, 'directory', None))],
2930
base_builder.finish_series()
2931
stacked_branch = self.make_branch('stacked', format='1.9')
2932
stacked_branch.set_stacked_on_url('../base')
2933
# start a server looking at this
2934
smart_server = server.SmartTCPServer_for_testing()
2935
self.start_server(smart_server)
2936
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2937
# can get its branch and repository
2938
remote_branch = remote_bzrdir.open_branch()
2939
remote_repo = remote_branch.repository
2940
remote_repo.lock_read()
2942
# it should have an appropriate fallback repository, which should also
2943
# be a RemoteRepository
2944
self.assertLength(1, remote_repo._fallback_repositories)
2945
self.assertIsInstance(remote_repo._fallback_repositories[0],
2947
# and it has the revision committed to the underlying repository;
2948
# these have varying implementations so we try several of them
2949
self.assertTrue(remote_repo.has_revisions([base_revid]))
2950
self.assertTrue(remote_repo.has_revision(base_revid))
2951
self.assertEqual(remote_repo.get_revision(base_revid).message,
2954
remote_repo.unlock()
2956
def prepare_stacked_remote_branch(self):
2957
"""Get stacked_upon and stacked branches with content in each."""
2958
self.setup_smart_server_with_call_log()
2959
tree1 = self.make_branch_and_tree('tree1', format='1.9')
2960
tree1.commit('rev1', rev_id='rev1')
2961
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2962
).open_workingtree()
2963
local_tree = tree2.branch.create_checkout('local')
2964
local_tree.commit('local changes make me feel good.')
2965
branch2 = Branch.open(self.get_url('tree2'))
2967
self.addCleanup(branch2.unlock)
2968
return tree1.branch, branch2
2970
def test_stacked_get_parent_map(self):
2971
# the public implementation of get_parent_map obeys stacking
2972
_, branch = self.prepare_stacked_remote_branch()
2973
repo = branch.repository
2974
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2976
def test_unstacked_get_parent_map(self):
2977
# _unstacked_provider.get_parent_map ignores stacking
2978
_, branch = self.prepare_stacked_remote_branch()
2979
provider = branch.repository._unstacked_provider
2980
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2982
def fetch_stream_to_rev_order(self, stream):
2984
for kind, substream in stream:
2985
if not kind == 'revisions':
2988
for content in substream:
2989
result.append(content.key[-1])
2992
def get_ordered_revs(self, format, order, branch_factory=None):
2993
"""Get a list of the revisions in a stream to format format.
2995
:param format: The format of the target.
2996
:param order: the order that target should have requested.
2997
:param branch_factory: A callable to create a trunk and stacked branch
2998
to fetch from. If none, self.prepare_stacked_remote_branch is used.
2999
:result: The revision ids in the stream, in the order seen,
3000
the topological order of revisions in the source.
3002
unordered_format = bzrdir.format_registry.get(format)()
3003
target_repository_format = unordered_format.repository_format
3005
self.assertEqual(order, target_repository_format._fetch_order)
3006
if branch_factory is None:
3007
branch_factory = self.prepare_stacked_remote_branch
3008
_, stacked = branch_factory()
3009
source = stacked.repository._get_source(target_repository_format)
3010
tip = stacked.last_revision()
3011
revs = stacked.repository.get_ancestry(tip)
3012
search = graph.PendingAncestryResult([tip], stacked.repository)
3013
self.reset_smart_call_log()
3014
stream = source.get_stream(search)
3017
# We trust that if a revision is in the stream the rest of the new
3018
# content for it is too, as per our main fetch tests; here we are
3019
# checking that the revisions are actually included at all, and their
3021
return self.fetch_stream_to_rev_order(stream), revs
3023
def test_stacked_get_stream_unordered(self):
3024
# Repository._get_source.get_stream() from a stacked repository with
3025
# unordered yields the full data from both stacked and stacked upon
3027
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3028
self.assertEqual(set(expected_revs), set(rev_ord))
3029
# Getting unordered results should have made a streaming data request
3030
# from the server, then one from the backing branch.
3031
self.assertLength(2, self.hpss_calls)
3033
def test_stacked_on_stacked_get_stream_unordered(self):
3034
# Repository._get_source.get_stream() from a stacked repository which
3035
# is itself stacked yields the full data from all three sources.
3036
def make_stacked_stacked():
3037
_, stacked = self.prepare_stacked_remote_branch()
3038
tree = stacked.bzrdir.sprout('tree3', stacked=True
3039
).open_workingtree()
3040
local_tree = tree.branch.create_checkout('local-tree3')
3041
local_tree.commit('more local changes are better')
3042
branch = Branch.open(self.get_url('tree3'))
3044
self.addCleanup(branch.unlock)
3046
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3047
branch_factory=make_stacked_stacked)
3048
self.assertEqual(set(expected_revs), set(rev_ord))
3049
# Getting unordered results should have made a streaming data request
3050
# from the server, and one from each backing repo
3051
self.assertLength(3, self.hpss_calls)
3053
def test_stacked_get_stream_topological(self):
3054
# Repository._get_source.get_stream() from a stacked repository with
3055
# topological sorting yields the full data from both stacked and
3056
# stacked upon sources in topological order.
3057
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3058
self.assertEqual(expected_revs, rev_ord)
3059
# Getting topological sort requires VFS calls still - one of which is
3060
# pushing up from the bound branch.
3061
self.assertLength(13, self.hpss_calls)
3063
def test_stacked_get_stream_groupcompress(self):
3064
# Repository._get_source.get_stream() from a stacked repository with
3065
# groupcompress sorting yields the full data from both stacked and
3066
# stacked upon sources in groupcompress order.
3067
raise tests.TestSkipped('No groupcompress ordered format available')
3068
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3069
self.assertEqual(expected_revs, reversed(rev_ord))
3070
# Getting unordered results should have made a streaming data request
3071
# from the backing branch, and one from the stacked on branch.
3072
self.assertLength(2, self.hpss_calls)
3074
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3075
# When pulling some fixed amount of content that is more than the
3076
# source has (because some is coming from a fallback branch, no error
3077
# should be received. This was reported as bug 360791.
3078
# Need three branches: a trunk, a stacked branch, and a preexisting
3079
# branch pulling content from stacked and trunk.
3080
self.setup_smart_server_with_call_log()
3081
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3082
r1 = trunk.commit('start')
3083
stacked_branch = trunk.branch.create_clone_on_transport(
3084
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3085
local = self.make_branch('local', format='1.9-rich-root')
3086
local.repository.fetch(stacked_branch.repository,
3087
stacked_branch.last_revision())
3090
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3093
super(TestRemoteBranchEffort, self).setUp()
3094
# Create a smart server that publishes whatever the backing VFS server
3096
self.smart_server = server.SmartTCPServer_for_testing()
3097
self.start_server(self.smart_server, self.get_server())
3098
# Log all HPSS calls into self.hpss_calls.
3099
_SmartClient.hooks.install_named_hook(
3100
'call', self.capture_hpss_call, None)
3101
self.hpss_calls = []
3103
def capture_hpss_call(self, params):
3104
self.hpss_calls.append(params.method)
3106
def test_copy_content_into_avoids_revision_history(self):
3107
local = self.make_branch('local')
3108
remote_backing_tree = self.make_branch_and_tree('remote')
3109
remote_backing_tree.commit("Commit.")
3110
remote_branch_url = self.smart_server.get_url() + 'remote'
3111
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3112
local.repository.fetch(remote_branch.repository)
3113
self.hpss_calls = []
3114
remote_branch.copy_content_into(local)
3115
self.assertFalse('Branch.revision_history' in self.hpss_calls)