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
46
from bzrlib.branch import Branch
47
from bzrlib.bzrdir import BzrDir, BzrDirFormat
48
from bzrlib.remote import (
54
RemoteRepositoryFormat,
56
from bzrlib.repofmt import groupcompress_repo, pack_repo
57
from bzrlib.revision import NULL_REVISION
58
from bzrlib.smart import medium
59
from bzrlib.smart.client import _SmartClient
60
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
61
from bzrlib.tests import (
63
split_suite_by_condition,
67
from bzrlib.transport.memory import MemoryTransport
68
from bzrlib.transport.remote import (
74
def load_tests(standard_tests, module, loader):
75
to_adapt, result = split_suite_by_condition(
76
standard_tests, condition_isinstance(BasicRemoteObjectTests))
77
smart_server_version_scenarios = [
79
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
81
{'transport_server': test_server.SmartTCPServer_for_testing})]
82
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
85
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
88
super(BasicRemoteObjectTests, self).setUp()
89
self.transport = self.get_transport()
90
# make a branch that can be opened over the smart transport
91
self.local_wt = BzrDir.create_standalone_workingtree('.')
92
self.addCleanup(self.transport.disconnect)
94
def test_create_remote_bzrdir(self):
95
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
96
self.assertIsInstance(b, BzrDir)
98
def test_open_remote_branch(self):
99
# open a standalone branch in the working directory
100
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
101
branch = b.open_branch()
102
self.assertIsInstance(branch, Branch)
104
def test_remote_repository(self):
105
b = BzrDir.open_from_transport(self.transport)
106
repo = b.open_repository()
107
revid = u'\xc823123123'.encode('utf8')
108
self.assertFalse(repo.has_revision(revid))
109
self.local_wt.commit(message='test commit', rev_id=revid)
110
self.assertTrue(repo.has_revision(revid))
112
def test_remote_branch_revision_history(self):
113
b = BzrDir.open_from_transport(self.transport).open_branch()
114
self.assertEqual([], b.revision_history())
115
r1 = self.local_wt.commit('1st commit')
116
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
117
self.assertEqual([r1, r2], b.revision_history())
119
def test_find_correct_format(self):
120
"""Should open a RemoteBzrDir over a RemoteTransport"""
121
fmt = BzrDirFormat.find_format(self.transport)
122
self.assertTrue(RemoteBzrDirFormat
123
in BzrDirFormat._control_server_formats)
124
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
126
def test_open_detected_smart_format(self):
127
fmt = BzrDirFormat.find_format(self.transport)
128
d = fmt.open(self.transport)
129
self.assertIsInstance(d, BzrDir)
131
def test_remote_branch_repr(self):
132
b = BzrDir.open_from_transport(self.transport).open_branch()
133
self.assertStartsWith(str(b), 'RemoteBranch(')
135
def test_remote_bzrdir_repr(self):
136
b = BzrDir.open_from_transport(self.transport)
137
self.assertStartsWith(str(b), 'RemoteBzrDir(')
139
def test_remote_branch_format_supports_stacking(self):
141
self.make_branch('unstackable', format='pack-0.92')
142
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
143
self.assertFalse(b._format.supports_stacking())
144
self.make_branch('stackable', format='1.9')
145
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
146
self.assertTrue(b._format.supports_stacking())
148
def test_remote_repo_format_supports_external_references(self):
150
bd = self.make_bzrdir('unstackable', format='pack-0.92')
151
r = bd.create_repository()
152
self.assertFalse(r._format.supports_external_lookups)
153
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
154
self.assertFalse(r._format.supports_external_lookups)
155
bd = self.make_bzrdir('stackable', format='1.9')
156
r = bd.create_repository()
157
self.assertTrue(r._format.supports_external_lookups)
158
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
159
self.assertTrue(r._format.supports_external_lookups)
161
def test_remote_branch_set_append_revisions_only(self):
162
# Make a format 1.9 branch, which supports append_revisions_only
163
branch = self.make_branch('branch', format='1.9')
164
config = branch.get_config()
165
branch.set_append_revisions_only(True)
167
'True', config.get_user_option('append_revisions_only'))
168
branch.set_append_revisions_only(False)
170
'False', config.get_user_option('append_revisions_only'))
172
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
173
branch = self.make_branch('branch', format='knit')
174
config = branch.get_config()
176
errors.UpgradeRequired, branch.set_append_revisions_only, True)
179
class FakeProtocol(object):
180
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
182
def __init__(self, body, fake_client):
184
self._body_buffer = None
185
self._fake_client = fake_client
187
def read_body_bytes(self, count=-1):
188
if self._body_buffer is None:
189
self._body_buffer = StringIO(self.body)
190
bytes = self._body_buffer.read(count)
191
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
192
self._fake_client.expecting_body = False
195
def cancel_read_body(self):
196
self._fake_client.expecting_body = False
198
def read_streamed_body(self):
202
class FakeClient(_SmartClient):
203
"""Lookalike for _SmartClient allowing testing."""
205
def __init__(self, fake_medium_base='fake base'):
206
"""Create a FakeClient."""
209
self.expecting_body = False
210
# if non-None, this is the list of expected calls, with only the
211
# method name and arguments included. the body might be hard to
212
# compute so is not included. If a call is None, that call can
214
self._expected_calls = None
215
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
217
def add_expected_call(self, call_name, call_args, response_type,
218
response_args, response_body=None):
219
if self._expected_calls is None:
220
self._expected_calls = []
221
self._expected_calls.append((call_name, call_args))
222
self.responses.append((response_type, response_args, response_body))
224
def add_success_response(self, *args):
225
self.responses.append(('success', args, None))
227
def add_success_response_with_body(self, body, *args):
228
self.responses.append(('success', args, body))
229
if self._expected_calls is not None:
230
self._expected_calls.append(None)
232
def add_error_response(self, *args):
233
self.responses.append(('error', args))
235
def add_unknown_method_response(self, verb):
236
self.responses.append(('unknown', verb))
238
def finished_test(self):
239
if self._expected_calls:
240
raise AssertionError("%r finished but was still expecting %r"
241
% (self, self._expected_calls[0]))
243
def _get_next_response(self):
245
response_tuple = self.responses.pop(0)
246
except IndexError, e:
247
raise AssertionError("%r didn't expect any more calls"
249
if response_tuple[0] == 'unknown':
250
raise errors.UnknownSmartMethod(response_tuple[1])
251
elif response_tuple[0] == 'error':
252
raise errors.ErrorFromSmartServer(response_tuple[1])
253
return response_tuple
255
def _check_call(self, method, args):
256
if self._expected_calls is None:
257
# the test should be updated to say what it expects
260
next_call = self._expected_calls.pop(0)
262
raise AssertionError("%r didn't expect any more calls "
264
% (self, method, args,))
265
if next_call is None:
267
if method != next_call[0] or args != next_call[1]:
268
raise AssertionError("%r expected %r%r "
270
% (self, next_call[0], next_call[1], method, args,))
272
def call(self, method, *args):
273
self._check_call(method, args)
274
self._calls.append(('call', method, args))
275
return self._get_next_response()[1]
277
def call_expecting_body(self, method, *args):
278
self._check_call(method, args)
279
self._calls.append(('call_expecting_body', method, args))
280
result = self._get_next_response()
281
self.expecting_body = True
282
return result[1], FakeProtocol(result[2], self)
284
def call_with_body_bytes(self, method, args, body):
285
self._check_call(method, args)
286
self._calls.append(('call_with_body_bytes', method, args, body))
287
result = self._get_next_response()
288
return result[1], FakeProtocol(result[2], self)
290
def call_with_body_bytes_expecting_body(self, method, args, body):
291
self._check_call(method, args)
292
self._calls.append(('call_with_body_bytes_expecting_body', method,
294
result = self._get_next_response()
295
self.expecting_body = True
296
return result[1], FakeProtocol(result[2], self)
298
def call_with_body_stream(self, args, stream):
299
# Explicitly consume the stream before checking for an error, because
300
# that's what happens a real medium.
301
stream = list(stream)
302
self._check_call(args[0], args[1:])
303
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
304
result = self._get_next_response()
305
# The second value returned from call_with_body_stream is supposed to
306
# be a response_handler object, but so far no tests depend on that.
307
response_handler = None
308
return result[1], response_handler
311
class FakeMedium(medium.SmartClientMedium):
313
def __init__(self, client_calls, base):
314
medium.SmartClientMedium.__init__(self, base)
315
self._client_calls = client_calls
317
def disconnect(self):
318
self._client_calls.append(('disconnect medium',))
321
class TestVfsHas(tests.TestCase):
323
def test_unicode_path(self):
324
client = FakeClient('/')
325
client.add_success_response('yes',)
326
transport = RemoteTransport('bzr://localhost/', _client=client)
327
filename = u'/hell\u00d8'.encode('utf8')
328
result = transport.has(filename)
330
[('call', 'has', (filename,))],
332
self.assertTrue(result)
335
class TestRemote(tests.TestCaseWithMemoryTransport):
337
def get_branch_format(self):
338
reference_bzrdir_format = bzrdir.format_registry.get('default')()
339
return reference_bzrdir_format.get_branch_format()
341
def get_repo_format(self):
342
reference_bzrdir_format = bzrdir.format_registry.get('default')()
343
return reference_bzrdir_format.repository_format
345
def assertFinished(self, fake_client):
346
"""Assert that all of a FakeClient's expected calls have occurred."""
347
fake_client.finished_test()
350
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
351
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
353
def assertRemotePath(self, expected, client_base, transport_base):
354
"""Assert that the result of
355
SmartClientMedium.remote_path_from_transport is the expected value for
356
a given client_base and transport_base.
358
client_medium = medium.SmartClientMedium(client_base)
359
t = transport.get_transport(transport_base)
360
result = client_medium.remote_path_from_transport(t)
361
self.assertEqual(expected, result)
363
def test_remote_path_from_transport(self):
364
"""SmartClientMedium.remote_path_from_transport calculates a URL for
365
the given transport relative to the root of the client base URL.
367
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
368
self.assertRemotePath(
369
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
371
def assertRemotePathHTTP(self, expected, transport_base, relpath):
372
"""Assert that the result of
373
HttpTransportBase.remote_path_from_transport is the expected value for
374
a given transport_base and relpath of that transport. (Note that
375
HttpTransportBase is a subclass of SmartClientMedium)
377
base_transport = transport.get_transport(transport_base)
378
client_medium = base_transport.get_smart_medium()
379
cloned_transport = base_transport.clone(relpath)
380
result = client_medium.remote_path_from_transport(cloned_transport)
381
self.assertEqual(expected, result)
383
def test_remote_path_from_transport_http(self):
384
"""Remote paths for HTTP transports are calculated differently to other
385
transports. They are just relative to the client base, not the root
386
directory of the host.
388
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
389
self.assertRemotePathHTTP(
390
'../xyz/', scheme + '//host/path', '../xyz/')
391
self.assertRemotePathHTTP(
392
'xyz/', scheme + '//host/path', 'xyz/')
395
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
396
"""Tests for the behaviour of client_medium.remote_is_at_least."""
398
def test_initially_unlimited(self):
399
"""A fresh medium assumes that the remote side supports all
402
client_medium = medium.SmartClientMedium('dummy base')
403
self.assertFalse(client_medium._is_remote_before((99, 99)))
405
def test__remember_remote_is_before(self):
406
"""Calling _remember_remote_is_before ratchets down the known remote
409
client_medium = medium.SmartClientMedium('dummy base')
410
# Mark the remote side as being less than 1.6. The remote side may
412
client_medium._remember_remote_is_before((1, 6))
413
self.assertTrue(client_medium._is_remote_before((1, 6)))
414
self.assertFalse(client_medium._is_remote_before((1, 5)))
415
# Calling _remember_remote_is_before again with a lower value works.
416
client_medium._remember_remote_is_before((1, 5))
417
self.assertTrue(client_medium._is_remote_before((1, 5)))
418
# If you call _remember_remote_is_before with a higher value it logs a
419
# warning, and continues to remember the lower value.
420
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
421
client_medium._remember_remote_is_before((1, 9))
422
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
423
self.assertTrue(client_medium._is_remote_before((1, 5)))
426
class TestBzrDirCloningMetaDir(TestRemote):
428
def test_backwards_compat(self):
429
self.setup_smart_server_with_call_log()
430
a_dir = self.make_bzrdir('.')
431
self.reset_smart_call_log()
432
verb = 'BzrDir.cloning_metadir'
433
self.disable_verb(verb)
434
format = a_dir.cloning_metadir()
435
call_count = len([call for call in self.hpss_calls if
436
call.call.method == verb])
437
self.assertEqual(1, call_count)
439
def test_branch_reference(self):
440
transport = self.get_transport('quack')
441
referenced = self.make_branch('referenced')
442
expected = referenced.bzrdir.cloning_metadir()
443
client = FakeClient(transport.base)
444
client.add_expected_call(
445
'BzrDir.cloning_metadir', ('quack/', 'False'),
446
'error', ('BranchReference',)),
447
client.add_expected_call(
448
'BzrDir.open_branchV3', ('quack/',),
449
'success', ('ref', self.get_url('referenced'))),
450
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
452
result = a_bzrdir.cloning_metadir()
453
# We should have got a control dir matching the referenced branch.
454
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
455
self.assertEqual(expected._repository_format, result._repository_format)
456
self.assertEqual(expected._branch_format, result._branch_format)
457
self.assertFinished(client)
459
def test_current_server(self):
460
transport = self.get_transport('.')
461
transport = transport.clone('quack')
462
self.make_bzrdir('quack')
463
client = FakeClient(transport.base)
464
reference_bzrdir_format = bzrdir.format_registry.get('default')()
465
control_name = reference_bzrdir_format.network_name()
466
client.add_expected_call(
467
'BzrDir.cloning_metadir', ('quack/', 'False'),
468
'success', (control_name, '', ('branch', ''))),
469
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
471
result = a_bzrdir.cloning_metadir()
472
# We should have got a reference control dir with default branch and
473
# repository formats.
474
# This pokes a little, just to be sure.
475
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
476
self.assertEqual(None, result._repository_format)
477
self.assertEqual(None, result._branch_format)
478
self.assertFinished(client)
481
class TestBzrDirOpen(TestRemote):
483
def make_fake_client_and_transport(self, path='quack'):
484
transport = MemoryTransport()
485
transport.mkdir(path)
486
transport = transport.clone(path)
487
client = FakeClient(transport.base)
488
return client, transport
490
def test_absent(self):
491
client, transport = self.make_fake_client_and_transport()
492
client.add_expected_call(
493
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
494
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
495
remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
496
self.assertFinished(client)
498
def test_present_without_workingtree(self):
499
client, transport = self.make_fake_client_and_transport()
500
client.add_expected_call(
501
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
502
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
503
_client=client, _force_probe=True)
504
self.assertIsInstance(bd, RemoteBzrDir)
505
self.assertFalse(bd.has_workingtree())
506
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
507
self.assertFinished(client)
509
def test_present_with_workingtree(self):
510
client, transport = self.make_fake_client_and_transport()
511
client.add_expected_call(
512
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
513
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
514
_client=client, _force_probe=True)
515
self.assertIsInstance(bd, RemoteBzrDir)
516
self.assertTrue(bd.has_workingtree())
517
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
518
self.assertFinished(client)
520
def test_backwards_compat(self):
521
client, transport = self.make_fake_client_and_transport()
522
client.add_expected_call(
523
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
524
client.add_expected_call(
525
'BzrDir.open', ('quack/',), 'success', ('yes',))
526
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
527
_client=client, _force_probe=True)
528
self.assertIsInstance(bd, RemoteBzrDir)
529
self.assertFinished(client)
531
def test_backwards_compat_hpss_v2(self):
532
client, transport = self.make_fake_client_and_transport()
533
# Monkey-patch fake client to simulate real-world behaviour with v2
534
# server: upon first RPC call detect the protocol version, and because
535
# the version is 2 also do _remember_remote_is_before((1, 6)) before
536
# continuing with the RPC.
537
orig_check_call = client._check_call
538
def check_call(method, args):
539
client._medium._protocol_version = 2
540
client._medium._remember_remote_is_before((1, 6))
541
client._check_call = orig_check_call
542
client._check_call(method, args)
543
client._check_call = check_call
544
client.add_expected_call(
545
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
546
client.add_expected_call(
547
'BzrDir.open', ('quack/',), 'success', ('yes',))
548
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
549
_client=client, _force_probe=True)
550
self.assertIsInstance(bd, RemoteBzrDir)
551
self.assertFinished(client)
554
class TestBzrDirOpenBranch(TestRemote):
556
def test_backwards_compat(self):
557
self.setup_smart_server_with_call_log()
558
self.make_branch('.')
559
a_dir = BzrDir.open(self.get_url('.'))
560
self.reset_smart_call_log()
561
verb = 'BzrDir.open_branchV3'
562
self.disable_verb(verb)
563
format = a_dir.open_branch()
564
call_count = len([call for call in self.hpss_calls if
565
call.call.method == verb])
566
self.assertEqual(1, call_count)
568
def test_branch_present(self):
569
reference_format = self.get_repo_format()
570
network_name = reference_format.network_name()
571
branch_network_name = self.get_branch_format().network_name()
572
transport = MemoryTransport()
573
transport.mkdir('quack')
574
transport = transport.clone('quack')
575
client = FakeClient(transport.base)
576
client.add_expected_call(
577
'BzrDir.open_branchV3', ('quack/',),
578
'success', ('branch', branch_network_name))
579
client.add_expected_call(
580
'BzrDir.find_repositoryV3', ('quack/',),
581
'success', ('ok', '', 'no', 'no', 'no', network_name))
582
client.add_expected_call(
583
'Branch.get_stacked_on_url', ('quack/',),
584
'error', ('NotStacked',))
585
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
587
result = bzrdir.open_branch()
588
self.assertIsInstance(result, RemoteBranch)
589
self.assertEqual(bzrdir, result.bzrdir)
590
self.assertFinished(client)
592
def test_branch_missing(self):
593
transport = MemoryTransport()
594
transport.mkdir('quack')
595
transport = transport.clone('quack')
596
client = FakeClient(transport.base)
597
client.add_error_response('nobranch')
598
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
600
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
602
[('call', 'BzrDir.open_branchV3', ('quack/',))],
605
def test__get_tree_branch(self):
606
# _get_tree_branch is a form of open_branch, but it should only ask for
607
# branch opening, not any other network requests.
609
def open_branch(name=None):
610
calls.append("Called")
612
transport = MemoryTransport()
613
# no requests on the network - catches other api calls being made.
614
client = FakeClient(transport.base)
615
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
617
# patch the open_branch call to record that it was called.
618
bzrdir.open_branch = open_branch
619
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
620
self.assertEqual(["Called"], calls)
621
self.assertEqual([], client._calls)
623
def test_url_quoting_of_path(self):
624
# Relpaths on the wire should not be URL-escaped. So "~" should be
625
# transmitted as "~", not "%7E".
626
transport = RemoteTCPTransport('bzr://localhost/~hello/')
627
client = FakeClient(transport.base)
628
reference_format = self.get_repo_format()
629
network_name = reference_format.network_name()
630
branch_network_name = self.get_branch_format().network_name()
631
client.add_expected_call(
632
'BzrDir.open_branchV3', ('~hello/',),
633
'success', ('branch', branch_network_name))
634
client.add_expected_call(
635
'BzrDir.find_repositoryV3', ('~hello/',),
636
'success', ('ok', '', 'no', 'no', 'no', network_name))
637
client.add_expected_call(
638
'Branch.get_stacked_on_url', ('~hello/',),
639
'error', ('NotStacked',))
640
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
642
result = bzrdir.open_branch()
643
self.assertFinished(client)
645
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
646
reference_format = self.get_repo_format()
647
network_name = reference_format.network_name()
648
transport = MemoryTransport()
649
transport.mkdir('quack')
650
transport = transport.clone('quack')
652
rich_response = 'yes'
656
subtree_response = 'yes'
658
subtree_response = 'no'
659
client = FakeClient(transport.base)
660
client.add_success_response(
661
'ok', '', rich_response, subtree_response, external_lookup,
663
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
665
result = bzrdir.open_repository()
667
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
669
self.assertIsInstance(result, RemoteRepository)
670
self.assertEqual(bzrdir, result.bzrdir)
671
self.assertEqual(rich_root, result._format.rich_root_data)
672
self.assertEqual(subtrees, result._format.supports_tree_reference)
674
def test_open_repository_sets_format_attributes(self):
675
self.check_open_repository(True, True)
676
self.check_open_repository(False, True)
677
self.check_open_repository(True, False)
678
self.check_open_repository(False, False)
679
self.check_open_repository(False, False, 'yes')
681
def test_old_server(self):
682
"""RemoteBzrDirFormat should fail to probe if the server version is too
685
self.assertRaises(errors.NotBranchError,
686
RemoteBzrDirFormat.probe_transport, OldServerTransport())
689
class TestBzrDirCreateBranch(TestRemote):
691
def test_backwards_compat(self):
692
self.setup_smart_server_with_call_log()
693
repo = self.make_repository('.')
694
self.reset_smart_call_log()
695
self.disable_verb('BzrDir.create_branch')
696
branch = repo.bzrdir.create_branch()
697
create_branch_call_count = len([call for call in self.hpss_calls if
698
call.call.method == 'BzrDir.create_branch'])
699
self.assertEqual(1, create_branch_call_count)
701
def test_current_server(self):
702
transport = self.get_transport('.')
703
transport = transport.clone('quack')
704
self.make_repository('quack')
705
client = FakeClient(transport.base)
706
reference_bzrdir_format = bzrdir.format_registry.get('default')()
707
reference_format = reference_bzrdir_format.get_branch_format()
708
network_name = reference_format.network_name()
709
reference_repo_fmt = reference_bzrdir_format.repository_format
710
reference_repo_name = reference_repo_fmt.network_name()
711
client.add_expected_call(
712
'BzrDir.create_branch', ('quack/', network_name),
713
'success', ('ok', network_name, '', 'no', 'no', 'yes',
714
reference_repo_name))
715
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
717
branch = a_bzrdir.create_branch()
718
# We should have got a remote branch
719
self.assertIsInstance(branch, remote.RemoteBranch)
720
# its format should have the settings from the response
721
format = branch._format
722
self.assertEqual(network_name, format.network_name())
725
class TestBzrDirCreateRepository(TestRemote):
727
def test_backwards_compat(self):
728
self.setup_smart_server_with_call_log()
729
bzrdir = self.make_bzrdir('.')
730
self.reset_smart_call_log()
731
self.disable_verb('BzrDir.create_repository')
732
repo = bzrdir.create_repository()
733
create_repo_call_count = len([call for call in self.hpss_calls if
734
call.call.method == 'BzrDir.create_repository'])
735
self.assertEqual(1, create_repo_call_count)
737
def test_current_server(self):
738
transport = self.get_transport('.')
739
transport = transport.clone('quack')
740
self.make_bzrdir('quack')
741
client = FakeClient(transport.base)
742
reference_bzrdir_format = bzrdir.format_registry.get('default')()
743
reference_format = reference_bzrdir_format.repository_format
744
network_name = reference_format.network_name()
745
client.add_expected_call(
746
'BzrDir.create_repository', ('quack/',
747
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
749
'success', ('ok', 'yes', 'yes', 'yes', network_name))
750
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
752
repo = a_bzrdir.create_repository()
753
# We should have got a remote repository
754
self.assertIsInstance(repo, remote.RemoteRepository)
755
# its format should have the settings from the response
756
format = repo._format
757
self.assertTrue(format.rich_root_data)
758
self.assertTrue(format.supports_tree_reference)
759
self.assertTrue(format.supports_external_lookups)
760
self.assertEqual(network_name, format.network_name())
763
class TestBzrDirOpenRepository(TestRemote):
765
def test_backwards_compat_1_2_3(self):
766
# fallback all the way to the first version.
767
reference_format = self.get_repo_format()
768
network_name = reference_format.network_name()
769
server_url = 'bzr://example.com/'
770
self.permit_url(server_url)
771
client = FakeClient(server_url)
772
client.add_unknown_method_response('BzrDir.find_repositoryV3')
773
client.add_unknown_method_response('BzrDir.find_repositoryV2')
774
client.add_success_response('ok', '', 'no', 'no')
775
# A real repository instance will be created to determine the network
777
client.add_success_response_with_body(
778
"Bazaar-NG meta directory, format 1\n", 'ok')
779
client.add_success_response_with_body(
780
reference_format.get_format_string(), 'ok')
781
# PackRepository wants to do a stat
782
client.add_success_response('stat', '0', '65535')
783
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
785
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
787
repo = bzrdir.open_repository()
789
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
790
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
791
('call', 'BzrDir.find_repository', ('quack/',)),
792
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
793
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
794
('call', 'stat', ('/quack/.bzr/repository',)),
797
self.assertEqual(network_name, repo._format.network_name())
799
def test_backwards_compat_2(self):
800
# fallback to find_repositoryV2
801
reference_format = self.get_repo_format()
802
network_name = reference_format.network_name()
803
server_url = 'bzr://example.com/'
804
self.permit_url(server_url)
805
client = FakeClient(server_url)
806
client.add_unknown_method_response('BzrDir.find_repositoryV3')
807
client.add_success_response('ok', '', 'no', 'no', 'no')
808
# A real repository instance will be created to determine the network
810
client.add_success_response_with_body(
811
"Bazaar-NG meta directory, format 1\n", 'ok')
812
client.add_success_response_with_body(
813
reference_format.get_format_string(), 'ok')
814
# PackRepository wants to do a stat
815
client.add_success_response('stat', '0', '65535')
816
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
818
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
820
repo = bzrdir.open_repository()
822
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
823
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
824
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
825
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
826
('call', 'stat', ('/quack/.bzr/repository',)),
829
self.assertEqual(network_name, repo._format.network_name())
831
def test_current_server(self):
832
reference_format = self.get_repo_format()
833
network_name = reference_format.network_name()
834
transport = MemoryTransport()
835
transport.mkdir('quack')
836
transport = transport.clone('quack')
837
client = FakeClient(transport.base)
838
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
839
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
841
repo = bzrdir.open_repository()
843
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
845
self.assertEqual(network_name, repo._format.network_name())
848
class TestBzrDirFormatInitializeEx(TestRemote):
850
def test_success(self):
851
"""Simple test for typical successful call."""
852
fmt = bzrdir.RemoteBzrDirFormat()
853
default_format_name = BzrDirFormat.get_default_format().network_name()
854
transport = self.get_transport()
855
client = FakeClient(transport.base)
856
client.add_expected_call(
857
'BzrDirFormat.initialize_ex_1.16',
858
(default_format_name, 'path', 'False', 'False', 'False', '',
859
'', '', '', 'False'),
861
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
862
'bzrdir fmt', 'False', '', '', 'repo lock token'))
863
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
864
# it's currently hard to test that without supplying a real remote
865
# transport connected to a real server.
866
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
867
transport, False, False, False, None, None, None, None, False)
868
self.assertFinished(client)
870
def test_error(self):
871
"""Error responses are translated, e.g. 'PermissionDenied' raises the
872
corresponding error from the client.
874
fmt = bzrdir.RemoteBzrDirFormat()
875
default_format_name = BzrDirFormat.get_default_format().network_name()
876
transport = self.get_transport()
877
client = FakeClient(transport.base)
878
client.add_expected_call(
879
'BzrDirFormat.initialize_ex_1.16',
880
(default_format_name, 'path', 'False', 'False', 'False', '',
881
'', '', '', 'False'),
883
('PermissionDenied', 'path', 'extra info'))
884
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
885
# it's currently hard to test that without supplying a real remote
886
# transport connected to a real server.
887
err = self.assertRaises(errors.PermissionDenied,
888
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
889
False, False, False, None, None, None, None, False)
890
self.assertEqual('path', err.path)
891
self.assertEqual(': extra info', err.extra)
892
self.assertFinished(client)
894
def test_error_from_real_server(self):
895
"""Integration test for error translation."""
896
transport = self.make_smart_server('foo')
897
transport = transport.clone('no-such-path')
898
fmt = bzrdir.RemoteBzrDirFormat()
899
err = self.assertRaises(errors.NoSuchFile,
900
fmt.initialize_on_transport_ex, transport, create_prefix=False)
903
class OldSmartClient(object):
904
"""A fake smart client for test_old_version that just returns a version one
905
response to the 'hello' (query version) command.
908
def get_request(self):
909
input_file = StringIO('ok\x011\n')
910
output_file = StringIO()
911
client_medium = medium.SmartSimplePipesClientMedium(
912
input_file, output_file)
913
return medium.SmartClientStreamMediumRequest(client_medium)
915
def protocol_version(self):
919
class OldServerTransport(object):
920
"""A fake transport for test_old_server that reports it's smart server
921
protocol version as version one.
927
def get_smart_client(self):
928
return OldSmartClient()
931
class RemoteBzrDirTestCase(TestRemote):
933
def make_remote_bzrdir(self, transport, client):
934
"""Make a RemotebzrDir using 'client' as the _client."""
935
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
939
class RemoteBranchTestCase(RemoteBzrDirTestCase):
941
def lock_remote_branch(self, branch):
942
"""Trick a RemoteBranch into thinking it is locked."""
943
branch._lock_mode = 'w'
944
branch._lock_count = 2
945
branch._lock_token = 'branch token'
946
branch._repo_lock_token = 'repo token'
947
branch.repository._lock_mode = 'w'
948
branch.repository._lock_count = 2
949
branch.repository._lock_token = 'repo token'
951
def make_remote_branch(self, transport, client):
952
"""Make a RemoteBranch using 'client' as its _SmartClient.
954
A RemoteBzrDir and RemoteRepository will also be created to fill out
955
the RemoteBranch, albeit with stub values for some of their attributes.
957
# we do not want bzrdir to make any remote calls, so use False as its
958
# _client. If it tries to make a remote call, this will fail
960
bzrdir = self.make_remote_bzrdir(transport, False)
961
repo = RemoteRepository(bzrdir, None, _client=client)
962
branch_format = self.get_branch_format()
963
format = RemoteBranchFormat(network_name=branch_format.network_name())
964
return RemoteBranch(bzrdir, repo, _client=client, format=format)
967
class TestBranchGetParent(RemoteBranchTestCase):
969
def test_no_parent(self):
970
# in an empty branch we decode the response properly
971
transport = MemoryTransport()
972
client = FakeClient(transport.base)
973
client.add_expected_call(
974
'Branch.get_stacked_on_url', ('quack/',),
975
'error', ('NotStacked',))
976
client.add_expected_call(
977
'Branch.get_parent', ('quack/',),
979
transport.mkdir('quack')
980
transport = transport.clone('quack')
981
branch = self.make_remote_branch(transport, client)
982
result = branch.get_parent()
983
self.assertFinished(client)
984
self.assertEqual(None, result)
986
def test_parent_relative(self):
987
transport = MemoryTransport()
988
client = FakeClient(transport.base)
989
client.add_expected_call(
990
'Branch.get_stacked_on_url', ('kwaak/',),
991
'error', ('NotStacked',))
992
client.add_expected_call(
993
'Branch.get_parent', ('kwaak/',),
994
'success', ('../foo/',))
995
transport.mkdir('kwaak')
996
transport = transport.clone('kwaak')
997
branch = self.make_remote_branch(transport, client)
998
result = branch.get_parent()
999
self.assertEqual(transport.clone('../foo').base, result)
1001
def test_parent_absolute(self):
1002
transport = MemoryTransport()
1003
client = FakeClient(transport.base)
1004
client.add_expected_call(
1005
'Branch.get_stacked_on_url', ('kwaak/',),
1006
'error', ('NotStacked',))
1007
client.add_expected_call(
1008
'Branch.get_parent', ('kwaak/',),
1009
'success', ('http://foo/',))
1010
transport.mkdir('kwaak')
1011
transport = transport.clone('kwaak')
1012
branch = self.make_remote_branch(transport, client)
1013
result = branch.get_parent()
1014
self.assertEqual('http://foo/', result)
1015
self.assertFinished(client)
1018
class TestBranchSetParentLocation(RemoteBranchTestCase):
1020
def test_no_parent(self):
1021
# We call the verb when setting parent to None
1022
transport = MemoryTransport()
1023
client = FakeClient(transport.base)
1024
client.add_expected_call(
1025
'Branch.get_stacked_on_url', ('quack/',),
1026
'error', ('NotStacked',))
1027
client.add_expected_call(
1028
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1030
transport.mkdir('quack')
1031
transport = transport.clone('quack')
1032
branch = self.make_remote_branch(transport, client)
1033
branch._lock_token = 'b'
1034
branch._repo_lock_token = 'r'
1035
branch._set_parent_location(None)
1036
self.assertFinished(client)
1038
def test_parent(self):
1039
transport = MemoryTransport()
1040
client = FakeClient(transport.base)
1041
client.add_expected_call(
1042
'Branch.get_stacked_on_url', ('kwaak/',),
1043
'error', ('NotStacked',))
1044
client.add_expected_call(
1045
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1047
transport.mkdir('kwaak')
1048
transport = transport.clone('kwaak')
1049
branch = self.make_remote_branch(transport, client)
1050
branch._lock_token = 'b'
1051
branch._repo_lock_token = 'r'
1052
branch._set_parent_location('foo')
1053
self.assertFinished(client)
1055
def test_backwards_compat(self):
1056
self.setup_smart_server_with_call_log()
1057
branch = self.make_branch('.')
1058
self.reset_smart_call_log()
1059
verb = 'Branch.set_parent_location'
1060
self.disable_verb(verb)
1061
branch.set_parent('http://foo/')
1062
self.assertLength(12, self.hpss_calls)
1065
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1067
def test_backwards_compat(self):
1068
self.setup_smart_server_with_call_log()
1069
branch = self.make_branch('.')
1070
self.reset_smart_call_log()
1071
verb = 'Branch.get_tags_bytes'
1072
self.disable_verb(verb)
1073
branch.tags.get_tag_dict()
1074
call_count = len([call for call in self.hpss_calls if
1075
call.call.method == verb])
1076
self.assertEqual(1, call_count)
1078
def test_trivial(self):
1079
transport = MemoryTransport()
1080
client = FakeClient(transport.base)
1081
client.add_expected_call(
1082
'Branch.get_stacked_on_url', ('quack/',),
1083
'error', ('NotStacked',))
1084
client.add_expected_call(
1085
'Branch.get_tags_bytes', ('quack/',),
1087
transport.mkdir('quack')
1088
transport = transport.clone('quack')
1089
branch = self.make_remote_branch(transport, client)
1090
result = branch.tags.get_tag_dict()
1091
self.assertFinished(client)
1092
self.assertEqual({}, result)
1095
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1097
def test_trivial(self):
1098
transport = MemoryTransport()
1099
client = FakeClient(transport.base)
1100
client.add_expected_call(
1101
'Branch.get_stacked_on_url', ('quack/',),
1102
'error', ('NotStacked',))
1103
client.add_expected_call(
1104
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1106
transport.mkdir('quack')
1107
transport = transport.clone('quack')
1108
branch = self.make_remote_branch(transport, client)
1109
self.lock_remote_branch(branch)
1110
branch._set_tags_bytes('tags bytes')
1111
self.assertFinished(client)
1112
self.assertEqual('tags bytes', client._calls[-1][-1])
1114
def test_backwards_compatible(self):
1115
transport = MemoryTransport()
1116
client = FakeClient(transport.base)
1117
client.add_expected_call(
1118
'Branch.get_stacked_on_url', ('quack/',),
1119
'error', ('NotStacked',))
1120
client.add_expected_call(
1121
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1122
'unknown', ('Branch.set_tags_bytes',))
1123
transport.mkdir('quack')
1124
transport = transport.clone('quack')
1125
branch = self.make_remote_branch(transport, client)
1126
self.lock_remote_branch(branch)
1127
class StubRealBranch(object):
1130
def _set_tags_bytes(self, bytes):
1131
self.calls.append(('set_tags_bytes', bytes))
1132
real_branch = StubRealBranch()
1133
branch._real_branch = real_branch
1134
branch._set_tags_bytes('tags bytes')
1135
# Call a second time, to exercise the 'remote version already inferred'
1137
branch._set_tags_bytes('tags bytes')
1138
self.assertFinished(client)
1140
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1143
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1145
def test_empty_branch(self):
1146
# in an empty branch we decode the response properly
1147
transport = MemoryTransport()
1148
client = FakeClient(transport.base)
1149
client.add_expected_call(
1150
'Branch.get_stacked_on_url', ('quack/',),
1151
'error', ('NotStacked',))
1152
client.add_expected_call(
1153
'Branch.last_revision_info', ('quack/',),
1154
'success', ('ok', '0', 'null:'))
1155
transport.mkdir('quack')
1156
transport = transport.clone('quack')
1157
branch = self.make_remote_branch(transport, client)
1158
result = branch.last_revision_info()
1159
self.assertFinished(client)
1160
self.assertEqual((0, NULL_REVISION), result)
1162
def test_non_empty_branch(self):
1163
# in a non-empty branch we also decode the response properly
1164
revid = u'\xc8'.encode('utf8')
1165
transport = MemoryTransport()
1166
client = FakeClient(transport.base)
1167
client.add_expected_call(
1168
'Branch.get_stacked_on_url', ('kwaak/',),
1169
'error', ('NotStacked',))
1170
client.add_expected_call(
1171
'Branch.last_revision_info', ('kwaak/',),
1172
'success', ('ok', '2', revid))
1173
transport.mkdir('kwaak')
1174
transport = transport.clone('kwaak')
1175
branch = self.make_remote_branch(transport, client)
1176
result = branch.last_revision_info()
1177
self.assertEqual((2, revid), result)
1180
class TestBranch_get_stacked_on_url(TestRemote):
1181
"""Test Branch._get_stacked_on_url rpc"""
1183
def test_get_stacked_on_invalid_url(self):
1184
# test that asking for a stacked on url the server can't access works.
1185
# This isn't perfect, but then as we're in the same process there
1186
# really isn't anything we can do to be 100% sure that the server
1187
# doesn't just open in - this test probably needs to be rewritten using
1188
# a spawn()ed server.
1189
stacked_branch = self.make_branch('stacked', format='1.9')
1190
memory_branch = self.make_branch('base', format='1.9')
1191
vfs_url = self.get_vfs_only_url('base')
1192
stacked_branch.set_stacked_on_url(vfs_url)
1193
transport = stacked_branch.bzrdir.root_transport
1194
client = FakeClient(transport.base)
1195
client.add_expected_call(
1196
'Branch.get_stacked_on_url', ('stacked/',),
1197
'success', ('ok', vfs_url))
1198
# XXX: Multiple calls are bad, this second call documents what is
1200
client.add_expected_call(
1201
'Branch.get_stacked_on_url', ('stacked/',),
1202
'success', ('ok', vfs_url))
1203
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1205
repo_fmt = remote.RemoteRepositoryFormat()
1206
repo_fmt._custom_format = stacked_branch.repository._format
1207
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1209
result = branch.get_stacked_on_url()
1210
self.assertEqual(vfs_url, result)
1212
def test_backwards_compatible(self):
1213
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1214
base_branch = self.make_branch('base', format='1.6')
1215
stacked_branch = self.make_branch('stacked', format='1.6')
1216
stacked_branch.set_stacked_on_url('../base')
1217
client = FakeClient(self.get_url())
1218
branch_network_name = self.get_branch_format().network_name()
1219
client.add_expected_call(
1220
'BzrDir.open_branchV3', ('stacked/',),
1221
'success', ('branch', branch_network_name))
1222
client.add_expected_call(
1223
'BzrDir.find_repositoryV3', ('stacked/',),
1224
'success', ('ok', '', 'no', 'no', 'yes',
1225
stacked_branch.repository._format.network_name()))
1226
# called twice, once from constructor and then again by us
1227
client.add_expected_call(
1228
'Branch.get_stacked_on_url', ('stacked/',),
1229
'unknown', ('Branch.get_stacked_on_url',))
1230
client.add_expected_call(
1231
'Branch.get_stacked_on_url', ('stacked/',),
1232
'unknown', ('Branch.get_stacked_on_url',))
1233
# this will also do vfs access, but that goes direct to the transport
1234
# and isn't seen by the FakeClient.
1235
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1236
remote.RemoteBzrDirFormat(), _client=client)
1237
branch = bzrdir.open_branch()
1238
result = branch.get_stacked_on_url()
1239
self.assertEqual('../base', result)
1240
self.assertFinished(client)
1241
# it's in the fallback list both for the RemoteRepository and its vfs
1243
self.assertEqual(1, len(branch.repository._fallback_repositories))
1245
len(branch.repository._real_repository._fallback_repositories))
1247
def test_get_stacked_on_real_branch(self):
1248
base_branch = self.make_branch('base')
1249
stacked_branch = self.make_branch('stacked')
1250
stacked_branch.set_stacked_on_url('../base')
1251
reference_format = self.get_repo_format()
1252
network_name = reference_format.network_name()
1253
client = FakeClient(self.get_url())
1254
branch_network_name = self.get_branch_format().network_name()
1255
client.add_expected_call(
1256
'BzrDir.open_branchV3', ('stacked/',),
1257
'success', ('branch', branch_network_name))
1258
client.add_expected_call(
1259
'BzrDir.find_repositoryV3', ('stacked/',),
1260
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1261
# called twice, once from constructor and then again by us
1262
client.add_expected_call(
1263
'Branch.get_stacked_on_url', ('stacked/',),
1264
'success', ('ok', '../base'))
1265
client.add_expected_call(
1266
'Branch.get_stacked_on_url', ('stacked/',),
1267
'success', ('ok', '../base'))
1268
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1269
remote.RemoteBzrDirFormat(), _client=client)
1270
branch = bzrdir.open_branch()
1271
result = branch.get_stacked_on_url()
1272
self.assertEqual('../base', result)
1273
self.assertFinished(client)
1274
# it's in the fallback list both for the RemoteRepository.
1275
self.assertEqual(1, len(branch.repository._fallback_repositories))
1276
# And we haven't had to construct a real repository.
1277
self.assertEqual(None, branch.repository._real_repository)
1280
class TestBranchSetLastRevision(RemoteBranchTestCase):
1282
def test_set_empty(self):
1283
# set_revision_history([]) is translated to calling
1284
# Branch.set_last_revision(path, '') on the wire.
1285
transport = MemoryTransport()
1286
transport.mkdir('branch')
1287
transport = transport.clone('branch')
1289
client = FakeClient(transport.base)
1290
client.add_expected_call(
1291
'Branch.get_stacked_on_url', ('branch/',),
1292
'error', ('NotStacked',))
1293
client.add_expected_call(
1294
'Branch.lock_write', ('branch/', '', ''),
1295
'success', ('ok', 'branch token', 'repo token'))
1296
client.add_expected_call(
1297
'Branch.last_revision_info',
1299
'success', ('ok', '0', 'null:'))
1300
client.add_expected_call(
1301
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1303
client.add_expected_call(
1304
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1306
branch = self.make_remote_branch(transport, client)
1307
# This is a hack to work around the problem that RemoteBranch currently
1308
# unnecessarily invokes _ensure_real upon a call to lock_write.
1309
branch._ensure_real = lambda: None
1311
result = branch.set_revision_history([])
1313
self.assertEqual(None, result)
1314
self.assertFinished(client)
1316
def test_set_nonempty(self):
1317
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1318
# Branch.set_last_revision(path, rev-idN) on the wire.
1319
transport = MemoryTransport()
1320
transport.mkdir('branch')
1321
transport = transport.clone('branch')
1323
client = FakeClient(transport.base)
1324
client.add_expected_call(
1325
'Branch.get_stacked_on_url', ('branch/',),
1326
'error', ('NotStacked',))
1327
client.add_expected_call(
1328
'Branch.lock_write', ('branch/', '', ''),
1329
'success', ('ok', 'branch token', 'repo token'))
1330
client.add_expected_call(
1331
'Branch.last_revision_info',
1333
'success', ('ok', '0', 'null:'))
1335
encoded_body = bz2.compress('\n'.join(lines))
1336
client.add_success_response_with_body(encoded_body, 'ok')
1337
client.add_expected_call(
1338
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1340
client.add_expected_call(
1341
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1343
branch = self.make_remote_branch(transport, client)
1344
# This is a hack to work around the problem that RemoteBranch currently
1345
# unnecessarily invokes _ensure_real upon a call to lock_write.
1346
branch._ensure_real = lambda: None
1347
# Lock the branch, reset the record of remote calls.
1349
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1351
self.assertEqual(None, result)
1352
self.assertFinished(client)
1354
def test_no_such_revision(self):
1355
transport = MemoryTransport()
1356
transport.mkdir('branch')
1357
transport = transport.clone('branch')
1358
# A response of 'NoSuchRevision' is translated into an exception.
1359
client = FakeClient(transport.base)
1360
client.add_expected_call(
1361
'Branch.get_stacked_on_url', ('branch/',),
1362
'error', ('NotStacked',))
1363
client.add_expected_call(
1364
'Branch.lock_write', ('branch/', '', ''),
1365
'success', ('ok', 'branch token', 'repo token'))
1366
client.add_expected_call(
1367
'Branch.last_revision_info',
1369
'success', ('ok', '0', 'null:'))
1370
# get_graph calls to construct the revision history, for the set_rh
1373
encoded_body = bz2.compress('\n'.join(lines))
1374
client.add_success_response_with_body(encoded_body, 'ok')
1375
client.add_expected_call(
1376
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1377
'error', ('NoSuchRevision', 'rev-id'))
1378
client.add_expected_call(
1379
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1382
branch = self.make_remote_branch(transport, client)
1385
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1387
self.assertFinished(client)
1389
def test_tip_change_rejected(self):
1390
"""TipChangeRejected responses cause a TipChangeRejected exception to
1393
transport = MemoryTransport()
1394
transport.mkdir('branch')
1395
transport = transport.clone('branch')
1396
client = FakeClient(transport.base)
1397
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1398
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1399
client.add_expected_call(
1400
'Branch.get_stacked_on_url', ('branch/',),
1401
'error', ('NotStacked',))
1402
client.add_expected_call(
1403
'Branch.lock_write', ('branch/', '', ''),
1404
'success', ('ok', 'branch token', 'repo token'))
1405
client.add_expected_call(
1406
'Branch.last_revision_info',
1408
'success', ('ok', '0', 'null:'))
1410
encoded_body = bz2.compress('\n'.join(lines))
1411
client.add_success_response_with_body(encoded_body, 'ok')
1412
client.add_expected_call(
1413
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1414
'error', ('TipChangeRejected', rejection_msg_utf8))
1415
client.add_expected_call(
1416
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1418
branch = self.make_remote_branch(transport, client)
1419
branch._ensure_real = lambda: None
1421
# The 'TipChangeRejected' error response triggered by calling
1422
# set_revision_history causes a TipChangeRejected exception.
1423
err = self.assertRaises(
1424
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1425
# The UTF-8 message from the response has been decoded into a unicode
1427
self.assertIsInstance(err.msg, unicode)
1428
self.assertEqual(rejection_msg_unicode, err.msg)
1430
self.assertFinished(client)
1433
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1435
def test_set_last_revision_info(self):
1436
# set_last_revision_info(num, 'rev-id') is translated to calling
1437
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1438
transport = MemoryTransport()
1439
transport.mkdir('branch')
1440
transport = transport.clone('branch')
1441
client = FakeClient(transport.base)
1442
# get_stacked_on_url
1443
client.add_error_response('NotStacked')
1445
client.add_success_response('ok', 'branch token', 'repo token')
1446
# query the current revision
1447
client.add_success_response('ok', '0', 'null:')
1449
client.add_success_response('ok')
1451
client.add_success_response('ok')
1453
branch = self.make_remote_branch(transport, client)
1454
# Lock the branch, reset the record of remote calls.
1457
result = branch.set_last_revision_info(1234, 'a-revision-id')
1459
[('call', 'Branch.last_revision_info', ('branch/',)),
1460
('call', 'Branch.set_last_revision_info',
1461
('branch/', 'branch token', 'repo token',
1462
'1234', 'a-revision-id'))],
1464
self.assertEqual(None, result)
1466
def test_no_such_revision(self):
1467
# A response of 'NoSuchRevision' is translated into an exception.
1468
transport = MemoryTransport()
1469
transport.mkdir('branch')
1470
transport = transport.clone('branch')
1471
client = FakeClient(transport.base)
1472
# get_stacked_on_url
1473
client.add_error_response('NotStacked')
1475
client.add_success_response('ok', 'branch token', 'repo token')
1477
client.add_error_response('NoSuchRevision', 'revid')
1479
client.add_success_response('ok')
1481
branch = self.make_remote_branch(transport, client)
1482
# Lock the branch, reset the record of remote calls.
1487
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1490
def test_backwards_compatibility(self):
1491
"""If the server does not support the Branch.set_last_revision_info
1492
verb (which is new in 1.4), then the client falls back to VFS methods.
1494
# This test is a little messy. Unlike most tests in this file, it
1495
# doesn't purely test what a Remote* object sends over the wire, and
1496
# how it reacts to responses from the wire. It instead relies partly
1497
# on asserting that the RemoteBranch will call
1498
# self._real_branch.set_last_revision_info(...).
1500
# First, set up our RemoteBranch with a FakeClient that raises
1501
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1502
transport = MemoryTransport()
1503
transport.mkdir('branch')
1504
transport = transport.clone('branch')
1505
client = FakeClient(transport.base)
1506
client.add_expected_call(
1507
'Branch.get_stacked_on_url', ('branch/',),
1508
'error', ('NotStacked',))
1509
client.add_expected_call(
1510
'Branch.last_revision_info',
1512
'success', ('ok', '0', 'null:'))
1513
client.add_expected_call(
1514
'Branch.set_last_revision_info',
1515
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1516
'unknown', 'Branch.set_last_revision_info')
1518
branch = self.make_remote_branch(transport, client)
1519
class StubRealBranch(object):
1522
def set_last_revision_info(self, revno, revision_id):
1524
('set_last_revision_info', revno, revision_id))
1525
def _clear_cached_state(self):
1527
real_branch = StubRealBranch()
1528
branch._real_branch = real_branch
1529
self.lock_remote_branch(branch)
1531
# Call set_last_revision_info, and verify it behaved as expected.
1532
result = branch.set_last_revision_info(1234, 'a-revision-id')
1534
[('set_last_revision_info', 1234, 'a-revision-id')],
1536
self.assertFinished(client)
1538
def test_unexpected_error(self):
1539
# If the server sends an error the client doesn't understand, it gets
1540
# turned into an UnknownErrorFromSmartServer, which is presented as a
1541
# non-internal error to the user.
1542
transport = MemoryTransport()
1543
transport.mkdir('branch')
1544
transport = transport.clone('branch')
1545
client = FakeClient(transport.base)
1546
# get_stacked_on_url
1547
client.add_error_response('NotStacked')
1549
client.add_success_response('ok', 'branch token', 'repo token')
1551
client.add_error_response('UnexpectedError')
1553
client.add_success_response('ok')
1555
branch = self.make_remote_branch(transport, client)
1556
# Lock the branch, reset the record of remote calls.
1560
err = self.assertRaises(
1561
errors.UnknownErrorFromSmartServer,
1562
branch.set_last_revision_info, 123, 'revid')
1563
self.assertEqual(('UnexpectedError',), err.error_tuple)
1566
def test_tip_change_rejected(self):
1567
"""TipChangeRejected responses cause a TipChangeRejected exception to
1570
transport = MemoryTransport()
1571
transport.mkdir('branch')
1572
transport = transport.clone('branch')
1573
client = FakeClient(transport.base)
1574
# get_stacked_on_url
1575
client.add_error_response('NotStacked')
1577
client.add_success_response('ok', 'branch token', 'repo token')
1579
client.add_error_response('TipChangeRejected', 'rejection message')
1581
client.add_success_response('ok')
1583
branch = self.make_remote_branch(transport, client)
1584
# Lock the branch, reset the record of remote calls.
1586
self.addCleanup(branch.unlock)
1589
# The 'TipChangeRejected' error response triggered by calling
1590
# set_last_revision_info causes a TipChangeRejected exception.
1591
err = self.assertRaises(
1592
errors.TipChangeRejected,
1593
branch.set_last_revision_info, 123, 'revid')
1594
self.assertEqual('rejection message', err.msg)
1597
class TestBranchGetSetConfig(RemoteBranchTestCase):
1599
def test_get_branch_conf(self):
1600
# in an empty branch we decode the response properly
1601
client = FakeClient()
1602
client.add_expected_call(
1603
'Branch.get_stacked_on_url', ('memory:///',),
1604
'error', ('NotStacked',),)
1605
client.add_success_response_with_body('# config file body', 'ok')
1606
transport = MemoryTransport()
1607
branch = self.make_remote_branch(transport, client)
1608
config = branch.get_config()
1609
config.has_explicit_nickname()
1611
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1612
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1615
def test_get_multi_line_branch_conf(self):
1616
# Make sure that multiple-line branch.conf files are supported
1618
# https://bugs.launchpad.net/bzr/+bug/354075
1619
client = FakeClient()
1620
client.add_expected_call(
1621
'Branch.get_stacked_on_url', ('memory:///',),
1622
'error', ('NotStacked',),)
1623
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1624
transport = MemoryTransport()
1625
branch = self.make_remote_branch(transport, client)
1626
config = branch.get_config()
1627
self.assertEqual(u'2', config.get_user_option('b'))
1629
def test_set_option(self):
1630
client = FakeClient()
1631
client.add_expected_call(
1632
'Branch.get_stacked_on_url', ('memory:///',),
1633
'error', ('NotStacked',),)
1634
client.add_expected_call(
1635
'Branch.lock_write', ('memory:///', '', ''),
1636
'success', ('ok', 'branch token', 'repo token'))
1637
client.add_expected_call(
1638
'Branch.set_config_option', ('memory:///', 'branch token',
1639
'repo token', 'foo', 'bar', ''),
1641
client.add_expected_call(
1642
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1644
transport = MemoryTransport()
1645
branch = self.make_remote_branch(transport, client)
1647
config = branch._get_config()
1648
config.set_option('foo', 'bar')
1650
self.assertFinished(client)
1652
def test_set_option_with_dict(self):
1653
client = FakeClient()
1654
client.add_expected_call(
1655
'Branch.get_stacked_on_url', ('memory:///',),
1656
'error', ('NotStacked',),)
1657
client.add_expected_call(
1658
'Branch.lock_write', ('memory:///', '', ''),
1659
'success', ('ok', 'branch token', 'repo token'))
1660
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1661
client.add_expected_call(
1662
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1663
'repo token', encoded_dict_value, 'foo', ''),
1665
client.add_expected_call(
1666
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1668
transport = MemoryTransport()
1669
branch = self.make_remote_branch(transport, client)
1671
config = branch._get_config()
1673
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1676
self.assertFinished(client)
1678
def test_backwards_compat_set_option(self):
1679
self.setup_smart_server_with_call_log()
1680
branch = self.make_branch('.')
1681
verb = 'Branch.set_config_option'
1682
self.disable_verb(verb)
1684
self.addCleanup(branch.unlock)
1685
self.reset_smart_call_log()
1686
branch._get_config().set_option('value', 'name')
1687
self.assertLength(10, self.hpss_calls)
1688
self.assertEqual('value', branch._get_config().get_option('name'))
1690
def test_backwards_compat_set_option_with_dict(self):
1691
self.setup_smart_server_with_call_log()
1692
branch = self.make_branch('.')
1693
verb = 'Branch.set_config_option_dict'
1694
self.disable_verb(verb)
1696
self.addCleanup(branch.unlock)
1697
self.reset_smart_call_log()
1698
config = branch._get_config()
1699
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
1700
config.set_option(value_dict, 'name')
1701
self.assertLength(10, self.hpss_calls)
1702
self.assertEqual(value_dict, branch._get_config().get_option('name'))
1705
class TestBranchLockWrite(RemoteBranchTestCase):
1707
def test_lock_write_unlockable(self):
1708
transport = MemoryTransport()
1709
client = FakeClient(transport.base)
1710
client.add_expected_call(
1711
'Branch.get_stacked_on_url', ('quack/',),
1712
'error', ('NotStacked',),)
1713
client.add_expected_call(
1714
'Branch.lock_write', ('quack/', '', ''),
1715
'error', ('UnlockableTransport',))
1716
transport.mkdir('quack')
1717
transport = transport.clone('quack')
1718
branch = self.make_remote_branch(transport, client)
1719
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1720
self.assertFinished(client)
1723
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1725
def test__get_config(self):
1726
client = FakeClient()
1727
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1728
transport = MemoryTransport()
1729
bzrdir = self.make_remote_bzrdir(transport, client)
1730
config = bzrdir.get_config()
1731
self.assertEqual('/', config.get_default_stack_on())
1733
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1736
def test_set_option_uses_vfs(self):
1737
self.setup_smart_server_with_call_log()
1738
bzrdir = self.make_bzrdir('.')
1739
self.reset_smart_call_log()
1740
config = bzrdir.get_config()
1741
config.set_default_stack_on('/')
1742
self.assertLength(3, self.hpss_calls)
1744
def test_backwards_compat_get_option(self):
1745
self.setup_smart_server_with_call_log()
1746
bzrdir = self.make_bzrdir('.')
1747
verb = 'BzrDir.get_config_file'
1748
self.disable_verb(verb)
1749
self.reset_smart_call_log()
1750
self.assertEqual(None,
1751
bzrdir._get_config().get_option('default_stack_on'))
1752
self.assertLength(3, self.hpss_calls)
1755
class TestTransportIsReadonly(tests.TestCase):
1757
def test_true(self):
1758
client = FakeClient()
1759
client.add_success_response('yes')
1760
transport = RemoteTransport('bzr://example.com/', medium=False,
1762
self.assertEqual(True, transport.is_readonly())
1764
[('call', 'Transport.is_readonly', ())],
1767
def test_false(self):
1768
client = FakeClient()
1769
client.add_success_response('no')
1770
transport = RemoteTransport('bzr://example.com/', medium=False,
1772
self.assertEqual(False, transport.is_readonly())
1774
[('call', 'Transport.is_readonly', ())],
1777
def test_error_from_old_server(self):
1778
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1780
Clients should treat it as a "no" response, because is_readonly is only
1781
advisory anyway (a transport could be read-write, but then the
1782
underlying filesystem could be readonly anyway).
1784
client = FakeClient()
1785
client.add_unknown_method_response('Transport.is_readonly')
1786
transport = RemoteTransport('bzr://example.com/', medium=False,
1788
self.assertEqual(False, transport.is_readonly())
1790
[('call', 'Transport.is_readonly', ())],
1794
class TestTransportMkdir(tests.TestCase):
1796
def test_permissiondenied(self):
1797
client = FakeClient()
1798
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1799
transport = RemoteTransport('bzr://example.com/', medium=False,
1801
exc = self.assertRaises(
1802
errors.PermissionDenied, transport.mkdir, 'client path')
1803
expected_error = errors.PermissionDenied('/client path', 'extra')
1804
self.assertEqual(expected_error, exc)
1807
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1809
def test_defaults_to_none(self):
1810
t = RemoteSSHTransport('bzr+ssh://example.com')
1811
self.assertIs(None, t._get_credentials()[0])
1813
def test_uses_authentication_config(self):
1814
conf = config.AuthenticationConfig()
1815
conf._get_config().update(
1816
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1819
t = RemoteSSHTransport('bzr+ssh://example.com')
1820
self.assertEqual('bar', t._get_credentials()[0])
1823
class TestRemoteRepository(TestRemote):
1824
"""Base for testing RemoteRepository protocol usage.
1826
These tests contain frozen requests and responses. We want any changes to
1827
what is sent or expected to be require a thoughtful update to these tests
1828
because they might break compatibility with different-versioned servers.
1831
def setup_fake_client_and_repository(self, transport_path):
1832
"""Create the fake client and repository for testing with.
1834
There's no real server here; we just have canned responses sent
1837
:param transport_path: Path below the root of the MemoryTransport
1838
where the repository will be created.
1840
transport = MemoryTransport()
1841
transport.mkdir(transport_path)
1842
client = FakeClient(transport.base)
1843
transport = transport.clone(transport_path)
1844
# we do not want bzrdir to make any remote calls
1845
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1847
repo = RemoteRepository(bzrdir, None, _client=client)
1851
def remoted_description(format):
1852
return 'Remote: ' + format.get_format_description()
1855
class TestBranchFormat(tests.TestCase):
1857
def test_get_format_description(self):
1858
remote_format = RemoteBranchFormat()
1859
real_format = branch.BranchFormat.get_default_format()
1860
remote_format._network_name = real_format.network_name()
1861
self.assertEqual(remoted_description(real_format),
1862
remote_format.get_format_description())
1865
class TestRepositoryFormat(TestRemoteRepository):
1867
def test_fast_delta(self):
1868
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1869
true_format = RemoteRepositoryFormat()
1870
true_format._network_name = true_name
1871
self.assertEqual(True, true_format.fast_deltas)
1872
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1873
false_format = RemoteRepositoryFormat()
1874
false_format._network_name = false_name
1875
self.assertEqual(False, false_format.fast_deltas)
1877
def test_get_format_description(self):
1878
remote_repo_format = RemoteRepositoryFormat()
1879
real_format = repository.RepositoryFormat.get_default_format()
1880
remote_repo_format._network_name = real_format.network_name()
1881
self.assertEqual(remoted_description(real_format),
1882
remote_repo_format.get_format_description())
1885
class TestRepositoryGatherStats(TestRemoteRepository):
1887
def test_revid_none(self):
1888
# ('ok',), body with revisions and size
1889
transport_path = 'quack'
1890
repo, client = self.setup_fake_client_and_repository(transport_path)
1891
client.add_success_response_with_body(
1892
'revisions: 2\nsize: 18\n', 'ok')
1893
result = repo.gather_stats(None)
1895
[('call_expecting_body', 'Repository.gather_stats',
1896
('quack/','','no'))],
1898
self.assertEqual({'revisions': 2, 'size': 18}, result)
1900
def test_revid_no_committers(self):
1901
# ('ok',), body without committers
1902
body = ('firstrev: 123456.300 3600\n'
1903
'latestrev: 654231.400 0\n'
1906
transport_path = 'quick'
1907
revid = u'\xc8'.encode('utf8')
1908
repo, client = self.setup_fake_client_and_repository(transport_path)
1909
client.add_success_response_with_body(body, 'ok')
1910
result = repo.gather_stats(revid)
1912
[('call_expecting_body', 'Repository.gather_stats',
1913
('quick/', revid, 'no'))],
1915
self.assertEqual({'revisions': 2, 'size': 18,
1916
'firstrev': (123456.300, 3600),
1917
'latestrev': (654231.400, 0),},
1920
def test_revid_with_committers(self):
1921
# ('ok',), body with committers
1922
body = ('committers: 128\n'
1923
'firstrev: 123456.300 3600\n'
1924
'latestrev: 654231.400 0\n'
1927
transport_path = 'buick'
1928
revid = u'\xc8'.encode('utf8')
1929
repo, client = self.setup_fake_client_and_repository(transport_path)
1930
client.add_success_response_with_body(body, 'ok')
1931
result = repo.gather_stats(revid, True)
1933
[('call_expecting_body', 'Repository.gather_stats',
1934
('buick/', revid, 'yes'))],
1936
self.assertEqual({'revisions': 2, 'size': 18,
1938
'firstrev': (123456.300, 3600),
1939
'latestrev': (654231.400, 0),},
1943
class TestRepositoryGetGraph(TestRemoteRepository):
1945
def test_get_graph(self):
1946
# get_graph returns a graph with a custom parents provider.
1947
transport_path = 'quack'
1948
repo, client = self.setup_fake_client_and_repository(transport_path)
1949
graph = repo.get_graph()
1950
self.assertNotEqual(graph._parents_provider, repo)
1953
class TestRepositoryGetParentMap(TestRemoteRepository):
1955
def test_get_parent_map_caching(self):
1956
# get_parent_map returns from cache until unlock()
1957
# setup a reponse with two revisions
1958
r1 = u'\u0e33'.encode('utf8')
1959
r2 = u'\u0dab'.encode('utf8')
1960
lines = [' '.join([r2, r1]), r1]
1961
encoded_body = bz2.compress('\n'.join(lines))
1963
transport_path = 'quack'
1964
repo, client = self.setup_fake_client_and_repository(transport_path)
1965
client.add_success_response_with_body(encoded_body, 'ok')
1966
client.add_success_response_with_body(encoded_body, 'ok')
1968
graph = repo.get_graph()
1969
parents = graph.get_parent_map([r2])
1970
self.assertEqual({r2: (r1,)}, parents)
1971
# locking and unlocking deeper should not reset
1974
parents = graph.get_parent_map([r1])
1975
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1977
[('call_with_body_bytes_expecting_body',
1978
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1982
# now we call again, and it should use the second response.
1984
graph = repo.get_graph()
1985
parents = graph.get_parent_map([r1])
1986
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1988
[('call_with_body_bytes_expecting_body',
1989
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1991
('call_with_body_bytes_expecting_body',
1992
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1998
def test_get_parent_map_reconnects_if_unknown_method(self):
1999
transport_path = 'quack'
2000
rev_id = 'revision-id'
2001
repo, client = self.setup_fake_client_and_repository(transport_path)
2002
client.add_unknown_method_response('Repository.get_parent_map')
2003
client.add_success_response_with_body(rev_id, 'ok')
2004
self.assertFalse(client._medium._is_remote_before((1, 2)))
2005
parents = repo.get_parent_map([rev_id])
2007
[('call_with_body_bytes_expecting_body',
2008
'Repository.get_parent_map', ('quack/', 'include-missing:',
2010
('disconnect medium',),
2011
('call_expecting_body', 'Repository.get_revision_graph',
2014
# The medium is now marked as being connected to an older server
2015
self.assertTrue(client._medium._is_remote_before((1, 2)))
2016
self.assertEqual({rev_id: ('null:',)}, parents)
2018
def test_get_parent_map_fallback_parentless_node(self):
2019
"""get_parent_map falls back to get_revision_graph on old servers. The
2020
results from get_revision_graph are tweaked to match the get_parent_map
2023
Specifically, a {key: ()} result from get_revision_graph means "no
2024
parents" for that key, which in get_parent_map results should be
2025
represented as {key: ('null:',)}.
2027
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2029
rev_id = 'revision-id'
2030
transport_path = 'quack'
2031
repo, client = self.setup_fake_client_and_repository(transport_path)
2032
client.add_success_response_with_body(rev_id, 'ok')
2033
client._medium._remember_remote_is_before((1, 2))
2034
parents = repo.get_parent_map([rev_id])
2036
[('call_expecting_body', 'Repository.get_revision_graph',
2039
self.assertEqual({rev_id: ('null:',)}, parents)
2041
def test_get_parent_map_unexpected_response(self):
2042
repo, client = self.setup_fake_client_and_repository('path')
2043
client.add_success_response('something unexpected!')
2045
errors.UnexpectedSmartServerResponse,
2046
repo.get_parent_map, ['a-revision-id'])
2048
def test_get_parent_map_negative_caches_missing_keys(self):
2049
self.setup_smart_server_with_call_log()
2050
repo = self.make_repository('foo')
2051
self.assertIsInstance(repo, RemoteRepository)
2053
self.addCleanup(repo.unlock)
2054
self.reset_smart_call_log()
2055
graph = repo.get_graph()
2056
self.assertEqual({},
2057
graph.get_parent_map(['some-missing', 'other-missing']))
2058
self.assertLength(1, self.hpss_calls)
2059
# No call if we repeat this
2060
self.reset_smart_call_log()
2061
graph = repo.get_graph()
2062
self.assertEqual({},
2063
graph.get_parent_map(['some-missing', 'other-missing']))
2064
self.assertLength(0, self.hpss_calls)
2065
# Asking for more unknown keys makes a request.
2066
self.reset_smart_call_log()
2067
graph = repo.get_graph()
2068
self.assertEqual({},
2069
graph.get_parent_map(['some-missing', 'other-missing',
2071
self.assertLength(1, self.hpss_calls)
2073
def disableExtraResults(self):
2074
self.overrideAttr(SmartServerRepositoryGetParentMap,
2075
'no_extra_results', True)
2077
def test_null_cached_missing_and_stop_key(self):
2078
self.setup_smart_server_with_call_log()
2079
# Make a branch with a single revision.
2080
builder = self.make_branch_builder('foo')
2081
builder.start_series()
2082
builder.build_snapshot('first', None, [
2083
('add', ('', 'root-id', 'directory', ''))])
2084
builder.finish_series()
2085
branch = builder.get_branch()
2086
repo = branch.repository
2087
self.assertIsInstance(repo, RemoteRepository)
2088
# Stop the server from sending extra results.
2089
self.disableExtraResults()
2091
self.addCleanup(repo.unlock)
2092
self.reset_smart_call_log()
2093
graph = repo.get_graph()
2094
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2095
# 'first' it will be a candidate for the stop_keys of subsequent
2096
# requests, and because 'null:' was queried but not returned it will be
2097
# cached as missing.
2098
self.assertEqual({'first': ('null:',)},
2099
graph.get_parent_map(['first', 'null:']))
2100
# Now query for another key. This request will pass along a recipe of
2101
# start and stop keys describing the already cached results, and this
2102
# recipe's revision count must be correct (or else it will trigger an
2103
# error from the server).
2104
self.assertEqual({}, graph.get_parent_map(['another-key']))
2105
# This assertion guards against disableExtraResults silently failing to
2106
# work, thus invalidating the test.
2107
self.assertLength(2, self.hpss_calls)
2109
def test_get_parent_map_gets_ghosts_from_result(self):
2110
# asking for a revision should negatively cache close ghosts in its
2112
self.setup_smart_server_with_call_log()
2113
tree = self.make_branch_and_memory_tree('foo')
2116
builder = treebuilder.TreeBuilder()
2117
builder.start_tree(tree)
2119
builder.finish_tree()
2120
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2121
rev_id = tree.commit('')
2125
self.addCleanup(tree.unlock)
2126
repo = tree.branch.repository
2127
self.assertIsInstance(repo, RemoteRepository)
2129
repo.get_parent_map([rev_id])
2130
self.reset_smart_call_log()
2131
# Now asking for rev_id's ghost parent should not make calls
2132
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2133
self.assertLength(0, self.hpss_calls)
2136
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2138
def test_allows_new_revisions(self):
2139
"""get_parent_map's results can be updated by commit."""
2140
smart_server = test_server.SmartTCPServer_for_testing()
2141
self.start_server(smart_server)
2142
self.make_branch('branch')
2143
branch = Branch.open(smart_server.get_url() + '/branch')
2144
tree = branch.create_checkout('tree', lightweight=True)
2146
self.addCleanup(tree.unlock)
2147
graph = tree.branch.repository.get_graph()
2148
# This provides an opportunity for the missing rev-id to be cached.
2149
self.assertEqual({}, graph.get_parent_map(['rev1']))
2150
tree.commit('message', rev_id='rev1')
2151
graph = tree.branch.repository.get_graph()
2152
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2155
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2157
def test_null_revision(self):
2158
# a null revision has the predictable result {}, we should have no wire
2159
# traffic when calling it with this argument
2160
transport_path = 'empty'
2161
repo, client = self.setup_fake_client_and_repository(transport_path)
2162
client.add_success_response('notused')
2163
# actual RemoteRepository.get_revision_graph is gone, but there's an
2164
# equivalent private method for testing
2165
result = repo._get_revision_graph(NULL_REVISION)
2166
self.assertEqual([], client._calls)
2167
self.assertEqual({}, result)
2169
def test_none_revision(self):
2170
# with none we want the entire graph
2171
r1 = u'\u0e33'.encode('utf8')
2172
r2 = u'\u0dab'.encode('utf8')
2173
lines = [' '.join([r2, r1]), r1]
2174
encoded_body = '\n'.join(lines)
2176
transport_path = 'sinhala'
2177
repo, client = self.setup_fake_client_and_repository(transport_path)
2178
client.add_success_response_with_body(encoded_body, 'ok')
2179
# actual RemoteRepository.get_revision_graph is gone, but there's an
2180
# equivalent private method for testing
2181
result = repo._get_revision_graph(None)
2183
[('call_expecting_body', 'Repository.get_revision_graph',
2186
self.assertEqual({r1: (), r2: (r1, )}, result)
2188
def test_specific_revision(self):
2189
# with a specific revision we want the graph for that
2190
# with none we want the entire graph
2191
r11 = u'\u0e33'.encode('utf8')
2192
r12 = u'\xc9'.encode('utf8')
2193
r2 = u'\u0dab'.encode('utf8')
2194
lines = [' '.join([r2, r11, r12]), r11, r12]
2195
encoded_body = '\n'.join(lines)
2197
transport_path = 'sinhala'
2198
repo, client = self.setup_fake_client_and_repository(transport_path)
2199
client.add_success_response_with_body(encoded_body, 'ok')
2200
result = repo._get_revision_graph(r2)
2202
[('call_expecting_body', 'Repository.get_revision_graph',
2205
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2207
def test_no_such_revision(self):
2209
transport_path = 'sinhala'
2210
repo, client = self.setup_fake_client_and_repository(transport_path)
2211
client.add_error_response('nosuchrevision', revid)
2212
# also check that the right revision is reported in the error
2213
self.assertRaises(errors.NoSuchRevision,
2214
repo._get_revision_graph, revid)
2216
[('call_expecting_body', 'Repository.get_revision_graph',
2217
('sinhala/', revid))],
2220
def test_unexpected_error(self):
2222
transport_path = 'sinhala'
2223
repo, client = self.setup_fake_client_and_repository(transport_path)
2224
client.add_error_response('AnUnexpectedError')
2225
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2226
repo._get_revision_graph, revid)
2227
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2230
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2233
repo, client = self.setup_fake_client_and_repository('quack')
2234
client.add_expected_call(
2235
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2236
'success', ('ok', 'rev-five'))
2237
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2238
self.assertEqual((True, 'rev-five'), result)
2239
self.assertFinished(client)
2241
def test_history_incomplete(self):
2242
repo, client = self.setup_fake_client_and_repository('quack')
2243
client.add_expected_call(
2244
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2245
'success', ('history-incomplete', 10, 'rev-ten'))
2246
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2247
self.assertEqual((False, (10, 'rev-ten')), result)
2248
self.assertFinished(client)
2250
def test_history_incomplete_with_fallback(self):
2251
"""A 'history-incomplete' response causes the fallback repository to be
2252
queried too, if one is set.
2254
# Make a repo with a fallback repo, both using a FakeClient.
2255
format = remote.response_tuple_to_repo_format(
2256
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2257
repo, client = self.setup_fake_client_and_repository('quack')
2258
repo._format = format
2259
fallback_repo, ignored = self.setup_fake_client_and_repository(
2261
fallback_repo._client = client
2262
fallback_repo._format = format
2263
repo.add_fallback_repository(fallback_repo)
2264
# First the client should ask the primary repo
2265
client.add_expected_call(
2266
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2267
'success', ('history-incomplete', 2, 'rev-two'))
2268
# Then it should ask the fallback, using revno/revid from the
2269
# history-incomplete response as the known revno/revid.
2270
client.add_expected_call(
2271
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2272
'success', ('ok', 'rev-one'))
2273
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2274
self.assertEqual((True, 'rev-one'), result)
2275
self.assertFinished(client)
2277
def test_nosuchrevision(self):
2278
# 'nosuchrevision' is returned when the known-revid is not found in the
2279
# remote repo. The client translates that response to NoSuchRevision.
2280
repo, client = self.setup_fake_client_and_repository('quack')
2281
client.add_expected_call(
2282
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2283
'error', ('nosuchrevision', 'rev-foo'))
2285
errors.NoSuchRevision,
2286
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2287
self.assertFinished(client)
2289
def test_branch_fallback_locking(self):
2290
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2291
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2292
will be invoked, which will fail if the repo is unlocked.
2294
self.setup_smart_server_with_call_log()
2295
tree = self.make_branch_and_memory_tree('.')
2298
rev1 = tree.commit('First')
2299
rev2 = tree.commit('Second')
2301
branch = tree.branch
2302
self.assertFalse(branch.is_locked())
2303
self.reset_smart_call_log()
2304
verb = 'Repository.get_rev_id_for_revno'
2305
self.disable_verb(verb)
2306
self.assertEqual(rev1, branch.get_rev_id(1))
2307
self.assertLength(1, [call for call in self.hpss_calls if
2308
call.call.method == verb])
2311
class TestRepositoryIsShared(TestRemoteRepository):
2313
def test_is_shared(self):
2314
# ('yes', ) for Repository.is_shared -> 'True'.
2315
transport_path = 'quack'
2316
repo, client = self.setup_fake_client_and_repository(transport_path)
2317
client.add_success_response('yes')
2318
result = repo.is_shared()
2320
[('call', 'Repository.is_shared', ('quack/',))],
2322
self.assertEqual(True, result)
2324
def test_is_not_shared(self):
2325
# ('no', ) for Repository.is_shared -> 'False'.
2326
transport_path = 'qwack'
2327
repo, client = self.setup_fake_client_and_repository(transport_path)
2328
client.add_success_response('no')
2329
result = repo.is_shared()
2331
[('call', 'Repository.is_shared', ('qwack/',))],
2333
self.assertEqual(False, result)
2336
class TestRepositoryLockWrite(TestRemoteRepository):
2338
def test_lock_write(self):
2339
transport_path = 'quack'
2340
repo, client = self.setup_fake_client_and_repository(transport_path)
2341
client.add_success_response('ok', 'a token')
2342
token = repo.lock_write().repository_token
2344
[('call', 'Repository.lock_write', ('quack/', ''))],
2346
self.assertEqual('a token', token)
2348
def test_lock_write_already_locked(self):
2349
transport_path = 'quack'
2350
repo, client = self.setup_fake_client_and_repository(transport_path)
2351
client.add_error_response('LockContention')
2352
self.assertRaises(errors.LockContention, repo.lock_write)
2354
[('call', 'Repository.lock_write', ('quack/', ''))],
2357
def test_lock_write_unlockable(self):
2358
transport_path = 'quack'
2359
repo, client = self.setup_fake_client_and_repository(transport_path)
2360
client.add_error_response('UnlockableTransport')
2361
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2363
[('call', 'Repository.lock_write', ('quack/', ''))],
2367
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2369
def test_backwards_compat(self):
2370
self.setup_smart_server_with_call_log()
2371
repo = self.make_repository('.')
2372
self.reset_smart_call_log()
2373
verb = 'Repository.set_make_working_trees'
2374
self.disable_verb(verb)
2375
repo.set_make_working_trees(True)
2376
call_count = len([call for call in self.hpss_calls if
2377
call.call.method == verb])
2378
self.assertEqual(1, call_count)
2380
def test_current(self):
2381
transport_path = 'quack'
2382
repo, client = self.setup_fake_client_and_repository(transport_path)
2383
client.add_expected_call(
2384
'Repository.set_make_working_trees', ('quack/', 'True'),
2386
client.add_expected_call(
2387
'Repository.set_make_working_trees', ('quack/', 'False'),
2389
repo.set_make_working_trees(True)
2390
repo.set_make_working_trees(False)
2393
class TestRepositoryUnlock(TestRemoteRepository):
2395
def test_unlock(self):
2396
transport_path = 'quack'
2397
repo, client = self.setup_fake_client_and_repository(transport_path)
2398
client.add_success_response('ok', 'a token')
2399
client.add_success_response('ok')
2403
[('call', 'Repository.lock_write', ('quack/', '')),
2404
('call', 'Repository.unlock', ('quack/', 'a token'))],
2407
def test_unlock_wrong_token(self):
2408
# If somehow the token is wrong, unlock will raise TokenMismatch.
2409
transport_path = 'quack'
2410
repo, client = self.setup_fake_client_and_repository(transport_path)
2411
client.add_success_response('ok', 'a token')
2412
client.add_error_response('TokenMismatch')
2414
self.assertRaises(errors.TokenMismatch, repo.unlock)
2417
class TestRepositoryHasRevision(TestRemoteRepository):
2419
def test_none(self):
2420
# repo.has_revision(None) should not cause any traffic.
2421
transport_path = 'quack'
2422
repo, client = self.setup_fake_client_and_repository(transport_path)
2424
# The null revision is always there, so has_revision(None) == True.
2425
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2427
# The remote repo shouldn't be accessed.
2428
self.assertEqual([], client._calls)
2431
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2432
"""Base class for Repository.insert_stream and .insert_stream_1.19
2436
def checkInsertEmptyStream(self, repo, client):
2437
"""Insert an empty stream, checking the result.
2439
This checks that there are no resume_tokens or missing_keys, and that
2440
the client is finished.
2442
sink = repo._get_sink()
2443
fmt = repository.RepositoryFormat.get_default_format()
2444
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2445
self.assertEqual([], resume_tokens)
2446
self.assertEqual(set(), missing_keys)
2447
self.assertFinished(client)
2450
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2451
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2454
This test case is very similar to TestRepositoryInsertStream_1_19.
2458
TestRemoteRepository.setUp(self)
2459
self.disable_verb('Repository.insert_stream_1.19')
2461
def test_unlocked_repo(self):
2462
transport_path = 'quack'
2463
repo, client = self.setup_fake_client_and_repository(transport_path)
2464
client.add_expected_call(
2465
'Repository.insert_stream_1.19', ('quack/', ''),
2466
'unknown', ('Repository.insert_stream_1.19',))
2467
client.add_expected_call(
2468
'Repository.insert_stream', ('quack/', ''),
2470
client.add_expected_call(
2471
'Repository.insert_stream', ('quack/', ''),
2473
self.checkInsertEmptyStream(repo, client)
2475
def test_locked_repo_with_no_lock_token(self):
2476
transport_path = 'quack'
2477
repo, client = self.setup_fake_client_and_repository(transport_path)
2478
client.add_expected_call(
2479
'Repository.lock_write', ('quack/', ''),
2480
'success', ('ok', ''))
2481
client.add_expected_call(
2482
'Repository.insert_stream_1.19', ('quack/', ''),
2483
'unknown', ('Repository.insert_stream_1.19',))
2484
client.add_expected_call(
2485
'Repository.insert_stream', ('quack/', ''),
2487
client.add_expected_call(
2488
'Repository.insert_stream', ('quack/', ''),
2491
self.checkInsertEmptyStream(repo, client)
2493
def test_locked_repo_with_lock_token(self):
2494
transport_path = 'quack'
2495
repo, client = self.setup_fake_client_and_repository(transport_path)
2496
client.add_expected_call(
2497
'Repository.lock_write', ('quack/', ''),
2498
'success', ('ok', 'a token'))
2499
client.add_expected_call(
2500
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2501
'unknown', ('Repository.insert_stream_1.19',))
2502
client.add_expected_call(
2503
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2505
client.add_expected_call(
2506
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2509
self.checkInsertEmptyStream(repo, client)
2511
def test_stream_with_inventory_deltas(self):
2512
"""'inventory-deltas' substreams cannot be sent to the
2513
Repository.insert_stream verb, because not all servers that implement
2514
that verb will accept them. So when one is encountered the RemoteSink
2515
immediately stops using that verb and falls back to VFS insert_stream.
2517
transport_path = 'quack'
2518
repo, client = self.setup_fake_client_and_repository(transport_path)
2519
client.add_expected_call(
2520
'Repository.insert_stream_1.19', ('quack/', ''),
2521
'unknown', ('Repository.insert_stream_1.19',))
2522
client.add_expected_call(
2523
'Repository.insert_stream', ('quack/', ''),
2525
client.add_expected_call(
2526
'Repository.insert_stream', ('quack/', ''),
2528
# Create a fake real repository for insert_stream to fall back on, so
2529
# that we can directly see the records the RemoteSink passes to the
2534
def insert_stream(self, stream, src_format, resume_tokens):
2535
for substream_kind, substream in stream:
2536
self.records.append(
2537
(substream_kind, [record.key for record in substream]))
2538
return ['fake tokens'], ['fake missing keys']
2539
fake_real_sink = FakeRealSink()
2540
class FakeRealRepository:
2541
def _get_sink(self):
2542
return fake_real_sink
2543
def is_in_write_group(self):
2545
def refresh_data(self):
2547
repo._real_repository = FakeRealRepository()
2548
sink = repo._get_sink()
2549
fmt = repository.RepositoryFormat.get_default_format()
2550
stream = self.make_stream_with_inv_deltas(fmt)
2551
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2552
# Every record from the first inventory delta should have been sent to
2554
expected_records = [
2555
('inventory-deltas', [('rev2',), ('rev3',)]),
2556
('texts', [('some-rev', 'some-file')])]
2557
self.assertEqual(expected_records, fake_real_sink.records)
2558
# The return values from the real sink's insert_stream are propagated
2559
# back to the original caller.
2560
self.assertEqual(['fake tokens'], resume_tokens)
2561
self.assertEqual(['fake missing keys'], missing_keys)
2562
self.assertFinished(client)
2564
def make_stream_with_inv_deltas(self, fmt):
2565
"""Make a simple stream with an inventory delta followed by more
2566
records and more substreams to test that all records and substreams
2567
from that point on are used.
2569
This sends, in order:
2570
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2572
* texts substream: (some-rev, some-file)
2574
# Define a stream using generators so that it isn't rewindable.
2575
inv = inventory.Inventory(revision_id='rev1')
2576
inv.root.revision = 'rev1'
2577
def stream_with_inv_delta():
2578
yield ('inventories', inventories_substream())
2579
yield ('inventory-deltas', inventory_delta_substream())
2581
versionedfile.FulltextContentFactory(
2582
('some-rev', 'some-file'), (), None, 'content')])
2583
def inventories_substream():
2584
# An empty inventory fulltext. This will be streamed normally.
2585
text = fmt._serializer.write_inventory_to_string(inv)
2586
yield versionedfile.FulltextContentFactory(
2587
('rev1',), (), None, text)
2588
def inventory_delta_substream():
2589
# An inventory delta. This can't be streamed via this verb, so it
2590
# will trigger a fallback to VFS insert_stream.
2591
entry = inv.make_entry(
2592
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2593
entry.revision = 'ghost'
2594
delta = [(None, 'newdir', 'newdir-id', entry)]
2595
serializer = inventory_delta.InventoryDeltaSerializer(
2596
versioned_root=True, tree_references=False)
2597
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2598
yield versionedfile.ChunkedContentFactory(
2599
('rev2',), (('rev1',)), None, lines)
2601
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2602
yield versionedfile.ChunkedContentFactory(
2603
('rev3',), (('rev1',)), None, lines)
2604
return stream_with_inv_delta()
2607
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2609
def test_unlocked_repo(self):
2610
transport_path = 'quack'
2611
repo, client = self.setup_fake_client_and_repository(transport_path)
2612
client.add_expected_call(
2613
'Repository.insert_stream_1.19', ('quack/', ''),
2615
client.add_expected_call(
2616
'Repository.insert_stream_1.19', ('quack/', ''),
2618
self.checkInsertEmptyStream(repo, client)
2620
def test_locked_repo_with_no_lock_token(self):
2621
transport_path = 'quack'
2622
repo, client = self.setup_fake_client_and_repository(transport_path)
2623
client.add_expected_call(
2624
'Repository.lock_write', ('quack/', ''),
2625
'success', ('ok', ''))
2626
client.add_expected_call(
2627
'Repository.insert_stream_1.19', ('quack/', ''),
2629
client.add_expected_call(
2630
'Repository.insert_stream_1.19', ('quack/', ''),
2633
self.checkInsertEmptyStream(repo, client)
2635
def test_locked_repo_with_lock_token(self):
2636
transport_path = 'quack'
2637
repo, client = self.setup_fake_client_and_repository(transport_path)
2638
client.add_expected_call(
2639
'Repository.lock_write', ('quack/', ''),
2640
'success', ('ok', 'a token'))
2641
client.add_expected_call(
2642
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2644
client.add_expected_call(
2645
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2648
self.checkInsertEmptyStream(repo, client)
2651
class TestRepositoryTarball(TestRemoteRepository):
2653
# This is a canned tarball reponse we can validate against
2655
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2656
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2657
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2658
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2659
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2660
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2661
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2662
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2663
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2664
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2665
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2666
'nWQ7QH/F3JFOFCQ0aSPfA='
2669
def test_repository_tarball(self):
2670
# Test that Repository.tarball generates the right operations
2671
transport_path = 'repo'
2672
expected_calls = [('call_expecting_body', 'Repository.tarball',
2673
('repo/', 'bz2',),),
2675
repo, client = self.setup_fake_client_and_repository(transport_path)
2676
client.add_success_response_with_body(self.tarball_content, 'ok')
2677
# Now actually ask for the tarball
2678
tarball_file = repo._get_tarball('bz2')
2680
self.assertEqual(expected_calls, client._calls)
2681
self.assertEqual(self.tarball_content, tarball_file.read())
2683
tarball_file.close()
2686
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2687
"""RemoteRepository.copy_content_into optimizations"""
2689
def test_copy_content_remote_to_local(self):
2690
self.transport_server = test_server.SmartTCPServer_for_testing
2691
src_repo = self.make_repository('repo1')
2692
src_repo = repository.Repository.open(self.get_url('repo1'))
2693
# At the moment the tarball-based copy_content_into can't write back
2694
# into a smart server. It would be good if it could upload the
2695
# tarball; once that works we'd have to create repositories of
2696
# different formats. -- mbp 20070410
2697
dest_url = self.get_vfs_only_url('repo2')
2698
dest_bzrdir = BzrDir.create(dest_url)
2699
dest_repo = dest_bzrdir.create_repository()
2700
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2701
self.assertTrue(isinstance(src_repo, RemoteRepository))
2702
src_repo.copy_content_into(dest_repo)
2705
class _StubRealPackRepository(object):
2707
def __init__(self, calls):
2709
self._pack_collection = _StubPackCollection(calls)
2711
def is_in_write_group(self):
2714
def refresh_data(self):
2715
self.calls.append(('pack collection reload_pack_names',))
2718
class _StubPackCollection(object):
2720
def __init__(self, calls):
2724
self.calls.append(('pack collection autopack',))
2727
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2728
"""Tests for RemoteRepository.autopack implementation."""
2731
"""When the server returns 'ok' and there's no _real_repository, then
2732
nothing else happens: the autopack method is done.
2734
transport_path = 'quack'
2735
repo, client = self.setup_fake_client_and_repository(transport_path)
2736
client.add_expected_call(
2737
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2739
self.assertFinished(client)
2741
def test_ok_with_real_repo(self):
2742
"""When the server returns 'ok' and there is a _real_repository, then
2743
the _real_repository's reload_pack_name's method will be called.
2745
transport_path = 'quack'
2746
repo, client = self.setup_fake_client_and_repository(transport_path)
2747
client.add_expected_call(
2748
'PackRepository.autopack', ('quack/',),
2750
repo._real_repository = _StubRealPackRepository(client._calls)
2753
[('call', 'PackRepository.autopack', ('quack/',)),
2754
('pack collection reload_pack_names',)],
2757
def test_backwards_compatibility(self):
2758
"""If the server does not recognise the PackRepository.autopack verb,
2759
fallback to the real_repository's implementation.
2761
transport_path = 'quack'
2762
repo, client = self.setup_fake_client_and_repository(transport_path)
2763
client.add_unknown_method_response('PackRepository.autopack')
2764
def stub_ensure_real():
2765
client._calls.append(('_ensure_real',))
2766
repo._real_repository = _StubRealPackRepository(client._calls)
2767
repo._ensure_real = stub_ensure_real
2770
[('call', 'PackRepository.autopack', ('quack/',)),
2772
('pack collection autopack',)],
2776
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2777
"""Base class for unit tests for bzrlib.remote._translate_error."""
2779
def translateTuple(self, error_tuple, **context):
2780
"""Call _translate_error with an ErrorFromSmartServer built from the
2783
:param error_tuple: A tuple of a smart server response, as would be
2784
passed to an ErrorFromSmartServer.
2785
:kwargs context: context items to call _translate_error with.
2787
:returns: The error raised by _translate_error.
2789
# Raise the ErrorFromSmartServer before passing it as an argument,
2790
# because _translate_error may need to re-raise it with a bare 'raise'
2792
server_error = errors.ErrorFromSmartServer(error_tuple)
2793
translated_error = self.translateErrorFromSmartServer(
2794
server_error, **context)
2795
return translated_error
2797
def translateErrorFromSmartServer(self, error_object, **context):
2798
"""Like translateTuple, but takes an already constructed
2799
ErrorFromSmartServer rather than a tuple.
2803
except errors.ErrorFromSmartServer, server_error:
2804
translated_error = self.assertRaises(
2805
errors.BzrError, remote._translate_error, server_error,
2807
return translated_error
2810
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2811
"""Unit tests for bzrlib.remote._translate_error.
2813
Given an ErrorFromSmartServer (which has an error tuple from a smart
2814
server) and some context, _translate_error raises more specific errors from
2817
This test case covers the cases where _translate_error succeeds in
2818
translating an ErrorFromSmartServer to something better. See
2819
TestErrorTranslationRobustness for other cases.
2822
def test_NoSuchRevision(self):
2823
branch = self.make_branch('')
2825
translated_error = self.translateTuple(
2826
('NoSuchRevision', revid), branch=branch)
2827
expected_error = errors.NoSuchRevision(branch, revid)
2828
self.assertEqual(expected_error, translated_error)
2830
def test_nosuchrevision(self):
2831
repository = self.make_repository('')
2833
translated_error = self.translateTuple(
2834
('nosuchrevision', revid), repository=repository)
2835
expected_error = errors.NoSuchRevision(repository, revid)
2836
self.assertEqual(expected_error, translated_error)
2838
def test_nobranch(self):
2839
bzrdir = self.make_bzrdir('')
2840
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2841
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2842
self.assertEqual(expected_error, translated_error)
2844
def test_nobranch_one_arg(self):
2845
bzrdir = self.make_bzrdir('')
2846
translated_error = self.translateTuple(
2847
('nobranch', 'extra detail'), bzrdir=bzrdir)
2848
expected_error = errors.NotBranchError(
2849
path=bzrdir.root_transport.base,
2850
detail='extra detail')
2851
self.assertEqual(expected_error, translated_error)
2853
def test_LockContention(self):
2854
translated_error = self.translateTuple(('LockContention',))
2855
expected_error = errors.LockContention('(remote lock)')
2856
self.assertEqual(expected_error, translated_error)
2858
def test_UnlockableTransport(self):
2859
bzrdir = self.make_bzrdir('')
2860
translated_error = self.translateTuple(
2861
('UnlockableTransport',), bzrdir=bzrdir)
2862
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2863
self.assertEqual(expected_error, translated_error)
2865
def test_LockFailed(self):
2866
lock = 'str() of a server lock'
2867
why = 'str() of why'
2868
translated_error = self.translateTuple(('LockFailed', lock, why))
2869
expected_error = errors.LockFailed(lock, why)
2870
self.assertEqual(expected_error, translated_error)
2872
def test_TokenMismatch(self):
2873
token = 'a lock token'
2874
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2875
expected_error = errors.TokenMismatch(token, '(remote token)')
2876
self.assertEqual(expected_error, translated_error)
2878
def test_Diverged(self):
2879
branch = self.make_branch('a')
2880
other_branch = self.make_branch('b')
2881
translated_error = self.translateTuple(
2882
('Diverged',), branch=branch, other_branch=other_branch)
2883
expected_error = errors.DivergedBranches(branch, other_branch)
2884
self.assertEqual(expected_error, translated_error)
2886
def test_ReadError_no_args(self):
2888
translated_error = self.translateTuple(('ReadError',), path=path)
2889
expected_error = errors.ReadError(path)
2890
self.assertEqual(expected_error, translated_error)
2892
def test_ReadError(self):
2894
translated_error = self.translateTuple(('ReadError', path))
2895
expected_error = errors.ReadError(path)
2896
self.assertEqual(expected_error, translated_error)
2898
def test_IncompatibleRepositories(self):
2899
translated_error = self.translateTuple(('IncompatibleRepositories',
2900
"repo1", "repo2", "details here"))
2901
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2903
self.assertEqual(expected_error, translated_error)
2905
def test_PermissionDenied_no_args(self):
2907
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2908
expected_error = errors.PermissionDenied(path)
2909
self.assertEqual(expected_error, translated_error)
2911
def test_PermissionDenied_one_arg(self):
2913
translated_error = self.translateTuple(('PermissionDenied', path))
2914
expected_error = errors.PermissionDenied(path)
2915
self.assertEqual(expected_error, translated_error)
2917
def test_PermissionDenied_one_arg_and_context(self):
2918
"""Given a choice between a path from the local context and a path on
2919
the wire, _translate_error prefers the path from the local context.
2921
local_path = 'local path'
2922
remote_path = 'remote path'
2923
translated_error = self.translateTuple(
2924
('PermissionDenied', remote_path), path=local_path)
2925
expected_error = errors.PermissionDenied(local_path)
2926
self.assertEqual(expected_error, translated_error)
2928
def test_PermissionDenied_two_args(self):
2930
extra = 'a string with extra info'
2931
translated_error = self.translateTuple(
2932
('PermissionDenied', path, extra))
2933
expected_error = errors.PermissionDenied(path, extra)
2934
self.assertEqual(expected_error, translated_error)
2937
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2938
"""Unit tests for bzrlib.remote._translate_error's robustness.
2940
TestErrorTranslationSuccess is for cases where _translate_error can
2941
translate successfully. This class about how _translate_err behaves when
2942
it fails to translate: it re-raises the original error.
2945
def test_unrecognised_server_error(self):
2946
"""If the error code from the server is not recognised, the original
2947
ErrorFromSmartServer is propagated unmodified.
2949
error_tuple = ('An unknown error tuple',)
2950
server_error = errors.ErrorFromSmartServer(error_tuple)
2951
translated_error = self.translateErrorFromSmartServer(server_error)
2952
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2953
self.assertEqual(expected_error, translated_error)
2955
def test_context_missing_a_key(self):
2956
"""In case of a bug in the client, or perhaps an unexpected response
2957
from a server, _translate_error returns the original error tuple from
2958
the server and mutters a warning.
2960
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2961
# in the context dict. So let's give it an empty context dict instead
2962
# to exercise its error recovery.
2964
error_tuple = ('NoSuchRevision', 'revid')
2965
server_error = errors.ErrorFromSmartServer(error_tuple)
2966
translated_error = self.translateErrorFromSmartServer(server_error)
2967
self.assertEqual(server_error, translated_error)
2968
# In addition to re-raising ErrorFromSmartServer, some debug info has
2969
# been muttered to the log file for developer to look at.
2970
self.assertContainsRe(
2972
"Missing key 'branch' in context")
2974
def test_path_missing(self):
2975
"""Some translations (PermissionDenied, ReadError) can determine the
2976
'path' variable from either the wire or the local context. If neither
2977
has it, then an error is raised.
2979
error_tuple = ('ReadError',)
2980
server_error = errors.ErrorFromSmartServer(error_tuple)
2981
translated_error = self.translateErrorFromSmartServer(server_error)
2982
self.assertEqual(server_error, translated_error)
2983
# In addition to re-raising ErrorFromSmartServer, some debug info has
2984
# been muttered to the log file for developer to look at.
2985
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
2988
class TestStacking(tests.TestCaseWithTransport):
2989
"""Tests for operations on stacked remote repositories.
2991
The underlying format type must support stacking.
2994
def test_access_stacked_remote(self):
2995
# based on <http://launchpad.net/bugs/261315>
2996
# make a branch stacked on another repository containing an empty
2997
# revision, then open it over hpss - we should be able to see that
2999
base_transport = self.get_transport()
3000
base_builder = self.make_branch_builder('base', format='1.9')
3001
base_builder.start_series()
3002
base_revid = base_builder.build_snapshot('rev-id', None,
3003
[('add', ('', None, 'directory', None))],
3005
base_builder.finish_series()
3006
stacked_branch = self.make_branch('stacked', format='1.9')
3007
stacked_branch.set_stacked_on_url('../base')
3008
# start a server looking at this
3009
smart_server = test_server.SmartTCPServer_for_testing()
3010
self.start_server(smart_server)
3011
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3012
# can get its branch and repository
3013
remote_branch = remote_bzrdir.open_branch()
3014
remote_repo = remote_branch.repository
3015
remote_repo.lock_read()
3017
# it should have an appropriate fallback repository, which should also
3018
# be a RemoteRepository
3019
self.assertLength(1, remote_repo._fallback_repositories)
3020
self.assertIsInstance(remote_repo._fallback_repositories[0],
3022
# and it has the revision committed to the underlying repository;
3023
# these have varying implementations so we try several of them
3024
self.assertTrue(remote_repo.has_revisions([base_revid]))
3025
self.assertTrue(remote_repo.has_revision(base_revid))
3026
self.assertEqual(remote_repo.get_revision(base_revid).message,
3029
remote_repo.unlock()
3031
def prepare_stacked_remote_branch(self):
3032
"""Get stacked_upon and stacked branches with content in each."""
3033
self.setup_smart_server_with_call_log()
3034
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3035
tree1.commit('rev1', rev_id='rev1')
3036
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3037
).open_workingtree()
3038
local_tree = tree2.branch.create_checkout('local')
3039
local_tree.commit('local changes make me feel good.')
3040
branch2 = Branch.open(self.get_url('tree2'))
3042
self.addCleanup(branch2.unlock)
3043
return tree1.branch, branch2
3045
def test_stacked_get_parent_map(self):
3046
# the public implementation of get_parent_map obeys stacking
3047
_, branch = self.prepare_stacked_remote_branch()
3048
repo = branch.repository
3049
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3051
def test_unstacked_get_parent_map(self):
3052
# _unstacked_provider.get_parent_map ignores stacking
3053
_, branch = self.prepare_stacked_remote_branch()
3054
provider = branch.repository._unstacked_provider
3055
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3057
def fetch_stream_to_rev_order(self, stream):
3059
for kind, substream in stream:
3060
if not kind == 'revisions':
3063
for content in substream:
3064
result.append(content.key[-1])
3067
def get_ordered_revs(self, format, order, branch_factory=None):
3068
"""Get a list of the revisions in a stream to format format.
3070
:param format: The format of the target.
3071
:param order: the order that target should have requested.
3072
:param branch_factory: A callable to create a trunk and stacked branch
3073
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3074
:result: The revision ids in the stream, in the order seen,
3075
the topological order of revisions in the source.
3077
unordered_format = bzrdir.format_registry.get(format)()
3078
target_repository_format = unordered_format.repository_format
3080
self.assertEqual(order, target_repository_format._fetch_order)
3081
if branch_factory is None:
3082
branch_factory = self.prepare_stacked_remote_branch
3083
_, stacked = branch_factory()
3084
source = stacked.repository._get_source(target_repository_format)
3085
tip = stacked.last_revision()
3086
revs = stacked.repository.get_ancestry(tip)
3087
search = graph.PendingAncestryResult([tip], stacked.repository)
3088
self.reset_smart_call_log()
3089
stream = source.get_stream(search)
3092
# We trust that if a revision is in the stream the rest of the new
3093
# content for it is too, as per our main fetch tests; here we are
3094
# checking that the revisions are actually included at all, and their
3096
return self.fetch_stream_to_rev_order(stream), revs
3098
def test_stacked_get_stream_unordered(self):
3099
# Repository._get_source.get_stream() from a stacked repository with
3100
# unordered yields the full data from both stacked and stacked upon
3102
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3103
self.assertEqual(set(expected_revs), set(rev_ord))
3104
# Getting unordered results should have made a streaming data request
3105
# from the server, then one from the backing branch.
3106
self.assertLength(2, self.hpss_calls)
3108
def test_stacked_on_stacked_get_stream_unordered(self):
3109
# Repository._get_source.get_stream() from a stacked repository which
3110
# is itself stacked yields the full data from all three sources.
3111
def make_stacked_stacked():
3112
_, stacked = self.prepare_stacked_remote_branch()
3113
tree = stacked.bzrdir.sprout('tree3', stacked=True
3114
).open_workingtree()
3115
local_tree = tree.branch.create_checkout('local-tree3')
3116
local_tree.commit('more local changes are better')
3117
branch = Branch.open(self.get_url('tree3'))
3119
self.addCleanup(branch.unlock)
3121
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3122
branch_factory=make_stacked_stacked)
3123
self.assertEqual(set(expected_revs), set(rev_ord))
3124
# Getting unordered results should have made a streaming data request
3125
# from the server, and one from each backing repo
3126
self.assertLength(3, self.hpss_calls)
3128
def test_stacked_get_stream_topological(self):
3129
# Repository._get_source.get_stream() from a stacked repository with
3130
# topological sorting yields the full data from both stacked and
3131
# stacked upon sources in topological order.
3132
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3133
self.assertEqual(expected_revs, rev_ord)
3134
# Getting topological sort requires VFS calls still - one of which is
3135
# pushing up from the bound branch.
3136
self.assertLength(13, self.hpss_calls)
3138
def test_stacked_get_stream_groupcompress(self):
3139
# Repository._get_source.get_stream() from a stacked repository with
3140
# groupcompress sorting yields the full data from both stacked and
3141
# stacked upon sources in groupcompress order.
3142
raise tests.TestSkipped('No groupcompress ordered format available')
3143
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3144
self.assertEqual(expected_revs, reversed(rev_ord))
3145
# Getting unordered results should have made a streaming data request
3146
# from the backing branch, and one from the stacked on branch.
3147
self.assertLength(2, self.hpss_calls)
3149
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3150
# When pulling some fixed amount of content that is more than the
3151
# source has (because some is coming from a fallback branch, no error
3152
# should be received. This was reported as bug 360791.
3153
# Need three branches: a trunk, a stacked branch, and a preexisting
3154
# branch pulling content from stacked and trunk.
3155
self.setup_smart_server_with_call_log()
3156
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3157
r1 = trunk.commit('start')
3158
stacked_branch = trunk.branch.create_clone_on_transport(
3159
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3160
local = self.make_branch('local', format='1.9-rich-root')
3161
local.repository.fetch(stacked_branch.repository,
3162
stacked_branch.last_revision())
3165
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3168
super(TestRemoteBranchEffort, self).setUp()
3169
# Create a smart server that publishes whatever the backing VFS server
3171
self.smart_server = test_server.SmartTCPServer_for_testing()
3172
self.start_server(self.smart_server, self.get_server())
3173
# Log all HPSS calls into self.hpss_calls.
3174
_SmartClient.hooks.install_named_hook(
3175
'call', self.capture_hpss_call, None)
3176
self.hpss_calls = []
3178
def capture_hpss_call(self, params):
3179
self.hpss_calls.append(params.method)
3181
def test_copy_content_into_avoids_revision_history(self):
3182
local = self.make_branch('local')
3183
remote_backing_tree = self.make_branch_and_tree('remote')
3184
remote_backing_tree.commit("Commit.")
3185
remote_branch_url = self.smart_server.get_url() + 'remote'
3186
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3187
local.repository.fetch(remote_branch.repository)
3188
self.hpss_calls = []
3189
remote_branch.copy_content_into(local)
3190
self.assertFalse('Branch.revision_history' in self.hpss_calls)