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 get_repo_format(self):
266
reference_bzrdir_format = bzrdir.format_registry.get('default')()
267
return reference_bzrdir_format.repository_format
269
def disable_verb(self, verb):
270
"""Disable a verb for one test."""
271
request_handlers = smart.request.request_handlers
272
orig_method = request_handlers.get(verb)
273
request_handlers.remove(verb)
275
request_handlers.register(verb, orig_method)
276
self.addCleanup(restoreVerb)
279
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
280
"""Tests for the behaviour of client_medium.remote_path_from_transport."""
282
def assertRemotePath(self, expected, client_base, transport_base):
283
"""Assert that the result of
284
SmartClientMedium.remote_path_from_transport is the expected value for
285
a given client_base and transport_base.
287
client_medium = medium.SmartClientMedium(client_base)
288
transport = get_transport(transport_base)
289
result = client_medium.remote_path_from_transport(transport)
290
self.assertEqual(expected, result)
292
def test_remote_path_from_transport(self):
293
"""SmartClientMedium.remote_path_from_transport calculates a URL for
294
the given transport relative to the root of the client base URL.
296
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
297
self.assertRemotePath(
298
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
300
def assertRemotePathHTTP(self, expected, transport_base, relpath):
301
"""Assert that the result of
302
HttpTransportBase.remote_path_from_transport is the expected value for
303
a given transport_base and relpath of that transport. (Note that
304
HttpTransportBase is a subclass of SmartClientMedium)
306
base_transport = get_transport(transport_base)
307
client_medium = base_transport.get_smart_medium()
308
cloned_transport = base_transport.clone(relpath)
309
result = client_medium.remote_path_from_transport(cloned_transport)
310
self.assertEqual(expected, result)
312
def test_remote_path_from_transport_http(self):
313
"""Remote paths for HTTP transports are calculated differently to other
314
transports. They are just relative to the client base, not the root
315
directory of the host.
317
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
318
self.assertRemotePathHTTP(
319
'../xyz/', scheme + '//host/path', '../xyz/')
320
self.assertRemotePathHTTP(
321
'xyz/', scheme + '//host/path', 'xyz/')
324
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
325
"""Tests for the behaviour of client_medium.remote_is_at_least."""
327
def test_initially_unlimited(self):
328
"""A fresh medium assumes that the remote side supports all
331
client_medium = medium.SmartClientMedium('dummy base')
332
self.assertFalse(client_medium._is_remote_before((99, 99)))
334
def test__remember_remote_is_before(self):
335
"""Calling _remember_remote_is_before ratchets down the known remote
338
client_medium = medium.SmartClientMedium('dummy base')
339
# Mark the remote side as being less than 1.6. The remote side may
341
client_medium._remember_remote_is_before((1, 6))
342
self.assertTrue(client_medium._is_remote_before((1, 6)))
343
self.assertFalse(client_medium._is_remote_before((1, 5)))
344
# Calling _remember_remote_is_before again with a lower value works.
345
client_medium._remember_remote_is_before((1, 5))
346
self.assertTrue(client_medium._is_remote_before((1, 5)))
347
# You cannot call _remember_remote_is_before with a larger value.
349
AssertionError, client_medium._remember_remote_is_before, (1, 9))
352
class TestBzrDirOpenBranch(TestRemote):
354
def test_branch_present(self):
355
reference_format = self.get_repo_format()
356
network_name = reference_format.network_name()
357
transport = MemoryTransport()
358
transport.mkdir('quack')
359
transport = transport.clone('quack')
360
client = FakeClient(transport.base)
361
client.add_expected_call(
362
'BzrDir.open_branch', ('quack/',),
363
'success', ('ok', ''))
364
client.add_expected_call(
365
'BzrDir.find_repositoryV3', ('quack/',),
366
'success', ('ok', '', 'no', 'no', 'no', network_name))
367
client.add_expected_call(
368
'Branch.get_stacked_on_url', ('quack/',),
369
'error', ('NotStacked',))
370
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
372
result = bzrdir.open_branch()
373
self.assertIsInstance(result, RemoteBranch)
374
self.assertEqual(bzrdir, result.bzrdir)
375
client.finished_test()
377
def test_branch_missing(self):
378
transport = MemoryTransport()
379
transport.mkdir('quack')
380
transport = transport.clone('quack')
381
client = FakeClient(transport.base)
382
client.add_error_response('nobranch')
383
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
385
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
387
[('call', 'BzrDir.open_branch', ('quack/',))],
390
def test__get_tree_branch(self):
391
# _get_tree_branch is a form of open_branch, but it should only ask for
392
# branch opening, not any other network requests.
395
calls.append("Called")
397
transport = MemoryTransport()
398
# no requests on the network - catches other api calls being made.
399
client = FakeClient(transport.base)
400
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
402
# patch the open_branch call to record that it was called.
403
bzrdir.open_branch = open_branch
404
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
405
self.assertEqual(["Called"], calls)
406
self.assertEqual([], client._calls)
408
def test_url_quoting_of_path(self):
409
# Relpaths on the wire should not be URL-escaped. So "~" should be
410
# transmitted as "~", not "%7E".
411
transport = RemoteTCPTransport('bzr://localhost/~hello/')
412
client = FakeClient(transport.base)
413
reference_format = self.get_repo_format()
414
network_name = reference_format.network_name()
415
client.add_expected_call(
416
'BzrDir.open_branch', ('~hello/',),
417
'success', ('ok', ''))
418
client.add_expected_call(
419
'BzrDir.find_repositoryV3', ('~hello/',),
420
'success', ('ok', '', 'no', 'no', 'no', network_name))
421
client.add_expected_call(
422
'Branch.get_stacked_on_url', ('~hello/',),
423
'error', ('NotStacked',))
424
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
426
result = bzrdir.open_branch()
427
client.finished_test()
429
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
430
reference_format = self.get_repo_format()
431
network_name = reference_format.network_name()
432
transport = MemoryTransport()
433
transport.mkdir('quack')
434
transport = transport.clone('quack')
436
rich_response = 'yes'
440
subtree_response = 'yes'
442
subtree_response = 'no'
443
client = FakeClient(transport.base)
444
client.add_success_response(
445
'ok', '', rich_response, subtree_response, external_lookup,
447
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
449
result = bzrdir.open_repository()
451
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
453
self.assertIsInstance(result, RemoteRepository)
454
self.assertEqual(bzrdir, result.bzrdir)
455
self.assertEqual(rich_root, result._format.rich_root_data)
456
self.assertEqual(subtrees, result._format.supports_tree_reference)
458
def test_open_repository_sets_format_attributes(self):
459
self.check_open_repository(True, True)
460
self.check_open_repository(False, True)
461
self.check_open_repository(True, False)
462
self.check_open_repository(False, False)
463
self.check_open_repository(False, False, 'yes')
465
def test_old_server(self):
466
"""RemoteBzrDirFormat should fail to probe if the server version is too
469
self.assertRaises(errors.NotBranchError,
470
RemoteBzrDirFormat.probe_transport, OldServerTransport())
473
class TestBzrDirCreateBranch(TestRemote):
475
def test_backwards_compat(self):
476
self.setup_smart_server_with_call_log()
477
repo = self.make_repository('.')
478
self.reset_smart_call_log()
479
self.disable_verb('BzrDir.create_branch')
480
branch = repo.bzrdir.create_branch()
481
create_branch_call_count = len([call for call in self.hpss_calls if
482
call[0].method == 'BzrDir.create_branch'])
483
self.assertEqual(1, create_branch_call_count)
485
def test_current_server(self):
486
transport = self.get_transport('.')
487
transport = transport.clone('quack')
488
self.make_repository('quack')
489
client = FakeClient(transport.base)
490
reference_bzrdir_format = bzrdir.format_registry.get('default')()
491
reference_format = reference_bzrdir_format.get_branch_format()
492
network_name = reference_format.network_name()
493
reference_repo_fmt = reference_bzrdir_format.repository_format
494
reference_repo_name = reference_repo_fmt.network_name()
495
client.add_expected_call(
496
'BzrDir.create_branch', ('quack/', network_name),
497
'success', ('ok', network_name, '', 'no', 'no', 'yes',
498
reference_repo_name))
499
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
501
branch = a_bzrdir.create_branch()
502
# We should have got a remote branch
503
self.assertIsInstance(branch, remote.RemoteBranch)
504
# its format should have the settings from the response
505
format = branch._format
506
self.assertEqual(network_name, format.network_name())
509
class TestBzrDirCreateRepository(TestRemote):
511
def test_backwards_compat(self):
512
self.setup_smart_server_with_call_log()
513
bzrdir = self.make_bzrdir('.')
514
self.reset_smart_call_log()
515
self.disable_verb('BzrDir.create_repository')
516
repo = bzrdir.create_repository()
517
create_repo_call_count = len([call for call in self.hpss_calls if
518
call[0].method == 'BzrDir.create_repository'])
519
self.assertEqual(1, create_repo_call_count)
521
def test_current_server(self):
522
transport = self.get_transport('.')
523
transport = transport.clone('quack')
524
self.make_bzrdir('quack')
525
client = FakeClient(transport.base)
526
reference_bzrdir_format = bzrdir.format_registry.get('default')()
527
reference_format = reference_bzrdir_format.repository_format
528
network_name = reference_format.network_name()
529
client.add_expected_call(
530
'BzrDir.create_repository', ('quack/',
531
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
532
'success', ('ok', 'no', 'no', 'no', network_name))
533
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
535
repo = a_bzrdir.create_repository()
536
# We should have got a remote repository
537
self.assertIsInstance(repo, remote.RemoteRepository)
538
# its format should have the settings from the response
539
format = repo._format
540
self.assertFalse(format.rich_root_data)
541
self.assertFalse(format.supports_tree_reference)
542
self.assertFalse(format.supports_external_lookups)
543
self.assertEqual(network_name, format.network_name())
546
class TestBzrDirOpenRepository(TestRemote):
548
def test_backwards_compat_1_2_3(self):
549
# fallback all the way to the first version.
550
reference_format = self.get_repo_format()
551
network_name = reference_format.network_name()
552
client = FakeClient('bzr://example.com/')
553
client.add_unknown_method_response('BzrDir.find_repositoryV3')
554
client.add_unknown_method_response('BzrDir.find_repositoryV2')
555
client.add_success_response('ok', '', 'no', 'no')
556
# A real repository instance will be created to determine the network
558
client.add_success_response_with_body(
559
"Bazaar-NG meta directory, format 1\n", 'ok')
560
client.add_success_response_with_body(
561
reference_format.get_format_string(), 'ok')
562
# PackRepository wants to do a stat
563
client.add_success_response('stat', '0', '65535')
564
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
566
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
568
repo = bzrdir.open_repository()
570
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
571
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
572
('call', 'BzrDir.find_repository', ('quack/',)),
573
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
574
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
575
('call', 'stat', ('/quack/.bzr/repository',)),
578
self.assertEqual(network_name, repo._format.network_name())
580
def test_backwards_compat_2(self):
581
# fallback to find_repositoryV2
582
reference_format = self.get_repo_format()
583
network_name = reference_format.network_name()
584
client = FakeClient('bzr://example.com/')
585
client.add_unknown_method_response('BzrDir.find_repositoryV3')
586
client.add_success_response('ok', '', 'no', 'no', 'no')
587
# A real repository instance will be created to determine the network
589
client.add_success_response_with_body(
590
"Bazaar-NG meta directory, format 1\n", 'ok')
591
client.add_success_response_with_body(
592
reference_format.get_format_string(), 'ok')
593
# PackRepository wants to do a stat
594
client.add_success_response('stat', '0', '65535')
595
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
597
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
599
repo = bzrdir.open_repository()
601
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
602
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
603
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
604
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
605
('call', 'stat', ('/quack/.bzr/repository',)),
608
self.assertEqual(network_name, repo._format.network_name())
610
def test_current_server(self):
611
reference_format = self.get_repo_format()
612
network_name = reference_format.network_name()
613
transport = MemoryTransport()
614
transport.mkdir('quack')
615
transport = transport.clone('quack')
616
client = FakeClient(transport.base)
617
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
618
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
620
repo = bzrdir.open_repository()
622
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
624
self.assertEqual(network_name, repo._format.network_name())
627
class OldSmartClient(object):
628
"""A fake smart client for test_old_version that just returns a version one
629
response to the 'hello' (query version) command.
632
def get_request(self):
633
input_file = StringIO('ok\x011\n')
634
output_file = StringIO()
635
client_medium = medium.SmartSimplePipesClientMedium(
636
input_file, output_file)
637
return medium.SmartClientStreamMediumRequest(client_medium)
639
def protocol_version(self):
643
class OldServerTransport(object):
644
"""A fake transport for test_old_server that reports it's smart server
645
protocol version as version one.
651
def get_smart_client(self):
652
return OldSmartClient()
655
class RemoteBranchTestCase(tests.TestCase):
657
def make_remote_branch(self, transport, client):
658
"""Make a RemoteBranch using 'client' as its _SmartClient.
660
A RemoteBzrDir and RemoteRepository will also be created to fill out
661
the RemoteBranch, albeit with stub values for some of their attributes.
663
# we do not want bzrdir to make any remote calls, so use False as its
664
# _client. If it tries to make a remote call, this will fail
666
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
668
repo = RemoteRepository(bzrdir, None, _client=client)
669
return RemoteBranch(bzrdir, repo, _client=client)
672
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
674
def test_empty_branch(self):
675
# in an empty branch we decode the response properly
676
transport = MemoryTransport()
677
client = FakeClient(transport.base)
678
client.add_expected_call(
679
'Branch.get_stacked_on_url', ('quack/',),
680
'error', ('NotStacked',))
681
client.add_expected_call(
682
'Branch.last_revision_info', ('quack/',),
683
'success', ('ok', '0', 'null:'))
684
transport.mkdir('quack')
685
transport = transport.clone('quack')
686
branch = self.make_remote_branch(transport, client)
687
result = branch.last_revision_info()
688
client.finished_test()
689
self.assertEqual((0, NULL_REVISION), result)
691
def test_non_empty_branch(self):
692
# in a non-empty branch we also decode the response properly
693
revid = u'\xc8'.encode('utf8')
694
transport = MemoryTransport()
695
client = FakeClient(transport.base)
696
client.add_expected_call(
697
'Branch.get_stacked_on_url', ('kwaak/',),
698
'error', ('NotStacked',))
699
client.add_expected_call(
700
'Branch.last_revision_info', ('kwaak/',),
701
'success', ('ok', '2', revid))
702
transport.mkdir('kwaak')
703
transport = transport.clone('kwaak')
704
branch = self.make_remote_branch(transport, client)
705
result = branch.last_revision_info()
706
self.assertEqual((2, revid), result)
709
class TestBranch_get_stacked_on_url(TestRemote):
710
"""Test Branch._get_stacked_on_url rpc"""
712
def test_get_stacked_on_invalid_url(self):
713
# test that asking for a stacked on url the server can't access works.
714
# This isn't perfect, but then as we're in the same process there
715
# really isn't anything we can do to be 100% sure that the server
716
# doesn't just open in - this test probably needs to be rewritten using
717
# a spawn()ed server.
718
stacked_branch = self.make_branch('stacked', format='1.9')
719
memory_branch = self.make_branch('base', format='1.9')
720
vfs_url = self.get_vfs_only_url('base')
721
stacked_branch.set_stacked_on_url(vfs_url)
722
transport = stacked_branch.bzrdir.root_transport
723
client = FakeClient(transport.base)
724
client.add_expected_call(
725
'Branch.get_stacked_on_url', ('stacked/',),
726
'success', ('ok', vfs_url))
727
# XXX: Multiple calls are bad, this second call documents what is
729
client.add_expected_call(
730
'Branch.get_stacked_on_url', ('stacked/',),
731
'success', ('ok', vfs_url))
732
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
734
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, None),
736
result = branch.get_stacked_on_url()
737
self.assertEqual(vfs_url, result)
739
def test_backwards_compatible(self):
740
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
741
base_branch = self.make_branch('base', format='1.6')
742
stacked_branch = self.make_branch('stacked', format='1.6')
743
stacked_branch.set_stacked_on_url('../base')
744
client = FakeClient(self.get_url())
745
client.add_expected_call(
746
'BzrDir.open_branch', ('stacked/',),
747
'success', ('ok', ''))
748
client.add_expected_call(
749
'BzrDir.find_repositoryV3', ('stacked/',),
750
'success', ('ok', '', 'no', 'no', 'no',
751
stacked_branch.repository._format.network_name()))
752
# called twice, once from constructor and then again by us
753
client.add_expected_call(
754
'Branch.get_stacked_on_url', ('stacked/',),
755
'unknown', ('Branch.get_stacked_on_url',))
756
client.add_expected_call(
757
'Branch.get_stacked_on_url', ('stacked/',),
758
'unknown', ('Branch.get_stacked_on_url',))
759
# this will also do vfs access, but that goes direct to the transport
760
# and isn't seen by the FakeClient.
761
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
762
remote.RemoteBzrDirFormat(), _client=client)
763
branch = bzrdir.open_branch()
764
result = branch.get_stacked_on_url()
765
self.assertEqual('../base', result)
766
client.finished_test()
767
# it's in the fallback list both for the RemoteRepository and its vfs
769
self.assertEqual(1, len(branch.repository._fallback_repositories))
771
len(branch.repository._real_repository._fallback_repositories))
773
def test_get_stacked_on_real_branch(self):
774
base_branch = self.make_branch('base', format='1.6')
775
stacked_branch = self.make_branch('stacked', format='1.6')
776
stacked_branch.set_stacked_on_url('../base')
777
reference_format = self.get_repo_format()
778
network_name = reference_format.network_name()
779
client = FakeClient(self.get_url())
780
client.add_expected_call(
781
'BzrDir.open_branch', ('stacked/',),
782
'success', ('ok', ''))
783
client.add_expected_call(
784
'BzrDir.find_repositoryV3', ('stacked/',),
785
'success', ('ok', '', 'no', 'no', 'no', network_name))
786
# called twice, once from constructor and then again by us
787
client.add_expected_call(
788
'Branch.get_stacked_on_url', ('stacked/',),
789
'success', ('ok', '../base'))
790
client.add_expected_call(
791
'Branch.get_stacked_on_url', ('stacked/',),
792
'success', ('ok', '../base'))
793
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
794
remote.RemoteBzrDirFormat(), _client=client)
795
branch = bzrdir.open_branch()
796
result = branch.get_stacked_on_url()
797
self.assertEqual('../base', result)
798
client.finished_test()
799
# it's in the fallback list both for the RemoteRepository and its vfs
801
self.assertEqual(1, len(branch.repository._fallback_repositories))
803
len(branch.repository._real_repository._fallback_repositories))
806
class TestBranchSetLastRevision(RemoteBranchTestCase):
808
def test_set_empty(self):
809
# set_revision_history([]) is translated to calling
810
# Branch.set_last_revision(path, '') on the wire.
811
transport = MemoryTransport()
812
transport.mkdir('branch')
813
transport = transport.clone('branch')
815
client = FakeClient(transport.base)
816
client.add_expected_call(
817
'Branch.get_stacked_on_url', ('branch/',),
818
'error', ('NotStacked',))
819
client.add_expected_call(
820
'Branch.lock_write', ('branch/', '', ''),
821
'success', ('ok', 'branch token', 'repo token'))
822
client.add_expected_call(
823
'Branch.last_revision_info',
825
'success', ('ok', '0', 'null:'))
826
client.add_expected_call(
827
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
829
client.add_expected_call(
830
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
832
branch = self.make_remote_branch(transport, client)
833
# This is a hack to work around the problem that RemoteBranch currently
834
# unnecessarily invokes _ensure_real upon a call to lock_write.
835
branch._ensure_real = lambda: None
837
result = branch.set_revision_history([])
839
self.assertEqual(None, result)
840
client.finished_test()
842
def test_set_nonempty(self):
843
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
844
# Branch.set_last_revision(path, rev-idN) on the wire.
845
transport = MemoryTransport()
846
transport.mkdir('branch')
847
transport = transport.clone('branch')
849
client = FakeClient(transport.base)
850
client.add_expected_call(
851
'Branch.get_stacked_on_url', ('branch/',),
852
'error', ('NotStacked',))
853
client.add_expected_call(
854
'Branch.lock_write', ('branch/', '', ''),
855
'success', ('ok', 'branch token', 'repo token'))
856
client.add_expected_call(
857
'Branch.last_revision_info',
859
'success', ('ok', '0', 'null:'))
861
encoded_body = bz2.compress('\n'.join(lines))
862
client.add_success_response_with_body(encoded_body, 'ok')
863
client.add_expected_call(
864
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
866
client.add_expected_call(
867
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
869
branch = self.make_remote_branch(transport, client)
870
# This is a hack to work around the problem that RemoteBranch currently
871
# unnecessarily invokes _ensure_real upon a call to lock_write.
872
branch._ensure_real = lambda: None
873
# Lock the branch, reset the record of remote calls.
875
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
877
self.assertEqual(None, result)
878
client.finished_test()
880
def test_no_such_revision(self):
881
transport = MemoryTransport()
882
transport.mkdir('branch')
883
transport = transport.clone('branch')
884
# A response of 'NoSuchRevision' is translated into an exception.
885
client = FakeClient(transport.base)
886
client.add_expected_call(
887
'Branch.get_stacked_on_url', ('branch/',),
888
'error', ('NotStacked',))
889
client.add_expected_call(
890
'Branch.lock_write', ('branch/', '', ''),
891
'success', ('ok', 'branch token', 'repo token'))
892
client.add_expected_call(
893
'Branch.last_revision_info',
895
'success', ('ok', '0', 'null:'))
896
# get_graph calls to construct the revision history, for the set_rh
899
encoded_body = bz2.compress('\n'.join(lines))
900
client.add_success_response_with_body(encoded_body, 'ok')
901
client.add_expected_call(
902
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
903
'error', ('NoSuchRevision', 'rev-id'))
904
client.add_expected_call(
905
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
908
branch = self.make_remote_branch(transport, client)
911
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
913
client.finished_test()
915
def test_tip_change_rejected(self):
916
"""TipChangeRejected responses cause a TipChangeRejected exception to
919
transport = MemoryTransport()
920
transport.mkdir('branch')
921
transport = transport.clone('branch')
922
client = FakeClient(transport.base)
923
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
924
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
925
client.add_expected_call(
926
'Branch.get_stacked_on_url', ('branch/',),
927
'error', ('NotStacked',))
928
client.add_expected_call(
929
'Branch.lock_write', ('branch/', '', ''),
930
'success', ('ok', 'branch token', 'repo token'))
931
client.add_expected_call(
932
'Branch.last_revision_info',
934
'success', ('ok', '0', 'null:'))
936
encoded_body = bz2.compress('\n'.join(lines))
937
client.add_success_response_with_body(encoded_body, 'ok')
938
client.add_expected_call(
939
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
940
'error', ('TipChangeRejected', rejection_msg_utf8))
941
client.add_expected_call(
942
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
944
branch = self.make_remote_branch(transport, client)
945
branch._ensure_real = lambda: None
947
# The 'TipChangeRejected' error response triggered by calling
948
# set_revision_history causes a TipChangeRejected exception.
949
err = self.assertRaises(
950
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
951
# The UTF-8 message from the response has been decoded into a unicode
953
self.assertIsInstance(err.msg, unicode)
954
self.assertEqual(rejection_msg_unicode, err.msg)
956
client.finished_test()
959
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
961
def test_set_last_revision_info(self):
962
# set_last_revision_info(num, 'rev-id') is translated to calling
963
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
964
transport = MemoryTransport()
965
transport.mkdir('branch')
966
transport = transport.clone('branch')
967
client = FakeClient(transport.base)
969
client.add_error_response('NotStacked')
971
client.add_success_response('ok', 'branch token', 'repo token')
972
# query the current revision
973
client.add_success_response('ok', '0', 'null:')
975
client.add_success_response('ok')
977
client.add_success_response('ok')
979
branch = self.make_remote_branch(transport, client)
980
# Lock the branch, reset the record of remote calls.
983
result = branch.set_last_revision_info(1234, 'a-revision-id')
985
[('call', 'Branch.last_revision_info', ('branch/',)),
986
('call', 'Branch.set_last_revision_info',
987
('branch/', 'branch token', 'repo token',
988
'1234', 'a-revision-id'))],
990
self.assertEqual(None, result)
992
def test_no_such_revision(self):
993
# A response of 'NoSuchRevision' is translated into an exception.
994
transport = MemoryTransport()
995
transport.mkdir('branch')
996
transport = transport.clone('branch')
997
client = FakeClient(transport.base)
999
client.add_error_response('NotStacked')
1001
client.add_success_response('ok', 'branch token', 'repo token')
1003
client.add_error_response('NoSuchRevision', 'revid')
1005
client.add_success_response('ok')
1007
branch = self.make_remote_branch(transport, client)
1008
# Lock the branch, reset the record of remote calls.
1013
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1016
def lock_remote_branch(self, branch):
1017
"""Trick a RemoteBranch into thinking it is locked."""
1018
branch._lock_mode = 'w'
1019
branch._lock_count = 2
1020
branch._lock_token = 'branch token'
1021
branch._repo_lock_token = 'repo token'
1022
branch.repository._lock_mode = 'w'
1023
branch.repository._lock_count = 2
1024
branch.repository._lock_token = 'repo token'
1026
def test_backwards_compatibility(self):
1027
"""If the server does not support the Branch.set_last_revision_info
1028
verb (which is new in 1.4), then the client falls back to VFS methods.
1030
# This test is a little messy. Unlike most tests in this file, it
1031
# doesn't purely test what a Remote* object sends over the wire, and
1032
# how it reacts to responses from the wire. It instead relies partly
1033
# on asserting that the RemoteBranch will call
1034
# self._real_branch.set_last_revision_info(...).
1036
# First, set up our RemoteBranch with a FakeClient that raises
1037
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1038
transport = MemoryTransport()
1039
transport.mkdir('branch')
1040
transport = transport.clone('branch')
1041
client = FakeClient(transport.base)
1042
client.add_expected_call(
1043
'Branch.get_stacked_on_url', ('branch/',),
1044
'error', ('NotStacked',))
1045
client.add_expected_call(
1046
'Branch.last_revision_info',
1048
'success', ('ok', '0', 'null:'))
1049
client.add_expected_call(
1050
'Branch.set_last_revision_info',
1051
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1052
'unknown', 'Branch.set_last_revision_info')
1054
branch = self.make_remote_branch(transport, client)
1055
class StubRealBranch(object):
1058
def set_last_revision_info(self, revno, revision_id):
1060
('set_last_revision_info', revno, revision_id))
1061
def _clear_cached_state(self):
1063
real_branch = StubRealBranch()
1064
branch._real_branch = real_branch
1065
self.lock_remote_branch(branch)
1067
# Call set_last_revision_info, and verify it behaved as expected.
1068
result = branch.set_last_revision_info(1234, 'a-revision-id')
1070
[('set_last_revision_info', 1234, 'a-revision-id')],
1072
client.finished_test()
1074
def test_unexpected_error(self):
1075
# If the server sends an error the client doesn't understand, it gets
1076
# turned into an UnknownErrorFromSmartServer, which is presented as a
1077
# non-internal error to the user.
1078
transport = MemoryTransport()
1079
transport.mkdir('branch')
1080
transport = transport.clone('branch')
1081
client = FakeClient(transport.base)
1082
# get_stacked_on_url
1083
client.add_error_response('NotStacked')
1085
client.add_success_response('ok', 'branch token', 'repo token')
1087
client.add_error_response('UnexpectedError')
1089
client.add_success_response('ok')
1091
branch = self.make_remote_branch(transport, client)
1092
# Lock the branch, reset the record of remote calls.
1096
err = self.assertRaises(
1097
errors.UnknownErrorFromSmartServer,
1098
branch.set_last_revision_info, 123, 'revid')
1099
self.assertEqual(('UnexpectedError',), err.error_tuple)
1102
def test_tip_change_rejected(self):
1103
"""TipChangeRejected responses cause a TipChangeRejected exception to
1106
transport = MemoryTransport()
1107
transport.mkdir('branch')
1108
transport = transport.clone('branch')
1109
client = FakeClient(transport.base)
1110
# get_stacked_on_url
1111
client.add_error_response('NotStacked')
1113
client.add_success_response('ok', 'branch token', 'repo token')
1115
client.add_error_response('TipChangeRejected', 'rejection message')
1117
client.add_success_response('ok')
1119
branch = self.make_remote_branch(transport, client)
1120
# Lock the branch, reset the record of remote calls.
1122
self.addCleanup(branch.unlock)
1125
# The 'TipChangeRejected' error response triggered by calling
1126
# set_last_revision_info causes a TipChangeRejected exception.
1127
err = self.assertRaises(
1128
errors.TipChangeRejected,
1129
branch.set_last_revision_info, 123, 'revid')
1130
self.assertEqual('rejection message', err.msg)
1133
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1134
"""Getting the branch configuration should use an abstract method not vfs.
1137
def test_get_branch_conf(self):
1138
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1139
## # We should see that branch.get_config() does a single rpc to get the
1140
## # remote configuration file, abstracting away where that is stored on
1141
## # the server. However at the moment it always falls back to using the
1142
## # vfs, and this would need some changes in config.py.
1144
## # in an empty branch we decode the response properly
1145
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1146
## # we need to make a real branch because the remote_branch.control_files
1147
## # will trigger _ensure_real.
1148
## branch = self.make_branch('quack')
1149
## transport = branch.bzrdir.root_transport
1150
## # we do not want bzrdir to make any remote calls
1151
## bzrdir = RemoteBzrDir(transport, _client=False)
1152
## branch = RemoteBranch(bzrdir, None, _client=client)
1153
## config = branch.get_config()
1154
## self.assertEqual(
1155
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1159
class TestBranchLockWrite(RemoteBranchTestCase):
1161
def test_lock_write_unlockable(self):
1162
transport = MemoryTransport()
1163
client = FakeClient(transport.base)
1164
client.add_expected_call(
1165
'Branch.get_stacked_on_url', ('quack/',),
1166
'error', ('NotStacked',),)
1167
client.add_expected_call(
1168
'Branch.lock_write', ('quack/', '', ''),
1169
'error', ('UnlockableTransport',))
1170
transport.mkdir('quack')
1171
transport = transport.clone('quack')
1172
branch = self.make_remote_branch(transport, client)
1173
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1174
client.finished_test()
1177
class TestTransportIsReadonly(tests.TestCase):
1179
def test_true(self):
1180
client = FakeClient()
1181
client.add_success_response('yes')
1182
transport = RemoteTransport('bzr://example.com/', medium=False,
1184
self.assertEqual(True, transport.is_readonly())
1186
[('call', 'Transport.is_readonly', ())],
1189
def test_false(self):
1190
client = FakeClient()
1191
client.add_success_response('no')
1192
transport = RemoteTransport('bzr://example.com/', medium=False,
1194
self.assertEqual(False, transport.is_readonly())
1196
[('call', 'Transport.is_readonly', ())],
1199
def test_error_from_old_server(self):
1200
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1202
Clients should treat it as a "no" response, because is_readonly is only
1203
advisory anyway (a transport could be read-write, but then the
1204
underlying filesystem could be readonly anyway).
1206
client = FakeClient()
1207
client.add_unknown_method_response('Transport.is_readonly')
1208
transport = RemoteTransport('bzr://example.com/', medium=False,
1210
self.assertEqual(False, transport.is_readonly())
1212
[('call', 'Transport.is_readonly', ())],
1216
class TestTransportMkdir(tests.TestCase):
1218
def test_permissiondenied(self):
1219
client = FakeClient()
1220
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1221
transport = RemoteTransport('bzr://example.com/', medium=False,
1223
exc = self.assertRaises(
1224
errors.PermissionDenied, transport.mkdir, 'client path')
1225
expected_error = errors.PermissionDenied('/client path', 'extra')
1226
self.assertEqual(expected_error, exc)
1229
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1231
def test_defaults_to_none(self):
1232
t = RemoteSSHTransport('bzr+ssh://example.com')
1233
self.assertIs(None, t._get_credentials()[0])
1235
def test_uses_authentication_config(self):
1236
conf = config.AuthenticationConfig()
1237
conf._get_config().update(
1238
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1241
t = RemoteSSHTransport('bzr+ssh://example.com')
1242
self.assertEqual('bar', t._get_credentials()[0])
1245
class TestRemoteRepository(TestRemote):
1246
"""Base for testing RemoteRepository protocol usage.
1248
These tests contain frozen requests and responses. We want any changes to
1249
what is sent or expected to be require a thoughtful update to these tests
1250
because they might break compatibility with different-versioned servers.
1253
def setup_fake_client_and_repository(self, transport_path):
1254
"""Create the fake client and repository for testing with.
1256
There's no real server here; we just have canned responses sent
1259
:param transport_path: Path below the root of the MemoryTransport
1260
where the repository will be created.
1262
transport = MemoryTransport()
1263
transport.mkdir(transport_path)
1264
client = FakeClient(transport.base)
1265
transport = transport.clone(transport_path)
1266
# we do not want bzrdir to make any remote calls
1267
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1269
repo = RemoteRepository(bzrdir, None, _client=client)
1273
class TestRepositoryGatherStats(TestRemoteRepository):
1275
def test_revid_none(self):
1276
# ('ok',), body with revisions and size
1277
transport_path = 'quack'
1278
repo, client = self.setup_fake_client_and_repository(transport_path)
1279
client.add_success_response_with_body(
1280
'revisions: 2\nsize: 18\n', 'ok')
1281
result = repo.gather_stats(None)
1283
[('call_expecting_body', 'Repository.gather_stats',
1284
('quack/','','no'))],
1286
self.assertEqual({'revisions': 2, 'size': 18}, result)
1288
def test_revid_no_committers(self):
1289
# ('ok',), body without committers
1290
body = ('firstrev: 123456.300 3600\n'
1291
'latestrev: 654231.400 0\n'
1294
transport_path = 'quick'
1295
revid = u'\xc8'.encode('utf8')
1296
repo, client = self.setup_fake_client_and_repository(transport_path)
1297
client.add_success_response_with_body(body, 'ok')
1298
result = repo.gather_stats(revid)
1300
[('call_expecting_body', 'Repository.gather_stats',
1301
('quick/', revid, 'no'))],
1303
self.assertEqual({'revisions': 2, 'size': 18,
1304
'firstrev': (123456.300, 3600),
1305
'latestrev': (654231.400, 0),},
1308
def test_revid_with_committers(self):
1309
# ('ok',), body with committers
1310
body = ('committers: 128\n'
1311
'firstrev: 123456.300 3600\n'
1312
'latestrev: 654231.400 0\n'
1315
transport_path = 'buick'
1316
revid = u'\xc8'.encode('utf8')
1317
repo, client = self.setup_fake_client_and_repository(transport_path)
1318
client.add_success_response_with_body(body, 'ok')
1319
result = repo.gather_stats(revid, True)
1321
[('call_expecting_body', 'Repository.gather_stats',
1322
('buick/', revid, 'yes'))],
1324
self.assertEqual({'revisions': 2, 'size': 18,
1326
'firstrev': (123456.300, 3600),
1327
'latestrev': (654231.400, 0),},
1331
class TestRepositoryGetGraph(TestRemoteRepository):
1333
def test_get_graph(self):
1334
# get_graph returns a graph with a custom parents provider.
1335
transport_path = 'quack'
1336
repo, client = self.setup_fake_client_and_repository(transport_path)
1337
graph = repo.get_graph()
1338
self.assertNotEqual(graph._parents_provider, repo)
1341
class TestRepositoryGetParentMap(TestRemoteRepository):
1343
def test_get_parent_map_caching(self):
1344
# get_parent_map returns from cache until unlock()
1345
# setup a reponse with two revisions
1346
r1 = u'\u0e33'.encode('utf8')
1347
r2 = u'\u0dab'.encode('utf8')
1348
lines = [' '.join([r2, r1]), r1]
1349
encoded_body = bz2.compress('\n'.join(lines))
1351
transport_path = 'quack'
1352
repo, client = self.setup_fake_client_and_repository(transport_path)
1353
client.add_success_response_with_body(encoded_body, 'ok')
1354
client.add_success_response_with_body(encoded_body, 'ok')
1356
graph = repo.get_graph()
1357
parents = graph.get_parent_map([r2])
1358
self.assertEqual({r2: (r1,)}, parents)
1359
# locking and unlocking deeper should not reset
1362
parents = graph.get_parent_map([r1])
1363
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1365
[('call_with_body_bytes_expecting_body',
1366
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
1369
# now we call again, and it should use the second response.
1371
graph = repo.get_graph()
1372
parents = graph.get_parent_map([r1])
1373
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1375
[('call_with_body_bytes_expecting_body',
1376
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
1377
('call_with_body_bytes_expecting_body',
1378
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
1383
def test_get_parent_map_reconnects_if_unknown_method(self):
1384
transport_path = 'quack'
1385
repo, client = self.setup_fake_client_and_repository(transport_path)
1386
client.add_unknown_method_response('Repository,get_parent_map')
1387
client.add_success_response_with_body('', 'ok')
1388
self.assertFalse(client._medium._is_remote_before((1, 2)))
1389
rev_id = 'revision-id'
1390
expected_deprecations = [
1391
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1393
parents = self.callDeprecated(
1394
expected_deprecations, repo.get_parent_map, [rev_id])
1396
[('call_with_body_bytes_expecting_body',
1397
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
1398
('disconnect medium',),
1399
('call_expecting_body', 'Repository.get_revision_graph',
1402
# The medium is now marked as being connected to an older server
1403
self.assertTrue(client._medium._is_remote_before((1, 2)))
1405
def test_get_parent_map_fallback_parentless_node(self):
1406
"""get_parent_map falls back to get_revision_graph on old servers. The
1407
results from get_revision_graph are tweaked to match the get_parent_map
1410
Specifically, a {key: ()} result from get_revision_graph means "no
1411
parents" for that key, which in get_parent_map results should be
1412
represented as {key: ('null:',)}.
1414
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1416
rev_id = 'revision-id'
1417
transport_path = 'quack'
1418
repo, client = self.setup_fake_client_and_repository(transport_path)
1419
client.add_success_response_with_body(rev_id, 'ok')
1420
client._medium._remember_remote_is_before((1, 2))
1421
expected_deprecations = [
1422
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1424
parents = self.callDeprecated(
1425
expected_deprecations, repo.get_parent_map, [rev_id])
1427
[('call_expecting_body', 'Repository.get_revision_graph',
1430
self.assertEqual({rev_id: ('null:',)}, parents)
1432
def test_get_parent_map_unexpected_response(self):
1433
repo, client = self.setup_fake_client_and_repository('path')
1434
client.add_success_response('something unexpected!')
1436
errors.UnexpectedSmartServerResponse,
1437
repo.get_parent_map, ['a-revision-id'])
1440
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1442
def test_allows_new_revisions(self):
1443
"""get_parent_map's results can be updated by commit."""
1444
smart_server = server.SmartTCPServer_for_testing()
1445
smart_server.setUp()
1446
self.addCleanup(smart_server.tearDown)
1447
self.make_branch('branch')
1448
branch = Branch.open(smart_server.get_url() + '/branch')
1449
tree = branch.create_checkout('tree', lightweight=True)
1451
self.addCleanup(tree.unlock)
1452
graph = tree.branch.repository.get_graph()
1453
# This provides an opportunity for the missing rev-id to be cached.
1454
self.assertEqual({}, graph.get_parent_map(['rev1']))
1455
tree.commit('message', rev_id='rev1')
1456
graph = tree.branch.repository.get_graph()
1457
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1460
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1462
def test_null_revision(self):
1463
# a null revision has the predictable result {}, we should have no wire
1464
# traffic when calling it with this argument
1465
transport_path = 'empty'
1466
repo, client = self.setup_fake_client_and_repository(transport_path)
1467
client.add_success_response('notused')
1468
result = self.applyDeprecated(one_four, repo.get_revision_graph,
1470
self.assertEqual([], client._calls)
1471
self.assertEqual({}, result)
1473
def test_none_revision(self):
1474
# with none we want the entire graph
1475
r1 = u'\u0e33'.encode('utf8')
1476
r2 = u'\u0dab'.encode('utf8')
1477
lines = [' '.join([r2, r1]), r1]
1478
encoded_body = '\n'.join(lines)
1480
transport_path = 'sinhala'
1481
repo, client = self.setup_fake_client_and_repository(transport_path)
1482
client.add_success_response_with_body(encoded_body, 'ok')
1483
result = self.applyDeprecated(one_four, repo.get_revision_graph)
1485
[('call_expecting_body', 'Repository.get_revision_graph',
1488
self.assertEqual({r1: (), r2: (r1, )}, result)
1490
def test_specific_revision(self):
1491
# with a specific revision we want the graph for that
1492
# with none we want the entire graph
1493
r11 = u'\u0e33'.encode('utf8')
1494
r12 = u'\xc9'.encode('utf8')
1495
r2 = u'\u0dab'.encode('utf8')
1496
lines = [' '.join([r2, r11, r12]), r11, r12]
1497
encoded_body = '\n'.join(lines)
1499
transport_path = 'sinhala'
1500
repo, client = self.setup_fake_client_and_repository(transport_path)
1501
client.add_success_response_with_body(encoded_body, 'ok')
1502
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
1504
[('call_expecting_body', 'Repository.get_revision_graph',
1507
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1509
def test_no_such_revision(self):
1511
transport_path = 'sinhala'
1512
repo, client = self.setup_fake_client_and_repository(transport_path)
1513
client.add_error_response('nosuchrevision', revid)
1514
# also check that the right revision is reported in the error
1515
self.assertRaises(errors.NoSuchRevision,
1516
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1518
[('call_expecting_body', 'Repository.get_revision_graph',
1519
('sinhala/', revid))],
1522
def test_unexpected_error(self):
1524
transport_path = 'sinhala'
1525
repo, client = self.setup_fake_client_and_repository(transport_path)
1526
client.add_error_response('AnUnexpectedError')
1527
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1528
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1529
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1532
class TestRepositoryIsShared(TestRemoteRepository):
1534
def test_is_shared(self):
1535
# ('yes', ) for Repository.is_shared -> 'True'.
1536
transport_path = 'quack'
1537
repo, client = self.setup_fake_client_and_repository(transport_path)
1538
client.add_success_response('yes')
1539
result = repo.is_shared()
1541
[('call', 'Repository.is_shared', ('quack/',))],
1543
self.assertEqual(True, result)
1545
def test_is_not_shared(self):
1546
# ('no', ) for Repository.is_shared -> 'False'.
1547
transport_path = 'qwack'
1548
repo, client = self.setup_fake_client_and_repository(transport_path)
1549
client.add_success_response('no')
1550
result = repo.is_shared()
1552
[('call', 'Repository.is_shared', ('qwack/',))],
1554
self.assertEqual(False, result)
1557
class TestRepositoryLockWrite(TestRemoteRepository):
1559
def test_lock_write(self):
1560
transport_path = 'quack'
1561
repo, client = self.setup_fake_client_and_repository(transport_path)
1562
client.add_success_response('ok', 'a token')
1563
result = repo.lock_write()
1565
[('call', 'Repository.lock_write', ('quack/', ''))],
1567
self.assertEqual('a token', result)
1569
def test_lock_write_already_locked(self):
1570
transport_path = 'quack'
1571
repo, client = self.setup_fake_client_and_repository(transport_path)
1572
client.add_error_response('LockContention')
1573
self.assertRaises(errors.LockContention, repo.lock_write)
1575
[('call', 'Repository.lock_write', ('quack/', ''))],
1578
def test_lock_write_unlockable(self):
1579
transport_path = 'quack'
1580
repo, client = self.setup_fake_client_and_repository(transport_path)
1581
client.add_error_response('UnlockableTransport')
1582
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1584
[('call', 'Repository.lock_write', ('quack/', ''))],
1588
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1590
def test_backwards_compat(self):
1591
self.setup_smart_server_with_call_log()
1592
repo = self.make_repository('.')
1593
self.reset_smart_call_log()
1594
verb = 'Repository.set_make_working_trees'
1595
self.disable_verb(verb)
1596
repo.set_make_working_trees(True)
1597
call_count = len([call for call in self.hpss_calls if
1598
call[0].method == verb])
1599
self.assertEqual(1, call_count)
1601
def test_current(self):
1602
transport_path = 'quack'
1603
repo, client = self.setup_fake_client_and_repository(transport_path)
1604
client.add_expected_call(
1605
'Repository.set_make_working_trees', ('quack/', 'True'),
1607
client.add_expected_call(
1608
'Repository.set_make_working_trees', ('quack/', 'False'),
1610
repo.set_make_working_trees(True)
1611
repo.set_make_working_trees(False)
1614
class TestRepositoryUnlock(TestRemoteRepository):
1616
def test_unlock(self):
1617
transport_path = 'quack'
1618
repo, client = self.setup_fake_client_and_repository(transport_path)
1619
client.add_success_response('ok', 'a token')
1620
client.add_success_response('ok')
1624
[('call', 'Repository.lock_write', ('quack/', '')),
1625
('call', 'Repository.unlock', ('quack/', 'a token'))],
1628
def test_unlock_wrong_token(self):
1629
# If somehow the token is wrong, unlock will raise TokenMismatch.
1630
transport_path = 'quack'
1631
repo, client = self.setup_fake_client_and_repository(transport_path)
1632
client.add_success_response('ok', 'a token')
1633
client.add_error_response('TokenMismatch')
1635
self.assertRaises(errors.TokenMismatch, repo.unlock)
1638
class TestRepositoryHasRevision(TestRemoteRepository):
1640
def test_none(self):
1641
# repo.has_revision(None) should not cause any traffic.
1642
transport_path = 'quack'
1643
repo, client = self.setup_fake_client_and_repository(transport_path)
1645
# The null revision is always there, so has_revision(None) == True.
1646
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1648
# The remote repo shouldn't be accessed.
1649
self.assertEqual([], client._calls)
1652
class TestRepositoryTarball(TestRemoteRepository):
1654
# This is a canned tarball reponse we can validate against
1656
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1657
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1658
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1659
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1660
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1661
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1662
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1663
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1664
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1665
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1666
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1667
'nWQ7QH/F3JFOFCQ0aSPfA='
1670
def test_repository_tarball(self):
1671
# Test that Repository.tarball generates the right operations
1672
transport_path = 'repo'
1673
expected_calls = [('call_expecting_body', 'Repository.tarball',
1674
('repo/', 'bz2',),),
1676
repo, client = self.setup_fake_client_and_repository(transport_path)
1677
client.add_success_response_with_body(self.tarball_content, 'ok')
1678
# Now actually ask for the tarball
1679
tarball_file = repo._get_tarball('bz2')
1681
self.assertEqual(expected_calls, client._calls)
1682
self.assertEqual(self.tarball_content, tarball_file.read())
1684
tarball_file.close()
1687
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
1688
"""RemoteRepository.copy_content_into optimizations"""
1690
def test_copy_content_remote_to_local(self):
1691
self.transport_server = server.SmartTCPServer_for_testing
1692
src_repo = self.make_repository('repo1')
1693
src_repo = repository.Repository.open(self.get_url('repo1'))
1694
# At the moment the tarball-based copy_content_into can't write back
1695
# into a smart server. It would be good if it could upload the
1696
# tarball; once that works we'd have to create repositories of
1697
# different formats. -- mbp 20070410
1698
dest_url = self.get_vfs_only_url('repo2')
1699
dest_bzrdir = BzrDir.create(dest_url)
1700
dest_repo = dest_bzrdir.create_repository()
1701
self.assertFalse(isinstance(dest_repo, RemoteRepository))
1702
self.assertTrue(isinstance(src_repo, RemoteRepository))
1703
src_repo.copy_content_into(dest_repo)
1706
class _StubRealPackRepository(object):
1708
def __init__(self, calls):
1709
self._pack_collection = _StubPackCollection(calls)
1712
class _StubPackCollection(object):
1714
def __init__(self, calls):
1718
self.calls.append(('pack collection autopack',))
1720
def reload_pack_names(self):
1721
self.calls.append(('pack collection reload_pack_names',))
1724
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1725
"""Tests for RemoteRepository.autopack implementation."""
1728
"""When the server returns 'ok' and there's no _real_repository, then
1729
nothing else happens: the autopack method is done.
1731
transport_path = 'quack'
1732
repo, client = self.setup_fake_client_and_repository(transport_path)
1733
client.add_expected_call(
1734
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
1736
client.finished_test()
1738
def test_ok_with_real_repo(self):
1739
"""When the server returns 'ok' and there is a _real_repository, then
1740
the _real_repository's reload_pack_name's method will be called.
1742
transport_path = 'quack'
1743
repo, client = self.setup_fake_client_and_repository(transport_path)
1744
client.add_expected_call(
1745
'PackRepository.autopack', ('quack/',),
1747
repo._real_repository = _StubRealPackRepository(client._calls)
1750
[('call', 'PackRepository.autopack', ('quack/',)),
1751
('pack collection reload_pack_names',)],
1754
def test_backwards_compatibility(self):
1755
"""If the server does not recognise the PackRepository.autopack verb,
1756
fallback to the real_repository's implementation.
1758
transport_path = 'quack'
1759
repo, client = self.setup_fake_client_and_repository(transport_path)
1760
client.add_unknown_method_response('PackRepository.autopack')
1761
def stub_ensure_real():
1762
client._calls.append(('_ensure_real',))
1763
repo._real_repository = _StubRealPackRepository(client._calls)
1764
repo._ensure_real = stub_ensure_real
1767
[('call', 'PackRepository.autopack', ('quack/',)),
1769
('pack collection autopack',)],
1773
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
1774
"""Base class for unit tests for bzrlib.remote._translate_error."""
1776
def translateTuple(self, error_tuple, **context):
1777
"""Call _translate_error with an ErrorFromSmartServer built from the
1780
:param error_tuple: A tuple of a smart server response, as would be
1781
passed to an ErrorFromSmartServer.
1782
:kwargs context: context items to call _translate_error with.
1784
:returns: The error raised by _translate_error.
1786
# Raise the ErrorFromSmartServer before passing it as an argument,
1787
# because _translate_error may need to re-raise it with a bare 'raise'
1789
server_error = errors.ErrorFromSmartServer(error_tuple)
1790
translated_error = self.translateErrorFromSmartServer(
1791
server_error, **context)
1792
return translated_error
1794
def translateErrorFromSmartServer(self, error_object, **context):
1795
"""Like translateTuple, but takes an already constructed
1796
ErrorFromSmartServer rather than a tuple.
1800
except errors.ErrorFromSmartServer, server_error:
1801
translated_error = self.assertRaises(
1802
errors.BzrError, remote._translate_error, server_error,
1804
return translated_error
1807
class TestErrorTranslationSuccess(TestErrorTranslationBase):
1808
"""Unit tests for bzrlib.remote._translate_error.
1810
Given an ErrorFromSmartServer (which has an error tuple from a smart
1811
server) and some context, _translate_error raises more specific errors from
1814
This test case covers the cases where _translate_error succeeds in
1815
translating an ErrorFromSmartServer to something better. See
1816
TestErrorTranslationRobustness for other cases.
1819
def test_NoSuchRevision(self):
1820
branch = self.make_branch('')
1822
translated_error = self.translateTuple(
1823
('NoSuchRevision', revid), branch=branch)
1824
expected_error = errors.NoSuchRevision(branch, revid)
1825
self.assertEqual(expected_error, translated_error)
1827
def test_nosuchrevision(self):
1828
repository = self.make_repository('')
1830
translated_error = self.translateTuple(
1831
('nosuchrevision', revid), repository=repository)
1832
expected_error = errors.NoSuchRevision(repository, revid)
1833
self.assertEqual(expected_error, translated_error)
1835
def test_nobranch(self):
1836
bzrdir = self.make_bzrdir('')
1837
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
1838
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
1839
self.assertEqual(expected_error, translated_error)
1841
def test_LockContention(self):
1842
translated_error = self.translateTuple(('LockContention',))
1843
expected_error = errors.LockContention('(remote lock)')
1844
self.assertEqual(expected_error, translated_error)
1846
def test_UnlockableTransport(self):
1847
bzrdir = self.make_bzrdir('')
1848
translated_error = self.translateTuple(
1849
('UnlockableTransport',), bzrdir=bzrdir)
1850
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
1851
self.assertEqual(expected_error, translated_error)
1853
def test_LockFailed(self):
1854
lock = 'str() of a server lock'
1855
why = 'str() of why'
1856
translated_error = self.translateTuple(('LockFailed', lock, why))
1857
expected_error = errors.LockFailed(lock, why)
1858
self.assertEqual(expected_error, translated_error)
1860
def test_TokenMismatch(self):
1861
token = 'a lock token'
1862
translated_error = self.translateTuple(('TokenMismatch',), token=token)
1863
expected_error = errors.TokenMismatch(token, '(remote token)')
1864
self.assertEqual(expected_error, translated_error)
1866
def test_Diverged(self):
1867
branch = self.make_branch('a')
1868
other_branch = self.make_branch('b')
1869
translated_error = self.translateTuple(
1870
('Diverged',), branch=branch, other_branch=other_branch)
1871
expected_error = errors.DivergedBranches(branch, other_branch)
1872
self.assertEqual(expected_error, translated_error)
1874
def test_ReadError_no_args(self):
1876
translated_error = self.translateTuple(('ReadError',), path=path)
1877
expected_error = errors.ReadError(path)
1878
self.assertEqual(expected_error, translated_error)
1880
def test_ReadError(self):
1882
translated_error = self.translateTuple(('ReadError', path))
1883
expected_error = errors.ReadError(path)
1884
self.assertEqual(expected_error, translated_error)
1886
def test_PermissionDenied_no_args(self):
1888
translated_error = self.translateTuple(('PermissionDenied',), path=path)
1889
expected_error = errors.PermissionDenied(path)
1890
self.assertEqual(expected_error, translated_error)
1892
def test_PermissionDenied_one_arg(self):
1894
translated_error = self.translateTuple(('PermissionDenied', path))
1895
expected_error = errors.PermissionDenied(path)
1896
self.assertEqual(expected_error, translated_error)
1898
def test_PermissionDenied_one_arg_and_context(self):
1899
"""Given a choice between a path from the local context and a path on
1900
the wire, _translate_error prefers the path from the local context.
1902
local_path = 'local path'
1903
remote_path = 'remote path'
1904
translated_error = self.translateTuple(
1905
('PermissionDenied', remote_path), path=local_path)
1906
expected_error = errors.PermissionDenied(local_path)
1907
self.assertEqual(expected_error, translated_error)
1909
def test_PermissionDenied_two_args(self):
1911
extra = 'a string with extra info'
1912
translated_error = self.translateTuple(
1913
('PermissionDenied', path, extra))
1914
expected_error = errors.PermissionDenied(path, extra)
1915
self.assertEqual(expected_error, translated_error)
1918
class TestErrorTranslationRobustness(TestErrorTranslationBase):
1919
"""Unit tests for bzrlib.remote._translate_error's robustness.
1921
TestErrorTranslationSuccess is for cases where _translate_error can
1922
translate successfully. This class about how _translate_err behaves when
1923
it fails to translate: it re-raises the original error.
1926
def test_unrecognised_server_error(self):
1927
"""If the error code from the server is not recognised, the original
1928
ErrorFromSmartServer is propagated unmodified.
1930
error_tuple = ('An unknown error tuple',)
1931
server_error = errors.ErrorFromSmartServer(error_tuple)
1932
translated_error = self.translateErrorFromSmartServer(server_error)
1933
expected_error = errors.UnknownErrorFromSmartServer(server_error)
1934
self.assertEqual(expected_error, translated_error)
1936
def test_context_missing_a_key(self):
1937
"""In case of a bug in the client, or perhaps an unexpected response
1938
from a server, _translate_error returns the original error tuple from
1939
the server and mutters a warning.
1941
# To translate a NoSuchRevision error _translate_error needs a 'branch'
1942
# in the context dict. So let's give it an empty context dict instead
1943
# to exercise its error recovery.
1945
error_tuple = ('NoSuchRevision', 'revid')
1946
server_error = errors.ErrorFromSmartServer(error_tuple)
1947
translated_error = self.translateErrorFromSmartServer(server_error)
1948
self.assertEqual(server_error, translated_error)
1949
# In addition to re-raising ErrorFromSmartServer, some debug info has
1950
# been muttered to the log file for developer to look at.
1951
self.assertContainsRe(
1952
self._get_log(keep_log_file=True),
1953
"Missing key 'branch' in context")
1955
def test_path_missing(self):
1956
"""Some translations (PermissionDenied, ReadError) can determine the
1957
'path' variable from either the wire or the local context. If neither
1958
has it, then an error is raised.
1960
error_tuple = ('ReadError',)
1961
server_error = errors.ErrorFromSmartServer(error_tuple)
1962
translated_error = self.translateErrorFromSmartServer(server_error)
1963
self.assertEqual(server_error, translated_error)
1964
# In addition to re-raising ErrorFromSmartServer, some debug info has
1965
# been muttered to the log file for developer to look at.
1966
self.assertContainsRe(
1967
self._get_log(keep_log_file=True), "Missing key 'path' in context")
1970
class TestStacking(tests.TestCaseWithTransport):
1971
"""Tests for operations on stacked remote repositories.
1973
The underlying format type must support stacking.
1976
def test_access_stacked_remote(self):
1977
# based on <http://launchpad.net/bugs/261315>
1978
# make a branch stacked on another repository containing an empty
1979
# revision, then open it over hpss - we should be able to see that
1981
base_transport = self.get_transport()
1982
base_builder = self.make_branch_builder('base', format='1.6')
1983
base_builder.start_series()
1984
base_revid = base_builder.build_snapshot('rev-id', None,
1985
[('add', ('', None, 'directory', None))],
1987
base_builder.finish_series()
1988
stacked_branch = self.make_branch('stacked', format='1.6')
1989
stacked_branch.set_stacked_on_url('../base')
1990
# start a server looking at this
1991
smart_server = server.SmartTCPServer_for_testing()
1992
smart_server.setUp()
1993
self.addCleanup(smart_server.tearDown)
1994
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
1995
# can get its branch and repository
1996
remote_branch = remote_bzrdir.open_branch()
1997
remote_repo = remote_branch.repository
1998
remote_repo.lock_read()
2000
# it should have an appropriate fallback repository, which should also
2001
# be a RemoteRepository
2002
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2003
self.assertIsInstance(remote_repo._fallback_repositories[0],
2005
# and it has the revision committed to the underlying repository;
2006
# these have varying implementations so we try several of them
2007
self.assertTrue(remote_repo.has_revisions([base_revid]))
2008
self.assertTrue(remote_repo.has_revision(base_revid))
2009
self.assertEqual(remote_repo.get_revision(base_revid).message,
2012
remote_repo.unlock()
2014
def prepare_stacked_remote_branch(self):
2015
smart_server = server.SmartTCPServer_for_testing()
2016
smart_server.setUp()
2017
self.addCleanup(smart_server.tearDown)
2018
tree1 = self.make_branch_and_tree('tree1')
2019
tree1.commit('rev1', rev_id='rev1')
2020
tree2 = self.make_branch_and_tree('tree2', format='1.6')
2021
tree2.branch.set_stacked_on_url(tree1.branch.base)
2022
branch2 = Branch.open(smart_server.get_url() + '/tree2')
2024
self.addCleanup(branch2.unlock)
2027
def test_stacked_get_parent_map(self):
2028
# the public implementation of get_parent_map obeys stacking
2029
branch = self.prepare_stacked_remote_branch()
2030
repo = branch.repository
2031
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2033
def test_unstacked_get_parent_map(self):
2034
# _unstacked_provider.get_parent_map ignores stacking
2035
branch = self.prepare_stacked_remote_branch()
2036
provider = branch.repository._unstacked_provider
2037
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2040
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2043
super(TestRemoteBranchEffort, self).setUp()
2044
# Create a smart server that publishes whatever the backing VFS server
2046
self.smart_server = server.SmartTCPServer_for_testing()
2047
self.smart_server.setUp(self.get_server())
2048
self.addCleanup(self.smart_server.tearDown)
2049
# Log all HPSS calls into self.hpss_calls.
2050
_SmartClient.hooks.install_named_hook(
2051
'call', self.capture_hpss_call, None)
2052
self.hpss_calls = []
2054
def capture_hpss_call(self, params):
2055
self.hpss_calls.append(params.method)
2057
def test_copy_content_into_avoids_revision_history(self):
2058
local = self.make_branch('local')
2059
remote_backing_tree = self.make_branch_and_tree('remote')
2060
remote_backing_tree.commit("Commit.")
2061
remote_branch_url = self.smart_server.get_url() + 'remote'
2062
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2063
local.repository.fetch(remote_branch.repository)
2064
self.hpss_calls = []
2065
remote_branch.copy_content_into(local)
2066
self.assertFalse('Branch.revision_history' in self.hpss_calls)