1
# Copyright (C) 2006, 2007, 2008 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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
41
from bzrlib.branch import Branch
42
from bzrlib.bzrdir import BzrDir, BzrDirFormat
43
from bzrlib.remote import (
49
from bzrlib.revision import NULL_REVISION
50
from bzrlib.smart import server, medium
51
from bzrlib.smart.client import _SmartClient
52
from bzrlib.symbol_versioning import one_four
53
from bzrlib.transport import get_transport, http
54
from bzrlib.transport.memory import MemoryTransport
55
from bzrlib.transport.remote import (
62
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
65
self.transport_server = server.SmartTCPServer_for_testing
66
super(BasicRemoteObjectTests, self).setUp()
67
self.transport = self.get_transport()
68
# make a branch that can be opened over the smart transport
69
self.local_wt = BzrDir.create_standalone_workingtree('.')
72
self.transport.disconnect()
73
tests.TestCaseWithTransport.tearDown(self)
75
def test_create_remote_bzrdir(self):
76
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
77
self.assertIsInstance(b, BzrDir)
79
def test_open_remote_branch(self):
80
# open a standalone branch in the working directory
81
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
82
branch = b.open_branch()
83
self.assertIsInstance(branch, Branch)
85
def test_remote_repository(self):
86
b = BzrDir.open_from_transport(self.transport)
87
repo = b.open_repository()
88
revid = u'\xc823123123'.encode('utf8')
89
self.assertFalse(repo.has_revision(revid))
90
self.local_wt.commit(message='test commit', rev_id=revid)
91
self.assertTrue(repo.has_revision(revid))
93
def test_remote_branch_revision_history(self):
94
b = BzrDir.open_from_transport(self.transport).open_branch()
95
self.assertEqual([], b.revision_history())
96
r1 = self.local_wt.commit('1st commit')
97
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
98
self.assertEqual([r1, r2], b.revision_history())
100
def test_find_correct_format(self):
101
"""Should open a RemoteBzrDir over a RemoteTransport"""
102
fmt = BzrDirFormat.find_format(self.transport)
103
self.assertTrue(RemoteBzrDirFormat
104
in BzrDirFormat._control_server_formats)
105
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
107
def test_open_detected_smart_format(self):
108
fmt = BzrDirFormat.find_format(self.transport)
109
d = fmt.open(self.transport)
110
self.assertIsInstance(d, BzrDir)
112
def test_remote_branch_repr(self):
113
b = BzrDir.open_from_transport(self.transport).open_branch()
114
self.assertStartsWith(str(b), 'RemoteBranch(')
117
class FakeProtocol(object):
118
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
120
def __init__(self, body, fake_client):
122
self._body_buffer = None
123
self._fake_client = fake_client
125
def read_body_bytes(self, count=-1):
126
if self._body_buffer is None:
127
self._body_buffer = StringIO(self.body)
128
bytes = self._body_buffer.read(count)
129
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
130
self._fake_client.expecting_body = False
133
def cancel_read_body(self):
134
self._fake_client.expecting_body = False
136
def read_streamed_body(self):
140
class FakeClient(_SmartClient):
141
"""Lookalike for _SmartClient allowing testing."""
143
def __init__(self, fake_medium_base='fake base'):
144
"""Create a FakeClient."""
147
self.expecting_body = False
148
# if non-None, this is the list of expected calls, with only the
149
# method name and arguments included. the body might be hard to
150
# compute so is not included. If a call is None, that call can
152
self._expected_calls = None
153
_SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
155
def add_expected_call(self, call_name, call_args, response_type,
156
response_args, response_body=None):
157
if self._expected_calls is None:
158
self._expected_calls = []
159
self._expected_calls.append((call_name, call_args))
160
self.responses.append((response_type, response_args, response_body))
162
def add_success_response(self, *args):
163
self.responses.append(('success', args, None))
165
def add_success_response_with_body(self, body, *args):
166
self.responses.append(('success', args, body))
167
if self._expected_calls is not None:
168
self._expected_calls.append(None)
170
def add_error_response(self, *args):
171
self.responses.append(('error', args))
173
def add_unknown_method_response(self, verb):
174
self.responses.append(('unknown', verb))
176
def finished_test(self):
177
if self._expected_calls:
178
raise AssertionError("%r finished but was still expecting %r"
179
% (self, self._expected_calls[0]))
181
def _get_next_response(self):
183
response_tuple = self.responses.pop(0)
184
except IndexError, e:
185
raise AssertionError("%r didn't expect any more calls"
187
if response_tuple[0] == 'unknown':
188
raise errors.UnknownSmartMethod(response_tuple[1])
189
elif response_tuple[0] == 'error':
190
raise errors.ErrorFromSmartServer(response_tuple[1])
191
return response_tuple
193
def _check_call(self, method, args):
194
if self._expected_calls is None:
195
# the test should be updated to say what it expects
198
next_call = self._expected_calls.pop(0)
200
raise AssertionError("%r didn't expect any more calls "
202
% (self, method, args,))
203
if next_call is None:
205
if method != next_call[0] or args != next_call[1]:
206
raise AssertionError("%r expected %r%r "
208
% (self, next_call[0], next_call[1], method, args,))
210
def call(self, method, *args):
211
self._check_call(method, args)
212
self._calls.append(('call', method, args))
213
return self._get_next_response()[1]
215
def call_expecting_body(self, method, *args):
216
self._check_call(method, args)
217
self._calls.append(('call_expecting_body', method, args))
218
result = self._get_next_response()
219
self.expecting_body = True
220
return result[1], FakeProtocol(result[2], self)
222
def call_with_body_bytes_expecting_body(self, method, args, body):
223
self._check_call(method, args)
224
self._calls.append(('call_with_body_bytes_expecting_body', method,
226
result = self._get_next_response()
227
self.expecting_body = True
228
return result[1], FakeProtocol(result[2], self)
230
def call_with_body_stream(self, args, stream):
231
# Explicitly consume the stream before checking for an error, because
232
# that's what happens a real medium.
233
stream = list(stream)
234
self._check_call(args[0], args[1:])
235
self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
236
return self._get_next_response()[1]
239
class FakeMedium(medium.SmartClientMedium):
241
def __init__(self, client_calls, base):
242
medium.SmartClientMedium.__init__(self, base)
243
self._client_calls = client_calls
245
def disconnect(self):
246
self._client_calls.append(('disconnect medium',))
249
class TestVfsHas(tests.TestCase):
251
def test_unicode_path(self):
252
client = FakeClient('/')
253
client.add_success_response('yes',)
254
transport = RemoteTransport('bzr://localhost/', _client=client)
255
filename = u'/hell\u00d8'.encode('utf8')
256
result = transport.has(filename)
258
[('call', 'has', (filename,))],
260
self.assertTrue(result)
263
class TestRemote(tests.TestCaseWithMemoryTransport):
265
def disable_verb(self, verb):
266
"""Disable a verb for one test."""
267
request_handlers = smart.request.request_handlers
268
orig_method = request_handlers.get(verb)
269
request_handlers.remove(verb)
271
request_handlers.register(verb, orig_method)
272
self.addCleanup(restoreVerb)
275
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
276
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
278
def assertRemotePath(self, expected, client_base, transport_base):
279
"""Assert that the result of
280
SmartClientMedium.remote_path_from_transport is the expected value for
281
a given client_base and transport_base.
283
client_medium = medium.SmartClientMedium(client_base)
284
transport = get_transport(transport_base)
285
result = client_medium.remote_path_from_transport(transport)
286
self.assertEqual(expected, result)
288
def test_remote_path_from_transport(self):
289
"""SmartClientMedium.remote_path_from_transport calculates a URL for
290
the given transport relative to the root of the client base URL.
292
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
293
self.assertRemotePath(
294
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
296
def assertRemotePathHTTP(self, expected, transport_base, relpath):
297
"""Assert that the result of
298
HttpTransportBase.remote_path_from_transport is the expected value for
299
a given transport_base and relpath of that transport. (Note that
300
HttpTransportBase is a subclass of SmartClientMedium)
302
base_transport = get_transport(transport_base)
303
client_medium = base_transport.get_smart_medium()
304
cloned_transport = base_transport.clone(relpath)
305
result = client_medium.remote_path_from_transport(cloned_transport)
306
self.assertEqual(expected, result)
308
def test_remote_path_from_transport_http(self):
309
"""Remote paths for HTTP transports are calculated differently to other
310
transports. They are just relative to the client base, not the root
311
directory of the host.
313
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
314
self.assertRemotePathHTTP(
315
'../xyz/', scheme + '//host/path', '../xyz/')
316
self.assertRemotePathHTTP(
317
'xyz/', scheme + '//host/path', 'xyz/')
320
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
321
"""Tests for the behaviour of client_medium.remote_is_at_least."""
323
def test_initially_unlimited(self):
324
"""A fresh medium assumes that the remote side supports all
327
client_medium = medium.SmartClientMedium('dummy base')
328
self.assertFalse(client_medium._is_remote_before((99, 99)))
330
def test__remember_remote_is_before(self):
331
"""Calling _remember_remote_is_before ratchets down the known remote
334
client_medium = medium.SmartClientMedium('dummy base')
335
# Mark the remote side as being less than 1.6. The remote side may
337
client_medium._remember_remote_is_before((1, 6))
338
self.assertTrue(client_medium._is_remote_before((1, 6)))
339
self.assertFalse(client_medium._is_remote_before((1, 5)))
340
# Calling _remember_remote_is_before again with a lower value works.
341
client_medium._remember_remote_is_before((1, 5))
342
self.assertTrue(client_medium._is_remote_before((1, 5)))
343
# You cannot call _remember_remote_is_before with a larger value.
345
AssertionError, client_medium._remember_remote_is_before, (1, 9))
348
class TestBzrDirOpenBranch(tests.TestCase):
350
def test_branch_present(self):
351
transport = MemoryTransport()
352
transport.mkdir('quack')
353
transport = transport.clone('quack')
354
client = FakeClient(transport.base)
355
client.add_expected_call(
356
'BzrDir.open_branch', ('quack/',),
357
'success', ('ok', ''))
358
client.add_expected_call(
359
'BzrDir.find_repositoryV2', ('quack/',),
360
'success', ('ok', '', 'no', 'no', 'no'))
361
client.add_expected_call(
362
'Branch.get_stacked_on_url', ('quack/',),
363
'error', ('NotStacked',))
364
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
366
result = bzrdir.open_branch()
367
self.assertIsInstance(result, RemoteBranch)
368
self.assertEqual(bzrdir, result.bzrdir)
369
client.finished_test()
371
def test_branch_missing(self):
372
transport = MemoryTransport()
373
transport.mkdir('quack')
374
transport = transport.clone('quack')
375
client = FakeClient(transport.base)
376
client.add_error_response('nobranch')
377
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
379
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
381
[('call', 'BzrDir.open_branch', ('quack/',))],
384
def test__get_tree_branch(self):
385
# _get_tree_branch is a form of open_branch, but it should only ask for
386
# branch opening, not any other network requests.
389
calls.append("Called")
391
transport = MemoryTransport()
392
# no requests on the network - catches other api calls being made.
393
client = FakeClient(transport.base)
394
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
396
# patch the open_branch call to record that it was called.
397
bzrdir.open_branch = open_branch
398
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
399
self.assertEqual(["Called"], calls)
400
self.assertEqual([], client._calls)
402
def test_url_quoting_of_path(self):
403
# Relpaths on the wire should not be URL-escaped. So "~" should be
404
# transmitted as "~", not "%7E".
405
transport = RemoteTCPTransport('bzr://localhost/~hello/')
406
client = FakeClient(transport.base)
407
client.add_expected_call(
408
'BzrDir.open_branch', ('~hello/',),
409
'success', ('ok', ''))
410
client.add_expected_call(
411
'BzrDir.find_repositoryV2', ('~hello/',),
412
'success', ('ok', '', 'no', 'no', 'no'))
413
client.add_expected_call(
414
'Branch.get_stacked_on_url', ('~hello/',),
415
'error', ('NotStacked',))
416
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
418
result = bzrdir.open_branch()
419
client.finished_test()
421
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
422
transport = MemoryTransport()
423
transport.mkdir('quack')
424
transport = transport.clone('quack')
426
rich_response = 'yes'
430
subtree_response = 'yes'
432
subtree_response = 'no'
433
client = FakeClient(transport.base)
434
client.add_success_response(
435
'ok', '', rich_response, subtree_response, external_lookup)
436
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
438
result = bzrdir.open_repository()
440
[('call', 'BzrDir.find_repositoryV2', ('quack/',))],
442
self.assertIsInstance(result, RemoteRepository)
443
self.assertEqual(bzrdir, result.bzrdir)
444
self.assertEqual(rich_root, result._format.rich_root_data)
445
self.assertEqual(subtrees, result._format.supports_tree_reference)
447
def test_open_repository_sets_format_attributes(self):
448
self.check_open_repository(True, True)
449
self.check_open_repository(False, True)
450
self.check_open_repository(True, False)
451
self.check_open_repository(False, False)
452
self.check_open_repository(False, False, 'yes')
454
def test_old_server(self):
455
"""RemoteBzrDirFormat should fail to probe if the server version is too
458
self.assertRaises(errors.NotBranchError,
459
RemoteBzrDirFormat.probe_transport, OldServerTransport())
462
class TestBzrDirCreateBranch(TestRemote):
464
def test_backwards_compat(self):
465
self.setup_smart_server_with_call_log()
466
repo = self.make_repository('.')
467
self.reset_smart_call_log()
468
self.disable_verb('BzrDir.create_branch')
469
branch = repo.bzrdir.create_branch()
470
create_branch_call_count = len([call for call in self.hpss_calls if
471
call[0].method == 'BzrDir.create_branch'])
472
self.assertEqual(1, create_branch_call_count)
474
def test_current_server(self):
475
transport = self.get_transport('.')
476
transport = transport.clone('quack')
477
self.make_repository('quack')
478
client = FakeClient(transport.base)
479
reference_bzrdir_format = bzrdir.format_registry.get('default')()
480
reference_format = reference_bzrdir_format.get_branch_format()
481
network_name = reference_format.network_name()
482
reference_repo_fmt = reference_bzrdir_format.repository_format
483
reference_repo_name = reference_repo_fmt.network_name()
484
client.add_expected_call(
485
'BzrDir.create_branch', ('quack/', network_name),
486
'success', ('ok', network_name, '', 'no', 'no', 'yes',
487
reference_repo_name))
488
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
490
branch = a_bzrdir.create_branch()
491
# We should have got a remote branch
492
self.assertIsInstance(branch, remote.RemoteBranch)
493
# its format should have the settings from the response
494
format = branch._format
495
self.assertEqual(network_name, format.network_name())
498
class TestBzrDirCreateRepository(TestRemote):
500
def test_backwards_compat(self):
501
self.setup_smart_server_with_call_log()
502
bzrdir = self.make_bzrdir('.')
503
self.reset_smart_call_log()
504
self.disable_verb('BzrDir.create_repository')
505
repo = bzrdir.create_repository()
506
create_repo_call_count = len([call for call in self.hpss_calls if
507
call[0].method == 'BzrDir.create_repository'])
508
self.assertEqual(1, create_repo_call_count)
510
def test_current_server(self):
511
transport = self.get_transport('.')
512
transport = transport.clone('quack')
513
self.make_bzrdir('quack')
514
client = FakeClient(transport.base)
515
reference_bzrdir_format = bzrdir.format_registry.get('default')()
516
reference_format = reference_bzrdir_format.repository_format
517
network_name = reference_format.network_name()
518
client.add_expected_call(
519
'BzrDir.create_repository', ('quack/',
520
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
521
'success', ('ok', 'no', 'no', 'no', network_name))
522
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
524
repo = a_bzrdir.create_repository()
525
# We should have got a remote repository
526
self.assertIsInstance(repo, remote.RemoteRepository)
527
# its format should have the settings from the response
528
format = repo._format
529
self.assertFalse(format.rich_root_data)
530
self.assertFalse(format.supports_tree_reference)
531
self.assertFalse(format.supports_external_lookups)
532
self.assertEqual(network_name, format.network_name())
535
class TestBzrDirOpenRepository(tests.TestCase):
537
def test_backwards_compat_1_2(self):
538
transport = MemoryTransport()
539
transport.mkdir('quack')
540
transport = transport.clone('quack')
541
client = FakeClient(transport.base)
542
client.add_unknown_method_response('BzrDir.find_repositoryV2')
543
client.add_success_response('ok', '', 'no', 'no')
544
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
546
repo = bzrdir.open_repository()
548
[('call', 'BzrDir.find_repositoryV2', ('quack/',)),
549
('call', 'BzrDir.find_repository', ('quack/',))],
553
class OldSmartClient(object):
554
"""A fake smart client for test_old_version that just returns a version one
555
response to the 'hello' (query version) command.
558
def get_request(self):
559
input_file = StringIO('ok\x011\n')
560
output_file = StringIO()
561
client_medium = medium.SmartSimplePipesClientMedium(
562
input_file, output_file)
563
return medium.SmartClientStreamMediumRequest(client_medium)
565
def protocol_version(self):
569
class OldServerTransport(object):
570
"""A fake transport for test_old_server that reports it's smart server
571
protocol version as version one.
577
def get_smart_client(self):
578
return OldSmartClient()
581
class RemoteBranchTestCase(tests.TestCase):
583
def make_remote_branch(self, transport, client):
584
"""Make a RemoteBranch using 'client' as its _SmartClient.
586
A RemoteBzrDir and RemoteRepository will also be created to fill out
587
the RemoteBranch, albeit with stub values for some of their attributes.
589
# we do not want bzrdir to make any remote calls, so use False as its
590
# _client. If it tries to make a remote call, this will fail
592
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
594
repo = RemoteRepository(bzrdir, None, _client=client)
595
return RemoteBranch(bzrdir, repo, _client=client)
598
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
600
def test_empty_branch(self):
601
# in an empty branch we decode the response properly
602
transport = MemoryTransport()
603
client = FakeClient(transport.base)
604
client.add_expected_call(
605
'Branch.get_stacked_on_url', ('quack/',),
606
'error', ('NotStacked',))
607
client.add_expected_call(
608
'Branch.last_revision_info', ('quack/',),
609
'success', ('ok', '0', 'null:'))
610
transport.mkdir('quack')
611
transport = transport.clone('quack')
612
branch = self.make_remote_branch(transport, client)
613
result = branch.last_revision_info()
614
client.finished_test()
615
self.assertEqual((0, NULL_REVISION), result)
617
def test_non_empty_branch(self):
618
# in a non-empty branch we also decode the response properly
619
revid = u'\xc8'.encode('utf8')
620
transport = MemoryTransport()
621
client = FakeClient(transport.base)
622
client.add_expected_call(
623
'Branch.get_stacked_on_url', ('kwaak/',),
624
'error', ('NotStacked',))
625
client.add_expected_call(
626
'Branch.last_revision_info', ('kwaak/',),
627
'success', ('ok', '2', revid))
628
transport.mkdir('kwaak')
629
transport = transport.clone('kwaak')
630
branch = self.make_remote_branch(transport, client)
631
result = branch.last_revision_info()
632
self.assertEqual((2, revid), result)
635
class TestBranch_get_stacked_on_url(tests.TestCaseWithMemoryTransport):
636
"""Test Branch._get_stacked_on_url rpc"""
638
def test_get_stacked_on_invalid_url(self):
639
# test that asking for a stacked on url the server can't access works.
640
# This isn't perfect, but then as we're in the same process there
641
# really isn't anything we can do to be 100% sure that the server
642
# doesn't just open in - this test probably needs to be rewritten using
643
# a spawn()ed server.
644
stacked_branch = self.make_branch('stacked', format='1.9')
645
memory_branch = self.make_branch('base', format='1.9')
646
vfs_url = self.get_vfs_only_url('base')
647
stacked_branch.set_stacked_on_url(vfs_url)
648
transport = stacked_branch.bzrdir.root_transport
649
client = FakeClient(transport.base)
650
client.add_expected_call(
651
'Branch.get_stacked_on_url', ('stacked/',),
652
'success', ('ok', vfs_url))
653
# XXX: Multiple calls are bad, this second call documents what is
655
client.add_expected_call(
656
'Branch.get_stacked_on_url', ('stacked/',),
657
'success', ('ok', vfs_url))
658
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
660
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, None),
662
result = branch.get_stacked_on_url()
663
self.assertEqual(vfs_url, result)
665
def test_backwards_compatible(self):
666
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
667
base_branch = self.make_branch('base', format='1.6')
668
stacked_branch = self.make_branch('stacked', format='1.6')
669
stacked_branch.set_stacked_on_url('../base')
670
client = FakeClient(self.get_url())
671
client.add_expected_call(
672
'BzrDir.open_branch', ('stacked/',),
673
'success', ('ok', ''))
674
client.add_expected_call(
675
'BzrDir.find_repositoryV2', ('stacked/',),
676
'success', ('ok', '', 'no', 'no', 'no'))
677
# called twice, once from constructor and then again by us
678
client.add_expected_call(
679
'Branch.get_stacked_on_url', ('stacked/',),
680
'unknown', ('Branch.get_stacked_on_url',))
681
client.add_expected_call(
682
'Branch.get_stacked_on_url', ('stacked/',),
683
'unknown', ('Branch.get_stacked_on_url',))
684
# this will also do vfs access, but that goes direct to the transport
685
# and isn't seen by the FakeClient.
686
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
687
remote.RemoteBzrDirFormat(), _client=client)
688
branch = bzrdir.open_branch()
689
result = branch.get_stacked_on_url()
690
self.assertEqual('../base', result)
691
client.finished_test()
692
# it's in the fallback list both for the RemoteRepository and its vfs
694
self.assertEqual(1, len(branch.repository._fallback_repositories))
696
len(branch.repository._real_repository._fallback_repositories))
698
def test_get_stacked_on_real_branch(self):
699
base_branch = self.make_branch('base', format='1.6')
700
stacked_branch = self.make_branch('stacked', format='1.6')
701
stacked_branch.set_stacked_on_url('../base')
702
client = FakeClient(self.get_url())
703
client.add_expected_call(
704
'BzrDir.open_branch', ('stacked/',),
705
'success', ('ok', ''))
706
client.add_expected_call(
707
'BzrDir.find_repositoryV2', ('stacked/',),
708
'success', ('ok', '', 'no', 'no', 'no'))
709
# called twice, once from constructor and then again by us
710
client.add_expected_call(
711
'Branch.get_stacked_on_url', ('stacked/',),
712
'success', ('ok', '../base'))
713
client.add_expected_call(
714
'Branch.get_stacked_on_url', ('stacked/',),
715
'success', ('ok', '../base'))
716
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
717
remote.RemoteBzrDirFormat(), _client=client)
718
branch = bzrdir.open_branch()
719
result = branch.get_stacked_on_url()
720
self.assertEqual('../base', result)
721
client.finished_test()
722
# it's in the fallback list both for the RemoteRepository and its vfs
724
self.assertEqual(1, len(branch.repository._fallback_repositories))
726
len(branch.repository._real_repository._fallback_repositories))
729
class TestBranchSetLastRevision(RemoteBranchTestCase):
731
def test_set_empty(self):
732
# set_revision_history([]) is translated to calling
733
# Branch.set_last_revision(path, '') on the wire.
734
transport = MemoryTransport()
735
transport.mkdir('branch')
736
transport = transport.clone('branch')
738
client = FakeClient(transport.base)
739
client.add_expected_call(
740
'Branch.get_stacked_on_url', ('branch/',),
741
'error', ('NotStacked',))
742
client.add_expected_call(
743
'Branch.lock_write', ('branch/', '', ''),
744
'success', ('ok', 'branch token', 'repo token'))
745
client.add_expected_call(
746
'Branch.last_revision_info',
748
'success', ('ok', '0', 'null:'))
749
client.add_expected_call(
750
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
752
client.add_expected_call(
753
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
755
branch = self.make_remote_branch(transport, client)
756
# This is a hack to work around the problem that RemoteBranch currently
757
# unnecessarily invokes _ensure_real upon a call to lock_write.
758
branch._ensure_real = lambda: None
760
result = branch.set_revision_history([])
762
self.assertEqual(None, result)
763
client.finished_test()
765
def test_set_nonempty(self):
766
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
767
# Branch.set_last_revision(path, rev-idN) on the wire.
768
transport = MemoryTransport()
769
transport.mkdir('branch')
770
transport = transport.clone('branch')
772
client = FakeClient(transport.base)
773
client.add_expected_call(
774
'Branch.get_stacked_on_url', ('branch/',),
775
'error', ('NotStacked',))
776
client.add_expected_call(
777
'Branch.lock_write', ('branch/', '', ''),
778
'success', ('ok', 'branch token', 'repo token'))
779
client.add_expected_call(
780
'Branch.last_revision_info',
782
'success', ('ok', '0', 'null:'))
784
encoded_body = bz2.compress('\n'.join(lines))
785
client.add_success_response_with_body(encoded_body, 'ok')
786
client.add_expected_call(
787
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
789
client.add_expected_call(
790
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
792
branch = self.make_remote_branch(transport, client)
793
# This is a hack to work around the problem that RemoteBranch currently
794
# unnecessarily invokes _ensure_real upon a call to lock_write.
795
branch._ensure_real = lambda: None
796
# Lock the branch, reset the record of remote calls.
798
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
800
self.assertEqual(None, result)
801
client.finished_test()
803
def test_no_such_revision(self):
804
transport = MemoryTransport()
805
transport.mkdir('branch')
806
transport = transport.clone('branch')
807
# A response of 'NoSuchRevision' is translated into an exception.
808
client = FakeClient(transport.base)
809
client.add_expected_call(
810
'Branch.get_stacked_on_url', ('branch/',),
811
'error', ('NotStacked',))
812
client.add_expected_call(
813
'Branch.lock_write', ('branch/', '', ''),
814
'success', ('ok', 'branch token', 'repo token'))
815
client.add_expected_call(
816
'Branch.last_revision_info',
818
'success', ('ok', '0', 'null:'))
819
# get_graph calls to construct the revision history, for the set_rh
822
encoded_body = bz2.compress('\n'.join(lines))
823
client.add_success_response_with_body(encoded_body, 'ok')
824
client.add_expected_call(
825
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
826
'error', ('NoSuchRevision', 'rev-id'))
827
client.add_expected_call(
828
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
831
branch = self.make_remote_branch(transport, client)
834
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
836
client.finished_test()
838
def test_tip_change_rejected(self):
839
"""TipChangeRejected responses cause a TipChangeRejected exception to
842
transport = MemoryTransport()
843
transport.mkdir('branch')
844
transport = transport.clone('branch')
845
client = FakeClient(transport.base)
846
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
847
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
848
client.add_expected_call(
849
'Branch.get_stacked_on_url', ('branch/',),
850
'error', ('NotStacked',))
851
client.add_expected_call(
852
'Branch.lock_write', ('branch/', '', ''),
853
'success', ('ok', 'branch token', 'repo token'))
854
client.add_expected_call(
855
'Branch.last_revision_info',
857
'success', ('ok', '0', 'null:'))
859
encoded_body = bz2.compress('\n'.join(lines))
860
client.add_success_response_with_body(encoded_body, 'ok')
861
client.add_expected_call(
862
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
863
'error', ('TipChangeRejected', rejection_msg_utf8))
864
client.add_expected_call(
865
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
867
branch = self.make_remote_branch(transport, client)
868
branch._ensure_real = lambda: None
870
# The 'TipChangeRejected' error response triggered by calling
871
# set_revision_history causes a TipChangeRejected exception.
872
err = self.assertRaises(
873
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
874
# The UTF-8 message from the response has been decoded into a unicode
876
self.assertIsInstance(err.msg, unicode)
877
self.assertEqual(rejection_msg_unicode, err.msg)
879
client.finished_test()
882
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
884
def test_set_last_revision_info(self):
885
# set_last_revision_info(num, 'rev-id') is translated to calling
886
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
887
transport = MemoryTransport()
888
transport.mkdir('branch')
889
transport = transport.clone('branch')
890
client = FakeClient(transport.base)
892
client.add_error_response('NotStacked')
894
client.add_success_response('ok', 'branch token', 'repo token')
895
# query the current revision
896
client.add_success_response('ok', '0', 'null:')
898
client.add_success_response('ok')
900
client.add_success_response('ok')
902
branch = self.make_remote_branch(transport, client)
903
# Lock the branch, reset the record of remote calls.
906
result = branch.set_last_revision_info(1234, 'a-revision-id')
908
[('call', 'Branch.last_revision_info', ('branch/',)),
909
('call', 'Branch.set_last_revision_info',
910
('branch/', 'branch token', 'repo token',
911
'1234', 'a-revision-id'))],
913
self.assertEqual(None, result)
915
def test_no_such_revision(self):
916
# A response of 'NoSuchRevision' is translated into an exception.
917
transport = MemoryTransport()
918
transport.mkdir('branch')
919
transport = transport.clone('branch')
920
client = FakeClient(transport.base)
922
client.add_error_response('NotStacked')
924
client.add_success_response('ok', 'branch token', 'repo token')
926
client.add_error_response('NoSuchRevision', 'revid')
928
client.add_success_response('ok')
930
branch = self.make_remote_branch(transport, client)
931
# Lock the branch, reset the record of remote calls.
936
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
939
def lock_remote_branch(self, branch):
940
"""Trick a RemoteBranch into thinking it is locked."""
941
branch._lock_mode = 'w'
942
branch._lock_count = 2
943
branch._lock_token = 'branch token'
944
branch._repo_lock_token = 'repo token'
945
branch.repository._lock_mode = 'w'
946
branch.repository._lock_count = 2
947
branch.repository._lock_token = 'repo token'
949
def test_backwards_compatibility(self):
950
"""If the server does not support the Branch.set_last_revision_info
951
verb (which is new in 1.4), then the client falls back to VFS methods.
953
# This test is a little messy. Unlike most tests in this file, it
954
# doesn't purely test what a Remote* object sends over the wire, and
955
# how it reacts to responses from the wire. It instead relies partly
956
# on asserting that the RemoteBranch will call
957
# self._real_branch.set_last_revision_info(...).
959
# First, set up our RemoteBranch with a FakeClient that raises
960
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
961
transport = MemoryTransport()
962
transport.mkdir('branch')
963
transport = transport.clone('branch')
964
client = FakeClient(transport.base)
965
client.add_expected_call(
966
'Branch.get_stacked_on_url', ('branch/',),
967
'error', ('NotStacked',))
968
client.add_expected_call(
969
'Branch.last_revision_info',
971
'success', ('ok', '0', 'null:'))
972
client.add_expected_call(
973
'Branch.set_last_revision_info',
974
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
975
'unknown', 'Branch.set_last_revision_info')
977
branch = self.make_remote_branch(transport, client)
978
class StubRealBranch(object):
981
def set_last_revision_info(self, revno, revision_id):
983
('set_last_revision_info', revno, revision_id))
984
def _clear_cached_state(self):
986
real_branch = StubRealBranch()
987
branch._real_branch = real_branch
988
self.lock_remote_branch(branch)
990
# Call set_last_revision_info, and verify it behaved as expected.
991
result = branch.set_last_revision_info(1234, 'a-revision-id')
993
[('set_last_revision_info', 1234, 'a-revision-id')],
995
client.finished_test()
997
def test_unexpected_error(self):
998
# If the server sends an error the client doesn't understand, it gets
999
# turned into an UnknownErrorFromSmartServer, which is presented as a
1000
# non-internal error to the user.
1001
transport = MemoryTransport()
1002
transport.mkdir('branch')
1003
transport = transport.clone('branch')
1004
client = FakeClient(transport.base)
1005
# get_stacked_on_url
1006
client.add_error_response('NotStacked')
1008
client.add_success_response('ok', 'branch token', 'repo token')
1010
client.add_error_response('UnexpectedError')
1012
client.add_success_response('ok')
1014
branch = self.make_remote_branch(transport, client)
1015
# Lock the branch, reset the record of remote calls.
1019
err = self.assertRaises(
1020
errors.UnknownErrorFromSmartServer,
1021
branch.set_last_revision_info, 123, 'revid')
1022
self.assertEqual(('UnexpectedError',), err.error_tuple)
1025
def test_tip_change_rejected(self):
1026
"""TipChangeRejected responses cause a TipChangeRejected exception to
1029
transport = MemoryTransport()
1030
transport.mkdir('branch')
1031
transport = transport.clone('branch')
1032
client = FakeClient(transport.base)
1033
# get_stacked_on_url
1034
client.add_error_response('NotStacked')
1036
client.add_success_response('ok', 'branch token', 'repo token')
1038
client.add_error_response('TipChangeRejected', 'rejection message')
1040
client.add_success_response('ok')
1042
branch = self.make_remote_branch(transport, client)
1043
# Lock the branch, reset the record of remote calls.
1045
self.addCleanup(branch.unlock)
1048
# The 'TipChangeRejected' error response triggered by calling
1049
# set_last_revision_info causes a TipChangeRejected exception.
1050
err = self.assertRaises(
1051
errors.TipChangeRejected,
1052
branch.set_last_revision_info, 123, 'revid')
1053
self.assertEqual('rejection message', err.msg)
1056
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1057
"""Getting the branch configuration should use an abstract method not vfs.
1060
def test_get_branch_conf(self):
1061
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1062
## # We should see that branch.get_config() does a single rpc to get the
1063
## # remote configuration file, abstracting away where that is stored on
1064
## # the server. However at the moment it always falls back to using the
1065
## # vfs, and this would need some changes in config.py.
1067
## # in an empty branch we decode the response properly
1068
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1069
## # we need to make a real branch because the remote_branch.control_files
1070
## # will trigger _ensure_real.
1071
## branch = self.make_branch('quack')
1072
## transport = branch.bzrdir.root_transport
1073
## # we do not want bzrdir to make any remote calls
1074
## bzrdir = RemoteBzrDir(transport, _client=False)
1075
## branch = RemoteBranch(bzrdir, None, _client=client)
1076
## config = branch.get_config()
1077
## self.assertEqual(
1078
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1082
class TestBranchLockWrite(RemoteBranchTestCase):
1084
def test_lock_write_unlockable(self):
1085
transport = MemoryTransport()
1086
client = FakeClient(transport.base)
1087
client.add_expected_call(
1088
'Branch.get_stacked_on_url', ('quack/',),
1089
'error', ('NotStacked',),)
1090
client.add_expected_call(
1091
'Branch.lock_write', ('quack/', '', ''),
1092
'error', ('UnlockableTransport',))
1093
transport.mkdir('quack')
1094
transport = transport.clone('quack')
1095
branch = self.make_remote_branch(transport, client)
1096
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1097
client.finished_test()
1100
class TestTransportIsReadonly(tests.TestCase):
1102
def test_true(self):
1103
client = FakeClient()
1104
client.add_success_response('yes')
1105
transport = RemoteTransport('bzr://example.com/', medium=False,
1107
self.assertEqual(True, transport.is_readonly())
1109
[('call', 'Transport.is_readonly', ())],
1112
def test_false(self):
1113
client = FakeClient()
1114
client.add_success_response('no')
1115
transport = RemoteTransport('bzr://example.com/', medium=False,
1117
self.assertEqual(False, transport.is_readonly())
1119
[('call', 'Transport.is_readonly', ())],
1122
def test_error_from_old_server(self):
1123
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1125
Clients should treat it as a "no" response, because is_readonly is only
1126
advisory anyway (a transport could be read-write, but then the
1127
underlying filesystem could be readonly anyway).
1129
client = FakeClient()
1130
client.add_unknown_method_response('Transport.is_readonly')
1131
transport = RemoteTransport('bzr://example.com/', medium=False,
1133
self.assertEqual(False, transport.is_readonly())
1135
[('call', 'Transport.is_readonly', ())],
1139
class TestTransportMkdir(tests.TestCase):
1141
def test_permissiondenied(self):
1142
client = FakeClient()
1143
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1144
transport = RemoteTransport('bzr://example.com/', medium=False,
1146
exc = self.assertRaises(
1147
errors.PermissionDenied, transport.mkdir, 'client path')
1148
expected_error = errors.PermissionDenied('/client path', 'extra')
1149
self.assertEqual(expected_error, exc)
1152
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1154
def test_defaults_to_none(self):
1155
t = RemoteSSHTransport('bzr+ssh://example.com')
1156
self.assertIs(None, t._get_credentials()[0])
1158
def test_uses_authentication_config(self):
1159
conf = config.AuthenticationConfig()
1160
conf._get_config().update(
1161
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1164
t = RemoteSSHTransport('bzr+ssh://example.com')
1165
self.assertEqual('bar', t._get_credentials()[0])
1168
class TestRemoteRepository(TestRemote):
1169
"""Base for testing RemoteRepository protocol usage.
1171
These tests contain frozen requests and responses. We want any changes to
1172
what is sent or expected to be require a thoughtful update to these tests
1173
because they might break compatibility with different-versioned servers.
1176
def setup_fake_client_and_repository(self, transport_path):
1177
"""Create the fake client and repository for testing with.
1179
There's no real server here; we just have canned responses sent
1182
:param transport_path: Path below the root of the MemoryTransport
1183
where the repository will be created.
1185
transport = MemoryTransport()
1186
transport.mkdir(transport_path)
1187
client = FakeClient(transport.base)
1188
transport = transport.clone(transport_path)
1189
# we do not want bzrdir to make any remote calls
1190
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1192
repo = RemoteRepository(bzrdir, None, _client=client)
1196
class TestRepositoryGatherStats(TestRemoteRepository):
1198
def test_revid_none(self):
1199
# ('ok',), body with revisions and size
1200
transport_path = 'quack'
1201
repo, client = self.setup_fake_client_and_repository(transport_path)
1202
client.add_success_response_with_body(
1203
'revisions: 2\nsize: 18\n', 'ok')
1204
result = repo.gather_stats(None)
1206
[('call_expecting_body', 'Repository.gather_stats',
1207
('quack/','','no'))],
1209
self.assertEqual({'revisions': 2, 'size': 18}, result)
1211
def test_revid_no_committers(self):
1212
# ('ok',), body without committers
1213
body = ('firstrev: 123456.300 3600\n'
1214
'latestrev: 654231.400 0\n'
1217
transport_path = 'quick'
1218
revid = u'\xc8'.encode('utf8')
1219
repo, client = self.setup_fake_client_and_repository(transport_path)
1220
client.add_success_response_with_body(body, 'ok')
1221
result = repo.gather_stats(revid)
1223
[('call_expecting_body', 'Repository.gather_stats',
1224
('quick/', revid, 'no'))],
1226
self.assertEqual({'revisions': 2, 'size': 18,
1227
'firstrev': (123456.300, 3600),
1228
'latestrev': (654231.400, 0),},
1231
def test_revid_with_committers(self):
1232
# ('ok',), body with committers
1233
body = ('committers: 128\n'
1234
'firstrev: 123456.300 3600\n'
1235
'latestrev: 654231.400 0\n'
1238
transport_path = 'buick'
1239
revid = u'\xc8'.encode('utf8')
1240
repo, client = self.setup_fake_client_and_repository(transport_path)
1241
client.add_success_response_with_body(body, 'ok')
1242
result = repo.gather_stats(revid, True)
1244
[('call_expecting_body', 'Repository.gather_stats',
1245
('buick/', revid, 'yes'))],
1247
self.assertEqual({'revisions': 2, 'size': 18,
1249
'firstrev': (123456.300, 3600),
1250
'latestrev': (654231.400, 0),},
1254
class TestRepositoryGetGraph(TestRemoteRepository):
1256
def test_get_graph(self):
1257
# get_graph returns a graph with a custom parents provider.
1258
transport_path = 'quack'
1259
repo, client = self.setup_fake_client_and_repository(transport_path)
1260
graph = repo.get_graph()
1261
self.assertNotEqual(graph._parents_provider, repo)
1264
class TestRepositoryGetParentMap(TestRemoteRepository):
1266
def test_get_parent_map_caching(self):
1267
# get_parent_map returns from cache until unlock()
1268
# setup a reponse with two revisions
1269
r1 = u'\u0e33'.encode('utf8')
1270
r2 = u'\u0dab'.encode('utf8')
1271
lines = [' '.join([r2, r1]), r1]
1272
encoded_body = bz2.compress('\n'.join(lines))
1274
transport_path = 'quack'
1275
repo, client = self.setup_fake_client_and_repository(transport_path)
1276
client.add_success_response_with_body(encoded_body, 'ok')
1277
client.add_success_response_with_body(encoded_body, 'ok')
1279
graph = repo.get_graph()
1280
parents = graph.get_parent_map([r2])
1281
self.assertEqual({r2: (r1,)}, parents)
1282
# locking and unlocking deeper should not reset
1285
parents = graph.get_parent_map([r1])
1286
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1288
[('call_with_body_bytes_expecting_body',
1289
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
1292
# now we call again, and it should use the second response.
1294
graph = repo.get_graph()
1295
parents = graph.get_parent_map([r1])
1296
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1298
[('call_with_body_bytes_expecting_body',
1299
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
1300
('call_with_body_bytes_expecting_body',
1301
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
1306
def test_get_parent_map_reconnects_if_unknown_method(self):
1307
transport_path = 'quack'
1308
repo, client = self.setup_fake_client_and_repository(transport_path)
1309
client.add_unknown_method_response('Repository,get_parent_map')
1310
client.add_success_response_with_body('', 'ok')
1311
self.assertFalse(client._medium._is_remote_before((1, 2)))
1312
rev_id = 'revision-id'
1313
expected_deprecations = [
1314
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1316
parents = self.callDeprecated(
1317
expected_deprecations, repo.get_parent_map, [rev_id])
1319
[('call_with_body_bytes_expecting_body',
1320
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
1321
('disconnect medium',),
1322
('call_expecting_body', 'Repository.get_revision_graph',
1325
# The medium is now marked as being connected to an older server
1326
self.assertTrue(client._medium._is_remote_before((1, 2)))
1328
def test_get_parent_map_fallback_parentless_node(self):
1329
"""get_parent_map falls back to get_revision_graph on old servers. The
1330
results from get_revision_graph are tweaked to match the get_parent_map
1333
Specifically, a {key: ()} result from get_revision_graph means "no
1334
parents" for that key, which in get_parent_map results should be
1335
represented as {key: ('null:',)}.
1337
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1339
rev_id = 'revision-id'
1340
transport_path = 'quack'
1341
repo, client = self.setup_fake_client_and_repository(transport_path)
1342
client.add_success_response_with_body(rev_id, 'ok')
1343
client._medium._remember_remote_is_before((1, 2))
1344
expected_deprecations = [
1345
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1347
parents = self.callDeprecated(
1348
expected_deprecations, repo.get_parent_map, [rev_id])
1350
[('call_expecting_body', 'Repository.get_revision_graph',
1353
self.assertEqual({rev_id: ('null:',)}, parents)
1355
def test_get_parent_map_unexpected_response(self):
1356
repo, client = self.setup_fake_client_and_repository('path')
1357
client.add_success_response('something unexpected!')
1359
errors.UnexpectedSmartServerResponse,
1360
repo.get_parent_map, ['a-revision-id'])
1363
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1365
def test_allows_new_revisions(self):
1366
"""get_parent_map's results can be updated by commit."""
1367
smart_server = server.SmartTCPServer_for_testing()
1368
smart_server.setUp()
1369
self.addCleanup(smart_server.tearDown)
1370
self.make_branch('branch')
1371
branch = Branch.open(smart_server.get_url() + '/branch')
1372
tree = branch.create_checkout('tree', lightweight=True)
1374
self.addCleanup(tree.unlock)
1375
graph = tree.branch.repository.get_graph()
1376
# This provides an opportunity for the missing rev-id to be cached.
1377
self.assertEqual({}, graph.get_parent_map(['rev1']))
1378
tree.commit('message', rev_id='rev1')
1379
graph = tree.branch.repository.get_graph()
1380
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1383
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1385
def test_null_revision(self):
1386
# a null revision has the predictable result {}, we should have no wire
1387
# traffic when calling it with this argument
1388
transport_path = 'empty'
1389
repo, client = self.setup_fake_client_and_repository(transport_path)
1390
client.add_success_response('notused')
1391
result = self.applyDeprecated(one_four, repo.get_revision_graph,
1393
self.assertEqual([], client._calls)
1394
self.assertEqual({}, result)
1396
def test_none_revision(self):
1397
# with none we want the entire graph
1398
r1 = u'\u0e33'.encode('utf8')
1399
r2 = u'\u0dab'.encode('utf8')
1400
lines = [' '.join([r2, r1]), r1]
1401
encoded_body = '\n'.join(lines)
1403
transport_path = 'sinhala'
1404
repo, client = self.setup_fake_client_and_repository(transport_path)
1405
client.add_success_response_with_body(encoded_body, 'ok')
1406
result = self.applyDeprecated(one_four, repo.get_revision_graph)
1408
[('call_expecting_body', 'Repository.get_revision_graph',
1411
self.assertEqual({r1: (), r2: (r1, )}, result)
1413
def test_specific_revision(self):
1414
# with a specific revision we want the graph for that
1415
# with none we want the entire graph
1416
r11 = u'\u0e33'.encode('utf8')
1417
r12 = u'\xc9'.encode('utf8')
1418
r2 = u'\u0dab'.encode('utf8')
1419
lines = [' '.join([r2, r11, r12]), r11, r12]
1420
encoded_body = '\n'.join(lines)
1422
transport_path = 'sinhala'
1423
repo, client = self.setup_fake_client_and_repository(transport_path)
1424
client.add_success_response_with_body(encoded_body, 'ok')
1425
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
1427
[('call_expecting_body', 'Repository.get_revision_graph',
1430
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1432
def test_no_such_revision(self):
1434
transport_path = 'sinhala'
1435
repo, client = self.setup_fake_client_and_repository(transport_path)
1436
client.add_error_response('nosuchrevision', revid)
1437
# also check that the right revision is reported in the error
1438
self.assertRaises(errors.NoSuchRevision,
1439
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1441
[('call_expecting_body', 'Repository.get_revision_graph',
1442
('sinhala/', revid))],
1445
def test_unexpected_error(self):
1447
transport_path = 'sinhala'
1448
repo, client = self.setup_fake_client_and_repository(transport_path)
1449
client.add_error_response('AnUnexpectedError')
1450
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1451
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1452
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1455
class TestRepositoryIsShared(TestRemoteRepository):
1457
def test_is_shared(self):
1458
# ('yes', ) for Repository.is_shared -> 'True'.
1459
transport_path = 'quack'
1460
repo, client = self.setup_fake_client_and_repository(transport_path)
1461
client.add_success_response('yes')
1462
result = repo.is_shared()
1464
[('call', 'Repository.is_shared', ('quack/',))],
1466
self.assertEqual(True, result)
1468
def test_is_not_shared(self):
1469
# ('no', ) for Repository.is_shared -> 'False'.
1470
transport_path = 'qwack'
1471
repo, client = self.setup_fake_client_and_repository(transport_path)
1472
client.add_success_response('no')
1473
result = repo.is_shared()
1475
[('call', 'Repository.is_shared', ('qwack/',))],
1477
self.assertEqual(False, result)
1480
class TestRepositoryLockWrite(TestRemoteRepository):
1482
def test_lock_write(self):
1483
transport_path = 'quack'
1484
repo, client = self.setup_fake_client_and_repository(transport_path)
1485
client.add_success_response('ok', 'a token')
1486
result = repo.lock_write()
1488
[('call', 'Repository.lock_write', ('quack/', ''))],
1490
self.assertEqual('a token', result)
1492
def test_lock_write_already_locked(self):
1493
transport_path = 'quack'
1494
repo, client = self.setup_fake_client_and_repository(transport_path)
1495
client.add_error_response('LockContention')
1496
self.assertRaises(errors.LockContention, repo.lock_write)
1498
[('call', 'Repository.lock_write', ('quack/', ''))],
1501
def test_lock_write_unlockable(self):
1502
transport_path = 'quack'
1503
repo, client = self.setup_fake_client_and_repository(transport_path)
1504
client.add_error_response('UnlockableTransport')
1505
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1507
[('call', 'Repository.lock_write', ('quack/', ''))],
1511
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1513
def test_backwards_compat(self):
1514
self.setup_smart_server_with_call_log()
1515
repo = self.make_repository('.')
1516
self.reset_smart_call_log()
1517
verb = 'Repository.set_make_working_trees'
1518
self.disable_verb(verb)
1519
repo.set_make_working_trees(True)
1520
call_count = len([call for call in self.hpss_calls if
1521
call[0].method == verb])
1522
self.assertEqual(1, call_count)
1524
def test_current(self):
1525
transport_path = 'quack'
1526
repo, client = self.setup_fake_client_and_repository(transport_path)
1527
client.add_expected_call(
1528
'Repository.set_make_working_trees', ('quack/', 'True'),
1530
client.add_expected_call(
1531
'Repository.set_make_working_trees', ('quack/', 'False'),
1533
repo.set_make_working_trees(True)
1534
repo.set_make_working_trees(False)
1537
class TestRepositoryUnlock(TestRemoteRepository):
1539
def test_unlock(self):
1540
transport_path = 'quack'
1541
repo, client = self.setup_fake_client_and_repository(transport_path)
1542
client.add_success_response('ok', 'a token')
1543
client.add_success_response('ok')
1547
[('call', 'Repository.lock_write', ('quack/', '')),
1548
('call', 'Repository.unlock', ('quack/', 'a token'))],
1551
def test_unlock_wrong_token(self):
1552
# If somehow the token is wrong, unlock will raise TokenMismatch.
1553
transport_path = 'quack'
1554
repo, client = self.setup_fake_client_and_repository(transport_path)
1555
client.add_success_response('ok', 'a token')
1556
client.add_error_response('TokenMismatch')
1558
self.assertRaises(errors.TokenMismatch, repo.unlock)
1561
class TestRepositoryHasRevision(TestRemoteRepository):
1563
def test_none(self):
1564
# repo.has_revision(None) should not cause any traffic.
1565
transport_path = 'quack'
1566
repo, client = self.setup_fake_client_and_repository(transport_path)
1568
# The null revision is always there, so has_revision(None) == True.
1569
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1571
# The remote repo shouldn't be accessed.
1572
self.assertEqual([], client._calls)
1575
class TestRepositoryTarball(TestRemoteRepository):
1577
# This is a canned tarball reponse we can validate against
1579
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1580
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1581
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1582
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1583
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1584
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1585
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1586
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1587
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1588
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1589
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1590
'nWQ7QH/F3JFOFCQ0aSPfA='
1593
def test_repository_tarball(self):
1594
# Test that Repository.tarball generates the right operations
1595
transport_path = 'repo'
1596
expected_calls = [('call_expecting_body', 'Repository.tarball',
1597
('repo/', 'bz2',),),
1599
repo, client = self.setup_fake_client_and_repository(transport_path)
1600
client.add_success_response_with_body(self.tarball_content, 'ok')
1601
# Now actually ask for the tarball
1602
tarball_file = repo._get_tarball('bz2')
1604
self.assertEqual(expected_calls, client._calls)
1605
self.assertEqual(self.tarball_content, tarball_file.read())
1607
tarball_file.close()
1610
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
1611
"""RemoteRepository.copy_content_into optimizations"""
1613
def test_copy_content_remote_to_local(self):
1614
self.transport_server = server.SmartTCPServer_for_testing
1615
src_repo = self.make_repository('repo1')
1616
src_repo = repository.Repository.open(self.get_url('repo1'))
1617
# At the moment the tarball-based copy_content_into can't write back
1618
# into a smart server. It would be good if it could upload the
1619
# tarball; once that works we'd have to create repositories of
1620
# different formats. -- mbp 20070410
1621
dest_url = self.get_vfs_only_url('repo2')
1622
dest_bzrdir = BzrDir.create(dest_url)
1623
dest_repo = dest_bzrdir.create_repository()
1624
self.assertFalse(isinstance(dest_repo, RemoteRepository))
1625
self.assertTrue(isinstance(src_repo, RemoteRepository))
1626
src_repo.copy_content_into(dest_repo)
1629
class _StubRealPackRepository(object):
1631
def __init__(self, calls):
1632
self._pack_collection = _StubPackCollection(calls)
1635
class _StubPackCollection(object):
1637
def __init__(self, calls):
1641
self.calls.append(('pack collection autopack',))
1643
def reload_pack_names(self):
1644
self.calls.append(('pack collection reload_pack_names',))
1647
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1648
"""Tests for RemoteRepository.autopack implementation."""
1651
"""When the server returns 'ok' and there's no _real_repository, then
1652
nothing else happens: the autopack method is done.
1654
transport_path = 'quack'
1655
repo, client = self.setup_fake_client_and_repository(transport_path)
1656
client.add_expected_call(
1657
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
1659
client.finished_test()
1661
def test_ok_with_real_repo(self):
1662
"""When the server returns 'ok' and there is a _real_repository, then
1663
the _real_repository's reload_pack_name's method will be called.
1665
transport_path = 'quack'
1666
repo, client = self.setup_fake_client_and_repository(transport_path)
1667
client.add_expected_call(
1668
'PackRepository.autopack', ('quack/',),
1670
repo._real_repository = _StubRealPackRepository(client._calls)
1673
[('call', 'PackRepository.autopack', ('quack/',)),
1674
('pack collection reload_pack_names',)],
1677
def test_backwards_compatibility(self):
1678
"""If the server does not recognise the PackRepository.autopack verb,
1679
fallback to the real_repository's implementation.
1681
transport_path = 'quack'
1682
repo, client = self.setup_fake_client_and_repository(transport_path)
1683
client.add_unknown_method_response('PackRepository.autopack')
1684
def stub_ensure_real():
1685
client._calls.append(('_ensure_real',))
1686
repo._real_repository = _StubRealPackRepository(client._calls)
1687
repo._ensure_real = stub_ensure_real
1690
[('call', 'PackRepository.autopack', ('quack/',)),
1692
('pack collection autopack',)],
1696
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
1697
"""Base class for unit tests for bzrlib.remote._translate_error."""
1699
def translateTuple(self, error_tuple, **context):
1700
"""Call _translate_error with an ErrorFromSmartServer built from the
1703
:param error_tuple: A tuple of a smart server response, as would be
1704
passed to an ErrorFromSmartServer.
1705
:kwargs context: context items to call _translate_error with.
1707
:returns: The error raised by _translate_error.
1709
# Raise the ErrorFromSmartServer before passing it as an argument,
1710
# because _translate_error may need to re-raise it with a bare 'raise'
1712
server_error = errors.ErrorFromSmartServer(error_tuple)
1713
translated_error = self.translateErrorFromSmartServer(
1714
server_error, **context)
1715
return translated_error
1717
def translateErrorFromSmartServer(self, error_object, **context):
1718
"""Like translateTuple, but takes an already constructed
1719
ErrorFromSmartServer rather than a tuple.
1723
except errors.ErrorFromSmartServer, server_error:
1724
translated_error = self.assertRaises(
1725
errors.BzrError, remote._translate_error, server_error,
1727
return translated_error
1730
class TestErrorTranslationSuccess(TestErrorTranslationBase):
1731
"""Unit tests for bzrlib.remote._translate_error.
1733
Given an ErrorFromSmartServer (which has an error tuple from a smart
1734
server) and some context, _translate_error raises more specific errors from
1737
This test case covers the cases where _translate_error succeeds in
1738
translating an ErrorFromSmartServer to something better. See
1739
TestErrorTranslationRobustness for other cases.
1742
def test_NoSuchRevision(self):
1743
branch = self.make_branch('')
1745
translated_error = self.translateTuple(
1746
('NoSuchRevision', revid), branch=branch)
1747
expected_error = errors.NoSuchRevision(branch, revid)
1748
self.assertEqual(expected_error, translated_error)
1750
def test_nosuchrevision(self):
1751
repository = self.make_repository('')
1753
translated_error = self.translateTuple(
1754
('nosuchrevision', revid), repository=repository)
1755
expected_error = errors.NoSuchRevision(repository, revid)
1756
self.assertEqual(expected_error, translated_error)
1758
def test_nobranch(self):
1759
bzrdir = self.make_bzrdir('')
1760
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
1761
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
1762
self.assertEqual(expected_error, translated_error)
1764
def test_LockContention(self):
1765
translated_error = self.translateTuple(('LockContention',))
1766
expected_error = errors.LockContention('(remote lock)')
1767
self.assertEqual(expected_error, translated_error)
1769
def test_UnlockableTransport(self):
1770
bzrdir = self.make_bzrdir('')
1771
translated_error = self.translateTuple(
1772
('UnlockableTransport',), bzrdir=bzrdir)
1773
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
1774
self.assertEqual(expected_error, translated_error)
1776
def test_LockFailed(self):
1777
lock = 'str() of a server lock'
1778
why = 'str() of why'
1779
translated_error = self.translateTuple(('LockFailed', lock, why))
1780
expected_error = errors.LockFailed(lock, why)
1781
self.assertEqual(expected_error, translated_error)
1783
def test_TokenMismatch(self):
1784
token = 'a lock token'
1785
translated_error = self.translateTuple(('TokenMismatch',), token=token)
1786
expected_error = errors.TokenMismatch(token, '(remote token)')
1787
self.assertEqual(expected_error, translated_error)
1789
def test_Diverged(self):
1790
branch = self.make_branch('a')
1791
other_branch = self.make_branch('b')
1792
translated_error = self.translateTuple(
1793
('Diverged',), branch=branch, other_branch=other_branch)
1794
expected_error = errors.DivergedBranches(branch, other_branch)
1795
self.assertEqual(expected_error, translated_error)
1797
def test_ReadError_no_args(self):
1799
translated_error = self.translateTuple(('ReadError',), path=path)
1800
expected_error = errors.ReadError(path)
1801
self.assertEqual(expected_error, translated_error)
1803
def test_ReadError(self):
1805
translated_error = self.translateTuple(('ReadError', path))
1806
expected_error = errors.ReadError(path)
1807
self.assertEqual(expected_error, translated_error)
1809
def test_PermissionDenied_no_args(self):
1811
translated_error = self.translateTuple(('PermissionDenied',), path=path)
1812
expected_error = errors.PermissionDenied(path)
1813
self.assertEqual(expected_error, translated_error)
1815
def test_PermissionDenied_one_arg(self):
1817
translated_error = self.translateTuple(('PermissionDenied', path))
1818
expected_error = errors.PermissionDenied(path)
1819
self.assertEqual(expected_error, translated_error)
1821
def test_PermissionDenied_one_arg_and_context(self):
1822
"""Given a choice between a path from the local context and a path on
1823
the wire, _translate_error prefers the path from the local context.
1825
local_path = 'local path'
1826
remote_path = 'remote path'
1827
translated_error = self.translateTuple(
1828
('PermissionDenied', remote_path), path=local_path)
1829
expected_error = errors.PermissionDenied(local_path)
1830
self.assertEqual(expected_error, translated_error)
1832
def test_PermissionDenied_two_args(self):
1834
extra = 'a string with extra info'
1835
translated_error = self.translateTuple(
1836
('PermissionDenied', path, extra))
1837
expected_error = errors.PermissionDenied(path, extra)
1838
self.assertEqual(expected_error, translated_error)
1841
class TestErrorTranslationRobustness(TestErrorTranslationBase):
1842
"""Unit tests for bzrlib.remote._translate_error's robustness.
1844
TestErrorTranslationSuccess is for cases where _translate_error can
1845
translate successfully. This class about how _translate_err behaves when
1846
it fails to translate: it re-raises the original error.
1849
def test_unrecognised_server_error(self):
1850
"""If the error code from the server is not recognised, the original
1851
ErrorFromSmartServer is propagated unmodified.
1853
error_tuple = ('An unknown error tuple',)
1854
server_error = errors.ErrorFromSmartServer(error_tuple)
1855
translated_error = self.translateErrorFromSmartServer(server_error)
1856
expected_error = errors.UnknownErrorFromSmartServer(server_error)
1857
self.assertEqual(expected_error, translated_error)
1859
def test_context_missing_a_key(self):
1860
"""In case of a bug in the client, or perhaps an unexpected response
1861
from a server, _translate_error returns the original error tuple from
1862
the server and mutters a warning.
1864
# To translate a NoSuchRevision error _translate_error needs a 'branch'
1865
# in the context dict. So let's give it an empty context dict instead
1866
# to exercise its error recovery.
1868
error_tuple = ('NoSuchRevision', 'revid')
1869
server_error = errors.ErrorFromSmartServer(error_tuple)
1870
translated_error = self.translateErrorFromSmartServer(server_error)
1871
self.assertEqual(server_error, translated_error)
1872
# In addition to re-raising ErrorFromSmartServer, some debug info has
1873
# been muttered to the log file for developer to look at.
1874
self.assertContainsRe(
1875
self._get_log(keep_log_file=True),
1876
"Missing key 'branch' in context")
1878
def test_path_missing(self):
1879
"""Some translations (PermissionDenied, ReadError) can determine the
1880
'path' variable from either the wire or the local context. If neither
1881
has it, then an error is raised.
1883
error_tuple = ('ReadError',)
1884
server_error = errors.ErrorFromSmartServer(error_tuple)
1885
translated_error = self.translateErrorFromSmartServer(server_error)
1886
self.assertEqual(server_error, translated_error)
1887
# In addition to re-raising ErrorFromSmartServer, some debug info has
1888
# been muttered to the log file for developer to look at.
1889
self.assertContainsRe(
1890
self._get_log(keep_log_file=True), "Missing key 'path' in context")
1893
class TestStacking(tests.TestCaseWithTransport):
1894
"""Tests for operations on stacked remote repositories.
1896
The underlying format type must support stacking.
1899
def test_access_stacked_remote(self):
1900
# based on <http://launchpad.net/bugs/261315>
1901
# make a branch stacked on another repository containing an empty
1902
# revision, then open it over hpss - we should be able to see that
1904
base_transport = self.get_transport()
1905
base_builder = self.make_branch_builder('base', format='1.6')
1906
base_builder.start_series()
1907
base_revid = base_builder.build_snapshot('rev-id', None,
1908
[('add', ('', None, 'directory', None))],
1910
base_builder.finish_series()
1911
stacked_branch = self.make_branch('stacked', format='1.6')
1912
stacked_branch.set_stacked_on_url('../base')
1913
# start a server looking at this
1914
smart_server = server.SmartTCPServer_for_testing()
1915
smart_server.setUp()
1916
self.addCleanup(smart_server.tearDown)
1917
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
1918
# can get its branch and repository
1919
remote_branch = remote_bzrdir.open_branch()
1920
remote_repo = remote_branch.repository
1921
remote_repo.lock_read()
1923
# it should have an appropriate fallback repository, which should also
1924
# be a RemoteRepository
1925
self.assertEquals(len(remote_repo._fallback_repositories), 1)
1926
self.assertIsInstance(remote_repo._fallback_repositories[0],
1928
# and it has the revision committed to the underlying repository;
1929
# these have varying implementations so we try several of them
1930
self.assertTrue(remote_repo.has_revisions([base_revid]))
1931
self.assertTrue(remote_repo.has_revision(base_revid))
1932
self.assertEqual(remote_repo.get_revision(base_revid).message,
1935
remote_repo.unlock()
1937
def prepare_stacked_remote_branch(self):
1938
smart_server = server.SmartTCPServer_for_testing()
1939
smart_server.setUp()
1940
self.addCleanup(smart_server.tearDown)
1941
tree1 = self.make_branch_and_tree('tree1')
1942
tree1.commit('rev1', rev_id='rev1')
1943
tree2 = self.make_branch_and_tree('tree2', format='1.6')
1944
tree2.branch.set_stacked_on_url(tree1.branch.base)
1945
branch2 = Branch.open(smart_server.get_url() + '/tree2')
1947
self.addCleanup(branch2.unlock)
1950
def test_stacked_get_parent_map(self):
1951
# the public implementation of get_parent_map obeys stacking
1952
branch = self.prepare_stacked_remote_branch()
1953
repo = branch.repository
1954
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
1956
def test_unstacked_get_parent_map(self):
1957
# _unstacked_provider.get_parent_map ignores stacking
1958
branch = self.prepare_stacked_remote_branch()
1959
provider = branch.repository._unstacked_provider
1960
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
1963
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
1966
super(TestRemoteBranchEffort, self).setUp()
1967
# Create a smart server that publishes whatever the backing VFS server
1969
self.smart_server = server.SmartTCPServer_for_testing()
1970
self.smart_server.setUp(self.get_server())
1971
self.addCleanup(self.smart_server.tearDown)
1972
# Log all HPSS calls into self.hpss_calls.
1973
_SmartClient.hooks.install_named_hook(
1974
'call', self.capture_hpss_call, None)
1975
self.hpss_calls = []
1977
def capture_hpss_call(self, params):
1978
self.hpss_calls.append(params.method)
1980
def test_copy_content_into_avoids_revision_history(self):
1981
local = self.make_branch('local')
1982
remote_backing_tree = self.make_branch_and_tree('remote')
1983
remote_backing_tree.commit("Commit.")
1984
remote_branch_url = self.smart_server.get_url() + 'remote'
1985
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
1986
local.repository.fetch(remote_branch.repository)
1987
self.hpss_calls = []
1988
remote_branch.copy_content_into(local)
1989
self.assertFalse('Branch.revision_history' in self.hpss_calls)