1
# Copyright (C) 2006-2010 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 (
51
from bzrlib.remote import (
56
RemoteRepositoryFormat,
58
from bzrlib.repofmt import groupcompress_repo, pack_repo
59
from bzrlib.revision import NULL_REVISION
60
from bzrlib.smart import medium
61
from bzrlib.smart.client import _SmartClient
62
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
63
from bzrlib.tests import (
65
split_suite_by_condition,
69
from bzrlib.transport.memory import MemoryTransport
70
from bzrlib.transport.remote import (
76
def load_tests(standard_tests, module, loader):
77
to_adapt, result = split_suite_by_condition(
78
standard_tests, condition_isinstance(BasicRemoteObjectTests))
79
smart_server_version_scenarios = [
81
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
83
{'transport_server': test_server.SmartTCPServer_for_testing})]
84
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
87
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
90
super(BasicRemoteObjectTests, self).setUp()
91
self.transport = self.get_transport()
92
# make a branch that can be opened over the smart transport
93
self.local_wt = BzrDir.create_standalone_workingtree('.')
94
self.addCleanup(self.transport.disconnect)
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(bzrdir.RemoteBzrProber
125
in controldir.ControlDirFormat._server_probers)
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_bzrdir_repr(self):
138
b = BzrDir.open_from_transport(self.transport)
139
self.assertStartsWith(str(b), 'RemoteBzrDir(')
141
def test_remote_branch_format_supports_stacking(self):
143
self.make_branch('unstackable', format='pack-0.92')
144
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
145
self.assertFalse(b._format.supports_stacking())
146
self.make_branch('stackable', format='1.9')
147
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
148
self.assertTrue(b._format.supports_stacking())
150
def test_remote_repo_format_supports_external_references(self):
152
bd = self.make_bzrdir('unstackable', format='pack-0.92')
153
r = bd.create_repository()
154
self.assertFalse(r._format.supports_external_lookups)
155
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
156
self.assertFalse(r._format.supports_external_lookups)
157
bd = self.make_bzrdir('stackable', format='1.9')
158
r = bd.create_repository()
159
self.assertTrue(r._format.supports_external_lookups)
160
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
161
self.assertTrue(r._format.supports_external_lookups)
163
def test_remote_branch_set_append_revisions_only(self):
164
# Make a format 1.9 branch, which supports append_revisions_only
165
branch = self.make_branch('branch', format='1.9')
166
config = branch.get_config()
167
branch.set_append_revisions_only(True)
169
'True', config.get_user_option('append_revisions_only'))
170
branch.set_append_revisions_only(False)
172
'False', config.get_user_option('append_revisions_only'))
174
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
175
branch = self.make_branch('branch', format='knit')
176
config = branch.get_config()
178
errors.UpgradeRequired, branch.set_append_revisions_only, True)
181
class FakeProtocol(object):
182
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
184
def __init__(self, body, fake_client):
186
self._body_buffer = None
187
self._fake_client = fake_client
189
def read_body_bytes(self, count=-1):
190
if self._body_buffer is None:
191
self._body_buffer = StringIO(self.body)
192
bytes = self._body_buffer.read(count)
193
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
194
self._fake_client.expecting_body = False
197
def cancel_read_body(self):
198
self._fake_client.expecting_body = False
200
def read_streamed_body(self):
204
class FakeClient(_SmartClient):
205
"""Lookalike for _SmartClient allowing testing."""
207
def __init__(self, fake_medium_base='fake base'):
208
"""Create a FakeClient."""
211
self.expecting_body = False
212
# if non-None, this is the list of expected calls, with only the
213
# method name and arguments included. the body might be hard to
214
# compute so is not included. If a call is None, that call can
216
self._expected_calls = None
217
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
219
def add_expected_call(self, call_name, call_args, response_type,
220
response_args, response_body=None):
221
if self._expected_calls is None:
222
self._expected_calls = []
223
self._expected_calls.append((call_name, call_args))
224
self.responses.append((response_type, response_args, response_body))
226
def add_success_response(self, *args):
227
self.responses.append(('success', args, None))
229
def add_success_response_with_body(self, body, *args):
230
self.responses.append(('success', args, body))
231
if self._expected_calls is not None:
232
self._expected_calls.append(None)
234
def add_error_response(self, *args):
235
self.responses.append(('error', args))
237
def add_unknown_method_response(self, verb):
238
self.responses.append(('unknown', verb))
240
def finished_test(self):
241
if self._expected_calls:
242
raise AssertionError("%r finished but was still expecting %r"
243
% (self, self._expected_calls[0]))
245
def _get_next_response(self):
247
response_tuple = self.responses.pop(0)
248
except IndexError, e:
249
raise AssertionError("%r didn't expect any more calls"
251
if response_tuple[0] == 'unknown':
252
raise errors.UnknownSmartMethod(response_tuple[1])
253
elif response_tuple[0] == 'error':
254
raise errors.ErrorFromSmartServer(response_tuple[1])
255
return response_tuple
257
def _check_call(self, method, args):
258
if self._expected_calls is None:
259
# the test should be updated to say what it expects
262
next_call = self._expected_calls.pop(0)
264
raise AssertionError("%r didn't expect any more calls "
266
% (self, method, args,))
267
if next_call is None:
269
if method != next_call[0] or args != next_call[1]:
270
raise AssertionError("%r expected %r%r "
272
% (self, next_call[0], next_call[1], method, args,))
274
def call(self, method, *args):
275
self._check_call(method, args)
276
self._calls.append(('call', method, args))
277
return self._get_next_response()[1]
279
def call_expecting_body(self, method, *args):
280
self._check_call(method, args)
281
self._calls.append(('call_expecting_body', method, args))
282
result = self._get_next_response()
283
self.expecting_body = True
284
return result[1], FakeProtocol(result[2], self)
286
def call_with_body_bytes(self, method, args, body):
287
self._check_call(method, args)
288
self._calls.append(('call_with_body_bytes', method, args, body))
289
result = self._get_next_response()
290
return result[1], FakeProtocol(result[2], self)
292
def call_with_body_bytes_expecting_body(self, method, args, body):
293
self._check_call(method, args)
294
self._calls.append(('call_with_body_bytes_expecting_body', method,
296
result = self._get_next_response()
297
self.expecting_body = True
298
return result[1], FakeProtocol(result[2], self)
300
def call_with_body_stream(self, args, stream):
301
# Explicitly consume the stream before checking for an error, because
302
# that's what happens a real medium.
303
stream = list(stream)
304
self._check_call(args[0], args[1:])
305
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
306
result = self._get_next_response()
307
# The second value returned from call_with_body_stream is supposed to
308
# be a response_handler object, but so far no tests depend on that.
309
response_handler = None
310
return result[1], response_handler
313
class FakeMedium(medium.SmartClientMedium):
315
def __init__(self, client_calls, base):
316
medium.SmartClientMedium.__init__(self, base)
317
self._client_calls = client_calls
319
def disconnect(self):
320
self._client_calls.append(('disconnect medium',))
323
class TestVfsHas(tests.TestCase):
325
def test_unicode_path(self):
326
client = FakeClient('/')
327
client.add_success_response('yes',)
328
transport = RemoteTransport('bzr://localhost/', _client=client)
329
filename = u'/hell\u00d8'.encode('utf8')
330
result = transport.has(filename)
332
[('call', 'has', (filename,))],
334
self.assertTrue(result)
337
class TestRemote(tests.TestCaseWithMemoryTransport):
339
def get_branch_format(self):
340
reference_bzrdir_format = bzrdir.format_registry.get('default')()
341
return reference_bzrdir_format.get_branch_format()
343
def get_repo_format(self):
344
reference_bzrdir_format = bzrdir.format_registry.get('default')()
345
return reference_bzrdir_format.repository_format
347
def assertFinished(self, fake_client):
348
"""Assert that all of a FakeClient's expected calls have occurred."""
349
fake_client.finished_test()
352
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
353
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
355
def assertRemotePath(self, expected, client_base, transport_base):
356
"""Assert that the result of
357
SmartClientMedium.remote_path_from_transport is the expected value for
358
a given client_base and transport_base.
360
client_medium = medium.SmartClientMedium(client_base)
361
t = transport.get_transport(transport_base)
362
result = client_medium.remote_path_from_transport(t)
363
self.assertEqual(expected, result)
365
def test_remote_path_from_transport(self):
366
"""SmartClientMedium.remote_path_from_transport calculates a URL for
367
the given transport relative to the root of the client base URL.
369
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
370
self.assertRemotePath(
371
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
373
def assertRemotePathHTTP(self, expected, transport_base, relpath):
374
"""Assert that the result of
375
HttpTransportBase.remote_path_from_transport is the expected value for
376
a given transport_base and relpath of that transport. (Note that
377
HttpTransportBase is a subclass of SmartClientMedium)
379
base_transport = transport.get_transport(transport_base)
380
client_medium = base_transport.get_smart_medium()
381
cloned_transport = base_transport.clone(relpath)
382
result = client_medium.remote_path_from_transport(cloned_transport)
383
self.assertEqual(expected, result)
385
def test_remote_path_from_transport_http(self):
386
"""Remote paths for HTTP transports are calculated differently to other
387
transports. They are just relative to the client base, not the root
388
directory of the host.
390
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
391
self.assertRemotePathHTTP(
392
'../xyz/', scheme + '//host/path', '../xyz/')
393
self.assertRemotePathHTTP(
394
'xyz/', scheme + '//host/path', 'xyz/')
397
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
398
"""Tests for the behaviour of client_medium.remote_is_at_least."""
400
def test_initially_unlimited(self):
401
"""A fresh medium assumes that the remote side supports all
404
client_medium = medium.SmartClientMedium('dummy base')
405
self.assertFalse(client_medium._is_remote_before((99, 99)))
407
def test__remember_remote_is_before(self):
408
"""Calling _remember_remote_is_before ratchets down the known remote
411
client_medium = medium.SmartClientMedium('dummy base')
412
# Mark the remote side as being less than 1.6. The remote side may
414
client_medium._remember_remote_is_before((1, 6))
415
self.assertTrue(client_medium._is_remote_before((1, 6)))
416
self.assertFalse(client_medium._is_remote_before((1, 5)))
417
# Calling _remember_remote_is_before again with a lower value works.
418
client_medium._remember_remote_is_before((1, 5))
419
self.assertTrue(client_medium._is_remote_before((1, 5)))
420
# If you call _remember_remote_is_before with a higher value it logs a
421
# warning, and continues to remember the lower value.
422
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
423
client_medium._remember_remote_is_before((1, 9))
424
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
425
self.assertTrue(client_medium._is_remote_before((1, 5)))
428
class TestBzrDirCloningMetaDir(TestRemote):
430
def test_backwards_compat(self):
431
self.setup_smart_server_with_call_log()
432
a_dir = self.make_bzrdir('.')
433
self.reset_smart_call_log()
434
verb = 'BzrDir.cloning_metadir'
435
self.disable_verb(verb)
436
format = a_dir.cloning_metadir()
437
call_count = len([call for call in self.hpss_calls if
438
call.call.method == verb])
439
self.assertEqual(1, call_count)
441
def test_branch_reference(self):
442
transport = self.get_transport('quack')
443
referenced = self.make_branch('referenced')
444
expected = referenced.bzrdir.cloning_metadir()
445
client = FakeClient(transport.base)
446
client.add_expected_call(
447
'BzrDir.cloning_metadir', ('quack/', 'False'),
448
'error', ('BranchReference',)),
449
client.add_expected_call(
450
'BzrDir.open_branchV3', ('quack/',),
451
'success', ('ref', self.get_url('referenced'))),
452
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
454
result = a_bzrdir.cloning_metadir()
455
# We should have got a control dir matching the referenced branch.
456
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
457
self.assertEqual(expected._repository_format, result._repository_format)
458
self.assertEqual(expected._branch_format, result._branch_format)
459
self.assertFinished(client)
461
def test_current_server(self):
462
transport = self.get_transport('.')
463
transport = transport.clone('quack')
464
self.make_bzrdir('quack')
465
client = FakeClient(transport.base)
466
reference_bzrdir_format = bzrdir.format_registry.get('default')()
467
control_name = reference_bzrdir_format.network_name()
468
client.add_expected_call(
469
'BzrDir.cloning_metadir', ('quack/', 'False'),
470
'success', (control_name, '', ('branch', ''))),
471
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
473
result = a_bzrdir.cloning_metadir()
474
# We should have got a reference control dir with default branch and
475
# repository formats.
476
# This pokes a little, just to be sure.
477
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
478
self.assertEqual(None, result._repository_format)
479
self.assertEqual(None, result._branch_format)
480
self.assertFinished(client)
483
class TestBzrDirOpen(TestRemote):
485
def make_fake_client_and_transport(self, path='quack'):
486
transport = MemoryTransport()
487
transport.mkdir(path)
488
transport = transport.clone(path)
489
client = FakeClient(transport.base)
490
return client, transport
492
def test_absent(self):
493
client, transport = self.make_fake_client_and_transport()
494
client.add_expected_call(
495
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
496
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
497
remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
498
self.assertFinished(client)
500
def test_present_without_workingtree(self):
501
client, transport = self.make_fake_client_and_transport()
502
client.add_expected_call(
503
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
504
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
505
_client=client, _force_probe=True)
506
self.assertIsInstance(bd, RemoteBzrDir)
507
self.assertFalse(bd.has_workingtree())
508
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
509
self.assertFinished(client)
511
def test_present_with_workingtree(self):
512
client, transport = self.make_fake_client_and_transport()
513
client.add_expected_call(
514
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
515
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
516
_client=client, _force_probe=True)
517
self.assertIsInstance(bd, RemoteBzrDir)
518
self.assertTrue(bd.has_workingtree())
519
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
520
self.assertFinished(client)
522
def test_backwards_compat(self):
523
client, transport = self.make_fake_client_and_transport()
524
client.add_expected_call(
525
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
526
client.add_expected_call(
527
'BzrDir.open', ('quack/',), 'success', ('yes',))
528
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
529
_client=client, _force_probe=True)
530
self.assertIsInstance(bd, RemoteBzrDir)
531
self.assertFinished(client)
533
def test_backwards_compat_hpss_v2(self):
534
client, transport = self.make_fake_client_and_transport()
535
# Monkey-patch fake client to simulate real-world behaviour with v2
536
# server: upon first RPC call detect the protocol version, and because
537
# the version is 2 also do _remember_remote_is_before((1, 6)) before
538
# continuing with the RPC.
539
orig_check_call = client._check_call
540
def check_call(method, args):
541
client._medium._protocol_version = 2
542
client._medium._remember_remote_is_before((1, 6))
543
client._check_call = orig_check_call
544
client._check_call(method, args)
545
client._check_call = check_call
546
client.add_expected_call(
547
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
548
client.add_expected_call(
549
'BzrDir.open', ('quack/',), 'success', ('yes',))
550
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
551
_client=client, _force_probe=True)
552
self.assertIsInstance(bd, RemoteBzrDir)
553
self.assertFinished(client)
556
class TestBzrDirOpenBranch(TestRemote):
558
def test_backwards_compat(self):
559
self.setup_smart_server_with_call_log()
560
self.make_branch('.')
561
a_dir = BzrDir.open(self.get_url('.'))
562
self.reset_smart_call_log()
563
verb = 'BzrDir.open_branchV3'
564
self.disable_verb(verb)
565
format = a_dir.open_branch()
566
call_count = len([call for call in self.hpss_calls if
567
call.call.method == verb])
568
self.assertEqual(1, call_count)
570
def test_branch_present(self):
571
reference_format = self.get_repo_format()
572
network_name = reference_format.network_name()
573
branch_network_name = self.get_branch_format().network_name()
574
transport = MemoryTransport()
575
transport.mkdir('quack')
576
transport = transport.clone('quack')
577
client = FakeClient(transport.base)
578
client.add_expected_call(
579
'BzrDir.open_branchV3', ('quack/',),
580
'success', ('branch', branch_network_name))
581
client.add_expected_call(
582
'BzrDir.find_repositoryV3', ('quack/',),
583
'success', ('ok', '', 'no', 'no', 'no', network_name))
584
client.add_expected_call(
585
'Branch.get_stacked_on_url', ('quack/',),
586
'error', ('NotStacked',))
587
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
589
result = bzrdir.open_branch()
590
self.assertIsInstance(result, RemoteBranch)
591
self.assertEqual(bzrdir, result.bzrdir)
592
self.assertFinished(client)
594
def test_branch_missing(self):
595
transport = MemoryTransport()
596
transport.mkdir('quack')
597
transport = transport.clone('quack')
598
client = FakeClient(transport.base)
599
client.add_error_response('nobranch')
600
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
602
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
604
[('call', 'BzrDir.open_branchV3', ('quack/',))],
607
def test__get_tree_branch(self):
608
# _get_tree_branch is a form of open_branch, but it should only ask for
609
# branch opening, not any other network requests.
611
def open_branch(name=None):
612
calls.append("Called")
614
transport = MemoryTransport()
615
# no requests on the network - catches other api calls being made.
616
client = FakeClient(transport.base)
617
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
619
# patch the open_branch call to record that it was called.
620
bzrdir.open_branch = open_branch
621
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
622
self.assertEqual(["Called"], calls)
623
self.assertEqual([], client._calls)
625
def test_url_quoting_of_path(self):
626
# Relpaths on the wire should not be URL-escaped. So "~" should be
627
# transmitted as "~", not "%7E".
628
transport = RemoteTCPTransport('bzr://localhost/~hello/')
629
client = FakeClient(transport.base)
630
reference_format = self.get_repo_format()
631
network_name = reference_format.network_name()
632
branch_network_name = self.get_branch_format().network_name()
633
client.add_expected_call(
634
'BzrDir.open_branchV3', ('~hello/',),
635
'success', ('branch', branch_network_name))
636
client.add_expected_call(
637
'BzrDir.find_repositoryV3', ('~hello/',),
638
'success', ('ok', '', 'no', 'no', 'no', network_name))
639
client.add_expected_call(
640
'Branch.get_stacked_on_url', ('~hello/',),
641
'error', ('NotStacked',))
642
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
644
result = bzrdir.open_branch()
645
self.assertFinished(client)
647
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
648
reference_format = self.get_repo_format()
649
network_name = reference_format.network_name()
650
transport = MemoryTransport()
651
transport.mkdir('quack')
652
transport = transport.clone('quack')
654
rich_response = 'yes'
658
subtree_response = 'yes'
660
subtree_response = 'no'
661
client = FakeClient(transport.base)
662
client.add_success_response(
663
'ok', '', rich_response, subtree_response, external_lookup,
665
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
667
result = bzrdir.open_repository()
669
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
671
self.assertIsInstance(result, RemoteRepository)
672
self.assertEqual(bzrdir, result.bzrdir)
673
self.assertEqual(rich_root, result._format.rich_root_data)
674
self.assertEqual(subtrees, result._format.supports_tree_reference)
676
def test_open_repository_sets_format_attributes(self):
677
self.check_open_repository(True, True)
678
self.check_open_repository(False, True)
679
self.check_open_repository(True, False)
680
self.check_open_repository(False, False)
681
self.check_open_repository(False, False, 'yes')
683
def test_old_server(self):
684
"""RemoteBzrDirFormat should fail to probe if the server version is too
687
self.assertRaises(errors.NotBranchError,
688
RemoteBzrProber.probe_transport, OldServerTransport())
691
class TestBzrDirCreateBranch(TestRemote):
693
def test_backwards_compat(self):
694
self.setup_smart_server_with_call_log()
695
repo = self.make_repository('.')
696
self.reset_smart_call_log()
697
self.disable_verb('BzrDir.create_branch')
698
branch = repo.bzrdir.create_branch()
699
create_branch_call_count = len([call for call in self.hpss_calls if
700
call.call.method == 'BzrDir.create_branch'])
701
self.assertEqual(1, create_branch_call_count)
703
def test_current_server(self):
704
transport = self.get_transport('.')
705
transport = transport.clone('quack')
706
self.make_repository('quack')
707
client = FakeClient(transport.base)
708
reference_bzrdir_format = bzrdir.format_registry.get('default')()
709
reference_format = reference_bzrdir_format.get_branch_format()
710
network_name = reference_format.network_name()
711
reference_repo_fmt = reference_bzrdir_format.repository_format
712
reference_repo_name = reference_repo_fmt.network_name()
713
client.add_expected_call(
714
'BzrDir.create_branch', ('quack/', network_name),
715
'success', ('ok', network_name, '', 'no', 'no', 'yes',
716
reference_repo_name))
717
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
719
branch = a_bzrdir.create_branch()
720
# We should have got a remote branch
721
self.assertIsInstance(branch, remote.RemoteBranch)
722
# its format should have the settings from the response
723
format = branch._format
724
self.assertEqual(network_name, format.network_name())
727
class TestBzrDirCreateRepository(TestRemote):
729
def test_backwards_compat(self):
730
self.setup_smart_server_with_call_log()
731
bzrdir = self.make_bzrdir('.')
732
self.reset_smart_call_log()
733
self.disable_verb('BzrDir.create_repository')
734
repo = bzrdir.create_repository()
735
create_repo_call_count = len([call for call in self.hpss_calls if
736
call.call.method == 'BzrDir.create_repository'])
737
self.assertEqual(1, create_repo_call_count)
739
def test_current_server(self):
740
transport = self.get_transport('.')
741
transport = transport.clone('quack')
742
self.make_bzrdir('quack')
743
client = FakeClient(transport.base)
744
reference_bzrdir_format = bzrdir.format_registry.get('default')()
745
reference_format = reference_bzrdir_format.repository_format
746
network_name = reference_format.network_name()
747
client.add_expected_call(
748
'BzrDir.create_repository', ('quack/',
749
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
751
'success', ('ok', 'yes', 'yes', 'yes', network_name))
752
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
754
repo = a_bzrdir.create_repository()
755
# We should have got a remote repository
756
self.assertIsInstance(repo, remote.RemoteRepository)
757
# its format should have the settings from the response
758
format = repo._format
759
self.assertTrue(format.rich_root_data)
760
self.assertTrue(format.supports_tree_reference)
761
self.assertTrue(format.supports_external_lookups)
762
self.assertEqual(network_name, format.network_name())
765
class TestBzrDirOpenRepository(TestRemote):
767
def test_backwards_compat_1_2_3(self):
768
# fallback all the way to the first version.
769
reference_format = self.get_repo_format()
770
network_name = reference_format.network_name()
771
server_url = 'bzr://example.com/'
772
self.permit_url(server_url)
773
client = FakeClient(server_url)
774
client.add_unknown_method_response('BzrDir.find_repositoryV3')
775
client.add_unknown_method_response('BzrDir.find_repositoryV2')
776
client.add_success_response('ok', '', 'no', 'no')
777
# A real repository instance will be created to determine the network
779
client.add_success_response_with_body(
780
"Bazaar-NG meta directory, format 1\n", 'ok')
781
client.add_success_response_with_body(
782
reference_format.get_format_string(), 'ok')
783
# PackRepository wants to do a stat
784
client.add_success_response('stat', '0', '65535')
785
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
787
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
789
repo = bzrdir.open_repository()
791
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
792
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
793
('call', 'BzrDir.find_repository', ('quack/',)),
794
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
795
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
796
('call', 'stat', ('/quack/.bzr/repository',)),
799
self.assertEqual(network_name, repo._format.network_name())
801
def test_backwards_compat_2(self):
802
# fallback to find_repositoryV2
803
reference_format = self.get_repo_format()
804
network_name = reference_format.network_name()
805
server_url = 'bzr://example.com/'
806
self.permit_url(server_url)
807
client = FakeClient(server_url)
808
client.add_unknown_method_response('BzrDir.find_repositoryV3')
809
client.add_success_response('ok', '', 'no', 'no', 'no')
810
# A real repository instance will be created to determine the network
812
client.add_success_response_with_body(
813
"Bazaar-NG meta directory, format 1\n", 'ok')
814
client.add_success_response_with_body(
815
reference_format.get_format_string(), 'ok')
816
# PackRepository wants to do a stat
817
client.add_success_response('stat', '0', '65535')
818
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
820
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
822
repo = bzrdir.open_repository()
824
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
825
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
826
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
827
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
828
('call', 'stat', ('/quack/.bzr/repository',)),
831
self.assertEqual(network_name, repo._format.network_name())
833
def test_current_server(self):
834
reference_format = self.get_repo_format()
835
network_name = reference_format.network_name()
836
transport = MemoryTransport()
837
transport.mkdir('quack')
838
transport = transport.clone('quack')
839
client = FakeClient(transport.base)
840
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
841
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
843
repo = bzrdir.open_repository()
845
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
847
self.assertEqual(network_name, repo._format.network_name())
850
class TestBzrDirFormatInitializeEx(TestRemote):
852
def test_success(self):
853
"""Simple test for typical successful call."""
854
fmt = bzrdir.RemoteBzrDirFormat()
855
default_format_name = BzrDirFormat.get_default_format().network_name()
856
transport = self.get_transport()
857
client = FakeClient(transport.base)
858
client.add_expected_call(
859
'BzrDirFormat.initialize_ex_1.16',
860
(default_format_name, 'path', 'False', 'False', 'False', '',
861
'', '', '', 'False'),
863
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
864
'bzrdir fmt', 'False', '', '', 'repo lock token'))
865
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
866
# it's currently hard to test that without supplying a real remote
867
# transport connected to a real server.
868
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
869
transport, False, False, False, None, None, None, None, False)
870
self.assertFinished(client)
872
def test_error(self):
873
"""Error responses are translated, e.g. 'PermissionDenied' raises the
874
corresponding error from the client.
876
fmt = bzrdir.RemoteBzrDirFormat()
877
default_format_name = BzrDirFormat.get_default_format().network_name()
878
transport = self.get_transport()
879
client = FakeClient(transport.base)
880
client.add_expected_call(
881
'BzrDirFormat.initialize_ex_1.16',
882
(default_format_name, 'path', 'False', 'False', 'False', '',
883
'', '', '', 'False'),
885
('PermissionDenied', 'path', 'extra info'))
886
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
887
# it's currently hard to test that without supplying a real remote
888
# transport connected to a real server.
889
err = self.assertRaises(errors.PermissionDenied,
890
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
891
False, False, False, None, None, None, None, False)
892
self.assertEqual('path', err.path)
893
self.assertEqual(': extra info', err.extra)
894
self.assertFinished(client)
896
def test_error_from_real_server(self):
897
"""Integration test for error translation."""
898
transport = self.make_smart_server('foo')
899
transport = transport.clone('no-such-path')
900
fmt = bzrdir.RemoteBzrDirFormat()
901
err = self.assertRaises(errors.NoSuchFile,
902
fmt.initialize_on_transport_ex, transport, create_prefix=False)
905
class OldSmartClient(object):
906
"""A fake smart client for test_old_version that just returns a version one
907
response to the 'hello' (query version) command.
910
def get_request(self):
911
input_file = StringIO('ok\x011\n')
912
output_file = StringIO()
913
client_medium = medium.SmartSimplePipesClientMedium(
914
input_file, output_file)
915
return medium.SmartClientStreamMediumRequest(client_medium)
917
def protocol_version(self):
921
class OldServerTransport(object):
922
"""A fake transport for test_old_server that reports it's smart server
923
protocol version as version one.
929
def get_smart_client(self):
930
return OldSmartClient()
933
class RemoteBzrDirTestCase(TestRemote):
935
def make_remote_bzrdir(self, transport, client):
936
"""Make a RemotebzrDir using 'client' as the _client."""
937
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
941
class RemoteBranchTestCase(RemoteBzrDirTestCase):
943
def lock_remote_branch(self, branch):
944
"""Trick a RemoteBranch into thinking it is locked."""
945
branch._lock_mode = 'w'
946
branch._lock_count = 2
947
branch._lock_token = 'branch token'
948
branch._repo_lock_token = 'repo token'
949
branch.repository._lock_mode = 'w'
950
branch.repository._lock_count = 2
951
branch.repository._lock_token = 'repo token'
953
def make_remote_branch(self, transport, client):
954
"""Make a RemoteBranch using 'client' as its _SmartClient.
956
A RemoteBzrDir and RemoteRepository will also be created to fill out
957
the RemoteBranch, albeit with stub values for some of their attributes.
959
# we do not want bzrdir to make any remote calls, so use False as its
960
# _client. If it tries to make a remote call, this will fail
962
bzrdir = self.make_remote_bzrdir(transport, False)
963
repo = RemoteRepository(bzrdir, None, _client=client)
964
branch_format = self.get_branch_format()
965
format = RemoteBranchFormat(network_name=branch_format.network_name())
966
return RemoteBranch(bzrdir, repo, _client=client, format=format)
969
class TestBranchGetParent(RemoteBranchTestCase):
971
def test_no_parent(self):
972
# in an empty branch we decode the response properly
973
transport = MemoryTransport()
974
client = FakeClient(transport.base)
975
client.add_expected_call(
976
'Branch.get_stacked_on_url', ('quack/',),
977
'error', ('NotStacked',))
978
client.add_expected_call(
979
'Branch.get_parent', ('quack/',),
981
transport.mkdir('quack')
982
transport = transport.clone('quack')
983
branch = self.make_remote_branch(transport, client)
984
result = branch.get_parent()
985
self.assertFinished(client)
986
self.assertEqual(None, result)
988
def test_parent_relative(self):
989
transport = MemoryTransport()
990
client = FakeClient(transport.base)
991
client.add_expected_call(
992
'Branch.get_stacked_on_url', ('kwaak/',),
993
'error', ('NotStacked',))
994
client.add_expected_call(
995
'Branch.get_parent', ('kwaak/',),
996
'success', ('../foo/',))
997
transport.mkdir('kwaak')
998
transport = transport.clone('kwaak')
999
branch = self.make_remote_branch(transport, client)
1000
result = branch.get_parent()
1001
self.assertEqual(transport.clone('../foo').base, result)
1003
def test_parent_absolute(self):
1004
transport = MemoryTransport()
1005
client = FakeClient(transport.base)
1006
client.add_expected_call(
1007
'Branch.get_stacked_on_url', ('kwaak/',),
1008
'error', ('NotStacked',))
1009
client.add_expected_call(
1010
'Branch.get_parent', ('kwaak/',),
1011
'success', ('http://foo/',))
1012
transport.mkdir('kwaak')
1013
transport = transport.clone('kwaak')
1014
branch = self.make_remote_branch(transport, client)
1015
result = branch.get_parent()
1016
self.assertEqual('http://foo/', result)
1017
self.assertFinished(client)
1020
class TestBranchSetParentLocation(RemoteBranchTestCase):
1022
def test_no_parent(self):
1023
# We call the verb when setting parent to None
1024
transport = MemoryTransport()
1025
client = FakeClient(transport.base)
1026
client.add_expected_call(
1027
'Branch.get_stacked_on_url', ('quack/',),
1028
'error', ('NotStacked',))
1029
client.add_expected_call(
1030
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1032
transport.mkdir('quack')
1033
transport = transport.clone('quack')
1034
branch = self.make_remote_branch(transport, client)
1035
branch._lock_token = 'b'
1036
branch._repo_lock_token = 'r'
1037
branch._set_parent_location(None)
1038
self.assertFinished(client)
1040
def test_parent(self):
1041
transport = MemoryTransport()
1042
client = FakeClient(transport.base)
1043
client.add_expected_call(
1044
'Branch.get_stacked_on_url', ('kwaak/',),
1045
'error', ('NotStacked',))
1046
client.add_expected_call(
1047
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1049
transport.mkdir('kwaak')
1050
transport = transport.clone('kwaak')
1051
branch = self.make_remote_branch(transport, client)
1052
branch._lock_token = 'b'
1053
branch._repo_lock_token = 'r'
1054
branch._set_parent_location('foo')
1055
self.assertFinished(client)
1057
def test_backwards_compat(self):
1058
self.setup_smart_server_with_call_log()
1059
branch = self.make_branch('.')
1060
self.reset_smart_call_log()
1061
verb = 'Branch.set_parent_location'
1062
self.disable_verb(verb)
1063
branch.set_parent('http://foo/')
1064
self.assertLength(12, self.hpss_calls)
1067
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1069
def test_backwards_compat(self):
1070
self.setup_smart_server_with_call_log()
1071
branch = self.make_branch('.')
1072
self.reset_smart_call_log()
1073
verb = 'Branch.get_tags_bytes'
1074
self.disable_verb(verb)
1075
branch.tags.get_tag_dict()
1076
call_count = len([call for call in self.hpss_calls if
1077
call.call.method == verb])
1078
self.assertEqual(1, call_count)
1080
def test_trivial(self):
1081
transport = MemoryTransport()
1082
client = FakeClient(transport.base)
1083
client.add_expected_call(
1084
'Branch.get_stacked_on_url', ('quack/',),
1085
'error', ('NotStacked',))
1086
client.add_expected_call(
1087
'Branch.get_tags_bytes', ('quack/',),
1089
transport.mkdir('quack')
1090
transport = transport.clone('quack')
1091
branch = self.make_remote_branch(transport, client)
1092
result = branch.tags.get_tag_dict()
1093
self.assertFinished(client)
1094
self.assertEqual({}, result)
1097
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1099
def test_trivial(self):
1100
transport = MemoryTransport()
1101
client = FakeClient(transport.base)
1102
client.add_expected_call(
1103
'Branch.get_stacked_on_url', ('quack/',),
1104
'error', ('NotStacked',))
1105
client.add_expected_call(
1106
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1108
transport.mkdir('quack')
1109
transport = transport.clone('quack')
1110
branch = self.make_remote_branch(transport, client)
1111
self.lock_remote_branch(branch)
1112
branch._set_tags_bytes('tags bytes')
1113
self.assertFinished(client)
1114
self.assertEqual('tags bytes', client._calls[-1][-1])
1116
def test_backwards_compatible(self):
1117
transport = MemoryTransport()
1118
client = FakeClient(transport.base)
1119
client.add_expected_call(
1120
'Branch.get_stacked_on_url', ('quack/',),
1121
'error', ('NotStacked',))
1122
client.add_expected_call(
1123
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1124
'unknown', ('Branch.set_tags_bytes',))
1125
transport.mkdir('quack')
1126
transport = transport.clone('quack')
1127
branch = self.make_remote_branch(transport, client)
1128
self.lock_remote_branch(branch)
1129
class StubRealBranch(object):
1132
def _set_tags_bytes(self, bytes):
1133
self.calls.append(('set_tags_bytes', bytes))
1134
real_branch = StubRealBranch()
1135
branch._real_branch = real_branch
1136
branch._set_tags_bytes('tags bytes')
1137
# Call a second time, to exercise the 'remote version already inferred'
1139
branch._set_tags_bytes('tags bytes')
1140
self.assertFinished(client)
1142
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1145
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1147
def test_empty_branch(self):
1148
# in an empty branch we decode the response properly
1149
transport = MemoryTransport()
1150
client = FakeClient(transport.base)
1151
client.add_expected_call(
1152
'Branch.get_stacked_on_url', ('quack/',),
1153
'error', ('NotStacked',))
1154
client.add_expected_call(
1155
'Branch.last_revision_info', ('quack/',),
1156
'success', ('ok', '0', 'null:'))
1157
transport.mkdir('quack')
1158
transport = transport.clone('quack')
1159
branch = self.make_remote_branch(transport, client)
1160
result = branch.last_revision_info()
1161
self.assertFinished(client)
1162
self.assertEqual((0, NULL_REVISION), result)
1164
def test_non_empty_branch(self):
1165
# in a non-empty branch we also decode the response properly
1166
revid = u'\xc8'.encode('utf8')
1167
transport = MemoryTransport()
1168
client = FakeClient(transport.base)
1169
client.add_expected_call(
1170
'Branch.get_stacked_on_url', ('kwaak/',),
1171
'error', ('NotStacked',))
1172
client.add_expected_call(
1173
'Branch.last_revision_info', ('kwaak/',),
1174
'success', ('ok', '2', revid))
1175
transport.mkdir('kwaak')
1176
transport = transport.clone('kwaak')
1177
branch = self.make_remote_branch(transport, client)
1178
result = branch.last_revision_info()
1179
self.assertEqual((2, revid), result)
1182
class TestBranch_get_stacked_on_url(TestRemote):
1183
"""Test Branch._get_stacked_on_url rpc"""
1185
def test_get_stacked_on_invalid_url(self):
1186
# test that asking for a stacked on url the server can't access works.
1187
# This isn't perfect, but then as we're in the same process there
1188
# really isn't anything we can do to be 100% sure that the server
1189
# doesn't just open in - this test probably needs to be rewritten using
1190
# a spawn()ed server.
1191
stacked_branch = self.make_branch('stacked', format='1.9')
1192
memory_branch = self.make_branch('base', format='1.9')
1193
vfs_url = self.get_vfs_only_url('base')
1194
stacked_branch.set_stacked_on_url(vfs_url)
1195
transport = stacked_branch.bzrdir.root_transport
1196
client = FakeClient(transport.base)
1197
client.add_expected_call(
1198
'Branch.get_stacked_on_url', ('stacked/',),
1199
'success', ('ok', vfs_url))
1200
# XXX: Multiple calls are bad, this second call documents what is
1202
client.add_expected_call(
1203
'Branch.get_stacked_on_url', ('stacked/',),
1204
'success', ('ok', vfs_url))
1205
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1207
repo_fmt = remote.RemoteRepositoryFormat()
1208
repo_fmt._custom_format = stacked_branch.repository._format
1209
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1211
result = branch.get_stacked_on_url()
1212
self.assertEqual(vfs_url, result)
1214
def test_backwards_compatible(self):
1215
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1216
base_branch = self.make_branch('base', format='1.6')
1217
stacked_branch = self.make_branch('stacked', format='1.6')
1218
stacked_branch.set_stacked_on_url('../base')
1219
client = FakeClient(self.get_url())
1220
branch_network_name = self.get_branch_format().network_name()
1221
client.add_expected_call(
1222
'BzrDir.open_branchV3', ('stacked/',),
1223
'success', ('branch', branch_network_name))
1224
client.add_expected_call(
1225
'BzrDir.find_repositoryV3', ('stacked/',),
1226
'success', ('ok', '', 'no', 'no', 'yes',
1227
stacked_branch.repository._format.network_name()))
1228
# called twice, once from constructor and then again by us
1229
client.add_expected_call(
1230
'Branch.get_stacked_on_url', ('stacked/',),
1231
'unknown', ('Branch.get_stacked_on_url',))
1232
client.add_expected_call(
1233
'Branch.get_stacked_on_url', ('stacked/',),
1234
'unknown', ('Branch.get_stacked_on_url',))
1235
# this will also do vfs access, but that goes direct to the transport
1236
# and isn't seen by the FakeClient.
1237
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1238
remote.RemoteBzrDirFormat(), _client=client)
1239
branch = bzrdir.open_branch()
1240
result = branch.get_stacked_on_url()
1241
self.assertEqual('../base', result)
1242
self.assertFinished(client)
1243
# it's in the fallback list both for the RemoteRepository and its vfs
1245
self.assertEqual(1, len(branch.repository._fallback_repositories))
1247
len(branch.repository._real_repository._fallback_repositories))
1249
def test_get_stacked_on_real_branch(self):
1250
base_branch = self.make_branch('base')
1251
stacked_branch = self.make_branch('stacked')
1252
stacked_branch.set_stacked_on_url('../base')
1253
reference_format = self.get_repo_format()
1254
network_name = reference_format.network_name()
1255
client = FakeClient(self.get_url())
1256
branch_network_name = self.get_branch_format().network_name()
1257
client.add_expected_call(
1258
'BzrDir.open_branchV3', ('stacked/',),
1259
'success', ('branch', branch_network_name))
1260
client.add_expected_call(
1261
'BzrDir.find_repositoryV3', ('stacked/',),
1262
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1263
# called twice, once from constructor and then again by us
1264
client.add_expected_call(
1265
'Branch.get_stacked_on_url', ('stacked/',),
1266
'success', ('ok', '../base'))
1267
client.add_expected_call(
1268
'Branch.get_stacked_on_url', ('stacked/',),
1269
'success', ('ok', '../base'))
1270
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1271
remote.RemoteBzrDirFormat(), _client=client)
1272
branch = bzrdir.open_branch()
1273
result = branch.get_stacked_on_url()
1274
self.assertEqual('../base', result)
1275
self.assertFinished(client)
1276
# it's in the fallback list both for the RemoteRepository.
1277
self.assertEqual(1, len(branch.repository._fallback_repositories))
1278
# And we haven't had to construct a real repository.
1279
self.assertEqual(None, branch.repository._real_repository)
1282
class TestBranchSetLastRevision(RemoteBranchTestCase):
1284
def test_set_empty(self):
1285
# set_revision_history([]) is translated to calling
1286
# Branch.set_last_revision(path, '') on the wire.
1287
transport = MemoryTransport()
1288
transport.mkdir('branch')
1289
transport = transport.clone('branch')
1291
client = FakeClient(transport.base)
1292
client.add_expected_call(
1293
'Branch.get_stacked_on_url', ('branch/',),
1294
'error', ('NotStacked',))
1295
client.add_expected_call(
1296
'Branch.lock_write', ('branch/', '', ''),
1297
'success', ('ok', 'branch token', 'repo token'))
1298
client.add_expected_call(
1299
'Branch.last_revision_info',
1301
'success', ('ok', '0', 'null:'))
1302
client.add_expected_call(
1303
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1305
client.add_expected_call(
1306
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1308
branch = self.make_remote_branch(transport, client)
1309
# This is a hack to work around the problem that RemoteBranch currently
1310
# unnecessarily invokes _ensure_real upon a call to lock_write.
1311
branch._ensure_real = lambda: None
1313
result = branch.set_revision_history([])
1315
self.assertEqual(None, result)
1316
self.assertFinished(client)
1318
def test_set_nonempty(self):
1319
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1320
# Branch.set_last_revision(path, rev-idN) on the wire.
1321
transport = MemoryTransport()
1322
transport.mkdir('branch')
1323
transport = transport.clone('branch')
1325
client = FakeClient(transport.base)
1326
client.add_expected_call(
1327
'Branch.get_stacked_on_url', ('branch/',),
1328
'error', ('NotStacked',))
1329
client.add_expected_call(
1330
'Branch.lock_write', ('branch/', '', ''),
1331
'success', ('ok', 'branch token', 'repo token'))
1332
client.add_expected_call(
1333
'Branch.last_revision_info',
1335
'success', ('ok', '0', 'null:'))
1337
encoded_body = bz2.compress('\n'.join(lines))
1338
client.add_success_response_with_body(encoded_body, 'ok')
1339
client.add_expected_call(
1340
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1342
client.add_expected_call(
1343
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1345
branch = self.make_remote_branch(transport, client)
1346
# This is a hack to work around the problem that RemoteBranch currently
1347
# unnecessarily invokes _ensure_real upon a call to lock_write.
1348
branch._ensure_real = lambda: None
1349
# Lock the branch, reset the record of remote calls.
1351
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1353
self.assertEqual(None, result)
1354
self.assertFinished(client)
1356
def test_no_such_revision(self):
1357
transport = MemoryTransport()
1358
transport.mkdir('branch')
1359
transport = transport.clone('branch')
1360
# A response of 'NoSuchRevision' is translated into an exception.
1361
client = FakeClient(transport.base)
1362
client.add_expected_call(
1363
'Branch.get_stacked_on_url', ('branch/',),
1364
'error', ('NotStacked',))
1365
client.add_expected_call(
1366
'Branch.lock_write', ('branch/', '', ''),
1367
'success', ('ok', 'branch token', 'repo token'))
1368
client.add_expected_call(
1369
'Branch.last_revision_info',
1371
'success', ('ok', '0', 'null:'))
1372
# get_graph calls to construct the revision history, for the set_rh
1375
encoded_body = bz2.compress('\n'.join(lines))
1376
client.add_success_response_with_body(encoded_body, 'ok')
1377
client.add_expected_call(
1378
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1379
'error', ('NoSuchRevision', 'rev-id'))
1380
client.add_expected_call(
1381
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1384
branch = self.make_remote_branch(transport, client)
1387
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1389
self.assertFinished(client)
1391
def test_tip_change_rejected(self):
1392
"""TipChangeRejected responses cause a TipChangeRejected exception to
1395
transport = MemoryTransport()
1396
transport.mkdir('branch')
1397
transport = transport.clone('branch')
1398
client = FakeClient(transport.base)
1399
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1400
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1401
client.add_expected_call(
1402
'Branch.get_stacked_on_url', ('branch/',),
1403
'error', ('NotStacked',))
1404
client.add_expected_call(
1405
'Branch.lock_write', ('branch/', '', ''),
1406
'success', ('ok', 'branch token', 'repo token'))
1407
client.add_expected_call(
1408
'Branch.last_revision_info',
1410
'success', ('ok', '0', 'null:'))
1412
encoded_body = bz2.compress('\n'.join(lines))
1413
client.add_success_response_with_body(encoded_body, 'ok')
1414
client.add_expected_call(
1415
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1416
'error', ('TipChangeRejected', rejection_msg_utf8))
1417
client.add_expected_call(
1418
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1420
branch = self.make_remote_branch(transport, client)
1421
branch._ensure_real = lambda: None
1423
# The 'TipChangeRejected' error response triggered by calling
1424
# set_revision_history causes a TipChangeRejected exception.
1425
err = self.assertRaises(
1426
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1427
# The UTF-8 message from the response has been decoded into a unicode
1429
self.assertIsInstance(err.msg, unicode)
1430
self.assertEqual(rejection_msg_unicode, err.msg)
1432
self.assertFinished(client)
1435
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1437
def test_set_last_revision_info(self):
1438
# set_last_revision_info(num, 'rev-id') is translated to calling
1439
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1440
transport = MemoryTransport()
1441
transport.mkdir('branch')
1442
transport = transport.clone('branch')
1443
client = FakeClient(transport.base)
1444
# get_stacked_on_url
1445
client.add_error_response('NotStacked')
1447
client.add_success_response('ok', 'branch token', 'repo token')
1448
# query the current revision
1449
client.add_success_response('ok', '0', 'null:')
1451
client.add_success_response('ok')
1453
client.add_success_response('ok')
1455
branch = self.make_remote_branch(transport, client)
1456
# Lock the branch, reset the record of remote calls.
1459
result = branch.set_last_revision_info(1234, 'a-revision-id')
1461
[('call', 'Branch.last_revision_info', ('branch/',)),
1462
('call', 'Branch.set_last_revision_info',
1463
('branch/', 'branch token', 'repo token',
1464
'1234', 'a-revision-id'))],
1466
self.assertEqual(None, result)
1468
def test_no_such_revision(self):
1469
# A response of 'NoSuchRevision' is translated into an exception.
1470
transport = MemoryTransport()
1471
transport.mkdir('branch')
1472
transport = transport.clone('branch')
1473
client = FakeClient(transport.base)
1474
# get_stacked_on_url
1475
client.add_error_response('NotStacked')
1477
client.add_success_response('ok', 'branch token', 'repo token')
1479
client.add_error_response('NoSuchRevision', 'revid')
1481
client.add_success_response('ok')
1483
branch = self.make_remote_branch(transport, client)
1484
# Lock the branch, reset the record of remote calls.
1489
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1492
def test_backwards_compatibility(self):
1493
"""If the server does not support the Branch.set_last_revision_info
1494
verb (which is new in 1.4), then the client falls back to VFS methods.
1496
# This test is a little messy. Unlike most tests in this file, it
1497
# doesn't purely test what a Remote* object sends over the wire, and
1498
# how it reacts to responses from the wire. It instead relies partly
1499
# on asserting that the RemoteBranch will call
1500
# self._real_branch.set_last_revision_info(...).
1502
# First, set up our RemoteBranch with a FakeClient that raises
1503
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1504
transport = MemoryTransport()
1505
transport.mkdir('branch')
1506
transport = transport.clone('branch')
1507
client = FakeClient(transport.base)
1508
client.add_expected_call(
1509
'Branch.get_stacked_on_url', ('branch/',),
1510
'error', ('NotStacked',))
1511
client.add_expected_call(
1512
'Branch.last_revision_info',
1514
'success', ('ok', '0', 'null:'))
1515
client.add_expected_call(
1516
'Branch.set_last_revision_info',
1517
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1518
'unknown', 'Branch.set_last_revision_info')
1520
branch = self.make_remote_branch(transport, client)
1521
class StubRealBranch(object):
1524
def set_last_revision_info(self, revno, revision_id):
1526
('set_last_revision_info', revno, revision_id))
1527
def _clear_cached_state(self):
1529
real_branch = StubRealBranch()
1530
branch._real_branch = real_branch
1531
self.lock_remote_branch(branch)
1533
# Call set_last_revision_info, and verify it behaved as expected.
1534
result = branch.set_last_revision_info(1234, 'a-revision-id')
1536
[('set_last_revision_info', 1234, 'a-revision-id')],
1538
self.assertFinished(client)
1540
def test_unexpected_error(self):
1541
# If the server sends an error the client doesn't understand, it gets
1542
# turned into an UnknownErrorFromSmartServer, which is presented as a
1543
# non-internal error to the user.
1544
transport = MemoryTransport()
1545
transport.mkdir('branch')
1546
transport = transport.clone('branch')
1547
client = FakeClient(transport.base)
1548
# get_stacked_on_url
1549
client.add_error_response('NotStacked')
1551
client.add_success_response('ok', 'branch token', 'repo token')
1553
client.add_error_response('UnexpectedError')
1555
client.add_success_response('ok')
1557
branch = self.make_remote_branch(transport, client)
1558
# Lock the branch, reset the record of remote calls.
1562
err = self.assertRaises(
1563
errors.UnknownErrorFromSmartServer,
1564
branch.set_last_revision_info, 123, 'revid')
1565
self.assertEqual(('UnexpectedError',), err.error_tuple)
1568
def test_tip_change_rejected(self):
1569
"""TipChangeRejected responses cause a TipChangeRejected exception to
1572
transport = MemoryTransport()
1573
transport.mkdir('branch')
1574
transport = transport.clone('branch')
1575
client = FakeClient(transport.base)
1576
# get_stacked_on_url
1577
client.add_error_response('NotStacked')
1579
client.add_success_response('ok', 'branch token', 'repo token')
1581
client.add_error_response('TipChangeRejected', 'rejection message')
1583
client.add_success_response('ok')
1585
branch = self.make_remote_branch(transport, client)
1586
# Lock the branch, reset the record of remote calls.
1588
self.addCleanup(branch.unlock)
1591
# The 'TipChangeRejected' error response triggered by calling
1592
# set_last_revision_info causes a TipChangeRejected exception.
1593
err = self.assertRaises(
1594
errors.TipChangeRejected,
1595
branch.set_last_revision_info, 123, 'revid')
1596
self.assertEqual('rejection message', err.msg)
1599
class TestBranchGetSetConfig(RemoteBranchTestCase):
1601
def test_get_branch_conf(self):
1602
# in an empty branch we decode the response properly
1603
client = FakeClient()
1604
client.add_expected_call(
1605
'Branch.get_stacked_on_url', ('memory:///',),
1606
'error', ('NotStacked',),)
1607
client.add_success_response_with_body('# config file body', 'ok')
1608
transport = MemoryTransport()
1609
branch = self.make_remote_branch(transport, client)
1610
config = branch.get_config()
1611
config.has_explicit_nickname()
1613
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1614
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1617
def test_get_multi_line_branch_conf(self):
1618
# Make sure that multiple-line branch.conf files are supported
1620
# https://bugs.launchpad.net/bzr/+bug/354075
1621
client = FakeClient()
1622
client.add_expected_call(
1623
'Branch.get_stacked_on_url', ('memory:///',),
1624
'error', ('NotStacked',),)
1625
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1626
transport = MemoryTransport()
1627
branch = self.make_remote_branch(transport, client)
1628
config = branch.get_config()
1629
self.assertEqual(u'2', config.get_user_option('b'))
1631
def test_set_option(self):
1632
client = FakeClient()
1633
client.add_expected_call(
1634
'Branch.get_stacked_on_url', ('memory:///',),
1635
'error', ('NotStacked',),)
1636
client.add_expected_call(
1637
'Branch.lock_write', ('memory:///', '', ''),
1638
'success', ('ok', 'branch token', 'repo token'))
1639
client.add_expected_call(
1640
'Branch.set_config_option', ('memory:///', 'branch token',
1641
'repo token', 'foo', 'bar', ''),
1643
client.add_expected_call(
1644
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1646
transport = MemoryTransport()
1647
branch = self.make_remote_branch(transport, client)
1649
config = branch._get_config()
1650
config.set_option('foo', 'bar')
1652
self.assertFinished(client)
1654
def test_set_option_with_dict(self):
1655
client = FakeClient()
1656
client.add_expected_call(
1657
'Branch.get_stacked_on_url', ('memory:///',),
1658
'error', ('NotStacked',),)
1659
client.add_expected_call(
1660
'Branch.lock_write', ('memory:///', '', ''),
1661
'success', ('ok', 'branch token', 'repo token'))
1662
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1663
client.add_expected_call(
1664
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1665
'repo token', encoded_dict_value, 'foo', ''),
1667
client.add_expected_call(
1668
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1670
transport = MemoryTransport()
1671
branch = self.make_remote_branch(transport, client)
1673
config = branch._get_config()
1675
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1678
self.assertFinished(client)
1680
def test_backwards_compat_set_option(self):
1681
self.setup_smart_server_with_call_log()
1682
branch = self.make_branch('.')
1683
verb = 'Branch.set_config_option'
1684
self.disable_verb(verb)
1686
self.addCleanup(branch.unlock)
1687
self.reset_smart_call_log()
1688
branch._get_config().set_option('value', 'name')
1689
self.assertLength(10, self.hpss_calls)
1690
self.assertEqual('value', branch._get_config().get_option('name'))
1692
def test_backwards_compat_set_option_with_dict(self):
1693
self.setup_smart_server_with_call_log()
1694
branch = self.make_branch('.')
1695
verb = 'Branch.set_config_option_dict'
1696
self.disable_verb(verb)
1698
self.addCleanup(branch.unlock)
1699
self.reset_smart_call_log()
1700
config = branch._get_config()
1701
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
1702
config.set_option(value_dict, 'name')
1703
self.assertLength(10, self.hpss_calls)
1704
self.assertEqual(value_dict, branch._get_config().get_option('name'))
1707
class TestBranchLockWrite(RemoteBranchTestCase):
1709
def test_lock_write_unlockable(self):
1710
transport = MemoryTransport()
1711
client = FakeClient(transport.base)
1712
client.add_expected_call(
1713
'Branch.get_stacked_on_url', ('quack/',),
1714
'error', ('NotStacked',),)
1715
client.add_expected_call(
1716
'Branch.lock_write', ('quack/', '', ''),
1717
'error', ('UnlockableTransport',))
1718
transport.mkdir('quack')
1719
transport = transport.clone('quack')
1720
branch = self.make_remote_branch(transport, client)
1721
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1722
self.assertFinished(client)
1725
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1727
def test__get_config(self):
1728
client = FakeClient()
1729
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1730
transport = MemoryTransport()
1731
bzrdir = self.make_remote_bzrdir(transport, client)
1732
config = bzrdir.get_config()
1733
self.assertEqual('/', config.get_default_stack_on())
1735
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1738
def test_set_option_uses_vfs(self):
1739
self.setup_smart_server_with_call_log()
1740
bzrdir = self.make_bzrdir('.')
1741
self.reset_smart_call_log()
1742
config = bzrdir.get_config()
1743
config.set_default_stack_on('/')
1744
self.assertLength(3, self.hpss_calls)
1746
def test_backwards_compat_get_option(self):
1747
self.setup_smart_server_with_call_log()
1748
bzrdir = self.make_bzrdir('.')
1749
verb = 'BzrDir.get_config_file'
1750
self.disable_verb(verb)
1751
self.reset_smart_call_log()
1752
self.assertEqual(None,
1753
bzrdir._get_config().get_option('default_stack_on'))
1754
self.assertLength(3, self.hpss_calls)
1757
class TestTransportIsReadonly(tests.TestCase):
1759
def test_true(self):
1760
client = FakeClient()
1761
client.add_success_response('yes')
1762
transport = RemoteTransport('bzr://example.com/', medium=False,
1764
self.assertEqual(True, transport.is_readonly())
1766
[('call', 'Transport.is_readonly', ())],
1769
def test_false(self):
1770
client = FakeClient()
1771
client.add_success_response('no')
1772
transport = RemoteTransport('bzr://example.com/', medium=False,
1774
self.assertEqual(False, transport.is_readonly())
1776
[('call', 'Transport.is_readonly', ())],
1779
def test_error_from_old_server(self):
1780
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1782
Clients should treat it as a "no" response, because is_readonly is only
1783
advisory anyway (a transport could be read-write, but then the
1784
underlying filesystem could be readonly anyway).
1786
client = FakeClient()
1787
client.add_unknown_method_response('Transport.is_readonly')
1788
transport = RemoteTransport('bzr://example.com/', medium=False,
1790
self.assertEqual(False, transport.is_readonly())
1792
[('call', 'Transport.is_readonly', ())],
1796
class TestTransportMkdir(tests.TestCase):
1798
def test_permissiondenied(self):
1799
client = FakeClient()
1800
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1801
transport = RemoteTransport('bzr://example.com/', medium=False,
1803
exc = self.assertRaises(
1804
errors.PermissionDenied, transport.mkdir, 'client path')
1805
expected_error = errors.PermissionDenied('/client path', 'extra')
1806
self.assertEqual(expected_error, exc)
1809
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1811
def test_defaults_to_none(self):
1812
t = RemoteSSHTransport('bzr+ssh://example.com')
1813
self.assertIs(None, t._get_credentials()[0])
1815
def test_uses_authentication_config(self):
1816
conf = config.AuthenticationConfig()
1817
conf._get_config().update(
1818
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1821
t = RemoteSSHTransport('bzr+ssh://example.com')
1822
self.assertEqual('bar', t._get_credentials()[0])
1825
class TestRemoteRepository(TestRemote):
1826
"""Base for testing RemoteRepository protocol usage.
1828
These tests contain frozen requests and responses. We want any changes to
1829
what is sent or expected to be require a thoughtful update to these tests
1830
because they might break compatibility with different-versioned servers.
1833
def setup_fake_client_and_repository(self, transport_path):
1834
"""Create the fake client and repository for testing with.
1836
There's no real server here; we just have canned responses sent
1839
:param transport_path: Path below the root of the MemoryTransport
1840
where the repository will be created.
1842
transport = MemoryTransport()
1843
transport.mkdir(transport_path)
1844
client = FakeClient(transport.base)
1845
transport = transport.clone(transport_path)
1846
# we do not want bzrdir to make any remote calls
1847
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1849
repo = RemoteRepository(bzrdir, None, _client=client)
1853
def remoted_description(format):
1854
return 'Remote: ' + format.get_format_description()
1857
class TestBranchFormat(tests.TestCase):
1859
def test_get_format_description(self):
1860
remote_format = RemoteBranchFormat()
1861
real_format = branch.BranchFormat.get_default_format()
1862
remote_format._network_name = real_format.network_name()
1863
self.assertEqual(remoted_description(real_format),
1864
remote_format.get_format_description())
1867
class TestRepositoryFormat(TestRemoteRepository):
1869
def test_fast_delta(self):
1870
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1871
true_format = RemoteRepositoryFormat()
1872
true_format._network_name = true_name
1873
self.assertEqual(True, true_format.fast_deltas)
1874
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1875
false_format = RemoteRepositoryFormat()
1876
false_format._network_name = false_name
1877
self.assertEqual(False, false_format.fast_deltas)
1879
def test_get_format_description(self):
1880
remote_repo_format = RemoteRepositoryFormat()
1881
real_format = repository.RepositoryFormat.get_default_format()
1882
remote_repo_format._network_name = real_format.network_name()
1883
self.assertEqual(remoted_description(real_format),
1884
remote_repo_format.get_format_description())
1887
class TestRepositoryGatherStats(TestRemoteRepository):
1889
def test_revid_none(self):
1890
# ('ok',), body with revisions and size
1891
transport_path = 'quack'
1892
repo, client = self.setup_fake_client_and_repository(transport_path)
1893
client.add_success_response_with_body(
1894
'revisions: 2\nsize: 18\n', 'ok')
1895
result = repo.gather_stats(None)
1897
[('call_expecting_body', 'Repository.gather_stats',
1898
('quack/','','no'))],
1900
self.assertEqual({'revisions': 2, 'size': 18}, result)
1902
def test_revid_no_committers(self):
1903
# ('ok',), body without committers
1904
body = ('firstrev: 123456.300 3600\n'
1905
'latestrev: 654231.400 0\n'
1908
transport_path = 'quick'
1909
revid = u'\xc8'.encode('utf8')
1910
repo, client = self.setup_fake_client_and_repository(transport_path)
1911
client.add_success_response_with_body(body, 'ok')
1912
result = repo.gather_stats(revid)
1914
[('call_expecting_body', 'Repository.gather_stats',
1915
('quick/', revid, 'no'))],
1917
self.assertEqual({'revisions': 2, 'size': 18,
1918
'firstrev': (123456.300, 3600),
1919
'latestrev': (654231.400, 0),},
1922
def test_revid_with_committers(self):
1923
# ('ok',), body with committers
1924
body = ('committers: 128\n'
1925
'firstrev: 123456.300 3600\n'
1926
'latestrev: 654231.400 0\n'
1929
transport_path = 'buick'
1930
revid = u'\xc8'.encode('utf8')
1931
repo, client = self.setup_fake_client_and_repository(transport_path)
1932
client.add_success_response_with_body(body, 'ok')
1933
result = repo.gather_stats(revid, True)
1935
[('call_expecting_body', 'Repository.gather_stats',
1936
('buick/', revid, 'yes'))],
1938
self.assertEqual({'revisions': 2, 'size': 18,
1940
'firstrev': (123456.300, 3600),
1941
'latestrev': (654231.400, 0),},
1945
class TestRepositoryGetGraph(TestRemoteRepository):
1947
def test_get_graph(self):
1948
# get_graph returns a graph with a custom parents provider.
1949
transport_path = 'quack'
1950
repo, client = self.setup_fake_client_and_repository(transport_path)
1951
graph = repo.get_graph()
1952
self.assertNotEqual(graph._parents_provider, repo)
1955
class TestRepositoryGetParentMap(TestRemoteRepository):
1957
def test_get_parent_map_caching(self):
1958
# get_parent_map returns from cache until unlock()
1959
# setup a reponse with two revisions
1960
r1 = u'\u0e33'.encode('utf8')
1961
r2 = u'\u0dab'.encode('utf8')
1962
lines = [' '.join([r2, r1]), r1]
1963
encoded_body = bz2.compress('\n'.join(lines))
1965
transport_path = 'quack'
1966
repo, client = self.setup_fake_client_and_repository(transport_path)
1967
client.add_success_response_with_body(encoded_body, 'ok')
1968
client.add_success_response_with_body(encoded_body, 'ok')
1970
graph = repo.get_graph()
1971
parents = graph.get_parent_map([r2])
1972
self.assertEqual({r2: (r1,)}, parents)
1973
# locking and unlocking deeper should not reset
1976
parents = graph.get_parent_map([r1])
1977
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1979
[('call_with_body_bytes_expecting_body',
1980
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1984
# now we call again, and it should use the second response.
1986
graph = repo.get_graph()
1987
parents = graph.get_parent_map([r1])
1988
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1990
[('call_with_body_bytes_expecting_body',
1991
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1993
('call_with_body_bytes_expecting_body',
1994
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2000
def test_get_parent_map_reconnects_if_unknown_method(self):
2001
transport_path = 'quack'
2002
rev_id = 'revision-id'
2003
repo, client = self.setup_fake_client_and_repository(transport_path)
2004
client.add_unknown_method_response('Repository.get_parent_map')
2005
client.add_success_response_with_body(rev_id, 'ok')
2006
self.assertFalse(client._medium._is_remote_before((1, 2)))
2007
parents = repo.get_parent_map([rev_id])
2009
[('call_with_body_bytes_expecting_body',
2010
'Repository.get_parent_map', ('quack/', 'include-missing:',
2012
('disconnect medium',),
2013
('call_expecting_body', 'Repository.get_revision_graph',
2016
# The medium is now marked as being connected to an older server
2017
self.assertTrue(client._medium._is_remote_before((1, 2)))
2018
self.assertEqual({rev_id: ('null:',)}, parents)
2020
def test_get_parent_map_fallback_parentless_node(self):
2021
"""get_parent_map falls back to get_revision_graph on old servers. The
2022
results from get_revision_graph are tweaked to match the get_parent_map
2025
Specifically, a {key: ()} result from get_revision_graph means "no
2026
parents" for that key, which in get_parent_map results should be
2027
represented as {key: ('null:',)}.
2029
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2031
rev_id = 'revision-id'
2032
transport_path = 'quack'
2033
repo, client = self.setup_fake_client_and_repository(transport_path)
2034
client.add_success_response_with_body(rev_id, 'ok')
2035
client._medium._remember_remote_is_before((1, 2))
2036
parents = repo.get_parent_map([rev_id])
2038
[('call_expecting_body', 'Repository.get_revision_graph',
2041
self.assertEqual({rev_id: ('null:',)}, parents)
2043
def test_get_parent_map_unexpected_response(self):
2044
repo, client = self.setup_fake_client_and_repository('path')
2045
client.add_success_response('something unexpected!')
2047
errors.UnexpectedSmartServerResponse,
2048
repo.get_parent_map, ['a-revision-id'])
2050
def test_get_parent_map_negative_caches_missing_keys(self):
2051
self.setup_smart_server_with_call_log()
2052
repo = self.make_repository('foo')
2053
self.assertIsInstance(repo, RemoteRepository)
2055
self.addCleanup(repo.unlock)
2056
self.reset_smart_call_log()
2057
graph = repo.get_graph()
2058
self.assertEqual({},
2059
graph.get_parent_map(['some-missing', 'other-missing']))
2060
self.assertLength(1, self.hpss_calls)
2061
# No call if we repeat this
2062
self.reset_smart_call_log()
2063
graph = repo.get_graph()
2064
self.assertEqual({},
2065
graph.get_parent_map(['some-missing', 'other-missing']))
2066
self.assertLength(0, self.hpss_calls)
2067
# Asking for more unknown keys makes a request.
2068
self.reset_smart_call_log()
2069
graph = repo.get_graph()
2070
self.assertEqual({},
2071
graph.get_parent_map(['some-missing', 'other-missing',
2073
self.assertLength(1, self.hpss_calls)
2075
def disableExtraResults(self):
2076
self.overrideAttr(SmartServerRepositoryGetParentMap,
2077
'no_extra_results', True)
2079
def test_null_cached_missing_and_stop_key(self):
2080
self.setup_smart_server_with_call_log()
2081
# Make a branch with a single revision.
2082
builder = self.make_branch_builder('foo')
2083
builder.start_series()
2084
builder.build_snapshot('first', None, [
2085
('add', ('', 'root-id', 'directory', ''))])
2086
builder.finish_series()
2087
branch = builder.get_branch()
2088
repo = branch.repository
2089
self.assertIsInstance(repo, RemoteRepository)
2090
# Stop the server from sending extra results.
2091
self.disableExtraResults()
2093
self.addCleanup(repo.unlock)
2094
self.reset_smart_call_log()
2095
graph = repo.get_graph()
2096
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2097
# 'first' it will be a candidate for the stop_keys of subsequent
2098
# requests, and because 'null:' was queried but not returned it will be
2099
# cached as missing.
2100
self.assertEqual({'first': ('null:',)},
2101
graph.get_parent_map(['first', 'null:']))
2102
# Now query for another key. This request will pass along a recipe of
2103
# start and stop keys describing the already cached results, and this
2104
# recipe's revision count must be correct (or else it will trigger an
2105
# error from the server).
2106
self.assertEqual({}, graph.get_parent_map(['another-key']))
2107
# This assertion guards against disableExtraResults silently failing to
2108
# work, thus invalidating the test.
2109
self.assertLength(2, self.hpss_calls)
2111
def test_get_parent_map_gets_ghosts_from_result(self):
2112
# asking for a revision should negatively cache close ghosts in its
2114
self.setup_smart_server_with_call_log()
2115
tree = self.make_branch_and_memory_tree('foo')
2118
builder = treebuilder.TreeBuilder()
2119
builder.start_tree(tree)
2121
builder.finish_tree()
2122
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2123
rev_id = tree.commit('')
2127
self.addCleanup(tree.unlock)
2128
repo = tree.branch.repository
2129
self.assertIsInstance(repo, RemoteRepository)
2131
repo.get_parent_map([rev_id])
2132
self.reset_smart_call_log()
2133
# Now asking for rev_id's ghost parent should not make calls
2134
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2135
self.assertLength(0, self.hpss_calls)
2138
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2140
def test_allows_new_revisions(self):
2141
"""get_parent_map's results can be updated by commit."""
2142
smart_server = test_server.SmartTCPServer_for_testing()
2143
self.start_server(smart_server)
2144
self.make_branch('branch')
2145
branch = Branch.open(smart_server.get_url() + '/branch')
2146
tree = branch.create_checkout('tree', lightweight=True)
2148
self.addCleanup(tree.unlock)
2149
graph = tree.branch.repository.get_graph()
2150
# This provides an opportunity for the missing rev-id to be cached.
2151
self.assertEqual({}, graph.get_parent_map(['rev1']))
2152
tree.commit('message', rev_id='rev1')
2153
graph = tree.branch.repository.get_graph()
2154
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2157
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2159
def test_null_revision(self):
2160
# a null revision has the predictable result {}, we should have no wire
2161
# traffic when calling it with this argument
2162
transport_path = 'empty'
2163
repo, client = self.setup_fake_client_and_repository(transport_path)
2164
client.add_success_response('notused')
2165
# actual RemoteRepository.get_revision_graph is gone, but there's an
2166
# equivalent private method for testing
2167
result = repo._get_revision_graph(NULL_REVISION)
2168
self.assertEqual([], client._calls)
2169
self.assertEqual({}, result)
2171
def test_none_revision(self):
2172
# with none we want the entire graph
2173
r1 = u'\u0e33'.encode('utf8')
2174
r2 = u'\u0dab'.encode('utf8')
2175
lines = [' '.join([r2, r1]), r1]
2176
encoded_body = '\n'.join(lines)
2178
transport_path = 'sinhala'
2179
repo, client = self.setup_fake_client_and_repository(transport_path)
2180
client.add_success_response_with_body(encoded_body, 'ok')
2181
# actual RemoteRepository.get_revision_graph is gone, but there's an
2182
# equivalent private method for testing
2183
result = repo._get_revision_graph(None)
2185
[('call_expecting_body', 'Repository.get_revision_graph',
2188
self.assertEqual({r1: (), r2: (r1, )}, result)
2190
def test_specific_revision(self):
2191
# with a specific revision we want the graph for that
2192
# with none we want the entire graph
2193
r11 = u'\u0e33'.encode('utf8')
2194
r12 = u'\xc9'.encode('utf8')
2195
r2 = u'\u0dab'.encode('utf8')
2196
lines = [' '.join([r2, r11, r12]), r11, r12]
2197
encoded_body = '\n'.join(lines)
2199
transport_path = 'sinhala'
2200
repo, client = self.setup_fake_client_and_repository(transport_path)
2201
client.add_success_response_with_body(encoded_body, 'ok')
2202
result = repo._get_revision_graph(r2)
2204
[('call_expecting_body', 'Repository.get_revision_graph',
2207
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2209
def test_no_such_revision(self):
2211
transport_path = 'sinhala'
2212
repo, client = self.setup_fake_client_and_repository(transport_path)
2213
client.add_error_response('nosuchrevision', revid)
2214
# also check that the right revision is reported in the error
2215
self.assertRaises(errors.NoSuchRevision,
2216
repo._get_revision_graph, revid)
2218
[('call_expecting_body', 'Repository.get_revision_graph',
2219
('sinhala/', revid))],
2222
def test_unexpected_error(self):
2224
transport_path = 'sinhala'
2225
repo, client = self.setup_fake_client_and_repository(transport_path)
2226
client.add_error_response('AnUnexpectedError')
2227
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2228
repo._get_revision_graph, revid)
2229
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2232
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2235
repo, client = self.setup_fake_client_and_repository('quack')
2236
client.add_expected_call(
2237
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2238
'success', ('ok', 'rev-five'))
2239
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2240
self.assertEqual((True, 'rev-five'), result)
2241
self.assertFinished(client)
2243
def test_history_incomplete(self):
2244
repo, client = self.setup_fake_client_and_repository('quack')
2245
client.add_expected_call(
2246
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2247
'success', ('history-incomplete', 10, 'rev-ten'))
2248
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2249
self.assertEqual((False, (10, 'rev-ten')), result)
2250
self.assertFinished(client)
2252
def test_history_incomplete_with_fallback(self):
2253
"""A 'history-incomplete' response causes the fallback repository to be
2254
queried too, if one is set.
2256
# Make a repo with a fallback repo, both using a FakeClient.
2257
format = remote.response_tuple_to_repo_format(
2258
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2259
repo, client = self.setup_fake_client_and_repository('quack')
2260
repo._format = format
2261
fallback_repo, ignored = self.setup_fake_client_and_repository(
2263
fallback_repo._client = client
2264
fallback_repo._format = format
2265
repo.add_fallback_repository(fallback_repo)
2266
# First the client should ask the primary repo
2267
client.add_expected_call(
2268
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2269
'success', ('history-incomplete', 2, 'rev-two'))
2270
# Then it should ask the fallback, using revno/revid from the
2271
# history-incomplete response as the known revno/revid.
2272
client.add_expected_call(
2273
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2274
'success', ('ok', 'rev-one'))
2275
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2276
self.assertEqual((True, 'rev-one'), result)
2277
self.assertFinished(client)
2279
def test_nosuchrevision(self):
2280
# 'nosuchrevision' is returned when the known-revid is not found in the
2281
# remote repo. The client translates that response to NoSuchRevision.
2282
repo, client = self.setup_fake_client_and_repository('quack')
2283
client.add_expected_call(
2284
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2285
'error', ('nosuchrevision', 'rev-foo'))
2287
errors.NoSuchRevision,
2288
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2289
self.assertFinished(client)
2291
def test_branch_fallback_locking(self):
2292
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2293
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2294
will be invoked, which will fail if the repo is unlocked.
2296
self.setup_smart_server_with_call_log()
2297
tree = self.make_branch_and_memory_tree('.')
2300
rev1 = tree.commit('First')
2301
rev2 = tree.commit('Second')
2303
branch = tree.branch
2304
self.assertFalse(branch.is_locked())
2305
self.reset_smart_call_log()
2306
verb = 'Repository.get_rev_id_for_revno'
2307
self.disable_verb(verb)
2308
self.assertEqual(rev1, branch.get_rev_id(1))
2309
self.assertLength(1, [call for call in self.hpss_calls if
2310
call.call.method == verb])
2313
class TestRepositoryIsShared(TestRemoteRepository):
2315
def test_is_shared(self):
2316
# ('yes', ) for Repository.is_shared -> 'True'.
2317
transport_path = 'quack'
2318
repo, client = self.setup_fake_client_and_repository(transport_path)
2319
client.add_success_response('yes')
2320
result = repo.is_shared()
2322
[('call', 'Repository.is_shared', ('quack/',))],
2324
self.assertEqual(True, result)
2326
def test_is_not_shared(self):
2327
# ('no', ) for Repository.is_shared -> 'False'.
2328
transport_path = 'qwack'
2329
repo, client = self.setup_fake_client_and_repository(transport_path)
2330
client.add_success_response('no')
2331
result = repo.is_shared()
2333
[('call', 'Repository.is_shared', ('qwack/',))],
2335
self.assertEqual(False, result)
2338
class TestRepositoryLockWrite(TestRemoteRepository):
2340
def test_lock_write(self):
2341
transport_path = 'quack'
2342
repo, client = self.setup_fake_client_and_repository(transport_path)
2343
client.add_success_response('ok', 'a token')
2344
token = repo.lock_write().repository_token
2346
[('call', 'Repository.lock_write', ('quack/', ''))],
2348
self.assertEqual('a token', token)
2350
def test_lock_write_already_locked(self):
2351
transport_path = 'quack'
2352
repo, client = self.setup_fake_client_and_repository(transport_path)
2353
client.add_error_response('LockContention')
2354
self.assertRaises(errors.LockContention, repo.lock_write)
2356
[('call', 'Repository.lock_write', ('quack/', ''))],
2359
def test_lock_write_unlockable(self):
2360
transport_path = 'quack'
2361
repo, client = self.setup_fake_client_and_repository(transport_path)
2362
client.add_error_response('UnlockableTransport')
2363
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2365
[('call', 'Repository.lock_write', ('quack/', ''))],
2369
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2371
def test_backwards_compat(self):
2372
self.setup_smart_server_with_call_log()
2373
repo = self.make_repository('.')
2374
self.reset_smart_call_log()
2375
verb = 'Repository.set_make_working_trees'
2376
self.disable_verb(verb)
2377
repo.set_make_working_trees(True)
2378
call_count = len([call for call in self.hpss_calls if
2379
call.call.method == verb])
2380
self.assertEqual(1, call_count)
2382
def test_current(self):
2383
transport_path = 'quack'
2384
repo, client = self.setup_fake_client_and_repository(transport_path)
2385
client.add_expected_call(
2386
'Repository.set_make_working_trees', ('quack/', 'True'),
2388
client.add_expected_call(
2389
'Repository.set_make_working_trees', ('quack/', 'False'),
2391
repo.set_make_working_trees(True)
2392
repo.set_make_working_trees(False)
2395
class TestRepositoryUnlock(TestRemoteRepository):
2397
def test_unlock(self):
2398
transport_path = 'quack'
2399
repo, client = self.setup_fake_client_and_repository(transport_path)
2400
client.add_success_response('ok', 'a token')
2401
client.add_success_response('ok')
2405
[('call', 'Repository.lock_write', ('quack/', '')),
2406
('call', 'Repository.unlock', ('quack/', 'a token'))],
2409
def test_unlock_wrong_token(self):
2410
# If somehow the token is wrong, unlock will raise TokenMismatch.
2411
transport_path = 'quack'
2412
repo, client = self.setup_fake_client_and_repository(transport_path)
2413
client.add_success_response('ok', 'a token')
2414
client.add_error_response('TokenMismatch')
2416
self.assertRaises(errors.TokenMismatch, repo.unlock)
2419
class TestRepositoryHasRevision(TestRemoteRepository):
2421
def test_none(self):
2422
# repo.has_revision(None) should not cause any traffic.
2423
transport_path = 'quack'
2424
repo, client = self.setup_fake_client_and_repository(transport_path)
2426
# The null revision is always there, so has_revision(None) == True.
2427
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2429
# The remote repo shouldn't be accessed.
2430
self.assertEqual([], client._calls)
2433
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2434
"""Base class for Repository.insert_stream and .insert_stream_1.19
2438
def checkInsertEmptyStream(self, repo, client):
2439
"""Insert an empty stream, checking the result.
2441
This checks that there are no resume_tokens or missing_keys, and that
2442
the client is finished.
2444
sink = repo._get_sink()
2445
fmt = repository.RepositoryFormat.get_default_format()
2446
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2447
self.assertEqual([], resume_tokens)
2448
self.assertEqual(set(), missing_keys)
2449
self.assertFinished(client)
2452
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2453
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2456
This test case is very similar to TestRepositoryInsertStream_1_19.
2460
TestRemoteRepository.setUp(self)
2461
self.disable_verb('Repository.insert_stream_1.19')
2463
def test_unlocked_repo(self):
2464
transport_path = 'quack'
2465
repo, client = self.setup_fake_client_and_repository(transport_path)
2466
client.add_expected_call(
2467
'Repository.insert_stream_1.19', ('quack/', ''),
2468
'unknown', ('Repository.insert_stream_1.19',))
2469
client.add_expected_call(
2470
'Repository.insert_stream', ('quack/', ''),
2472
client.add_expected_call(
2473
'Repository.insert_stream', ('quack/', ''),
2475
self.checkInsertEmptyStream(repo, client)
2477
def test_locked_repo_with_no_lock_token(self):
2478
transport_path = 'quack'
2479
repo, client = self.setup_fake_client_and_repository(transport_path)
2480
client.add_expected_call(
2481
'Repository.lock_write', ('quack/', ''),
2482
'success', ('ok', ''))
2483
client.add_expected_call(
2484
'Repository.insert_stream_1.19', ('quack/', ''),
2485
'unknown', ('Repository.insert_stream_1.19',))
2486
client.add_expected_call(
2487
'Repository.insert_stream', ('quack/', ''),
2489
client.add_expected_call(
2490
'Repository.insert_stream', ('quack/', ''),
2493
self.checkInsertEmptyStream(repo, client)
2495
def test_locked_repo_with_lock_token(self):
2496
transport_path = 'quack'
2497
repo, client = self.setup_fake_client_and_repository(transport_path)
2498
client.add_expected_call(
2499
'Repository.lock_write', ('quack/', ''),
2500
'success', ('ok', 'a token'))
2501
client.add_expected_call(
2502
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2503
'unknown', ('Repository.insert_stream_1.19',))
2504
client.add_expected_call(
2505
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2507
client.add_expected_call(
2508
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2511
self.checkInsertEmptyStream(repo, client)
2513
def test_stream_with_inventory_deltas(self):
2514
"""'inventory-deltas' substreams cannot be sent to the
2515
Repository.insert_stream verb, because not all servers that implement
2516
that verb will accept them. So when one is encountered the RemoteSink
2517
immediately stops using that verb and falls back to VFS insert_stream.
2519
transport_path = 'quack'
2520
repo, client = self.setup_fake_client_and_repository(transport_path)
2521
client.add_expected_call(
2522
'Repository.insert_stream_1.19', ('quack/', ''),
2523
'unknown', ('Repository.insert_stream_1.19',))
2524
client.add_expected_call(
2525
'Repository.insert_stream', ('quack/', ''),
2527
client.add_expected_call(
2528
'Repository.insert_stream', ('quack/', ''),
2530
# Create a fake real repository for insert_stream to fall back on, so
2531
# that we can directly see the records the RemoteSink passes to the
2536
def insert_stream(self, stream, src_format, resume_tokens):
2537
for substream_kind, substream in stream:
2538
self.records.append(
2539
(substream_kind, [record.key for record in substream]))
2540
return ['fake tokens'], ['fake missing keys']
2541
fake_real_sink = FakeRealSink()
2542
class FakeRealRepository:
2543
def _get_sink(self):
2544
return fake_real_sink
2545
def is_in_write_group(self):
2547
def refresh_data(self):
2549
repo._real_repository = FakeRealRepository()
2550
sink = repo._get_sink()
2551
fmt = repository.RepositoryFormat.get_default_format()
2552
stream = self.make_stream_with_inv_deltas(fmt)
2553
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2554
# Every record from the first inventory delta should have been sent to
2556
expected_records = [
2557
('inventory-deltas', [('rev2',), ('rev3',)]),
2558
('texts', [('some-rev', 'some-file')])]
2559
self.assertEqual(expected_records, fake_real_sink.records)
2560
# The return values from the real sink's insert_stream are propagated
2561
# back to the original caller.
2562
self.assertEqual(['fake tokens'], resume_tokens)
2563
self.assertEqual(['fake missing keys'], missing_keys)
2564
self.assertFinished(client)
2566
def make_stream_with_inv_deltas(self, fmt):
2567
"""Make a simple stream with an inventory delta followed by more
2568
records and more substreams to test that all records and substreams
2569
from that point on are used.
2571
This sends, in order:
2572
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2574
* texts substream: (some-rev, some-file)
2576
# Define a stream using generators so that it isn't rewindable.
2577
inv = inventory.Inventory(revision_id='rev1')
2578
inv.root.revision = 'rev1'
2579
def stream_with_inv_delta():
2580
yield ('inventories', inventories_substream())
2581
yield ('inventory-deltas', inventory_delta_substream())
2583
versionedfile.FulltextContentFactory(
2584
('some-rev', 'some-file'), (), None, 'content')])
2585
def inventories_substream():
2586
# An empty inventory fulltext. This will be streamed normally.
2587
text = fmt._serializer.write_inventory_to_string(inv)
2588
yield versionedfile.FulltextContentFactory(
2589
('rev1',), (), None, text)
2590
def inventory_delta_substream():
2591
# An inventory delta. This can't be streamed via this verb, so it
2592
# will trigger a fallback to VFS insert_stream.
2593
entry = inv.make_entry(
2594
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2595
entry.revision = 'ghost'
2596
delta = [(None, 'newdir', 'newdir-id', entry)]
2597
serializer = inventory_delta.InventoryDeltaSerializer(
2598
versioned_root=True, tree_references=False)
2599
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2600
yield versionedfile.ChunkedContentFactory(
2601
('rev2',), (('rev1',)), None, lines)
2603
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2604
yield versionedfile.ChunkedContentFactory(
2605
('rev3',), (('rev1',)), None, lines)
2606
return stream_with_inv_delta()
2609
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2611
def test_unlocked_repo(self):
2612
transport_path = 'quack'
2613
repo, client = self.setup_fake_client_and_repository(transport_path)
2614
client.add_expected_call(
2615
'Repository.insert_stream_1.19', ('quack/', ''),
2617
client.add_expected_call(
2618
'Repository.insert_stream_1.19', ('quack/', ''),
2620
self.checkInsertEmptyStream(repo, client)
2622
def test_locked_repo_with_no_lock_token(self):
2623
transport_path = 'quack'
2624
repo, client = self.setup_fake_client_and_repository(transport_path)
2625
client.add_expected_call(
2626
'Repository.lock_write', ('quack/', ''),
2627
'success', ('ok', ''))
2628
client.add_expected_call(
2629
'Repository.insert_stream_1.19', ('quack/', ''),
2631
client.add_expected_call(
2632
'Repository.insert_stream_1.19', ('quack/', ''),
2635
self.checkInsertEmptyStream(repo, client)
2637
def test_locked_repo_with_lock_token(self):
2638
transport_path = 'quack'
2639
repo, client = self.setup_fake_client_and_repository(transport_path)
2640
client.add_expected_call(
2641
'Repository.lock_write', ('quack/', ''),
2642
'success', ('ok', 'a token'))
2643
client.add_expected_call(
2644
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2646
client.add_expected_call(
2647
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2650
self.checkInsertEmptyStream(repo, client)
2653
class TestRepositoryTarball(TestRemoteRepository):
2655
# This is a canned tarball reponse we can validate against
2657
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2658
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2659
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2660
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2661
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2662
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2663
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2664
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2665
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2666
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2667
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2668
'nWQ7QH/F3JFOFCQ0aSPfA='
2671
def test_repository_tarball(self):
2672
# Test that Repository.tarball generates the right operations
2673
transport_path = 'repo'
2674
expected_calls = [('call_expecting_body', 'Repository.tarball',
2675
('repo/', 'bz2',),),
2677
repo, client = self.setup_fake_client_and_repository(transport_path)
2678
client.add_success_response_with_body(self.tarball_content, 'ok')
2679
# Now actually ask for the tarball
2680
tarball_file = repo._get_tarball('bz2')
2682
self.assertEqual(expected_calls, client._calls)
2683
self.assertEqual(self.tarball_content, tarball_file.read())
2685
tarball_file.close()
2688
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2689
"""RemoteRepository.copy_content_into optimizations"""
2691
def test_copy_content_remote_to_local(self):
2692
self.transport_server = test_server.SmartTCPServer_for_testing
2693
src_repo = self.make_repository('repo1')
2694
src_repo = repository.Repository.open(self.get_url('repo1'))
2695
# At the moment the tarball-based copy_content_into can't write back
2696
# into a smart server. It would be good if it could upload the
2697
# tarball; once that works we'd have to create repositories of
2698
# different formats. -- mbp 20070410
2699
dest_url = self.get_vfs_only_url('repo2')
2700
dest_bzrdir = BzrDir.create(dest_url)
2701
dest_repo = dest_bzrdir.create_repository()
2702
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2703
self.assertTrue(isinstance(src_repo, RemoteRepository))
2704
src_repo.copy_content_into(dest_repo)
2707
class _StubRealPackRepository(object):
2709
def __init__(self, calls):
2711
self._pack_collection = _StubPackCollection(calls)
2713
def is_in_write_group(self):
2716
def refresh_data(self):
2717
self.calls.append(('pack collection reload_pack_names',))
2720
class _StubPackCollection(object):
2722
def __init__(self, calls):
2726
self.calls.append(('pack collection autopack',))
2729
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2730
"""Tests for RemoteRepository.autopack implementation."""
2733
"""When the server returns 'ok' and there's no _real_repository, then
2734
nothing else happens: the autopack method is done.
2736
transport_path = 'quack'
2737
repo, client = self.setup_fake_client_and_repository(transport_path)
2738
client.add_expected_call(
2739
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2741
self.assertFinished(client)
2743
def test_ok_with_real_repo(self):
2744
"""When the server returns 'ok' and there is a _real_repository, then
2745
the _real_repository's reload_pack_name's method will be called.
2747
transport_path = 'quack'
2748
repo, client = self.setup_fake_client_and_repository(transport_path)
2749
client.add_expected_call(
2750
'PackRepository.autopack', ('quack/',),
2752
repo._real_repository = _StubRealPackRepository(client._calls)
2755
[('call', 'PackRepository.autopack', ('quack/',)),
2756
('pack collection reload_pack_names',)],
2759
def test_backwards_compatibility(self):
2760
"""If the server does not recognise the PackRepository.autopack verb,
2761
fallback to the real_repository's implementation.
2763
transport_path = 'quack'
2764
repo, client = self.setup_fake_client_and_repository(transport_path)
2765
client.add_unknown_method_response('PackRepository.autopack')
2766
def stub_ensure_real():
2767
client._calls.append(('_ensure_real',))
2768
repo._real_repository = _StubRealPackRepository(client._calls)
2769
repo._ensure_real = stub_ensure_real
2772
[('call', 'PackRepository.autopack', ('quack/',)),
2774
('pack collection autopack',)],
2778
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2779
"""Base class for unit tests for bzrlib.remote._translate_error."""
2781
def translateTuple(self, error_tuple, **context):
2782
"""Call _translate_error with an ErrorFromSmartServer built from the
2785
:param error_tuple: A tuple of a smart server response, as would be
2786
passed to an ErrorFromSmartServer.
2787
:kwargs context: context items to call _translate_error with.
2789
:returns: The error raised by _translate_error.
2791
# Raise the ErrorFromSmartServer before passing it as an argument,
2792
# because _translate_error may need to re-raise it with a bare 'raise'
2794
server_error = errors.ErrorFromSmartServer(error_tuple)
2795
translated_error = self.translateErrorFromSmartServer(
2796
server_error, **context)
2797
return translated_error
2799
def translateErrorFromSmartServer(self, error_object, **context):
2800
"""Like translateTuple, but takes an already constructed
2801
ErrorFromSmartServer rather than a tuple.
2805
except errors.ErrorFromSmartServer, server_error:
2806
translated_error = self.assertRaises(
2807
errors.BzrError, remote._translate_error, server_error,
2809
return translated_error
2812
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2813
"""Unit tests for bzrlib.remote._translate_error.
2815
Given an ErrorFromSmartServer (which has an error tuple from a smart
2816
server) and some context, _translate_error raises more specific errors from
2819
This test case covers the cases where _translate_error succeeds in
2820
translating an ErrorFromSmartServer to something better. See
2821
TestErrorTranslationRobustness for other cases.
2824
def test_NoSuchRevision(self):
2825
branch = self.make_branch('')
2827
translated_error = self.translateTuple(
2828
('NoSuchRevision', revid), branch=branch)
2829
expected_error = errors.NoSuchRevision(branch, revid)
2830
self.assertEqual(expected_error, translated_error)
2832
def test_nosuchrevision(self):
2833
repository = self.make_repository('')
2835
translated_error = self.translateTuple(
2836
('nosuchrevision', revid), repository=repository)
2837
expected_error = errors.NoSuchRevision(repository, revid)
2838
self.assertEqual(expected_error, translated_error)
2840
def test_nobranch(self):
2841
bzrdir = self.make_bzrdir('')
2842
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2843
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2844
self.assertEqual(expected_error, translated_error)
2846
def test_nobranch_one_arg(self):
2847
bzrdir = self.make_bzrdir('')
2848
translated_error = self.translateTuple(
2849
('nobranch', 'extra detail'), bzrdir=bzrdir)
2850
expected_error = errors.NotBranchError(
2851
path=bzrdir.root_transport.base,
2852
detail='extra detail')
2853
self.assertEqual(expected_error, translated_error)
2855
def test_LockContention(self):
2856
translated_error = self.translateTuple(('LockContention',))
2857
expected_error = errors.LockContention('(remote lock)')
2858
self.assertEqual(expected_error, translated_error)
2860
def test_UnlockableTransport(self):
2861
bzrdir = self.make_bzrdir('')
2862
translated_error = self.translateTuple(
2863
('UnlockableTransport',), bzrdir=bzrdir)
2864
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2865
self.assertEqual(expected_error, translated_error)
2867
def test_LockFailed(self):
2868
lock = 'str() of a server lock'
2869
why = 'str() of why'
2870
translated_error = self.translateTuple(('LockFailed', lock, why))
2871
expected_error = errors.LockFailed(lock, why)
2872
self.assertEqual(expected_error, translated_error)
2874
def test_TokenMismatch(self):
2875
token = 'a lock token'
2876
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2877
expected_error = errors.TokenMismatch(token, '(remote token)')
2878
self.assertEqual(expected_error, translated_error)
2880
def test_Diverged(self):
2881
branch = self.make_branch('a')
2882
other_branch = self.make_branch('b')
2883
translated_error = self.translateTuple(
2884
('Diverged',), branch=branch, other_branch=other_branch)
2885
expected_error = errors.DivergedBranches(branch, other_branch)
2886
self.assertEqual(expected_error, translated_error)
2888
def test_ReadError_no_args(self):
2890
translated_error = self.translateTuple(('ReadError',), path=path)
2891
expected_error = errors.ReadError(path)
2892
self.assertEqual(expected_error, translated_error)
2894
def test_ReadError(self):
2896
translated_error = self.translateTuple(('ReadError', path))
2897
expected_error = errors.ReadError(path)
2898
self.assertEqual(expected_error, translated_error)
2900
def test_IncompatibleRepositories(self):
2901
translated_error = self.translateTuple(('IncompatibleRepositories',
2902
"repo1", "repo2", "details here"))
2903
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2905
self.assertEqual(expected_error, translated_error)
2907
def test_PermissionDenied_no_args(self):
2909
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2910
expected_error = errors.PermissionDenied(path)
2911
self.assertEqual(expected_error, translated_error)
2913
def test_PermissionDenied_one_arg(self):
2915
translated_error = self.translateTuple(('PermissionDenied', path))
2916
expected_error = errors.PermissionDenied(path)
2917
self.assertEqual(expected_error, translated_error)
2919
def test_PermissionDenied_one_arg_and_context(self):
2920
"""Given a choice between a path from the local context and a path on
2921
the wire, _translate_error prefers the path from the local context.
2923
local_path = 'local path'
2924
remote_path = 'remote path'
2925
translated_error = self.translateTuple(
2926
('PermissionDenied', remote_path), path=local_path)
2927
expected_error = errors.PermissionDenied(local_path)
2928
self.assertEqual(expected_error, translated_error)
2930
def test_PermissionDenied_two_args(self):
2932
extra = 'a string with extra info'
2933
translated_error = self.translateTuple(
2934
('PermissionDenied', path, extra))
2935
expected_error = errors.PermissionDenied(path, extra)
2936
self.assertEqual(expected_error, translated_error)
2939
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2940
"""Unit tests for bzrlib.remote._translate_error's robustness.
2942
TestErrorTranslationSuccess is for cases where _translate_error can
2943
translate successfully. This class about how _translate_err behaves when
2944
it fails to translate: it re-raises the original error.
2947
def test_unrecognised_server_error(self):
2948
"""If the error code from the server is not recognised, the original
2949
ErrorFromSmartServer is propagated unmodified.
2951
error_tuple = ('An unknown error tuple',)
2952
server_error = errors.ErrorFromSmartServer(error_tuple)
2953
translated_error = self.translateErrorFromSmartServer(server_error)
2954
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2955
self.assertEqual(expected_error, translated_error)
2957
def test_context_missing_a_key(self):
2958
"""In case of a bug in the client, or perhaps an unexpected response
2959
from a server, _translate_error returns the original error tuple from
2960
the server and mutters a warning.
2962
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2963
# in the context dict. So let's give it an empty context dict instead
2964
# to exercise its error recovery.
2966
error_tuple = ('NoSuchRevision', 'revid')
2967
server_error = errors.ErrorFromSmartServer(error_tuple)
2968
translated_error = self.translateErrorFromSmartServer(server_error)
2969
self.assertEqual(server_error, translated_error)
2970
# In addition to re-raising ErrorFromSmartServer, some debug info has
2971
# been muttered to the log file for developer to look at.
2972
self.assertContainsRe(
2974
"Missing key 'branch' in context")
2976
def test_path_missing(self):
2977
"""Some translations (PermissionDenied, ReadError) can determine the
2978
'path' variable from either the wire or the local context. If neither
2979
has it, then an error is raised.
2981
error_tuple = ('ReadError',)
2982
server_error = errors.ErrorFromSmartServer(error_tuple)
2983
translated_error = self.translateErrorFromSmartServer(server_error)
2984
self.assertEqual(server_error, translated_error)
2985
# In addition to re-raising ErrorFromSmartServer, some debug info has
2986
# been muttered to the log file for developer to look at.
2987
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
2990
class TestStacking(tests.TestCaseWithTransport):
2991
"""Tests for operations on stacked remote repositories.
2993
The underlying format type must support stacking.
2996
def test_access_stacked_remote(self):
2997
# based on <http://launchpad.net/bugs/261315>
2998
# make a branch stacked on another repository containing an empty
2999
# revision, then open it over hpss - we should be able to see that
3001
base_transport = self.get_transport()
3002
base_builder = self.make_branch_builder('base', format='1.9')
3003
base_builder.start_series()
3004
base_revid = base_builder.build_snapshot('rev-id', None,
3005
[('add', ('', None, 'directory', None))],
3007
base_builder.finish_series()
3008
stacked_branch = self.make_branch('stacked', format='1.9')
3009
stacked_branch.set_stacked_on_url('../base')
3010
# start a server looking at this
3011
smart_server = test_server.SmartTCPServer_for_testing()
3012
self.start_server(smart_server)
3013
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3014
# can get its branch and repository
3015
remote_branch = remote_bzrdir.open_branch()
3016
remote_repo = remote_branch.repository
3017
remote_repo.lock_read()
3019
# it should have an appropriate fallback repository, which should also
3020
# be a RemoteRepository
3021
self.assertLength(1, remote_repo._fallback_repositories)
3022
self.assertIsInstance(remote_repo._fallback_repositories[0],
3024
# and it has the revision committed to the underlying repository;
3025
# these have varying implementations so we try several of them
3026
self.assertTrue(remote_repo.has_revisions([base_revid]))
3027
self.assertTrue(remote_repo.has_revision(base_revid))
3028
self.assertEqual(remote_repo.get_revision(base_revid).message,
3031
remote_repo.unlock()
3033
def prepare_stacked_remote_branch(self):
3034
"""Get stacked_upon and stacked branches with content in each."""
3035
self.setup_smart_server_with_call_log()
3036
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3037
tree1.commit('rev1', rev_id='rev1')
3038
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3039
).open_workingtree()
3040
local_tree = tree2.branch.create_checkout('local')
3041
local_tree.commit('local changes make me feel good.')
3042
branch2 = Branch.open(self.get_url('tree2'))
3044
self.addCleanup(branch2.unlock)
3045
return tree1.branch, branch2
3047
def test_stacked_get_parent_map(self):
3048
# the public implementation of get_parent_map obeys stacking
3049
_, branch = self.prepare_stacked_remote_branch()
3050
repo = branch.repository
3051
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3053
def test_unstacked_get_parent_map(self):
3054
# _unstacked_provider.get_parent_map ignores stacking
3055
_, branch = self.prepare_stacked_remote_branch()
3056
provider = branch.repository._unstacked_provider
3057
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3059
def fetch_stream_to_rev_order(self, stream):
3061
for kind, substream in stream:
3062
if not kind == 'revisions':
3065
for content in substream:
3066
result.append(content.key[-1])
3069
def get_ordered_revs(self, format, order, branch_factory=None):
3070
"""Get a list of the revisions in a stream to format format.
3072
:param format: The format of the target.
3073
:param order: the order that target should have requested.
3074
:param branch_factory: A callable to create a trunk and stacked branch
3075
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3076
:result: The revision ids in the stream, in the order seen,
3077
the topological order of revisions in the source.
3079
unordered_format = bzrdir.format_registry.get(format)()
3080
target_repository_format = unordered_format.repository_format
3082
self.assertEqual(order, target_repository_format._fetch_order)
3083
if branch_factory is None:
3084
branch_factory = self.prepare_stacked_remote_branch
3085
_, stacked = branch_factory()
3086
source = stacked.repository._get_source(target_repository_format)
3087
tip = stacked.last_revision()
3088
revs = stacked.repository.get_ancestry(tip)
3089
search = graph.PendingAncestryResult([tip], stacked.repository)
3090
self.reset_smart_call_log()
3091
stream = source.get_stream(search)
3094
# We trust that if a revision is in the stream the rest of the new
3095
# content for it is too, as per our main fetch tests; here we are
3096
# checking that the revisions are actually included at all, and their
3098
return self.fetch_stream_to_rev_order(stream), revs
3100
def test_stacked_get_stream_unordered(self):
3101
# Repository._get_source.get_stream() from a stacked repository with
3102
# unordered yields the full data from both stacked and stacked upon
3104
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3105
self.assertEqual(set(expected_revs), set(rev_ord))
3106
# Getting unordered results should have made a streaming data request
3107
# from the server, then one from the backing branch.
3108
self.assertLength(2, self.hpss_calls)
3110
def test_stacked_on_stacked_get_stream_unordered(self):
3111
# Repository._get_source.get_stream() from a stacked repository which
3112
# is itself stacked yields the full data from all three sources.
3113
def make_stacked_stacked():
3114
_, stacked = self.prepare_stacked_remote_branch()
3115
tree = stacked.bzrdir.sprout('tree3', stacked=True
3116
).open_workingtree()
3117
local_tree = tree.branch.create_checkout('local-tree3')
3118
local_tree.commit('more local changes are better')
3119
branch = Branch.open(self.get_url('tree3'))
3121
self.addCleanup(branch.unlock)
3123
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3124
branch_factory=make_stacked_stacked)
3125
self.assertEqual(set(expected_revs), set(rev_ord))
3126
# Getting unordered results should have made a streaming data request
3127
# from the server, and one from each backing repo
3128
self.assertLength(3, self.hpss_calls)
3130
def test_stacked_get_stream_topological(self):
3131
# Repository._get_source.get_stream() from a stacked repository with
3132
# topological sorting yields the full data from both stacked and
3133
# stacked upon sources in topological order.
3134
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3135
self.assertEqual(expected_revs, rev_ord)
3136
# Getting topological sort requires VFS calls still - one of which is
3137
# pushing up from the bound branch.
3138
self.assertLength(13, self.hpss_calls)
3140
def test_stacked_get_stream_groupcompress(self):
3141
# Repository._get_source.get_stream() from a stacked repository with
3142
# groupcompress sorting yields the full data from both stacked and
3143
# stacked upon sources in groupcompress order.
3144
raise tests.TestSkipped('No groupcompress ordered format available')
3145
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3146
self.assertEqual(expected_revs, reversed(rev_ord))
3147
# Getting unordered results should have made a streaming data request
3148
# from the backing branch, and one from the stacked on branch.
3149
self.assertLength(2, self.hpss_calls)
3151
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3152
# When pulling some fixed amount of content that is more than the
3153
# source has (because some is coming from a fallback branch, no error
3154
# should be received. This was reported as bug 360791.
3155
# Need three branches: a trunk, a stacked branch, and a preexisting
3156
# branch pulling content from stacked and trunk.
3157
self.setup_smart_server_with_call_log()
3158
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3159
r1 = trunk.commit('start')
3160
stacked_branch = trunk.branch.create_clone_on_transport(
3161
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3162
local = self.make_branch('local', format='1.9-rich-root')
3163
local.repository.fetch(stacked_branch.repository,
3164
stacked_branch.last_revision())
3167
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3170
super(TestRemoteBranchEffort, self).setUp()
3171
# Create a smart server that publishes whatever the backing VFS server
3173
self.smart_server = test_server.SmartTCPServer_for_testing()
3174
self.start_server(self.smart_server, self.get_server())
3175
# Log all HPSS calls into self.hpss_calls.
3176
_SmartClient.hooks.install_named_hook(
3177
'call', self.capture_hpss_call, None)
3178
self.hpss_calls = []
3180
def capture_hpss_call(self, params):
3181
self.hpss_calls.append(params.method)
3183
def test_copy_content_into_avoids_revision_history(self):
3184
local = self.make_branch('local')
3185
remote_backing_tree = self.make_branch_and_tree('remote')
3186
remote_backing_tree.commit("Commit.")
3187
remote_branch_url = self.smart_server.get_url() + 'remote'
3188
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3189
local.repository.fetch(remote_branch.repository)
3190
self.hpss_calls = []
3191
remote_branch.copy_content_into(local)
3192
self.assertFalse('Branch.revision_history' in self.hpss_calls)