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('.')
94
self.transport.disconnect()
95
tests.TestCaseWithTransport.tearDown(self)
97
def test_create_remote_bzrdir(self):
98
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
99
self.assertIsInstance(b, BzrDir)
101
def test_open_remote_branch(self):
102
# open a standalone branch in the working directory
103
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
104
branch = b.open_branch()
105
self.assertIsInstance(branch, Branch)
107
def test_remote_repository(self):
108
b = BzrDir.open_from_transport(self.transport)
109
repo = b.open_repository()
110
revid = u'\xc823123123'.encode('utf8')
111
self.assertFalse(repo.has_revision(revid))
112
self.local_wt.commit(message='test commit', rev_id=revid)
113
self.assertTrue(repo.has_revision(revid))
115
def test_remote_branch_revision_history(self):
116
b = BzrDir.open_from_transport(self.transport).open_branch()
117
self.assertEqual([], b.revision_history())
118
r1 = self.local_wt.commit('1st commit')
119
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
120
self.assertEqual([r1, r2], b.revision_history())
122
def test_find_correct_format(self):
123
"""Should open a RemoteBzrDir over a RemoteTransport"""
124
fmt = BzrDirFormat.find_format(self.transport)
125
self.assertTrue(RemoteBzrDirFormat
126
in BzrDirFormat._control_server_formats)
127
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
129
def test_open_detected_smart_format(self):
130
fmt = BzrDirFormat.find_format(self.transport)
131
d = fmt.open(self.transport)
132
self.assertIsInstance(d, BzrDir)
134
def test_remote_branch_repr(self):
135
b = BzrDir.open_from_transport(self.transport).open_branch()
136
self.assertStartsWith(str(b), 'RemoteBranch(')
138
def test_remote_bzrdir_repr(self):
139
b = BzrDir.open_from_transport(self.transport)
140
self.assertStartsWith(str(b), 'RemoteBzrDir(')
142
def test_remote_branch_format_supports_stacking(self):
144
self.make_branch('unstackable', format='pack-0.92')
145
b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
146
self.assertFalse(b._format.supports_stacking())
147
self.make_branch('stackable', format='1.9')
148
b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
149
self.assertTrue(b._format.supports_stacking())
151
def test_remote_repo_format_supports_external_references(self):
153
bd = self.make_bzrdir('unstackable', format='pack-0.92')
154
r = bd.create_repository()
155
self.assertFalse(r._format.supports_external_lookups)
156
r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
157
self.assertFalse(r._format.supports_external_lookups)
158
bd = self.make_bzrdir('stackable', format='1.9')
159
r = bd.create_repository()
160
self.assertTrue(r._format.supports_external_lookups)
161
r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
162
self.assertTrue(r._format.supports_external_lookups)
164
def test_remote_branch_set_append_revisions_only(self):
165
# Make a format 1.9 branch, which supports append_revisions_only
166
branch = self.make_branch('branch', format='1.9')
167
config = branch.get_config()
168
branch.set_append_revisions_only(True)
170
'True', config.get_user_option('append_revisions_only'))
171
branch.set_append_revisions_only(False)
173
'False', config.get_user_option('append_revisions_only'))
175
def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
176
branch = self.make_branch('branch', format='knit')
177
config = branch.get_config()
179
errors.UpgradeRequired, branch.set_append_revisions_only, True)
182
class FakeProtocol(object):
183
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
185
def __init__(self, body, fake_client):
187
self._body_buffer = None
188
self._fake_client = fake_client
190
def read_body_bytes(self, count=-1):
191
if self._body_buffer is None:
192
self._body_buffer = StringIO(self.body)
193
bytes = self._body_buffer.read(count)
194
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
195
self._fake_client.expecting_body = False
198
def cancel_read_body(self):
199
self._fake_client.expecting_body = False
201
def read_streamed_body(self):
205
class FakeClient(_SmartClient):
206
"""Lookalike for _SmartClient allowing testing."""
208
def __init__(self, fake_medium_base='fake base'):
209
"""Create a FakeClient."""
212
self.expecting_body = False
213
# if non-None, this is the list of expected calls, with only the
214
# method name and arguments included. the body might be hard to
215
# compute so is not included. If a call is None, that call can
217
self._expected_calls = None
218
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
220
def add_expected_call(self, call_name, call_args, response_type,
221
response_args, response_body=None):
222
if self._expected_calls is None:
223
self._expected_calls = []
224
self._expected_calls.append((call_name, call_args))
225
self.responses.append((response_type, response_args, response_body))
227
def add_success_response(self, *args):
228
self.responses.append(('success', args, None))
230
def add_success_response_with_body(self, body, *args):
231
self.responses.append(('success', args, body))
232
if self._expected_calls is not None:
233
self._expected_calls.append(None)
235
def add_error_response(self, *args):
236
self.responses.append(('error', args))
238
def add_unknown_method_response(self, verb):
239
self.responses.append(('unknown', verb))
241
def finished_test(self):
242
if self._expected_calls:
243
raise AssertionError("%r finished but was still expecting %r"
244
% (self, self._expected_calls[0]))
246
def _get_next_response(self):
248
response_tuple = self.responses.pop(0)
249
except IndexError, e:
250
raise AssertionError("%r didn't expect any more calls"
252
if response_tuple[0] == 'unknown':
253
raise errors.UnknownSmartMethod(response_tuple[1])
254
elif response_tuple[0] == 'error':
255
raise errors.ErrorFromSmartServer(response_tuple[1])
256
return response_tuple
258
def _check_call(self, method, args):
259
if self._expected_calls is None:
260
# the test should be updated to say what it expects
263
next_call = self._expected_calls.pop(0)
265
raise AssertionError("%r didn't expect any more calls "
267
% (self, method, args,))
268
if next_call is None:
270
if method != next_call[0] or args != next_call[1]:
271
raise AssertionError("%r expected %r%r "
273
% (self, next_call[0], next_call[1], method, args,))
275
def call(self, method, *args):
276
self._check_call(method, args)
277
self._calls.append(('call', method, args))
278
return self._get_next_response()[1]
280
def call_expecting_body(self, method, *args):
281
self._check_call(method, args)
282
self._calls.append(('call_expecting_body', method, args))
283
result = self._get_next_response()
284
self.expecting_body = True
285
return result[1], FakeProtocol(result[2], self)
287
def call_with_body_bytes(self, method, args, body):
288
self._check_call(method, args)
289
self._calls.append(('call_with_body_bytes', method, args, body))
290
result = self._get_next_response()
291
return result[1], FakeProtocol(result[2], self)
293
def call_with_body_bytes_expecting_body(self, method, args, body):
294
self._check_call(method, args)
295
self._calls.append(('call_with_body_bytes_expecting_body', method,
297
result = self._get_next_response()
298
self.expecting_body = True
299
return result[1], FakeProtocol(result[2], self)
301
def call_with_body_stream(self, args, stream):
302
# Explicitly consume the stream before checking for an error, because
303
# that's what happens a real medium.
304
stream = list(stream)
305
self._check_call(args[0], args[1:])
306
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
307
result = self._get_next_response()
308
# The second value returned from call_with_body_stream is supposed to
309
# be a response_handler object, but so far no tests depend on that.
310
response_handler = None
311
return result[1], response_handler
314
class FakeMedium(medium.SmartClientMedium):
316
def __init__(self, client_calls, base):
317
medium.SmartClientMedium.__init__(self, base)
318
self._client_calls = client_calls
320
def disconnect(self):
321
self._client_calls.append(('disconnect medium',))
324
class TestVfsHas(tests.TestCase):
326
def test_unicode_path(self):
327
client = FakeClient('/')
328
client.add_success_response('yes',)
329
transport = RemoteTransport('bzr://localhost/', _client=client)
330
filename = u'/hell\u00d8'.encode('utf8')
331
result = transport.has(filename)
333
[('call', 'has', (filename,))],
335
self.assertTrue(result)
338
class TestRemote(tests.TestCaseWithMemoryTransport):
340
def get_branch_format(self):
341
reference_bzrdir_format = bzrdir.format_registry.get('default')()
342
return reference_bzrdir_format.get_branch_format()
344
def get_repo_format(self):
345
reference_bzrdir_format = bzrdir.format_registry.get('default')()
346
return reference_bzrdir_format.repository_format
348
def assertFinished(self, fake_client):
349
"""Assert that all of a FakeClient's expected calls have occurred."""
350
fake_client.finished_test()
353
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
354
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
356
def assertRemotePath(self, expected, client_base, transport_base):
357
"""Assert that the result of
358
SmartClientMedium.remote_path_from_transport is the expected value for
359
a given client_base and transport_base.
361
client_medium = medium.SmartClientMedium(client_base)
362
t = transport.get_transport(transport_base)
363
result = client_medium.remote_path_from_transport(t)
364
self.assertEqual(expected, result)
366
def test_remote_path_from_transport(self):
367
"""SmartClientMedium.remote_path_from_transport calculates a URL for
368
the given transport relative to the root of the client base URL.
370
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
371
self.assertRemotePath(
372
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
374
def assertRemotePathHTTP(self, expected, transport_base, relpath):
375
"""Assert that the result of
376
HttpTransportBase.remote_path_from_transport is the expected value for
377
a given transport_base and relpath of that transport. (Note that
378
HttpTransportBase is a subclass of SmartClientMedium)
380
base_transport = transport.get_transport(transport_base)
381
client_medium = base_transport.get_smart_medium()
382
cloned_transport = base_transport.clone(relpath)
383
result = client_medium.remote_path_from_transport(cloned_transport)
384
self.assertEqual(expected, result)
386
def test_remote_path_from_transport_http(self):
387
"""Remote paths for HTTP transports are calculated differently to other
388
transports. They are just relative to the client base, not the root
389
directory of the host.
391
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
392
self.assertRemotePathHTTP(
393
'../xyz/', scheme + '//host/path', '../xyz/')
394
self.assertRemotePathHTTP(
395
'xyz/', scheme + '//host/path', 'xyz/')
398
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
399
"""Tests for the behaviour of client_medium.remote_is_at_least."""
401
def test_initially_unlimited(self):
402
"""A fresh medium assumes that the remote side supports all
405
client_medium = medium.SmartClientMedium('dummy base')
406
self.assertFalse(client_medium._is_remote_before((99, 99)))
408
def test__remember_remote_is_before(self):
409
"""Calling _remember_remote_is_before ratchets down the known remote
412
client_medium = medium.SmartClientMedium('dummy base')
413
# Mark the remote side as being less than 1.6. The remote side may
415
client_medium._remember_remote_is_before((1, 6))
416
self.assertTrue(client_medium._is_remote_before((1, 6)))
417
self.assertFalse(client_medium._is_remote_before((1, 5)))
418
# Calling _remember_remote_is_before again with a lower value works.
419
client_medium._remember_remote_is_before((1, 5))
420
self.assertTrue(client_medium._is_remote_before((1, 5)))
421
# If you call _remember_remote_is_before with a higher value it logs a
422
# warning, and continues to remember the lower value.
423
self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
424
client_medium._remember_remote_is_before((1, 9))
425
self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
426
self.assertTrue(client_medium._is_remote_before((1, 5)))
429
class TestBzrDirCloningMetaDir(TestRemote):
431
def test_backwards_compat(self):
432
self.setup_smart_server_with_call_log()
433
a_dir = self.make_bzrdir('.')
434
self.reset_smart_call_log()
435
verb = 'BzrDir.cloning_metadir'
436
self.disable_verb(verb)
437
format = a_dir.cloning_metadir()
438
call_count = len([call for call in self.hpss_calls if
439
call.call.method == verb])
440
self.assertEqual(1, call_count)
442
def test_branch_reference(self):
443
transport = self.get_transport('quack')
444
referenced = self.make_branch('referenced')
445
expected = referenced.bzrdir.cloning_metadir()
446
client = FakeClient(transport.base)
447
client.add_expected_call(
448
'BzrDir.cloning_metadir', ('quack/', 'False'),
449
'error', ('BranchReference',)),
450
client.add_expected_call(
451
'BzrDir.open_branchV3', ('quack/',),
452
'success', ('ref', self.get_url('referenced'))),
453
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
455
result = a_bzrdir.cloning_metadir()
456
# We should have got a control dir matching the referenced branch.
457
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
458
self.assertEqual(expected._repository_format, result._repository_format)
459
self.assertEqual(expected._branch_format, result._branch_format)
460
self.assertFinished(client)
462
def test_current_server(self):
463
transport = self.get_transport('.')
464
transport = transport.clone('quack')
465
self.make_bzrdir('quack')
466
client = FakeClient(transport.base)
467
reference_bzrdir_format = bzrdir.format_registry.get('default')()
468
control_name = reference_bzrdir_format.network_name()
469
client.add_expected_call(
470
'BzrDir.cloning_metadir', ('quack/', 'False'),
471
'success', (control_name, '', ('branch', ''))),
472
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
474
result = a_bzrdir.cloning_metadir()
475
# We should have got a reference control dir with default branch and
476
# repository formats.
477
# This pokes a little, just to be sure.
478
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
479
self.assertEqual(None, result._repository_format)
480
self.assertEqual(None, result._branch_format)
481
self.assertFinished(client)
484
class TestBzrDirOpen(TestRemote):
486
def make_fake_client_and_transport(self, path='quack'):
487
transport = MemoryTransport()
488
transport.mkdir(path)
489
transport = transport.clone(path)
490
client = FakeClient(transport.base)
491
return client, transport
493
def test_absent(self):
494
client, transport = self.make_fake_client_and_transport()
495
client.add_expected_call(
496
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
497
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
498
remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
499
self.assertFinished(client)
501
def test_present_without_workingtree(self):
502
client, transport = self.make_fake_client_and_transport()
503
client.add_expected_call(
504
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
505
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
506
_client=client, _force_probe=True)
507
self.assertIsInstance(bd, RemoteBzrDir)
508
self.assertFalse(bd.has_workingtree())
509
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
510
self.assertFinished(client)
512
def test_present_with_workingtree(self):
513
client, transport = self.make_fake_client_and_transport()
514
client.add_expected_call(
515
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
516
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
517
_client=client, _force_probe=True)
518
self.assertIsInstance(bd, RemoteBzrDir)
519
self.assertTrue(bd.has_workingtree())
520
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
521
self.assertFinished(client)
523
def test_backwards_compat(self):
524
client, transport = self.make_fake_client_and_transport()
525
client.add_expected_call(
526
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
527
client.add_expected_call(
528
'BzrDir.open', ('quack/',), 'success', ('yes',))
529
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
530
_client=client, _force_probe=True)
531
self.assertIsInstance(bd, RemoteBzrDir)
532
self.assertFinished(client)
534
def test_backwards_compat_hpss_v2(self):
535
client, transport = self.make_fake_client_and_transport()
536
# Monkey-patch fake client to simulate real-world behaviour with v2
537
# server: upon first RPC call detect the protocol version, and because
538
# the version is 2 also do _remember_remote_is_before((1, 6)) before
539
# continuing with the RPC.
540
orig_check_call = client._check_call
541
def check_call(method, args):
542
client._medium._protocol_version = 2
543
client._medium._remember_remote_is_before((1, 6))
544
client._check_call = orig_check_call
545
client._check_call(method, args)
546
client._check_call = check_call
547
client.add_expected_call(
548
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
549
client.add_expected_call(
550
'BzrDir.open', ('quack/',), 'success', ('yes',))
551
bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
552
_client=client, _force_probe=True)
553
self.assertIsInstance(bd, RemoteBzrDir)
554
self.assertFinished(client)
557
class TestBzrDirOpenBranch(TestRemote):
559
def test_backwards_compat(self):
560
self.setup_smart_server_with_call_log()
561
self.make_branch('.')
562
a_dir = BzrDir.open(self.get_url('.'))
563
self.reset_smart_call_log()
564
verb = 'BzrDir.open_branchV3'
565
self.disable_verb(verb)
566
format = a_dir.open_branch()
567
call_count = len([call for call in self.hpss_calls if
568
call.call.method == verb])
569
self.assertEqual(1, call_count)
571
def test_branch_present(self):
572
reference_format = self.get_repo_format()
573
network_name = reference_format.network_name()
574
branch_network_name = self.get_branch_format().network_name()
575
transport = MemoryTransport()
576
transport.mkdir('quack')
577
transport = transport.clone('quack')
578
client = FakeClient(transport.base)
579
client.add_expected_call(
580
'BzrDir.open_branchV3', ('quack/',),
581
'success', ('branch', branch_network_name))
582
client.add_expected_call(
583
'BzrDir.find_repositoryV3', ('quack/',),
584
'success', ('ok', '', 'no', 'no', 'no', network_name))
585
client.add_expected_call(
586
'Branch.get_stacked_on_url', ('quack/',),
587
'error', ('NotStacked',))
588
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
590
result = bzrdir.open_branch()
591
self.assertIsInstance(result, RemoteBranch)
592
self.assertEqual(bzrdir, result.bzrdir)
593
self.assertFinished(client)
595
def test_branch_missing(self):
596
transport = MemoryTransport()
597
transport.mkdir('quack')
598
transport = transport.clone('quack')
599
client = FakeClient(transport.base)
600
client.add_error_response('nobranch')
601
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
603
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
605
[('call', 'BzrDir.open_branchV3', ('quack/',))],
608
def test__get_tree_branch(self):
609
# _get_tree_branch is a form of open_branch, but it should only ask for
610
# branch opening, not any other network requests.
612
def open_branch(name=None):
613
calls.append("Called")
615
transport = MemoryTransport()
616
# no requests on the network - catches other api calls being made.
617
client = FakeClient(transport.base)
618
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
620
# patch the open_branch call to record that it was called.
621
bzrdir.open_branch = open_branch
622
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
623
self.assertEqual(["Called"], calls)
624
self.assertEqual([], client._calls)
626
def test_url_quoting_of_path(self):
627
# Relpaths on the wire should not be URL-escaped. So "~" should be
628
# transmitted as "~", not "%7E".
629
transport = RemoteTCPTransport('bzr://localhost/~hello/')
630
client = FakeClient(transport.base)
631
reference_format = self.get_repo_format()
632
network_name = reference_format.network_name()
633
branch_network_name = self.get_branch_format().network_name()
634
client.add_expected_call(
635
'BzrDir.open_branchV3', ('~hello/',),
636
'success', ('branch', branch_network_name))
637
client.add_expected_call(
638
'BzrDir.find_repositoryV3', ('~hello/',),
639
'success', ('ok', '', 'no', 'no', 'no', network_name))
640
client.add_expected_call(
641
'Branch.get_stacked_on_url', ('~hello/',),
642
'error', ('NotStacked',))
643
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
645
result = bzrdir.open_branch()
646
self.assertFinished(client)
648
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
649
reference_format = self.get_repo_format()
650
network_name = reference_format.network_name()
651
transport = MemoryTransport()
652
transport.mkdir('quack')
653
transport = transport.clone('quack')
655
rich_response = 'yes'
659
subtree_response = 'yes'
661
subtree_response = 'no'
662
client = FakeClient(transport.base)
663
client.add_success_response(
664
'ok', '', rich_response, subtree_response, external_lookup,
666
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
668
result = bzrdir.open_repository()
670
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
672
self.assertIsInstance(result, RemoteRepository)
673
self.assertEqual(bzrdir, result.bzrdir)
674
self.assertEqual(rich_root, result._format.rich_root_data)
675
self.assertEqual(subtrees, result._format.supports_tree_reference)
677
def test_open_repository_sets_format_attributes(self):
678
self.check_open_repository(True, True)
679
self.check_open_repository(False, True)
680
self.check_open_repository(True, False)
681
self.check_open_repository(False, False)
682
self.check_open_repository(False, False, 'yes')
684
def test_old_server(self):
685
"""RemoteBzrDirFormat should fail to probe if the server version is too
688
self.assertRaises(errors.NotBranchError,
689
RemoteBzrDirFormat.probe_transport, OldServerTransport())
692
class TestBzrDirCreateBranch(TestRemote):
694
def test_backwards_compat(self):
695
self.setup_smart_server_with_call_log()
696
repo = self.make_repository('.')
697
self.reset_smart_call_log()
698
self.disable_verb('BzrDir.create_branch')
699
branch = repo.bzrdir.create_branch()
700
create_branch_call_count = len([call for call in self.hpss_calls if
701
call.call.method == 'BzrDir.create_branch'])
702
self.assertEqual(1, create_branch_call_count)
704
def test_current_server(self):
705
transport = self.get_transport('.')
706
transport = transport.clone('quack')
707
self.make_repository('quack')
708
client = FakeClient(transport.base)
709
reference_bzrdir_format = bzrdir.format_registry.get('default')()
710
reference_format = reference_bzrdir_format.get_branch_format()
711
network_name = reference_format.network_name()
712
reference_repo_fmt = reference_bzrdir_format.repository_format
713
reference_repo_name = reference_repo_fmt.network_name()
714
client.add_expected_call(
715
'BzrDir.create_branch', ('quack/', network_name),
716
'success', ('ok', network_name, '', 'no', 'no', 'yes',
717
reference_repo_name))
718
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
720
branch = a_bzrdir.create_branch()
721
# We should have got a remote branch
722
self.assertIsInstance(branch, remote.RemoteBranch)
723
# its format should have the settings from the response
724
format = branch._format
725
self.assertEqual(network_name, format.network_name())
728
class TestBzrDirCreateRepository(TestRemote):
730
def test_backwards_compat(self):
731
self.setup_smart_server_with_call_log()
732
bzrdir = self.make_bzrdir('.')
733
self.reset_smart_call_log()
734
self.disable_verb('BzrDir.create_repository')
735
repo = bzrdir.create_repository()
736
create_repo_call_count = len([call for call in self.hpss_calls if
737
call.call.method == 'BzrDir.create_repository'])
738
self.assertEqual(1, create_repo_call_count)
740
def test_current_server(self):
741
transport = self.get_transport('.')
742
transport = transport.clone('quack')
743
self.make_bzrdir('quack')
744
client = FakeClient(transport.base)
745
reference_bzrdir_format = bzrdir.format_registry.get('default')()
746
reference_format = reference_bzrdir_format.repository_format
747
network_name = reference_format.network_name()
748
client.add_expected_call(
749
'BzrDir.create_repository', ('quack/',
750
'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
752
'success', ('ok', 'yes', 'yes', 'yes', network_name))
753
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
755
repo = a_bzrdir.create_repository()
756
# We should have got a remote repository
757
self.assertIsInstance(repo, remote.RemoteRepository)
758
# its format should have the settings from the response
759
format = repo._format
760
self.assertTrue(format.rich_root_data)
761
self.assertTrue(format.supports_tree_reference)
762
self.assertTrue(format.supports_external_lookups)
763
self.assertEqual(network_name, format.network_name())
766
class TestBzrDirOpenRepository(TestRemote):
768
def test_backwards_compat_1_2_3(self):
769
# fallback all the way to the first version.
770
reference_format = self.get_repo_format()
771
network_name = reference_format.network_name()
772
server_url = 'bzr://example.com/'
773
self.permit_url(server_url)
774
client = FakeClient(server_url)
775
client.add_unknown_method_response('BzrDir.find_repositoryV3')
776
client.add_unknown_method_response('BzrDir.find_repositoryV2')
777
client.add_success_response('ok', '', 'no', 'no')
778
# A real repository instance will be created to determine the network
780
client.add_success_response_with_body(
781
"Bazaar-NG meta directory, format 1\n", 'ok')
782
client.add_success_response_with_body(
783
reference_format.get_format_string(), 'ok')
784
# PackRepository wants to do a stat
785
client.add_success_response('stat', '0', '65535')
786
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
788
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
790
repo = bzrdir.open_repository()
792
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
793
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
794
('call', 'BzrDir.find_repository', ('quack/',)),
795
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
796
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
797
('call', 'stat', ('/quack/.bzr/repository',)),
800
self.assertEqual(network_name, repo._format.network_name())
802
def test_backwards_compat_2(self):
803
# fallback to find_repositoryV2
804
reference_format = self.get_repo_format()
805
network_name = reference_format.network_name()
806
server_url = 'bzr://example.com/'
807
self.permit_url(server_url)
808
client = FakeClient(server_url)
809
client.add_unknown_method_response('BzrDir.find_repositoryV3')
810
client.add_success_response('ok', '', 'no', 'no', 'no')
811
# A real repository instance will be created to determine the network
813
client.add_success_response_with_body(
814
"Bazaar-NG meta directory, format 1\n", 'ok')
815
client.add_success_response_with_body(
816
reference_format.get_format_string(), 'ok')
817
# PackRepository wants to do a stat
818
client.add_success_response('stat', '0', '65535')
819
remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
821
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
823
repo = bzrdir.open_repository()
825
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
826
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
827
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
828
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
829
('call', 'stat', ('/quack/.bzr/repository',)),
832
self.assertEqual(network_name, repo._format.network_name())
834
def test_current_server(self):
835
reference_format = self.get_repo_format()
836
network_name = reference_format.network_name()
837
transport = MemoryTransport()
838
transport.mkdir('quack')
839
transport = transport.clone('quack')
840
client = FakeClient(transport.base)
841
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
842
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
844
repo = bzrdir.open_repository()
846
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
848
self.assertEqual(network_name, repo._format.network_name())
851
class TestBzrDirFormatInitializeEx(TestRemote):
853
def test_success(self):
854
"""Simple test for typical successful call."""
855
fmt = bzrdir.RemoteBzrDirFormat()
856
default_format_name = BzrDirFormat.get_default_format().network_name()
857
transport = self.get_transport()
858
client = FakeClient(transport.base)
859
client.add_expected_call(
860
'BzrDirFormat.initialize_ex_1.16',
861
(default_format_name, 'path', 'False', 'False', 'False', '',
862
'', '', '', 'False'),
864
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
865
'bzrdir fmt', 'False', '', '', 'repo lock token'))
866
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
867
# it's currently hard to test that without supplying a real remote
868
# transport connected to a real server.
869
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
870
transport, False, False, False, None, None, None, None, False)
871
self.assertFinished(client)
873
def test_error(self):
874
"""Error responses are translated, e.g. 'PermissionDenied' raises the
875
corresponding error from the client.
877
fmt = bzrdir.RemoteBzrDirFormat()
878
default_format_name = BzrDirFormat.get_default_format().network_name()
879
transport = self.get_transport()
880
client = FakeClient(transport.base)
881
client.add_expected_call(
882
'BzrDirFormat.initialize_ex_1.16',
883
(default_format_name, 'path', 'False', 'False', 'False', '',
884
'', '', '', 'False'),
886
('PermissionDenied', 'path', 'extra info'))
887
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
888
# it's currently hard to test that without supplying a real remote
889
# transport connected to a real server.
890
err = self.assertRaises(errors.PermissionDenied,
891
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
892
False, False, False, None, None, None, None, False)
893
self.assertEqual('path', err.path)
894
self.assertEqual(': extra info', err.extra)
895
self.assertFinished(client)
897
def test_error_from_real_server(self):
898
"""Integration test for error translation."""
899
transport = self.make_smart_server('foo')
900
transport = transport.clone('no-such-path')
901
fmt = bzrdir.RemoteBzrDirFormat()
902
err = self.assertRaises(errors.NoSuchFile,
903
fmt.initialize_on_transport_ex, transport, create_prefix=False)
906
class OldSmartClient(object):
907
"""A fake smart client for test_old_version that just returns a version one
908
response to the 'hello' (query version) command.
911
def get_request(self):
912
input_file = StringIO('ok\x011\n')
913
output_file = StringIO()
914
client_medium = medium.SmartSimplePipesClientMedium(
915
input_file, output_file)
916
return medium.SmartClientStreamMediumRequest(client_medium)
918
def protocol_version(self):
922
class OldServerTransport(object):
923
"""A fake transport for test_old_server that reports it's smart server
924
protocol version as version one.
930
def get_smart_client(self):
931
return OldSmartClient()
934
class RemoteBzrDirTestCase(TestRemote):
936
def make_remote_bzrdir(self, transport, client):
937
"""Make a RemotebzrDir using 'client' as the _client."""
938
return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
942
class RemoteBranchTestCase(RemoteBzrDirTestCase):
944
def lock_remote_branch(self, branch):
945
"""Trick a RemoteBranch into thinking it is locked."""
946
branch._lock_mode = 'w'
947
branch._lock_count = 2
948
branch._lock_token = 'branch token'
949
branch._repo_lock_token = 'repo token'
950
branch.repository._lock_mode = 'w'
951
branch.repository._lock_count = 2
952
branch.repository._lock_token = 'repo token'
954
def make_remote_branch(self, transport, client):
955
"""Make a RemoteBranch using 'client' as its _SmartClient.
957
A RemoteBzrDir and RemoteRepository will also be created to fill out
958
the RemoteBranch, albeit with stub values for some of their attributes.
960
# we do not want bzrdir to make any remote calls, so use False as its
961
# _client. If it tries to make a remote call, this will fail
963
bzrdir = self.make_remote_bzrdir(transport, False)
964
repo = RemoteRepository(bzrdir, None, _client=client)
965
branch_format = self.get_branch_format()
966
format = RemoteBranchFormat(network_name=branch_format.network_name())
967
return RemoteBranch(bzrdir, repo, _client=client, format=format)
970
class TestBranchGetParent(RemoteBranchTestCase):
972
def test_no_parent(self):
973
# in an empty branch we decode the response properly
974
transport = MemoryTransport()
975
client = FakeClient(transport.base)
976
client.add_expected_call(
977
'Branch.get_stacked_on_url', ('quack/',),
978
'error', ('NotStacked',))
979
client.add_expected_call(
980
'Branch.get_parent', ('quack/',),
982
transport.mkdir('quack')
983
transport = transport.clone('quack')
984
branch = self.make_remote_branch(transport, client)
985
result = branch.get_parent()
986
self.assertFinished(client)
987
self.assertEqual(None, result)
989
def test_parent_relative(self):
990
transport = MemoryTransport()
991
client = FakeClient(transport.base)
992
client.add_expected_call(
993
'Branch.get_stacked_on_url', ('kwaak/',),
994
'error', ('NotStacked',))
995
client.add_expected_call(
996
'Branch.get_parent', ('kwaak/',),
997
'success', ('../foo/',))
998
transport.mkdir('kwaak')
999
transport = transport.clone('kwaak')
1000
branch = self.make_remote_branch(transport, client)
1001
result = branch.get_parent()
1002
self.assertEqual(transport.clone('../foo').base, result)
1004
def test_parent_absolute(self):
1005
transport = MemoryTransport()
1006
client = FakeClient(transport.base)
1007
client.add_expected_call(
1008
'Branch.get_stacked_on_url', ('kwaak/',),
1009
'error', ('NotStacked',))
1010
client.add_expected_call(
1011
'Branch.get_parent', ('kwaak/',),
1012
'success', ('http://foo/',))
1013
transport.mkdir('kwaak')
1014
transport = transport.clone('kwaak')
1015
branch = self.make_remote_branch(transport, client)
1016
result = branch.get_parent()
1017
self.assertEqual('http://foo/', result)
1018
self.assertFinished(client)
1021
class TestBranchSetParentLocation(RemoteBranchTestCase):
1023
def test_no_parent(self):
1024
# We call the verb when setting parent to None
1025
transport = MemoryTransport()
1026
client = FakeClient(transport.base)
1027
client.add_expected_call(
1028
'Branch.get_stacked_on_url', ('quack/',),
1029
'error', ('NotStacked',))
1030
client.add_expected_call(
1031
'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
1033
transport.mkdir('quack')
1034
transport = transport.clone('quack')
1035
branch = self.make_remote_branch(transport, client)
1036
branch._lock_token = 'b'
1037
branch._repo_lock_token = 'r'
1038
branch._set_parent_location(None)
1039
self.assertFinished(client)
1041
def test_parent(self):
1042
transport = MemoryTransport()
1043
client = FakeClient(transport.base)
1044
client.add_expected_call(
1045
'Branch.get_stacked_on_url', ('kwaak/',),
1046
'error', ('NotStacked',))
1047
client.add_expected_call(
1048
'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
1050
transport.mkdir('kwaak')
1051
transport = transport.clone('kwaak')
1052
branch = self.make_remote_branch(transport, client)
1053
branch._lock_token = 'b'
1054
branch._repo_lock_token = 'r'
1055
branch._set_parent_location('foo')
1056
self.assertFinished(client)
1058
def test_backwards_compat(self):
1059
self.setup_smart_server_with_call_log()
1060
branch = self.make_branch('.')
1061
self.reset_smart_call_log()
1062
verb = 'Branch.set_parent_location'
1063
self.disable_verb(verb)
1064
branch.set_parent('http://foo/')
1065
self.assertLength(12, self.hpss_calls)
1068
class TestBranchGetTagsBytes(RemoteBranchTestCase):
1070
def test_backwards_compat(self):
1071
self.setup_smart_server_with_call_log()
1072
branch = self.make_branch('.')
1073
self.reset_smart_call_log()
1074
verb = 'Branch.get_tags_bytes'
1075
self.disable_verb(verb)
1076
branch.tags.get_tag_dict()
1077
call_count = len([call for call in self.hpss_calls if
1078
call.call.method == verb])
1079
self.assertEqual(1, call_count)
1081
def test_trivial(self):
1082
transport = MemoryTransport()
1083
client = FakeClient(transport.base)
1084
client.add_expected_call(
1085
'Branch.get_stacked_on_url', ('quack/',),
1086
'error', ('NotStacked',))
1087
client.add_expected_call(
1088
'Branch.get_tags_bytes', ('quack/',),
1090
transport.mkdir('quack')
1091
transport = transport.clone('quack')
1092
branch = self.make_remote_branch(transport, client)
1093
result = branch.tags.get_tag_dict()
1094
self.assertFinished(client)
1095
self.assertEqual({}, result)
1098
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1100
def test_trivial(self):
1101
transport = MemoryTransport()
1102
client = FakeClient(transport.base)
1103
client.add_expected_call(
1104
'Branch.get_stacked_on_url', ('quack/',),
1105
'error', ('NotStacked',))
1106
client.add_expected_call(
1107
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1109
transport.mkdir('quack')
1110
transport = transport.clone('quack')
1111
branch = self.make_remote_branch(transport, client)
1112
self.lock_remote_branch(branch)
1113
branch._set_tags_bytes('tags bytes')
1114
self.assertFinished(client)
1115
self.assertEqual('tags bytes', client._calls[-1][-1])
1117
def test_backwards_compatible(self):
1118
transport = MemoryTransport()
1119
client = FakeClient(transport.base)
1120
client.add_expected_call(
1121
'Branch.get_stacked_on_url', ('quack/',),
1122
'error', ('NotStacked',))
1123
client.add_expected_call(
1124
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1125
'unknown', ('Branch.set_tags_bytes',))
1126
transport.mkdir('quack')
1127
transport = transport.clone('quack')
1128
branch = self.make_remote_branch(transport, client)
1129
self.lock_remote_branch(branch)
1130
class StubRealBranch(object):
1133
def _set_tags_bytes(self, bytes):
1134
self.calls.append(('set_tags_bytes', bytes))
1135
real_branch = StubRealBranch()
1136
branch._real_branch = real_branch
1137
branch._set_tags_bytes('tags bytes')
1138
# Call a second time, to exercise the 'remote version already inferred'
1140
branch._set_tags_bytes('tags bytes')
1141
self.assertFinished(client)
1143
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1146
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1148
def test_empty_branch(self):
1149
# in an empty branch we decode the response properly
1150
transport = MemoryTransport()
1151
client = FakeClient(transport.base)
1152
client.add_expected_call(
1153
'Branch.get_stacked_on_url', ('quack/',),
1154
'error', ('NotStacked',))
1155
client.add_expected_call(
1156
'Branch.last_revision_info', ('quack/',),
1157
'success', ('ok', '0', 'null:'))
1158
transport.mkdir('quack')
1159
transport = transport.clone('quack')
1160
branch = self.make_remote_branch(transport, client)
1161
result = branch.last_revision_info()
1162
self.assertFinished(client)
1163
self.assertEqual((0, NULL_REVISION), result)
1165
def test_non_empty_branch(self):
1166
# in a non-empty branch we also decode the response properly
1167
revid = u'\xc8'.encode('utf8')
1168
transport = MemoryTransport()
1169
client = FakeClient(transport.base)
1170
client.add_expected_call(
1171
'Branch.get_stacked_on_url', ('kwaak/',),
1172
'error', ('NotStacked',))
1173
client.add_expected_call(
1174
'Branch.last_revision_info', ('kwaak/',),
1175
'success', ('ok', '2', revid))
1176
transport.mkdir('kwaak')
1177
transport = transport.clone('kwaak')
1178
branch = self.make_remote_branch(transport, client)
1179
result = branch.last_revision_info()
1180
self.assertEqual((2, revid), result)
1183
class TestBranch_get_stacked_on_url(TestRemote):
1184
"""Test Branch._get_stacked_on_url rpc"""
1186
def test_get_stacked_on_invalid_url(self):
1187
# test that asking for a stacked on url the server can't access works.
1188
# This isn't perfect, but then as we're in the same process there
1189
# really isn't anything we can do to be 100% sure that the server
1190
# doesn't just open in - this test probably needs to be rewritten using
1191
# a spawn()ed server.
1192
stacked_branch = self.make_branch('stacked', format='1.9')
1193
memory_branch = self.make_branch('base', format='1.9')
1194
vfs_url = self.get_vfs_only_url('base')
1195
stacked_branch.set_stacked_on_url(vfs_url)
1196
transport = stacked_branch.bzrdir.root_transport
1197
client = FakeClient(transport.base)
1198
client.add_expected_call(
1199
'Branch.get_stacked_on_url', ('stacked/',),
1200
'success', ('ok', vfs_url))
1201
# XXX: Multiple calls are bad, this second call documents what is
1203
client.add_expected_call(
1204
'Branch.get_stacked_on_url', ('stacked/',),
1205
'success', ('ok', vfs_url))
1206
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1208
repo_fmt = remote.RemoteRepositoryFormat()
1209
repo_fmt._custom_format = stacked_branch.repository._format
1210
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
1212
result = branch.get_stacked_on_url()
1213
self.assertEqual(vfs_url, result)
1215
def test_backwards_compatible(self):
1216
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
1217
base_branch = self.make_branch('base', format='1.6')
1218
stacked_branch = self.make_branch('stacked', format='1.6')
1219
stacked_branch.set_stacked_on_url('../base')
1220
client = FakeClient(self.get_url())
1221
branch_network_name = self.get_branch_format().network_name()
1222
client.add_expected_call(
1223
'BzrDir.open_branchV3', ('stacked/',),
1224
'success', ('branch', branch_network_name))
1225
client.add_expected_call(
1226
'BzrDir.find_repositoryV3', ('stacked/',),
1227
'success', ('ok', '', 'no', 'no', 'yes',
1228
stacked_branch.repository._format.network_name()))
1229
# called twice, once from constructor and then again by us
1230
client.add_expected_call(
1231
'Branch.get_stacked_on_url', ('stacked/',),
1232
'unknown', ('Branch.get_stacked_on_url',))
1233
client.add_expected_call(
1234
'Branch.get_stacked_on_url', ('stacked/',),
1235
'unknown', ('Branch.get_stacked_on_url',))
1236
# this will also do vfs access, but that goes direct to the transport
1237
# and isn't seen by the FakeClient.
1238
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1239
remote.RemoteBzrDirFormat(), _client=client)
1240
branch = bzrdir.open_branch()
1241
result = branch.get_stacked_on_url()
1242
self.assertEqual('../base', result)
1243
self.assertFinished(client)
1244
# it's in the fallback list both for the RemoteRepository and its vfs
1246
self.assertEqual(1, len(branch.repository._fallback_repositories))
1248
len(branch.repository._real_repository._fallback_repositories))
1250
def test_get_stacked_on_real_branch(self):
1251
base_branch = self.make_branch('base')
1252
stacked_branch = self.make_branch('stacked')
1253
stacked_branch.set_stacked_on_url('../base')
1254
reference_format = self.get_repo_format()
1255
network_name = reference_format.network_name()
1256
client = FakeClient(self.get_url())
1257
branch_network_name = self.get_branch_format().network_name()
1258
client.add_expected_call(
1259
'BzrDir.open_branchV3', ('stacked/',),
1260
'success', ('branch', branch_network_name))
1261
client.add_expected_call(
1262
'BzrDir.find_repositoryV3', ('stacked/',),
1263
'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1264
# called twice, once from constructor and then again by us
1265
client.add_expected_call(
1266
'Branch.get_stacked_on_url', ('stacked/',),
1267
'success', ('ok', '../base'))
1268
client.add_expected_call(
1269
'Branch.get_stacked_on_url', ('stacked/',),
1270
'success', ('ok', '../base'))
1271
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1272
remote.RemoteBzrDirFormat(), _client=client)
1273
branch = bzrdir.open_branch()
1274
result = branch.get_stacked_on_url()
1275
self.assertEqual('../base', result)
1276
self.assertFinished(client)
1277
# it's in the fallback list both for the RemoteRepository.
1278
self.assertEqual(1, len(branch.repository._fallback_repositories))
1279
# And we haven't had to construct a real repository.
1280
self.assertEqual(None, branch.repository._real_repository)
1283
class TestBranchSetLastRevision(RemoteBranchTestCase):
1285
def test_set_empty(self):
1286
# set_revision_history([]) is translated to calling
1287
# Branch.set_last_revision(path, '') on the wire.
1288
transport = MemoryTransport()
1289
transport.mkdir('branch')
1290
transport = transport.clone('branch')
1292
client = FakeClient(transport.base)
1293
client.add_expected_call(
1294
'Branch.get_stacked_on_url', ('branch/',),
1295
'error', ('NotStacked',))
1296
client.add_expected_call(
1297
'Branch.lock_write', ('branch/', '', ''),
1298
'success', ('ok', 'branch token', 'repo token'))
1299
client.add_expected_call(
1300
'Branch.last_revision_info',
1302
'success', ('ok', '0', 'null:'))
1303
client.add_expected_call(
1304
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1306
client.add_expected_call(
1307
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1309
branch = self.make_remote_branch(transport, client)
1310
# This is a hack to work around the problem that RemoteBranch currently
1311
# unnecessarily invokes _ensure_real upon a call to lock_write.
1312
branch._ensure_real = lambda: None
1314
result = branch.set_revision_history([])
1316
self.assertEqual(None, result)
1317
self.assertFinished(client)
1319
def test_set_nonempty(self):
1320
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1321
# Branch.set_last_revision(path, rev-idN) on the wire.
1322
transport = MemoryTransport()
1323
transport.mkdir('branch')
1324
transport = transport.clone('branch')
1326
client = FakeClient(transport.base)
1327
client.add_expected_call(
1328
'Branch.get_stacked_on_url', ('branch/',),
1329
'error', ('NotStacked',))
1330
client.add_expected_call(
1331
'Branch.lock_write', ('branch/', '', ''),
1332
'success', ('ok', 'branch token', 'repo token'))
1333
client.add_expected_call(
1334
'Branch.last_revision_info',
1336
'success', ('ok', '0', 'null:'))
1338
encoded_body = bz2.compress('\n'.join(lines))
1339
client.add_success_response_with_body(encoded_body, 'ok')
1340
client.add_expected_call(
1341
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1343
client.add_expected_call(
1344
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1346
branch = self.make_remote_branch(transport, client)
1347
# This is a hack to work around the problem that RemoteBranch currently
1348
# unnecessarily invokes _ensure_real upon a call to lock_write.
1349
branch._ensure_real = lambda: None
1350
# Lock the branch, reset the record of remote calls.
1352
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1354
self.assertEqual(None, result)
1355
self.assertFinished(client)
1357
def test_no_such_revision(self):
1358
transport = MemoryTransport()
1359
transport.mkdir('branch')
1360
transport = transport.clone('branch')
1361
# A response of 'NoSuchRevision' is translated into an exception.
1362
client = FakeClient(transport.base)
1363
client.add_expected_call(
1364
'Branch.get_stacked_on_url', ('branch/',),
1365
'error', ('NotStacked',))
1366
client.add_expected_call(
1367
'Branch.lock_write', ('branch/', '', ''),
1368
'success', ('ok', 'branch token', 'repo token'))
1369
client.add_expected_call(
1370
'Branch.last_revision_info',
1372
'success', ('ok', '0', 'null:'))
1373
# get_graph calls to construct the revision history, for the set_rh
1376
encoded_body = bz2.compress('\n'.join(lines))
1377
client.add_success_response_with_body(encoded_body, 'ok')
1378
client.add_expected_call(
1379
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1380
'error', ('NoSuchRevision', 'rev-id'))
1381
client.add_expected_call(
1382
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1385
branch = self.make_remote_branch(transport, client)
1388
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1390
self.assertFinished(client)
1392
def test_tip_change_rejected(self):
1393
"""TipChangeRejected responses cause a TipChangeRejected exception to
1396
transport = MemoryTransport()
1397
transport.mkdir('branch')
1398
transport = transport.clone('branch')
1399
client = FakeClient(transport.base)
1400
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1401
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1402
client.add_expected_call(
1403
'Branch.get_stacked_on_url', ('branch/',),
1404
'error', ('NotStacked',))
1405
client.add_expected_call(
1406
'Branch.lock_write', ('branch/', '', ''),
1407
'success', ('ok', 'branch token', 'repo token'))
1408
client.add_expected_call(
1409
'Branch.last_revision_info',
1411
'success', ('ok', '0', 'null:'))
1413
encoded_body = bz2.compress('\n'.join(lines))
1414
client.add_success_response_with_body(encoded_body, 'ok')
1415
client.add_expected_call(
1416
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1417
'error', ('TipChangeRejected', rejection_msg_utf8))
1418
client.add_expected_call(
1419
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1421
branch = self.make_remote_branch(transport, client)
1422
branch._ensure_real = lambda: None
1424
# The 'TipChangeRejected' error response triggered by calling
1425
# set_revision_history causes a TipChangeRejected exception.
1426
err = self.assertRaises(
1427
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1428
# The UTF-8 message from the response has been decoded into a unicode
1430
self.assertIsInstance(err.msg, unicode)
1431
self.assertEqual(rejection_msg_unicode, err.msg)
1433
self.assertFinished(client)
1436
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1438
def test_set_last_revision_info(self):
1439
# set_last_revision_info(num, 'rev-id') is translated to calling
1440
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1441
transport = MemoryTransport()
1442
transport.mkdir('branch')
1443
transport = transport.clone('branch')
1444
client = FakeClient(transport.base)
1445
# get_stacked_on_url
1446
client.add_error_response('NotStacked')
1448
client.add_success_response('ok', 'branch token', 'repo token')
1449
# query the current revision
1450
client.add_success_response('ok', '0', 'null:')
1452
client.add_success_response('ok')
1454
client.add_success_response('ok')
1456
branch = self.make_remote_branch(transport, client)
1457
# Lock the branch, reset the record of remote calls.
1460
result = branch.set_last_revision_info(1234, 'a-revision-id')
1462
[('call', 'Branch.last_revision_info', ('branch/',)),
1463
('call', 'Branch.set_last_revision_info',
1464
('branch/', 'branch token', 'repo token',
1465
'1234', 'a-revision-id'))],
1467
self.assertEqual(None, result)
1469
def test_no_such_revision(self):
1470
# A response of 'NoSuchRevision' is translated into an exception.
1471
transport = MemoryTransport()
1472
transport.mkdir('branch')
1473
transport = transport.clone('branch')
1474
client = FakeClient(transport.base)
1475
# get_stacked_on_url
1476
client.add_error_response('NotStacked')
1478
client.add_success_response('ok', 'branch token', 'repo token')
1480
client.add_error_response('NoSuchRevision', 'revid')
1482
client.add_success_response('ok')
1484
branch = self.make_remote_branch(transport, client)
1485
# Lock the branch, reset the record of remote calls.
1490
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1493
def test_backwards_compatibility(self):
1494
"""If the server does not support the Branch.set_last_revision_info
1495
verb (which is new in 1.4), then the client falls back to VFS methods.
1497
# This test is a little messy. Unlike most tests in this file, it
1498
# doesn't purely test what a Remote* object sends over the wire, and
1499
# how it reacts to responses from the wire. It instead relies partly
1500
# on asserting that the RemoteBranch will call
1501
# self._real_branch.set_last_revision_info(...).
1503
# First, set up our RemoteBranch with a FakeClient that raises
1504
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1505
transport = MemoryTransport()
1506
transport.mkdir('branch')
1507
transport = transport.clone('branch')
1508
client = FakeClient(transport.base)
1509
client.add_expected_call(
1510
'Branch.get_stacked_on_url', ('branch/',),
1511
'error', ('NotStacked',))
1512
client.add_expected_call(
1513
'Branch.last_revision_info',
1515
'success', ('ok', '0', 'null:'))
1516
client.add_expected_call(
1517
'Branch.set_last_revision_info',
1518
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1519
'unknown', 'Branch.set_last_revision_info')
1521
branch = self.make_remote_branch(transport, client)
1522
class StubRealBranch(object):
1525
def set_last_revision_info(self, revno, revision_id):
1527
('set_last_revision_info', revno, revision_id))
1528
def _clear_cached_state(self):
1530
real_branch = StubRealBranch()
1531
branch._real_branch = real_branch
1532
self.lock_remote_branch(branch)
1534
# Call set_last_revision_info, and verify it behaved as expected.
1535
result = branch.set_last_revision_info(1234, 'a-revision-id')
1537
[('set_last_revision_info', 1234, 'a-revision-id')],
1539
self.assertFinished(client)
1541
def test_unexpected_error(self):
1542
# If the server sends an error the client doesn't understand, it gets
1543
# turned into an UnknownErrorFromSmartServer, which is presented as a
1544
# non-internal error to the user.
1545
transport = MemoryTransport()
1546
transport.mkdir('branch')
1547
transport = transport.clone('branch')
1548
client = FakeClient(transport.base)
1549
# get_stacked_on_url
1550
client.add_error_response('NotStacked')
1552
client.add_success_response('ok', 'branch token', 'repo token')
1554
client.add_error_response('UnexpectedError')
1556
client.add_success_response('ok')
1558
branch = self.make_remote_branch(transport, client)
1559
# Lock the branch, reset the record of remote calls.
1563
err = self.assertRaises(
1564
errors.UnknownErrorFromSmartServer,
1565
branch.set_last_revision_info, 123, 'revid')
1566
self.assertEqual(('UnexpectedError',), err.error_tuple)
1569
def test_tip_change_rejected(self):
1570
"""TipChangeRejected responses cause a TipChangeRejected exception to
1573
transport = MemoryTransport()
1574
transport.mkdir('branch')
1575
transport = transport.clone('branch')
1576
client = FakeClient(transport.base)
1577
# get_stacked_on_url
1578
client.add_error_response('NotStacked')
1580
client.add_success_response('ok', 'branch token', 'repo token')
1582
client.add_error_response('TipChangeRejected', 'rejection message')
1584
client.add_success_response('ok')
1586
branch = self.make_remote_branch(transport, client)
1587
# Lock the branch, reset the record of remote calls.
1589
self.addCleanup(branch.unlock)
1592
# The 'TipChangeRejected' error response triggered by calling
1593
# set_last_revision_info causes a TipChangeRejected exception.
1594
err = self.assertRaises(
1595
errors.TipChangeRejected,
1596
branch.set_last_revision_info, 123, 'revid')
1597
self.assertEqual('rejection message', err.msg)
1600
class TestBranchGetSetConfig(RemoteBranchTestCase):
1602
def test_get_branch_conf(self):
1603
# in an empty branch we decode the response properly
1604
client = FakeClient()
1605
client.add_expected_call(
1606
'Branch.get_stacked_on_url', ('memory:///',),
1607
'error', ('NotStacked',),)
1608
client.add_success_response_with_body('# config file body', 'ok')
1609
transport = MemoryTransport()
1610
branch = self.make_remote_branch(transport, client)
1611
config = branch.get_config()
1612
config.has_explicit_nickname()
1614
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1615
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1618
def test_get_multi_line_branch_conf(self):
1619
# Make sure that multiple-line branch.conf files are supported
1621
# https://bugs.launchpad.net/bzr/+bug/354075
1622
client = FakeClient()
1623
client.add_expected_call(
1624
'Branch.get_stacked_on_url', ('memory:///',),
1625
'error', ('NotStacked',),)
1626
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1627
transport = MemoryTransport()
1628
branch = self.make_remote_branch(transport, client)
1629
config = branch.get_config()
1630
self.assertEqual(u'2', config.get_user_option('b'))
1632
def test_set_option(self):
1633
client = FakeClient()
1634
client.add_expected_call(
1635
'Branch.get_stacked_on_url', ('memory:///',),
1636
'error', ('NotStacked',),)
1637
client.add_expected_call(
1638
'Branch.lock_write', ('memory:///', '', ''),
1639
'success', ('ok', 'branch token', 'repo token'))
1640
client.add_expected_call(
1641
'Branch.set_config_option', ('memory:///', 'branch token',
1642
'repo token', 'foo', 'bar', ''),
1644
client.add_expected_call(
1645
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1647
transport = MemoryTransport()
1648
branch = self.make_remote_branch(transport, client)
1650
config = branch._get_config()
1651
config.set_option('foo', 'bar')
1653
self.assertFinished(client)
1655
def test_set_option_with_dict(self):
1656
client = FakeClient()
1657
client.add_expected_call(
1658
'Branch.get_stacked_on_url', ('memory:///',),
1659
'error', ('NotStacked',),)
1660
client.add_expected_call(
1661
'Branch.lock_write', ('memory:///', '', ''),
1662
'success', ('ok', 'branch token', 'repo token'))
1663
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1664
client.add_expected_call(
1665
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1666
'repo token', encoded_dict_value, 'foo', ''),
1668
client.add_expected_call(
1669
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1671
transport = MemoryTransport()
1672
branch = self.make_remote_branch(transport, client)
1674
config = branch._get_config()
1676
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1679
self.assertFinished(client)
1681
def test_backwards_compat_set_option(self):
1682
self.setup_smart_server_with_call_log()
1683
branch = self.make_branch('.')
1684
verb = 'Branch.set_config_option'
1685
self.disable_verb(verb)
1687
self.addCleanup(branch.unlock)
1688
self.reset_smart_call_log()
1689
branch._get_config().set_option('value', 'name')
1690
self.assertLength(10, self.hpss_calls)
1691
self.assertEqual('value', branch._get_config().get_option('name'))
1693
def test_backwards_compat_set_option_with_dict(self):
1694
self.setup_smart_server_with_call_log()
1695
branch = self.make_branch('.')
1696
verb = 'Branch.set_config_option_dict'
1697
self.disable_verb(verb)
1699
self.addCleanup(branch.unlock)
1700
self.reset_smart_call_log()
1701
config = branch._get_config()
1702
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
1703
config.set_option(value_dict, 'name')
1704
self.assertLength(10, self.hpss_calls)
1705
self.assertEqual(value_dict, branch._get_config().get_option('name'))
1708
class TestBranchLockWrite(RemoteBranchTestCase):
1710
def test_lock_write_unlockable(self):
1711
transport = MemoryTransport()
1712
client = FakeClient(transport.base)
1713
client.add_expected_call(
1714
'Branch.get_stacked_on_url', ('quack/',),
1715
'error', ('NotStacked',),)
1716
client.add_expected_call(
1717
'Branch.lock_write', ('quack/', '', ''),
1718
'error', ('UnlockableTransport',))
1719
transport.mkdir('quack')
1720
transport = transport.clone('quack')
1721
branch = self.make_remote_branch(transport, client)
1722
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1723
self.assertFinished(client)
1726
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1728
def test__get_config(self):
1729
client = FakeClient()
1730
client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1731
transport = MemoryTransport()
1732
bzrdir = self.make_remote_bzrdir(transport, client)
1733
config = bzrdir.get_config()
1734
self.assertEqual('/', config.get_default_stack_on())
1736
[('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1739
def test_set_option_uses_vfs(self):
1740
self.setup_smart_server_with_call_log()
1741
bzrdir = self.make_bzrdir('.')
1742
self.reset_smart_call_log()
1743
config = bzrdir.get_config()
1744
config.set_default_stack_on('/')
1745
self.assertLength(3, self.hpss_calls)
1747
def test_backwards_compat_get_option(self):
1748
self.setup_smart_server_with_call_log()
1749
bzrdir = self.make_bzrdir('.')
1750
verb = 'BzrDir.get_config_file'
1751
self.disable_verb(verb)
1752
self.reset_smart_call_log()
1753
self.assertEqual(None,
1754
bzrdir._get_config().get_option('default_stack_on'))
1755
self.assertLength(3, self.hpss_calls)
1758
class TestTransportIsReadonly(tests.TestCase):
1760
def test_true(self):
1761
client = FakeClient()
1762
client.add_success_response('yes')
1763
transport = RemoteTransport('bzr://example.com/', medium=False,
1765
self.assertEqual(True, transport.is_readonly())
1767
[('call', 'Transport.is_readonly', ())],
1770
def test_false(self):
1771
client = FakeClient()
1772
client.add_success_response('no')
1773
transport = RemoteTransport('bzr://example.com/', medium=False,
1775
self.assertEqual(False, transport.is_readonly())
1777
[('call', 'Transport.is_readonly', ())],
1780
def test_error_from_old_server(self):
1781
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1783
Clients should treat it as a "no" response, because is_readonly is only
1784
advisory anyway (a transport could be read-write, but then the
1785
underlying filesystem could be readonly anyway).
1787
client = FakeClient()
1788
client.add_unknown_method_response('Transport.is_readonly')
1789
transport = RemoteTransport('bzr://example.com/', medium=False,
1791
self.assertEqual(False, transport.is_readonly())
1793
[('call', 'Transport.is_readonly', ())],
1797
class TestTransportMkdir(tests.TestCase):
1799
def test_permissiondenied(self):
1800
client = FakeClient()
1801
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1802
transport = RemoteTransport('bzr://example.com/', medium=False,
1804
exc = self.assertRaises(
1805
errors.PermissionDenied, transport.mkdir, 'client path')
1806
expected_error = errors.PermissionDenied('/client path', 'extra')
1807
self.assertEqual(expected_error, exc)
1810
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1812
def test_defaults_to_none(self):
1813
t = RemoteSSHTransport('bzr+ssh://example.com')
1814
self.assertIs(None, t._get_credentials()[0])
1816
def test_uses_authentication_config(self):
1817
conf = config.AuthenticationConfig()
1818
conf._get_config().update(
1819
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1822
t = RemoteSSHTransport('bzr+ssh://example.com')
1823
self.assertEqual('bar', t._get_credentials()[0])
1826
class TestRemoteRepository(TestRemote):
1827
"""Base for testing RemoteRepository protocol usage.
1829
These tests contain frozen requests and responses. We want any changes to
1830
what is sent or expected to be require a thoughtful update to these tests
1831
because they might break compatibility with different-versioned servers.
1834
def setup_fake_client_and_repository(self, transport_path):
1835
"""Create the fake client and repository for testing with.
1837
There's no real server here; we just have canned responses sent
1840
:param transport_path: Path below the root of the MemoryTransport
1841
where the repository will be created.
1843
transport = MemoryTransport()
1844
transport.mkdir(transport_path)
1845
client = FakeClient(transport.base)
1846
transport = transport.clone(transport_path)
1847
# we do not want bzrdir to make any remote calls
1848
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1850
repo = RemoteRepository(bzrdir, None, _client=client)
1854
def remoted_description(format):
1855
return 'Remote: ' + format.get_format_description()
1858
class TestBranchFormat(tests.TestCase):
1860
def test_get_format_description(self):
1861
remote_format = RemoteBranchFormat()
1862
real_format = branch.BranchFormat.get_default_format()
1863
remote_format._network_name = real_format.network_name()
1864
self.assertEqual(remoted_description(real_format),
1865
remote_format.get_format_description())
1868
class TestRepositoryFormat(TestRemoteRepository):
1870
def test_fast_delta(self):
1871
true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1872
true_format = RemoteRepositoryFormat()
1873
true_format._network_name = true_name
1874
self.assertEqual(True, true_format.fast_deltas)
1875
false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1876
false_format = RemoteRepositoryFormat()
1877
false_format._network_name = false_name
1878
self.assertEqual(False, false_format.fast_deltas)
1880
def test_get_format_description(self):
1881
remote_repo_format = RemoteRepositoryFormat()
1882
real_format = repository.RepositoryFormat.get_default_format()
1883
remote_repo_format._network_name = real_format.network_name()
1884
self.assertEqual(remoted_description(real_format),
1885
remote_repo_format.get_format_description())
1888
class TestRepositoryGatherStats(TestRemoteRepository):
1890
def test_revid_none(self):
1891
# ('ok',), body with revisions and size
1892
transport_path = 'quack'
1893
repo, client = self.setup_fake_client_and_repository(transport_path)
1894
client.add_success_response_with_body(
1895
'revisions: 2\nsize: 18\n', 'ok')
1896
result = repo.gather_stats(None)
1898
[('call_expecting_body', 'Repository.gather_stats',
1899
('quack/','','no'))],
1901
self.assertEqual({'revisions': 2, 'size': 18}, result)
1903
def test_revid_no_committers(self):
1904
# ('ok',), body without committers
1905
body = ('firstrev: 123456.300 3600\n'
1906
'latestrev: 654231.400 0\n'
1909
transport_path = 'quick'
1910
revid = u'\xc8'.encode('utf8')
1911
repo, client = self.setup_fake_client_and_repository(transport_path)
1912
client.add_success_response_with_body(body, 'ok')
1913
result = repo.gather_stats(revid)
1915
[('call_expecting_body', 'Repository.gather_stats',
1916
('quick/', revid, 'no'))],
1918
self.assertEqual({'revisions': 2, 'size': 18,
1919
'firstrev': (123456.300, 3600),
1920
'latestrev': (654231.400, 0),},
1923
def test_revid_with_committers(self):
1924
# ('ok',), body with committers
1925
body = ('committers: 128\n'
1926
'firstrev: 123456.300 3600\n'
1927
'latestrev: 654231.400 0\n'
1930
transport_path = 'buick'
1931
revid = u'\xc8'.encode('utf8')
1932
repo, client = self.setup_fake_client_and_repository(transport_path)
1933
client.add_success_response_with_body(body, 'ok')
1934
result = repo.gather_stats(revid, True)
1936
[('call_expecting_body', 'Repository.gather_stats',
1937
('buick/', revid, 'yes'))],
1939
self.assertEqual({'revisions': 2, 'size': 18,
1941
'firstrev': (123456.300, 3600),
1942
'latestrev': (654231.400, 0),},
1946
class TestRepositoryGetGraph(TestRemoteRepository):
1948
def test_get_graph(self):
1949
# get_graph returns a graph with a custom parents provider.
1950
transport_path = 'quack'
1951
repo, client = self.setup_fake_client_and_repository(transport_path)
1952
graph = repo.get_graph()
1953
self.assertNotEqual(graph._parents_provider, repo)
1956
class TestRepositoryGetParentMap(TestRemoteRepository):
1958
def test_get_parent_map_caching(self):
1959
# get_parent_map returns from cache until unlock()
1960
# setup a reponse with two revisions
1961
r1 = u'\u0e33'.encode('utf8')
1962
r2 = u'\u0dab'.encode('utf8')
1963
lines = [' '.join([r2, r1]), r1]
1964
encoded_body = bz2.compress('\n'.join(lines))
1966
transport_path = 'quack'
1967
repo, client = self.setup_fake_client_and_repository(transport_path)
1968
client.add_success_response_with_body(encoded_body, 'ok')
1969
client.add_success_response_with_body(encoded_body, 'ok')
1971
graph = repo.get_graph()
1972
parents = graph.get_parent_map([r2])
1973
self.assertEqual({r2: (r1,)}, parents)
1974
# locking and unlocking deeper should not reset
1977
parents = graph.get_parent_map([r1])
1978
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1980
[('call_with_body_bytes_expecting_body',
1981
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1985
# now we call again, and it should use the second response.
1987
graph = repo.get_graph()
1988
parents = graph.get_parent_map([r1])
1989
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1991
[('call_with_body_bytes_expecting_body',
1992
'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1994
('call_with_body_bytes_expecting_body',
1995
'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
2001
def test_get_parent_map_reconnects_if_unknown_method(self):
2002
transport_path = 'quack'
2003
rev_id = 'revision-id'
2004
repo, client = self.setup_fake_client_and_repository(transport_path)
2005
client.add_unknown_method_response('Repository.get_parent_map')
2006
client.add_success_response_with_body(rev_id, 'ok')
2007
self.assertFalse(client._medium._is_remote_before((1, 2)))
2008
parents = repo.get_parent_map([rev_id])
2010
[('call_with_body_bytes_expecting_body',
2011
'Repository.get_parent_map', ('quack/', 'include-missing:',
2013
('disconnect medium',),
2014
('call_expecting_body', 'Repository.get_revision_graph',
2017
# The medium is now marked as being connected to an older server
2018
self.assertTrue(client._medium._is_remote_before((1, 2)))
2019
self.assertEqual({rev_id: ('null:',)}, parents)
2021
def test_get_parent_map_fallback_parentless_node(self):
2022
"""get_parent_map falls back to get_revision_graph on old servers. The
2023
results from get_revision_graph are tweaked to match the get_parent_map
2026
Specifically, a {key: ()} result from get_revision_graph means "no
2027
parents" for that key, which in get_parent_map results should be
2028
represented as {key: ('null:',)}.
2030
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
2032
rev_id = 'revision-id'
2033
transport_path = 'quack'
2034
repo, client = self.setup_fake_client_and_repository(transport_path)
2035
client.add_success_response_with_body(rev_id, 'ok')
2036
client._medium._remember_remote_is_before((1, 2))
2037
parents = repo.get_parent_map([rev_id])
2039
[('call_expecting_body', 'Repository.get_revision_graph',
2042
self.assertEqual({rev_id: ('null:',)}, parents)
2044
def test_get_parent_map_unexpected_response(self):
2045
repo, client = self.setup_fake_client_and_repository('path')
2046
client.add_success_response('something unexpected!')
2048
errors.UnexpectedSmartServerResponse,
2049
repo.get_parent_map, ['a-revision-id'])
2051
def test_get_parent_map_negative_caches_missing_keys(self):
2052
self.setup_smart_server_with_call_log()
2053
repo = self.make_repository('foo')
2054
self.assertIsInstance(repo, RemoteRepository)
2056
self.addCleanup(repo.unlock)
2057
self.reset_smart_call_log()
2058
graph = repo.get_graph()
2059
self.assertEqual({},
2060
graph.get_parent_map(['some-missing', 'other-missing']))
2061
self.assertLength(1, self.hpss_calls)
2062
# No call if we repeat this
2063
self.reset_smart_call_log()
2064
graph = repo.get_graph()
2065
self.assertEqual({},
2066
graph.get_parent_map(['some-missing', 'other-missing']))
2067
self.assertLength(0, self.hpss_calls)
2068
# Asking for more unknown keys makes a request.
2069
self.reset_smart_call_log()
2070
graph = repo.get_graph()
2071
self.assertEqual({},
2072
graph.get_parent_map(['some-missing', 'other-missing',
2074
self.assertLength(1, self.hpss_calls)
2076
def disableExtraResults(self):
2077
self.overrideAttr(SmartServerRepositoryGetParentMap,
2078
'no_extra_results', True)
2080
def test_null_cached_missing_and_stop_key(self):
2081
self.setup_smart_server_with_call_log()
2082
# Make a branch with a single revision.
2083
builder = self.make_branch_builder('foo')
2084
builder.start_series()
2085
builder.build_snapshot('first', None, [
2086
('add', ('', 'root-id', 'directory', ''))])
2087
builder.finish_series()
2088
branch = builder.get_branch()
2089
repo = branch.repository
2090
self.assertIsInstance(repo, RemoteRepository)
2091
# Stop the server from sending extra results.
2092
self.disableExtraResults()
2094
self.addCleanup(repo.unlock)
2095
self.reset_smart_call_log()
2096
graph = repo.get_graph()
2097
# Query for 'first' and 'null:'. Because 'null:' is a parent of
2098
# 'first' it will be a candidate for the stop_keys of subsequent
2099
# requests, and because 'null:' was queried but not returned it will be
2100
# cached as missing.
2101
self.assertEqual({'first': ('null:',)},
2102
graph.get_parent_map(['first', 'null:']))
2103
# Now query for another key. This request will pass along a recipe of
2104
# start and stop keys describing the already cached results, and this
2105
# recipe's revision count must be correct (or else it will trigger an
2106
# error from the server).
2107
self.assertEqual({}, graph.get_parent_map(['another-key']))
2108
# This assertion guards against disableExtraResults silently failing to
2109
# work, thus invalidating the test.
2110
self.assertLength(2, self.hpss_calls)
2112
def test_get_parent_map_gets_ghosts_from_result(self):
2113
# asking for a revision should negatively cache close ghosts in its
2115
self.setup_smart_server_with_call_log()
2116
tree = self.make_branch_and_memory_tree('foo')
2119
builder = treebuilder.TreeBuilder()
2120
builder.start_tree(tree)
2122
builder.finish_tree()
2123
tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
2124
rev_id = tree.commit('')
2128
self.addCleanup(tree.unlock)
2129
repo = tree.branch.repository
2130
self.assertIsInstance(repo, RemoteRepository)
2132
repo.get_parent_map([rev_id])
2133
self.reset_smart_call_log()
2134
# Now asking for rev_id's ghost parent should not make calls
2135
self.assertEqual({}, repo.get_parent_map(['non-existant']))
2136
self.assertLength(0, self.hpss_calls)
2139
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
2141
def test_allows_new_revisions(self):
2142
"""get_parent_map's results can be updated by commit."""
2143
smart_server = test_server.SmartTCPServer_for_testing()
2144
self.start_server(smart_server)
2145
self.make_branch('branch')
2146
branch = Branch.open(smart_server.get_url() + '/branch')
2147
tree = branch.create_checkout('tree', lightweight=True)
2149
self.addCleanup(tree.unlock)
2150
graph = tree.branch.repository.get_graph()
2151
# This provides an opportunity for the missing rev-id to be cached.
2152
self.assertEqual({}, graph.get_parent_map(['rev1']))
2153
tree.commit('message', rev_id='rev1')
2154
graph = tree.branch.repository.get_graph()
2155
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
2158
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
2160
def test_null_revision(self):
2161
# a null revision has the predictable result {}, we should have no wire
2162
# traffic when calling it with this argument
2163
transport_path = 'empty'
2164
repo, client = self.setup_fake_client_and_repository(transport_path)
2165
client.add_success_response('notused')
2166
# actual RemoteRepository.get_revision_graph is gone, but there's an
2167
# equivalent private method for testing
2168
result = repo._get_revision_graph(NULL_REVISION)
2169
self.assertEqual([], client._calls)
2170
self.assertEqual({}, result)
2172
def test_none_revision(self):
2173
# with none we want the entire graph
2174
r1 = u'\u0e33'.encode('utf8')
2175
r2 = u'\u0dab'.encode('utf8')
2176
lines = [' '.join([r2, r1]), r1]
2177
encoded_body = '\n'.join(lines)
2179
transport_path = 'sinhala'
2180
repo, client = self.setup_fake_client_and_repository(transport_path)
2181
client.add_success_response_with_body(encoded_body, 'ok')
2182
# actual RemoteRepository.get_revision_graph is gone, but there's an
2183
# equivalent private method for testing
2184
result = repo._get_revision_graph(None)
2186
[('call_expecting_body', 'Repository.get_revision_graph',
2189
self.assertEqual({r1: (), r2: (r1, )}, result)
2191
def test_specific_revision(self):
2192
# with a specific revision we want the graph for that
2193
# with none we want the entire graph
2194
r11 = u'\u0e33'.encode('utf8')
2195
r12 = u'\xc9'.encode('utf8')
2196
r2 = u'\u0dab'.encode('utf8')
2197
lines = [' '.join([r2, r11, r12]), r11, r12]
2198
encoded_body = '\n'.join(lines)
2200
transport_path = 'sinhala'
2201
repo, client = self.setup_fake_client_and_repository(transport_path)
2202
client.add_success_response_with_body(encoded_body, 'ok')
2203
result = repo._get_revision_graph(r2)
2205
[('call_expecting_body', 'Repository.get_revision_graph',
2208
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
2210
def test_no_such_revision(self):
2212
transport_path = 'sinhala'
2213
repo, client = self.setup_fake_client_and_repository(transport_path)
2214
client.add_error_response('nosuchrevision', revid)
2215
# also check that the right revision is reported in the error
2216
self.assertRaises(errors.NoSuchRevision,
2217
repo._get_revision_graph, revid)
2219
[('call_expecting_body', 'Repository.get_revision_graph',
2220
('sinhala/', revid))],
2223
def test_unexpected_error(self):
2225
transport_path = 'sinhala'
2226
repo, client = self.setup_fake_client_and_repository(transport_path)
2227
client.add_error_response('AnUnexpectedError')
2228
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
2229
repo._get_revision_graph, revid)
2230
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2233
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2236
repo, client = self.setup_fake_client_and_repository('quack')
2237
client.add_expected_call(
2238
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2239
'success', ('ok', 'rev-five'))
2240
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2241
self.assertEqual((True, 'rev-five'), result)
2242
self.assertFinished(client)
2244
def test_history_incomplete(self):
2245
repo, client = self.setup_fake_client_and_repository('quack')
2246
client.add_expected_call(
2247
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2248
'success', ('history-incomplete', 10, 'rev-ten'))
2249
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2250
self.assertEqual((False, (10, 'rev-ten')), result)
2251
self.assertFinished(client)
2253
def test_history_incomplete_with_fallback(self):
2254
"""A 'history-incomplete' response causes the fallback repository to be
2255
queried too, if one is set.
2257
# Make a repo with a fallback repo, both using a FakeClient.
2258
format = remote.response_tuple_to_repo_format(
2259
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2260
repo, client = self.setup_fake_client_and_repository('quack')
2261
repo._format = format
2262
fallback_repo, ignored = self.setup_fake_client_and_repository(
2264
fallback_repo._client = client
2265
fallback_repo._format = format
2266
repo.add_fallback_repository(fallback_repo)
2267
# First the client should ask the primary repo
2268
client.add_expected_call(
2269
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2270
'success', ('history-incomplete', 2, 'rev-two'))
2271
# Then it should ask the fallback, using revno/revid from the
2272
# history-incomplete response as the known revno/revid.
2273
client.add_expected_call(
2274
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2275
'success', ('ok', 'rev-one'))
2276
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2277
self.assertEqual((True, 'rev-one'), result)
2278
self.assertFinished(client)
2280
def test_nosuchrevision(self):
2281
# 'nosuchrevision' is returned when the known-revid is not found in the
2282
# remote repo. The client translates that response to NoSuchRevision.
2283
repo, client = self.setup_fake_client_and_repository('quack')
2284
client.add_expected_call(
2285
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2286
'error', ('nosuchrevision', 'rev-foo'))
2288
errors.NoSuchRevision,
2289
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2290
self.assertFinished(client)
2292
def test_branch_fallback_locking(self):
2293
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2294
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2295
will be invoked, which will fail if the repo is unlocked.
2297
self.setup_smart_server_with_call_log()
2298
tree = self.make_branch_and_memory_tree('.')
2301
rev1 = tree.commit('First')
2302
rev2 = tree.commit('Second')
2304
branch = tree.branch
2305
self.assertFalse(branch.is_locked())
2306
self.reset_smart_call_log()
2307
verb = 'Repository.get_rev_id_for_revno'
2308
self.disable_verb(verb)
2309
self.assertEqual(rev1, branch.get_rev_id(1))
2310
self.assertLength(1, [call for call in self.hpss_calls if
2311
call.call.method == verb])
2314
class TestRepositoryIsShared(TestRemoteRepository):
2316
def test_is_shared(self):
2317
# ('yes', ) for Repository.is_shared -> 'True'.
2318
transport_path = 'quack'
2319
repo, client = self.setup_fake_client_and_repository(transport_path)
2320
client.add_success_response('yes')
2321
result = repo.is_shared()
2323
[('call', 'Repository.is_shared', ('quack/',))],
2325
self.assertEqual(True, result)
2327
def test_is_not_shared(self):
2328
# ('no', ) for Repository.is_shared -> 'False'.
2329
transport_path = 'qwack'
2330
repo, client = self.setup_fake_client_and_repository(transport_path)
2331
client.add_success_response('no')
2332
result = repo.is_shared()
2334
[('call', 'Repository.is_shared', ('qwack/',))],
2336
self.assertEqual(False, result)
2339
class TestRepositoryLockWrite(TestRemoteRepository):
2341
def test_lock_write(self):
2342
transport_path = 'quack'
2343
repo, client = self.setup_fake_client_and_repository(transport_path)
2344
client.add_success_response('ok', 'a token')
2345
token = repo.lock_write().repository_token
2347
[('call', 'Repository.lock_write', ('quack/', ''))],
2349
self.assertEqual('a token', token)
2351
def test_lock_write_already_locked(self):
2352
transport_path = 'quack'
2353
repo, client = self.setup_fake_client_and_repository(transport_path)
2354
client.add_error_response('LockContention')
2355
self.assertRaises(errors.LockContention, repo.lock_write)
2357
[('call', 'Repository.lock_write', ('quack/', ''))],
2360
def test_lock_write_unlockable(self):
2361
transport_path = 'quack'
2362
repo, client = self.setup_fake_client_and_repository(transport_path)
2363
client.add_error_response('UnlockableTransport')
2364
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2366
[('call', 'Repository.lock_write', ('quack/', ''))],
2370
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2372
def test_backwards_compat(self):
2373
self.setup_smart_server_with_call_log()
2374
repo = self.make_repository('.')
2375
self.reset_smart_call_log()
2376
verb = 'Repository.set_make_working_trees'
2377
self.disable_verb(verb)
2378
repo.set_make_working_trees(True)
2379
call_count = len([call for call in self.hpss_calls if
2380
call.call.method == verb])
2381
self.assertEqual(1, call_count)
2383
def test_current(self):
2384
transport_path = 'quack'
2385
repo, client = self.setup_fake_client_and_repository(transport_path)
2386
client.add_expected_call(
2387
'Repository.set_make_working_trees', ('quack/', 'True'),
2389
client.add_expected_call(
2390
'Repository.set_make_working_trees', ('quack/', 'False'),
2392
repo.set_make_working_trees(True)
2393
repo.set_make_working_trees(False)
2396
class TestRepositoryUnlock(TestRemoteRepository):
2398
def test_unlock(self):
2399
transport_path = 'quack'
2400
repo, client = self.setup_fake_client_and_repository(transport_path)
2401
client.add_success_response('ok', 'a token')
2402
client.add_success_response('ok')
2406
[('call', 'Repository.lock_write', ('quack/', '')),
2407
('call', 'Repository.unlock', ('quack/', 'a token'))],
2410
def test_unlock_wrong_token(self):
2411
# If somehow the token is wrong, unlock will raise TokenMismatch.
2412
transport_path = 'quack'
2413
repo, client = self.setup_fake_client_and_repository(transport_path)
2414
client.add_success_response('ok', 'a token')
2415
client.add_error_response('TokenMismatch')
2417
self.assertRaises(errors.TokenMismatch, repo.unlock)
2420
class TestRepositoryHasRevision(TestRemoteRepository):
2422
def test_none(self):
2423
# repo.has_revision(None) should not cause any traffic.
2424
transport_path = 'quack'
2425
repo, client = self.setup_fake_client_and_repository(transport_path)
2427
# The null revision is always there, so has_revision(None) == True.
2428
self.assertEqual(True, repo.has_revision(NULL_REVISION))
2430
# The remote repo shouldn't be accessed.
2431
self.assertEqual([], client._calls)
2434
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2435
"""Base class for Repository.insert_stream and .insert_stream_1.19
2439
def checkInsertEmptyStream(self, repo, client):
2440
"""Insert an empty stream, checking the result.
2442
This checks that there are no resume_tokens or missing_keys, and that
2443
the client is finished.
2445
sink = repo._get_sink()
2446
fmt = repository.RepositoryFormat.get_default_format()
2447
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2448
self.assertEqual([], resume_tokens)
2449
self.assertEqual(set(), missing_keys)
2450
self.assertFinished(client)
2453
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2454
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2457
This test case is very similar to TestRepositoryInsertStream_1_19.
2461
TestRemoteRepository.setUp(self)
2462
self.disable_verb('Repository.insert_stream_1.19')
2464
def test_unlocked_repo(self):
2465
transport_path = 'quack'
2466
repo, client = self.setup_fake_client_and_repository(transport_path)
2467
client.add_expected_call(
2468
'Repository.insert_stream_1.19', ('quack/', ''),
2469
'unknown', ('Repository.insert_stream_1.19',))
2470
client.add_expected_call(
2471
'Repository.insert_stream', ('quack/', ''),
2473
client.add_expected_call(
2474
'Repository.insert_stream', ('quack/', ''),
2476
self.checkInsertEmptyStream(repo, client)
2478
def test_locked_repo_with_no_lock_token(self):
2479
transport_path = 'quack'
2480
repo, client = self.setup_fake_client_and_repository(transport_path)
2481
client.add_expected_call(
2482
'Repository.lock_write', ('quack/', ''),
2483
'success', ('ok', ''))
2484
client.add_expected_call(
2485
'Repository.insert_stream_1.19', ('quack/', ''),
2486
'unknown', ('Repository.insert_stream_1.19',))
2487
client.add_expected_call(
2488
'Repository.insert_stream', ('quack/', ''),
2490
client.add_expected_call(
2491
'Repository.insert_stream', ('quack/', ''),
2494
self.checkInsertEmptyStream(repo, client)
2496
def test_locked_repo_with_lock_token(self):
2497
transport_path = 'quack'
2498
repo, client = self.setup_fake_client_and_repository(transport_path)
2499
client.add_expected_call(
2500
'Repository.lock_write', ('quack/', ''),
2501
'success', ('ok', 'a token'))
2502
client.add_expected_call(
2503
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2504
'unknown', ('Repository.insert_stream_1.19',))
2505
client.add_expected_call(
2506
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2508
client.add_expected_call(
2509
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2512
self.checkInsertEmptyStream(repo, client)
2514
def test_stream_with_inventory_deltas(self):
2515
"""'inventory-deltas' substreams cannot be sent to the
2516
Repository.insert_stream verb, because not all servers that implement
2517
that verb will accept them. So when one is encountered the RemoteSink
2518
immediately stops using that verb and falls back to VFS insert_stream.
2520
transport_path = 'quack'
2521
repo, client = self.setup_fake_client_and_repository(transport_path)
2522
client.add_expected_call(
2523
'Repository.insert_stream_1.19', ('quack/', ''),
2524
'unknown', ('Repository.insert_stream_1.19',))
2525
client.add_expected_call(
2526
'Repository.insert_stream', ('quack/', ''),
2528
client.add_expected_call(
2529
'Repository.insert_stream', ('quack/', ''),
2531
# Create a fake real repository for insert_stream to fall back on, so
2532
# that we can directly see the records the RemoteSink passes to the
2537
def insert_stream(self, stream, src_format, resume_tokens):
2538
for substream_kind, substream in stream:
2539
self.records.append(
2540
(substream_kind, [record.key for record in substream]))
2541
return ['fake tokens'], ['fake missing keys']
2542
fake_real_sink = FakeRealSink()
2543
class FakeRealRepository:
2544
def _get_sink(self):
2545
return fake_real_sink
2546
def is_in_write_group(self):
2548
def refresh_data(self):
2550
repo._real_repository = FakeRealRepository()
2551
sink = repo._get_sink()
2552
fmt = repository.RepositoryFormat.get_default_format()
2553
stream = self.make_stream_with_inv_deltas(fmt)
2554
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2555
# Every record from the first inventory delta should have been sent to
2557
expected_records = [
2558
('inventory-deltas', [('rev2',), ('rev3',)]),
2559
('texts', [('some-rev', 'some-file')])]
2560
self.assertEqual(expected_records, fake_real_sink.records)
2561
# The return values from the real sink's insert_stream are propagated
2562
# back to the original caller.
2563
self.assertEqual(['fake tokens'], resume_tokens)
2564
self.assertEqual(['fake missing keys'], missing_keys)
2565
self.assertFinished(client)
2567
def make_stream_with_inv_deltas(self, fmt):
2568
"""Make a simple stream with an inventory delta followed by more
2569
records and more substreams to test that all records and substreams
2570
from that point on are used.
2572
This sends, in order:
2573
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2575
* texts substream: (some-rev, some-file)
2577
# Define a stream using generators so that it isn't rewindable.
2578
inv = inventory.Inventory(revision_id='rev1')
2579
inv.root.revision = 'rev1'
2580
def stream_with_inv_delta():
2581
yield ('inventories', inventories_substream())
2582
yield ('inventory-deltas', inventory_delta_substream())
2584
versionedfile.FulltextContentFactory(
2585
('some-rev', 'some-file'), (), None, 'content')])
2586
def inventories_substream():
2587
# An empty inventory fulltext. This will be streamed normally.
2588
text = fmt._serializer.write_inventory_to_string(inv)
2589
yield versionedfile.FulltextContentFactory(
2590
('rev1',), (), None, text)
2591
def inventory_delta_substream():
2592
# An inventory delta. This can't be streamed via this verb, so it
2593
# will trigger a fallback to VFS insert_stream.
2594
entry = inv.make_entry(
2595
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2596
entry.revision = 'ghost'
2597
delta = [(None, 'newdir', 'newdir-id', entry)]
2598
serializer = inventory_delta.InventoryDeltaSerializer(
2599
versioned_root=True, tree_references=False)
2600
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2601
yield versionedfile.ChunkedContentFactory(
2602
('rev2',), (('rev1',)), None, lines)
2604
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2605
yield versionedfile.ChunkedContentFactory(
2606
('rev3',), (('rev1',)), None, lines)
2607
return stream_with_inv_delta()
2610
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2612
def test_unlocked_repo(self):
2613
transport_path = 'quack'
2614
repo, client = self.setup_fake_client_and_repository(transport_path)
2615
client.add_expected_call(
2616
'Repository.insert_stream_1.19', ('quack/', ''),
2618
client.add_expected_call(
2619
'Repository.insert_stream_1.19', ('quack/', ''),
2621
self.checkInsertEmptyStream(repo, client)
2623
def test_locked_repo_with_no_lock_token(self):
2624
transport_path = 'quack'
2625
repo, client = self.setup_fake_client_and_repository(transport_path)
2626
client.add_expected_call(
2627
'Repository.lock_write', ('quack/', ''),
2628
'success', ('ok', ''))
2629
client.add_expected_call(
2630
'Repository.insert_stream_1.19', ('quack/', ''),
2632
client.add_expected_call(
2633
'Repository.insert_stream_1.19', ('quack/', ''),
2636
self.checkInsertEmptyStream(repo, client)
2638
def test_locked_repo_with_lock_token(self):
2639
transport_path = 'quack'
2640
repo, client = self.setup_fake_client_and_repository(transport_path)
2641
client.add_expected_call(
2642
'Repository.lock_write', ('quack/', ''),
2643
'success', ('ok', 'a token'))
2644
client.add_expected_call(
2645
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2647
client.add_expected_call(
2648
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2651
self.checkInsertEmptyStream(repo, client)
2654
class TestRepositoryTarball(TestRemoteRepository):
2656
# This is a canned tarball reponse we can validate against
2658
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2659
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2660
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2661
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2662
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2663
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2664
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2665
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2666
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2667
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2668
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2669
'nWQ7QH/F3JFOFCQ0aSPfA='
2672
def test_repository_tarball(self):
2673
# Test that Repository.tarball generates the right operations
2674
transport_path = 'repo'
2675
expected_calls = [('call_expecting_body', 'Repository.tarball',
2676
('repo/', 'bz2',),),
2678
repo, client = self.setup_fake_client_and_repository(transport_path)
2679
client.add_success_response_with_body(self.tarball_content, 'ok')
2680
# Now actually ask for the tarball
2681
tarball_file = repo._get_tarball('bz2')
2683
self.assertEqual(expected_calls, client._calls)
2684
self.assertEqual(self.tarball_content, tarball_file.read())
2686
tarball_file.close()
2689
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2690
"""RemoteRepository.copy_content_into optimizations"""
2692
def test_copy_content_remote_to_local(self):
2693
self.transport_server = test_server.SmartTCPServer_for_testing
2694
src_repo = self.make_repository('repo1')
2695
src_repo = repository.Repository.open(self.get_url('repo1'))
2696
# At the moment the tarball-based copy_content_into can't write back
2697
# into a smart server. It would be good if it could upload the
2698
# tarball; once that works we'd have to create repositories of
2699
# different formats. -- mbp 20070410
2700
dest_url = self.get_vfs_only_url('repo2')
2701
dest_bzrdir = BzrDir.create(dest_url)
2702
dest_repo = dest_bzrdir.create_repository()
2703
self.assertFalse(isinstance(dest_repo, RemoteRepository))
2704
self.assertTrue(isinstance(src_repo, RemoteRepository))
2705
src_repo.copy_content_into(dest_repo)
2708
class _StubRealPackRepository(object):
2710
def __init__(self, calls):
2712
self._pack_collection = _StubPackCollection(calls)
2714
def is_in_write_group(self):
2717
def refresh_data(self):
2718
self.calls.append(('pack collection reload_pack_names',))
2721
class _StubPackCollection(object):
2723
def __init__(self, calls):
2727
self.calls.append(('pack collection autopack',))
2730
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2731
"""Tests for RemoteRepository.autopack implementation."""
2734
"""When the server returns 'ok' and there's no _real_repository, then
2735
nothing else happens: the autopack method is done.
2737
transport_path = 'quack'
2738
repo, client = self.setup_fake_client_and_repository(transport_path)
2739
client.add_expected_call(
2740
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2742
self.assertFinished(client)
2744
def test_ok_with_real_repo(self):
2745
"""When the server returns 'ok' and there is a _real_repository, then
2746
the _real_repository's reload_pack_name's method will be called.
2748
transport_path = 'quack'
2749
repo, client = self.setup_fake_client_and_repository(transport_path)
2750
client.add_expected_call(
2751
'PackRepository.autopack', ('quack/',),
2753
repo._real_repository = _StubRealPackRepository(client._calls)
2756
[('call', 'PackRepository.autopack', ('quack/',)),
2757
('pack collection reload_pack_names',)],
2760
def test_backwards_compatibility(self):
2761
"""If the server does not recognise the PackRepository.autopack verb,
2762
fallback to the real_repository's implementation.
2764
transport_path = 'quack'
2765
repo, client = self.setup_fake_client_and_repository(transport_path)
2766
client.add_unknown_method_response('PackRepository.autopack')
2767
def stub_ensure_real():
2768
client._calls.append(('_ensure_real',))
2769
repo._real_repository = _StubRealPackRepository(client._calls)
2770
repo._ensure_real = stub_ensure_real
2773
[('call', 'PackRepository.autopack', ('quack/',)),
2775
('pack collection autopack',)],
2779
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2780
"""Base class for unit tests for bzrlib.remote._translate_error."""
2782
def translateTuple(self, error_tuple, **context):
2783
"""Call _translate_error with an ErrorFromSmartServer built from the
2786
:param error_tuple: A tuple of a smart server response, as would be
2787
passed to an ErrorFromSmartServer.
2788
:kwargs context: context items to call _translate_error with.
2790
:returns: The error raised by _translate_error.
2792
# Raise the ErrorFromSmartServer before passing it as an argument,
2793
# because _translate_error may need to re-raise it with a bare 'raise'
2795
server_error = errors.ErrorFromSmartServer(error_tuple)
2796
translated_error = self.translateErrorFromSmartServer(
2797
server_error, **context)
2798
return translated_error
2800
def translateErrorFromSmartServer(self, error_object, **context):
2801
"""Like translateTuple, but takes an already constructed
2802
ErrorFromSmartServer rather than a tuple.
2806
except errors.ErrorFromSmartServer, server_error:
2807
translated_error = self.assertRaises(
2808
errors.BzrError, remote._translate_error, server_error,
2810
return translated_error
2813
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2814
"""Unit tests for bzrlib.remote._translate_error.
2816
Given an ErrorFromSmartServer (which has an error tuple from a smart
2817
server) and some context, _translate_error raises more specific errors from
2820
This test case covers the cases where _translate_error succeeds in
2821
translating an ErrorFromSmartServer to something better. See
2822
TestErrorTranslationRobustness for other cases.
2825
def test_NoSuchRevision(self):
2826
branch = self.make_branch('')
2828
translated_error = self.translateTuple(
2829
('NoSuchRevision', revid), branch=branch)
2830
expected_error = errors.NoSuchRevision(branch, revid)
2831
self.assertEqual(expected_error, translated_error)
2833
def test_nosuchrevision(self):
2834
repository = self.make_repository('')
2836
translated_error = self.translateTuple(
2837
('nosuchrevision', revid), repository=repository)
2838
expected_error = errors.NoSuchRevision(repository, revid)
2839
self.assertEqual(expected_error, translated_error)
2841
def test_nobranch(self):
2842
bzrdir = self.make_bzrdir('')
2843
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2844
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2845
self.assertEqual(expected_error, translated_error)
2847
def test_nobranch_one_arg(self):
2848
bzrdir = self.make_bzrdir('')
2849
translated_error = self.translateTuple(
2850
('nobranch', 'extra detail'), bzrdir=bzrdir)
2851
expected_error = errors.NotBranchError(
2852
path=bzrdir.root_transport.base,
2853
detail='extra detail')
2854
self.assertEqual(expected_error, translated_error)
2856
def test_LockContention(self):
2857
translated_error = self.translateTuple(('LockContention',))
2858
expected_error = errors.LockContention('(remote lock)')
2859
self.assertEqual(expected_error, translated_error)
2861
def test_UnlockableTransport(self):
2862
bzrdir = self.make_bzrdir('')
2863
translated_error = self.translateTuple(
2864
('UnlockableTransport',), bzrdir=bzrdir)
2865
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2866
self.assertEqual(expected_error, translated_error)
2868
def test_LockFailed(self):
2869
lock = 'str() of a server lock'
2870
why = 'str() of why'
2871
translated_error = self.translateTuple(('LockFailed', lock, why))
2872
expected_error = errors.LockFailed(lock, why)
2873
self.assertEqual(expected_error, translated_error)
2875
def test_TokenMismatch(self):
2876
token = 'a lock token'
2877
translated_error = self.translateTuple(('TokenMismatch',), token=token)
2878
expected_error = errors.TokenMismatch(token, '(remote token)')
2879
self.assertEqual(expected_error, translated_error)
2881
def test_Diverged(self):
2882
branch = self.make_branch('a')
2883
other_branch = self.make_branch('b')
2884
translated_error = self.translateTuple(
2885
('Diverged',), branch=branch, other_branch=other_branch)
2886
expected_error = errors.DivergedBranches(branch, other_branch)
2887
self.assertEqual(expected_error, translated_error)
2889
def test_ReadError_no_args(self):
2891
translated_error = self.translateTuple(('ReadError',), path=path)
2892
expected_error = errors.ReadError(path)
2893
self.assertEqual(expected_error, translated_error)
2895
def test_ReadError(self):
2897
translated_error = self.translateTuple(('ReadError', path))
2898
expected_error = errors.ReadError(path)
2899
self.assertEqual(expected_error, translated_error)
2901
def test_IncompatibleRepositories(self):
2902
translated_error = self.translateTuple(('IncompatibleRepositories',
2903
"repo1", "repo2", "details here"))
2904
expected_error = errors.IncompatibleRepositories("repo1", "repo2",
2906
self.assertEqual(expected_error, translated_error)
2908
def test_PermissionDenied_no_args(self):
2910
translated_error = self.translateTuple(('PermissionDenied',), path=path)
2911
expected_error = errors.PermissionDenied(path)
2912
self.assertEqual(expected_error, translated_error)
2914
def test_PermissionDenied_one_arg(self):
2916
translated_error = self.translateTuple(('PermissionDenied', path))
2917
expected_error = errors.PermissionDenied(path)
2918
self.assertEqual(expected_error, translated_error)
2920
def test_PermissionDenied_one_arg_and_context(self):
2921
"""Given a choice between a path from the local context and a path on
2922
the wire, _translate_error prefers the path from the local context.
2924
local_path = 'local path'
2925
remote_path = 'remote path'
2926
translated_error = self.translateTuple(
2927
('PermissionDenied', remote_path), path=local_path)
2928
expected_error = errors.PermissionDenied(local_path)
2929
self.assertEqual(expected_error, translated_error)
2931
def test_PermissionDenied_two_args(self):
2933
extra = 'a string with extra info'
2934
translated_error = self.translateTuple(
2935
('PermissionDenied', path, extra))
2936
expected_error = errors.PermissionDenied(path, extra)
2937
self.assertEqual(expected_error, translated_error)
2940
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2941
"""Unit tests for bzrlib.remote._translate_error's robustness.
2943
TestErrorTranslationSuccess is for cases where _translate_error can
2944
translate successfully. This class about how _translate_err behaves when
2945
it fails to translate: it re-raises the original error.
2948
def test_unrecognised_server_error(self):
2949
"""If the error code from the server is not recognised, the original
2950
ErrorFromSmartServer is propagated unmodified.
2952
error_tuple = ('An unknown error tuple',)
2953
server_error = errors.ErrorFromSmartServer(error_tuple)
2954
translated_error = self.translateErrorFromSmartServer(server_error)
2955
expected_error = errors.UnknownErrorFromSmartServer(server_error)
2956
self.assertEqual(expected_error, translated_error)
2958
def test_context_missing_a_key(self):
2959
"""In case of a bug in the client, or perhaps an unexpected response
2960
from a server, _translate_error returns the original error tuple from
2961
the server and mutters a warning.
2963
# To translate a NoSuchRevision error _translate_error needs a 'branch'
2964
# in the context dict. So let's give it an empty context dict instead
2965
# to exercise its error recovery.
2967
error_tuple = ('NoSuchRevision', 'revid')
2968
server_error = errors.ErrorFromSmartServer(error_tuple)
2969
translated_error = self.translateErrorFromSmartServer(server_error)
2970
self.assertEqual(server_error, translated_error)
2971
# In addition to re-raising ErrorFromSmartServer, some debug info has
2972
# been muttered to the log file for developer to look at.
2973
self.assertContainsRe(
2975
"Missing key 'branch' in context")
2977
def test_path_missing(self):
2978
"""Some translations (PermissionDenied, ReadError) can determine the
2979
'path' variable from either the wire or the local context. If neither
2980
has it, then an error is raised.
2982
error_tuple = ('ReadError',)
2983
server_error = errors.ErrorFromSmartServer(error_tuple)
2984
translated_error = self.translateErrorFromSmartServer(server_error)
2985
self.assertEqual(server_error, translated_error)
2986
# In addition to re-raising ErrorFromSmartServer, some debug info has
2987
# been muttered to the log file for developer to look at.
2988
self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
2991
class TestStacking(tests.TestCaseWithTransport):
2992
"""Tests for operations on stacked remote repositories.
2994
The underlying format type must support stacking.
2997
def test_access_stacked_remote(self):
2998
# based on <http://launchpad.net/bugs/261315>
2999
# make a branch stacked on another repository containing an empty
3000
# revision, then open it over hpss - we should be able to see that
3002
base_transport = self.get_transport()
3003
base_builder = self.make_branch_builder('base', format='1.9')
3004
base_builder.start_series()
3005
base_revid = base_builder.build_snapshot('rev-id', None,
3006
[('add', ('', None, 'directory', None))],
3008
base_builder.finish_series()
3009
stacked_branch = self.make_branch('stacked', format='1.9')
3010
stacked_branch.set_stacked_on_url('../base')
3011
# start a server looking at this
3012
smart_server = test_server.SmartTCPServer_for_testing()
3013
self.start_server(smart_server)
3014
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
3015
# can get its branch and repository
3016
remote_branch = remote_bzrdir.open_branch()
3017
remote_repo = remote_branch.repository
3018
remote_repo.lock_read()
3020
# it should have an appropriate fallback repository, which should also
3021
# be a RemoteRepository
3022
self.assertLength(1, remote_repo._fallback_repositories)
3023
self.assertIsInstance(remote_repo._fallback_repositories[0],
3025
# and it has the revision committed to the underlying repository;
3026
# these have varying implementations so we try several of them
3027
self.assertTrue(remote_repo.has_revisions([base_revid]))
3028
self.assertTrue(remote_repo.has_revision(base_revid))
3029
self.assertEqual(remote_repo.get_revision(base_revid).message,
3032
remote_repo.unlock()
3034
def prepare_stacked_remote_branch(self):
3035
"""Get stacked_upon and stacked branches with content in each."""
3036
self.setup_smart_server_with_call_log()
3037
tree1 = self.make_branch_and_tree('tree1', format='1.9')
3038
tree1.commit('rev1', rev_id='rev1')
3039
tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
3040
).open_workingtree()
3041
local_tree = tree2.branch.create_checkout('local')
3042
local_tree.commit('local changes make me feel good.')
3043
branch2 = Branch.open(self.get_url('tree2'))
3045
self.addCleanup(branch2.unlock)
3046
return tree1.branch, branch2
3048
def test_stacked_get_parent_map(self):
3049
# the public implementation of get_parent_map obeys stacking
3050
_, branch = self.prepare_stacked_remote_branch()
3051
repo = branch.repository
3052
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
3054
def test_unstacked_get_parent_map(self):
3055
# _unstacked_provider.get_parent_map ignores stacking
3056
_, branch = self.prepare_stacked_remote_branch()
3057
provider = branch.repository._unstacked_provider
3058
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
3060
def fetch_stream_to_rev_order(self, stream):
3062
for kind, substream in stream:
3063
if not kind == 'revisions':
3066
for content in substream:
3067
result.append(content.key[-1])
3070
def get_ordered_revs(self, format, order, branch_factory=None):
3071
"""Get a list of the revisions in a stream to format format.
3073
:param format: The format of the target.
3074
:param order: the order that target should have requested.
3075
:param branch_factory: A callable to create a trunk and stacked branch
3076
to fetch from. If none, self.prepare_stacked_remote_branch is used.
3077
:result: The revision ids in the stream, in the order seen,
3078
the topological order of revisions in the source.
3080
unordered_format = bzrdir.format_registry.get(format)()
3081
target_repository_format = unordered_format.repository_format
3083
self.assertEqual(order, target_repository_format._fetch_order)
3084
if branch_factory is None:
3085
branch_factory = self.prepare_stacked_remote_branch
3086
_, stacked = branch_factory()
3087
source = stacked.repository._get_source(target_repository_format)
3088
tip = stacked.last_revision()
3089
revs = stacked.repository.get_ancestry(tip)
3090
search = graph.PendingAncestryResult([tip], stacked.repository)
3091
self.reset_smart_call_log()
3092
stream = source.get_stream(search)
3095
# We trust that if a revision is in the stream the rest of the new
3096
# content for it is too, as per our main fetch tests; here we are
3097
# checking that the revisions are actually included at all, and their
3099
return self.fetch_stream_to_rev_order(stream), revs
3101
def test_stacked_get_stream_unordered(self):
3102
# Repository._get_source.get_stream() from a stacked repository with
3103
# unordered yields the full data from both stacked and stacked upon
3105
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
3106
self.assertEqual(set(expected_revs), set(rev_ord))
3107
# Getting unordered results should have made a streaming data request
3108
# from the server, then one from the backing branch.
3109
self.assertLength(2, self.hpss_calls)
3111
def test_stacked_on_stacked_get_stream_unordered(self):
3112
# Repository._get_source.get_stream() from a stacked repository which
3113
# is itself stacked yields the full data from all three sources.
3114
def make_stacked_stacked():
3115
_, stacked = self.prepare_stacked_remote_branch()
3116
tree = stacked.bzrdir.sprout('tree3', stacked=True
3117
).open_workingtree()
3118
local_tree = tree.branch.create_checkout('local-tree3')
3119
local_tree.commit('more local changes are better')
3120
branch = Branch.open(self.get_url('tree3'))
3122
self.addCleanup(branch.unlock)
3124
rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
3125
branch_factory=make_stacked_stacked)
3126
self.assertEqual(set(expected_revs), set(rev_ord))
3127
# Getting unordered results should have made a streaming data request
3128
# from the server, and one from each backing repo
3129
self.assertLength(3, self.hpss_calls)
3131
def test_stacked_get_stream_topological(self):
3132
# Repository._get_source.get_stream() from a stacked repository with
3133
# topological sorting yields the full data from both stacked and
3134
# stacked upon sources in topological order.
3135
rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
3136
self.assertEqual(expected_revs, rev_ord)
3137
# Getting topological sort requires VFS calls still - one of which is
3138
# pushing up from the bound branch.
3139
self.assertLength(13, self.hpss_calls)
3141
def test_stacked_get_stream_groupcompress(self):
3142
# Repository._get_source.get_stream() from a stacked repository with
3143
# groupcompress sorting yields the full data from both stacked and
3144
# stacked upon sources in groupcompress order.
3145
raise tests.TestSkipped('No groupcompress ordered format available')
3146
rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
3147
self.assertEqual(expected_revs, reversed(rev_ord))
3148
# Getting unordered results should have made a streaming data request
3149
# from the backing branch, and one from the stacked on branch.
3150
self.assertLength(2, self.hpss_calls)
3152
def test_stacked_pull_more_than_stacking_has_bug_360791(self):
3153
# When pulling some fixed amount of content that is more than the
3154
# source has (because some is coming from a fallback branch, no error
3155
# should be received. This was reported as bug 360791.
3156
# Need three branches: a trunk, a stacked branch, and a preexisting
3157
# branch pulling content from stacked and trunk.
3158
self.setup_smart_server_with_call_log()
3159
trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
3160
r1 = trunk.commit('start')
3161
stacked_branch = trunk.branch.create_clone_on_transport(
3162
self.get_transport('stacked'), stacked_on=trunk.branch.base)
3163
local = self.make_branch('local', format='1.9-rich-root')
3164
local.repository.fetch(stacked_branch.repository,
3165
stacked_branch.last_revision())
3168
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
3171
super(TestRemoteBranchEffort, self).setUp()
3172
# Create a smart server that publishes whatever the backing VFS server
3174
self.smart_server = test_server.SmartTCPServer_for_testing()
3175
self.start_server(self.smart_server, self.get_server())
3176
# Log all HPSS calls into self.hpss_calls.
3177
_SmartClient.hooks.install_named_hook(
3178
'call', self.capture_hpss_call, None)
3179
self.hpss_calls = []
3181
def capture_hpss_call(self, params):
3182
self.hpss_calls.append(params.method)
3184
def test_copy_content_into_avoids_revision_history(self):
3185
local = self.make_branch('local')
3186
remote_backing_tree = self.make_branch_and_tree('remote')
3187
remote_backing_tree.commit("Commit.")
3188
remote_branch_url = self.smart_server.get_url() + 'remote'
3189
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3190
local.repository.fetch(remote_branch.repository)
3191
self.hpss_calls = []
3192
remote_branch.copy_content_into(local)
3193
self.assertFalse('Branch.revision_history' in self.hpss_calls)