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 TestBzrDirCloningMetaDir(TestRemote):
354
def test_backwards_compat(self):
355
self.setup_smart_server_with_call_log()
356
a_dir = self.make_bzrdir('.')
357
self.reset_smart_call_log()
358
verb = 'BzrDir.cloning_metadir'
359
self.disable_verb(verb)
360
format = a_dir.cloning_metadir()
361
call_count = len([call for call in self.hpss_calls if
362
call.call.method == verb])
363
self.assertEqual(1, call_count)
365
def test_current_server(self):
366
transport = self.get_transport('.')
367
transport = transport.clone('quack')
368
self.make_bzrdir('quack')
369
client = FakeClient(transport.base)
370
reference_bzrdir_format = bzrdir.format_registry.get('default')()
371
control_name = reference_bzrdir_format.network_name()
372
client.add_expected_call(
373
'BzrDir.cloning_metadir', ('quack/', 'False'),
374
'success', (control_name, '', '')),
375
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
377
result = a_bzrdir.cloning_metadir()
378
# We should have got a reference control dir with default branch and
379
# repository formats.
380
# This pokes a little, just to be sure.
381
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
382
self.assertEqual(None, result._repository_format)
383
self.assertEqual(None, result._branch_format)
384
client.finished_test()
387
class TestBzrDirOpenBranch(TestRemote):
389
def test_branch_present(self):
390
reference_format = self.get_repo_format()
391
network_name = reference_format.network_name()
392
transport = MemoryTransport()
393
transport.mkdir('quack')
394
transport = transport.clone('quack')
395
client = FakeClient(transport.base)
396
client.add_expected_call(
397
'BzrDir.open_branch', ('quack/',),
398
'success', ('ok', ''))
399
client.add_expected_call(
400
'BzrDir.find_repositoryV3', ('quack/',),
401
'success', ('ok', '', 'no', 'no', 'no', network_name))
402
client.add_expected_call(
403
'Branch.get_stacked_on_url', ('quack/',),
404
'error', ('NotStacked',))
405
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
407
result = bzrdir.open_branch()
408
self.assertIsInstance(result, RemoteBranch)
409
self.assertEqual(bzrdir, result.bzrdir)
410
client.finished_test()
412
def test_branch_missing(self):
413
transport = MemoryTransport()
414
transport.mkdir('quack')
415
transport = transport.clone('quack')
416
client = FakeClient(transport.base)
417
client.add_error_response('nobranch')
418
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
420
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
422
[('call', 'BzrDir.open_branch', ('quack/',))],
425
def test__get_tree_branch(self):
426
# _get_tree_branch is a form of open_branch, but it should only ask for
427
# branch opening, not any other network requests.
430
calls.append("Called")
432
transport = MemoryTransport()
433
# no requests on the network - catches other api calls being made.
434
client = FakeClient(transport.base)
435
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
437
# patch the open_branch call to record that it was called.
438
bzrdir.open_branch = open_branch
439
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
440
self.assertEqual(["Called"], calls)
441
self.assertEqual([], client._calls)
443
def test_url_quoting_of_path(self):
444
# Relpaths on the wire should not be URL-escaped. So "~" should be
445
# transmitted as "~", not "%7E".
446
transport = RemoteTCPTransport('bzr://localhost/~hello/')
447
client = FakeClient(transport.base)
448
reference_format = self.get_repo_format()
449
network_name = reference_format.network_name()
450
client.add_expected_call(
451
'BzrDir.open_branch', ('~hello/',),
452
'success', ('ok', ''))
453
client.add_expected_call(
454
'BzrDir.find_repositoryV3', ('~hello/',),
455
'success', ('ok', '', 'no', 'no', 'no', network_name))
456
client.add_expected_call(
457
'Branch.get_stacked_on_url', ('~hello/',),
458
'error', ('NotStacked',))
459
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
461
result = bzrdir.open_branch()
462
client.finished_test()
464
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
465
reference_format = self.get_repo_format()
466
network_name = reference_format.network_name()
467
transport = MemoryTransport()
468
transport.mkdir('quack')
469
transport = transport.clone('quack')
471
rich_response = 'yes'
475
subtree_response = 'yes'
477
subtree_response = 'no'
478
client = FakeClient(transport.base)
479
client.add_success_response(
480
'ok', '', rich_response, subtree_response, external_lookup,
482
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
484
result = bzrdir.open_repository()
486
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
488
self.assertIsInstance(result, RemoteRepository)
489
self.assertEqual(bzrdir, result.bzrdir)
490
self.assertEqual(rich_root, result._format.rich_root_data)
491
self.assertEqual(subtrees, result._format.supports_tree_reference)
493
def test_open_repository_sets_format_attributes(self):
494
self.check_open_repository(True, True)
495
self.check_open_repository(False, True)
496
self.check_open_repository(True, False)
497
self.check_open_repository(False, False)
498
self.check_open_repository(False, False, 'yes')
500
def test_old_server(self):
501
"""RemoteBzrDirFormat should fail to probe if the server version is too
504
self.assertRaises(errors.NotBranchError,
505
RemoteBzrDirFormat.probe_transport, OldServerTransport())
508
class TestBzrDirCreateBranch(TestRemote):
510
def test_backwards_compat(self):
511
self.setup_smart_server_with_call_log()
512
repo = self.make_repository('.')
513
self.reset_smart_call_log()
514
self.disable_verb('BzrDir.create_branch')
515
branch = repo.bzrdir.create_branch()
516
create_branch_call_count = len([call for call in self.hpss_calls if
517
call.call.method == 'BzrDir.create_branch'])
518
self.assertEqual(1, create_branch_call_count)
520
def test_current_server(self):
521
transport = self.get_transport('.')
522
transport = transport.clone('quack')
523
self.make_repository('quack')
524
client = FakeClient(transport.base)
525
reference_bzrdir_format = bzrdir.format_registry.get('default')()
526
reference_format = reference_bzrdir_format.get_branch_format()
527
network_name = reference_format.network_name()
528
reference_repo_fmt = reference_bzrdir_format.repository_format
529
reference_repo_name = reference_repo_fmt.network_name()
530
client.add_expected_call(
531
'BzrDir.create_branch', ('quack/', network_name),
532
'success', ('ok', network_name, '', 'no', 'no', 'yes',
533
reference_repo_name))
534
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
536
branch = a_bzrdir.create_branch()
537
# We should have got a remote branch
538
self.assertIsInstance(branch, remote.RemoteBranch)
539
# its format should have the settings from the response
540
format = branch._format
541
self.assertEqual(network_name, format.network_name())
544
class TestBzrDirCreateRepository(TestRemote):
546
def test_backwards_compat(self):
547
self.setup_smart_server_with_call_log()
548
bzrdir = self.make_bzrdir('.')
549
self.reset_smart_call_log()
550
self.disable_verb('BzrDir.create_repository')
551
repo = bzrdir.create_repository()
552
create_repo_call_count = len([call for call in self.hpss_calls if
553
call.call.method == 'BzrDir.create_repository'])
554
self.assertEqual(1, create_repo_call_count)
556
def test_current_server(self):
557
transport = self.get_transport('.')
558
transport = transport.clone('quack')
559
self.make_bzrdir('quack')
560
client = FakeClient(transport.base)
561
reference_bzrdir_format = bzrdir.format_registry.get('default')()
562
reference_format = reference_bzrdir_format.repository_format
563
network_name = reference_format.network_name()
564
client.add_expected_call(
565
'BzrDir.create_repository', ('quack/',
566
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
567
'success', ('ok', 'no', 'no', 'no', network_name))
568
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
570
repo = a_bzrdir.create_repository()
571
# We should have got a remote repository
572
self.assertIsInstance(repo, remote.RemoteRepository)
573
# its format should have the settings from the response
574
format = repo._format
575
self.assertFalse(format.rich_root_data)
576
self.assertFalse(format.supports_tree_reference)
577
self.assertFalse(format.supports_external_lookups)
578
self.assertEqual(network_name, format.network_name())
581
class TestBzrDirOpenRepository(TestRemote):
583
def test_backwards_compat_1_2_3(self):
584
# fallback all the way to the first version.
585
reference_format = self.get_repo_format()
586
network_name = reference_format.network_name()
587
client = FakeClient('bzr://example.com/')
588
client.add_unknown_method_response('BzrDir.find_repositoryV3')
589
client.add_unknown_method_response('BzrDir.find_repositoryV2')
590
client.add_success_response('ok', '', 'no', 'no')
591
# A real repository instance will be created to determine the network
593
client.add_success_response_with_body(
594
"Bazaar-NG meta directory, format 1\n", 'ok')
595
client.add_success_response_with_body(
596
reference_format.get_format_string(), 'ok')
597
# PackRepository wants to do a stat
598
client.add_success_response('stat', '0', '65535')
599
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
601
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
603
repo = bzrdir.open_repository()
605
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
606
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
607
('call', 'BzrDir.find_repository', ('quack/',)),
608
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
609
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
610
('call', 'stat', ('/quack/.bzr/repository',)),
613
self.assertEqual(network_name, repo._format.network_name())
615
def test_backwards_compat_2(self):
616
# fallback to find_repositoryV2
617
reference_format = self.get_repo_format()
618
network_name = reference_format.network_name()
619
client = FakeClient('bzr://example.com/')
620
client.add_unknown_method_response('BzrDir.find_repositoryV3')
621
client.add_success_response('ok', '', 'no', 'no', 'no')
622
# A real repository instance will be created to determine the network
624
client.add_success_response_with_body(
625
"Bazaar-NG meta directory, format 1\n", 'ok')
626
client.add_success_response_with_body(
627
reference_format.get_format_string(), 'ok')
628
# PackRepository wants to do a stat
629
client.add_success_response('stat', '0', '65535')
630
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
632
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
634
repo = bzrdir.open_repository()
636
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
637
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
638
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
639
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
640
('call', 'stat', ('/quack/.bzr/repository',)),
643
self.assertEqual(network_name, repo._format.network_name())
645
def test_current_server(self):
646
reference_format = self.get_repo_format()
647
network_name = reference_format.network_name()
648
transport = MemoryTransport()
649
transport.mkdir('quack')
650
transport = transport.clone('quack')
651
client = FakeClient(transport.base)
652
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
653
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
655
repo = bzrdir.open_repository()
657
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
659
self.assertEqual(network_name, repo._format.network_name())
662
class OldSmartClient(object):
663
"""A fake smart client for test_old_version that just returns a version one
664
response to the 'hello' (query version) command.
667
def get_request(self):
668
input_file = StringIO('ok\x011\n')
669
output_file = StringIO()
670
client_medium = medium.SmartSimplePipesClientMedium(
671
input_file, output_file)
672
return medium.SmartClientStreamMediumRequest(client_medium)
674
def protocol_version(self):
678
class OldServerTransport(object):
679
"""A fake transport for test_old_server that reports it's smart server
680
protocol version as version one.
686
def get_smart_client(self):
687
return OldSmartClient()
690
class RemoteBranchTestCase(tests.TestCase):
692
def make_remote_branch(self, transport, client):
693
"""Make a RemoteBranch using 'client' as its _SmartClient.
695
A RemoteBzrDir and RemoteRepository will also be created to fill out
696
the RemoteBranch, albeit with stub values for some of their attributes.
698
# we do not want bzrdir to make any remote calls, so use False as its
699
# _client. If it tries to make a remote call, this will fail
701
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
703
repo = RemoteRepository(bzrdir, None, _client=client)
704
return RemoteBranch(bzrdir, repo, _client=client)
707
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
709
def test_empty_branch(self):
710
# in an empty branch we decode the response properly
711
transport = MemoryTransport()
712
client = FakeClient(transport.base)
713
client.add_expected_call(
714
'Branch.get_stacked_on_url', ('quack/',),
715
'error', ('NotStacked',))
716
client.add_expected_call(
717
'Branch.last_revision_info', ('quack/',),
718
'success', ('ok', '0', 'null:'))
719
transport.mkdir('quack')
720
transport = transport.clone('quack')
721
branch = self.make_remote_branch(transport, client)
722
result = branch.last_revision_info()
723
client.finished_test()
724
self.assertEqual((0, NULL_REVISION), result)
726
def test_non_empty_branch(self):
727
# in a non-empty branch we also decode the response properly
728
revid = u'\xc8'.encode('utf8')
729
transport = MemoryTransport()
730
client = FakeClient(transport.base)
731
client.add_expected_call(
732
'Branch.get_stacked_on_url', ('kwaak/',),
733
'error', ('NotStacked',))
734
client.add_expected_call(
735
'Branch.last_revision_info', ('kwaak/',),
736
'success', ('ok', '2', revid))
737
transport.mkdir('kwaak')
738
transport = transport.clone('kwaak')
739
branch = self.make_remote_branch(transport, client)
740
result = branch.last_revision_info()
741
self.assertEqual((2, revid), result)
744
class TestBranch_get_stacked_on_url(TestRemote):
745
"""Test Branch._get_stacked_on_url rpc"""
747
def test_get_stacked_on_invalid_url(self):
748
# test that asking for a stacked on url the server can't access works.
749
# This isn't perfect, but then as we're in the same process there
750
# really isn't anything we can do to be 100% sure that the server
751
# doesn't just open in - this test probably needs to be rewritten using
752
# a spawn()ed server.
753
stacked_branch = self.make_branch('stacked', format='1.9')
754
memory_branch = self.make_branch('base', format='1.9')
755
vfs_url = self.get_vfs_only_url('base')
756
stacked_branch.set_stacked_on_url(vfs_url)
757
transport = stacked_branch.bzrdir.root_transport
758
client = FakeClient(transport.base)
759
client.add_expected_call(
760
'Branch.get_stacked_on_url', ('stacked/',),
761
'success', ('ok', vfs_url))
762
# XXX: Multiple calls are bad, this second call documents what is
764
client.add_expected_call(
765
'Branch.get_stacked_on_url', ('stacked/',),
766
'success', ('ok', vfs_url))
767
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
769
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, None),
771
result = branch.get_stacked_on_url()
772
self.assertEqual(vfs_url, result)
774
def test_backwards_compatible(self):
775
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
776
base_branch = self.make_branch('base', format='1.6')
777
stacked_branch = self.make_branch('stacked', format='1.6')
778
stacked_branch.set_stacked_on_url('../base')
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',
786
stacked_branch.repository._format.network_name()))
787
# called twice, once from constructor and then again by us
788
client.add_expected_call(
789
'Branch.get_stacked_on_url', ('stacked/',),
790
'unknown', ('Branch.get_stacked_on_url',))
791
client.add_expected_call(
792
'Branch.get_stacked_on_url', ('stacked/',),
793
'unknown', ('Branch.get_stacked_on_url',))
794
# this will also do vfs access, but that goes direct to the transport
795
# and isn't seen by the FakeClient.
796
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
797
remote.RemoteBzrDirFormat(), _client=client)
798
branch = bzrdir.open_branch()
799
result = branch.get_stacked_on_url()
800
self.assertEqual('../base', result)
801
client.finished_test()
802
# it's in the fallback list both for the RemoteRepository and its vfs
804
self.assertEqual(1, len(branch.repository._fallback_repositories))
806
len(branch.repository._real_repository._fallback_repositories))
808
def test_get_stacked_on_real_branch(self):
809
base_branch = self.make_branch('base', format='1.6')
810
stacked_branch = self.make_branch('stacked', format='1.6')
811
stacked_branch.set_stacked_on_url('../base')
812
reference_format = self.get_repo_format()
813
network_name = reference_format.network_name()
814
client = FakeClient(self.get_url())
815
client.add_expected_call(
816
'BzrDir.open_branch', ('stacked/',),
817
'success', ('ok', ''))
818
client.add_expected_call(
819
'BzrDir.find_repositoryV3', ('stacked/',),
820
'success', ('ok', '', 'no', 'no', 'no', network_name))
821
# called twice, once from constructor and then again by us
822
client.add_expected_call(
823
'Branch.get_stacked_on_url', ('stacked/',),
824
'success', ('ok', '../base'))
825
client.add_expected_call(
826
'Branch.get_stacked_on_url', ('stacked/',),
827
'success', ('ok', '../base'))
828
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
829
remote.RemoteBzrDirFormat(), _client=client)
830
branch = bzrdir.open_branch()
831
result = branch.get_stacked_on_url()
832
self.assertEqual('../base', result)
833
client.finished_test()
834
# it's in the fallback list both for the RemoteRepository and its vfs
836
self.assertEqual(1, len(branch.repository._fallback_repositories))
838
len(branch.repository._real_repository._fallback_repositories))
841
class TestBranchSetLastRevision(RemoteBranchTestCase):
843
def test_set_empty(self):
844
# set_revision_history([]) is translated to calling
845
# Branch.set_last_revision(path, '') on the wire.
846
transport = MemoryTransport()
847
transport.mkdir('branch')
848
transport = transport.clone('branch')
850
client = FakeClient(transport.base)
851
client.add_expected_call(
852
'Branch.get_stacked_on_url', ('branch/',),
853
'error', ('NotStacked',))
854
client.add_expected_call(
855
'Branch.lock_write', ('branch/', '', ''),
856
'success', ('ok', 'branch token', 'repo token'))
857
client.add_expected_call(
858
'Branch.last_revision_info',
860
'success', ('ok', '0', 'null:'))
861
client.add_expected_call(
862
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
864
client.add_expected_call(
865
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
867
branch = self.make_remote_branch(transport, client)
868
# This is a hack to work around the problem that RemoteBranch currently
869
# unnecessarily invokes _ensure_real upon a call to lock_write.
870
branch._ensure_real = lambda: None
872
result = branch.set_revision_history([])
874
self.assertEqual(None, result)
875
client.finished_test()
877
def test_set_nonempty(self):
878
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
879
# Branch.set_last_revision(path, rev-idN) on the wire.
880
transport = MemoryTransport()
881
transport.mkdir('branch')
882
transport = transport.clone('branch')
884
client = FakeClient(transport.base)
885
client.add_expected_call(
886
'Branch.get_stacked_on_url', ('branch/',),
887
'error', ('NotStacked',))
888
client.add_expected_call(
889
'Branch.lock_write', ('branch/', '', ''),
890
'success', ('ok', 'branch token', 'repo token'))
891
client.add_expected_call(
892
'Branch.last_revision_info',
894
'success', ('ok', '0', 'null:'))
896
encoded_body = bz2.compress('\n'.join(lines))
897
client.add_success_response_with_body(encoded_body, 'ok')
898
client.add_expected_call(
899
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
901
client.add_expected_call(
902
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
904
branch = self.make_remote_branch(transport, client)
905
# This is a hack to work around the problem that RemoteBranch currently
906
# unnecessarily invokes _ensure_real upon a call to lock_write.
907
branch._ensure_real = lambda: None
908
# Lock the branch, reset the record of remote calls.
910
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
912
self.assertEqual(None, result)
913
client.finished_test()
915
def test_no_such_revision(self):
916
transport = MemoryTransport()
917
transport.mkdir('branch')
918
transport = transport.clone('branch')
919
# A response of 'NoSuchRevision' is translated into an exception.
920
client = FakeClient(transport.base)
921
client.add_expected_call(
922
'Branch.get_stacked_on_url', ('branch/',),
923
'error', ('NotStacked',))
924
client.add_expected_call(
925
'Branch.lock_write', ('branch/', '', ''),
926
'success', ('ok', 'branch token', 'repo token'))
927
client.add_expected_call(
928
'Branch.last_revision_info',
930
'success', ('ok', '0', 'null:'))
931
# get_graph calls to construct the revision history, for the set_rh
934
encoded_body = bz2.compress('\n'.join(lines))
935
client.add_success_response_with_body(encoded_body, 'ok')
936
client.add_expected_call(
937
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
938
'error', ('NoSuchRevision', 'rev-id'))
939
client.add_expected_call(
940
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
943
branch = self.make_remote_branch(transport, client)
946
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
948
client.finished_test()
950
def test_tip_change_rejected(self):
951
"""TipChangeRejected responses cause a TipChangeRejected exception to
954
transport = MemoryTransport()
955
transport.mkdir('branch')
956
transport = transport.clone('branch')
957
client = FakeClient(transport.base)
958
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
959
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
960
client.add_expected_call(
961
'Branch.get_stacked_on_url', ('branch/',),
962
'error', ('NotStacked',))
963
client.add_expected_call(
964
'Branch.lock_write', ('branch/', '', ''),
965
'success', ('ok', 'branch token', 'repo token'))
966
client.add_expected_call(
967
'Branch.last_revision_info',
969
'success', ('ok', '0', 'null:'))
971
encoded_body = bz2.compress('\n'.join(lines))
972
client.add_success_response_with_body(encoded_body, 'ok')
973
client.add_expected_call(
974
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
975
'error', ('TipChangeRejected', rejection_msg_utf8))
976
client.add_expected_call(
977
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
979
branch = self.make_remote_branch(transport, client)
980
branch._ensure_real = lambda: None
982
# The 'TipChangeRejected' error response triggered by calling
983
# set_revision_history causes a TipChangeRejected exception.
984
err = self.assertRaises(
985
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
986
# The UTF-8 message from the response has been decoded into a unicode
988
self.assertIsInstance(err.msg, unicode)
989
self.assertEqual(rejection_msg_unicode, err.msg)
991
client.finished_test()
994
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
996
def test_set_last_revision_info(self):
997
# set_last_revision_info(num, 'rev-id') is translated to calling
998
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
999
transport = MemoryTransport()
1000
transport.mkdir('branch')
1001
transport = transport.clone('branch')
1002
client = FakeClient(transport.base)
1003
# get_stacked_on_url
1004
client.add_error_response('NotStacked')
1006
client.add_success_response('ok', 'branch token', 'repo token')
1007
# query the current revision
1008
client.add_success_response('ok', '0', 'null:')
1010
client.add_success_response('ok')
1012
client.add_success_response('ok')
1014
branch = self.make_remote_branch(transport, client)
1015
# Lock the branch, reset the record of remote calls.
1018
result = branch.set_last_revision_info(1234, 'a-revision-id')
1020
[('call', 'Branch.last_revision_info', ('branch/',)),
1021
('call', 'Branch.set_last_revision_info',
1022
('branch/', 'branch token', 'repo token',
1023
'1234', 'a-revision-id'))],
1025
self.assertEqual(None, result)
1027
def test_no_such_revision(self):
1028
# A response of 'NoSuchRevision' is translated into an exception.
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('NoSuchRevision', 'revid')
1040
client.add_success_response('ok')
1042
branch = self.make_remote_branch(transport, client)
1043
# Lock the branch, reset the record of remote calls.
1048
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1051
def lock_remote_branch(self, branch):
1052
"""Trick a RemoteBranch into thinking it is locked."""
1053
branch._lock_mode = 'w'
1054
branch._lock_count = 2
1055
branch._lock_token = 'branch token'
1056
branch._repo_lock_token = 'repo token'
1057
branch.repository._lock_mode = 'w'
1058
branch.repository._lock_count = 2
1059
branch.repository._lock_token = 'repo token'
1061
def test_backwards_compatibility(self):
1062
"""If the server does not support the Branch.set_last_revision_info
1063
verb (which is new in 1.4), then the client falls back to VFS methods.
1065
# This test is a little messy. Unlike most tests in this file, it
1066
# doesn't purely test what a Remote* object sends over the wire, and
1067
# how it reacts to responses from the wire. It instead relies partly
1068
# on asserting that the RemoteBranch will call
1069
# self._real_branch.set_last_revision_info(...).
1071
# First, set up our RemoteBranch with a FakeClient that raises
1072
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1073
transport = MemoryTransport()
1074
transport.mkdir('branch')
1075
transport = transport.clone('branch')
1076
client = FakeClient(transport.base)
1077
client.add_expected_call(
1078
'Branch.get_stacked_on_url', ('branch/',),
1079
'error', ('NotStacked',))
1080
client.add_expected_call(
1081
'Branch.last_revision_info',
1083
'success', ('ok', '0', 'null:'))
1084
client.add_expected_call(
1085
'Branch.set_last_revision_info',
1086
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1087
'unknown', 'Branch.set_last_revision_info')
1089
branch = self.make_remote_branch(transport, client)
1090
class StubRealBranch(object):
1093
def set_last_revision_info(self, revno, revision_id):
1095
('set_last_revision_info', revno, revision_id))
1096
def _clear_cached_state(self):
1098
real_branch = StubRealBranch()
1099
branch._real_branch = real_branch
1100
self.lock_remote_branch(branch)
1102
# Call set_last_revision_info, and verify it behaved as expected.
1103
result = branch.set_last_revision_info(1234, 'a-revision-id')
1105
[('set_last_revision_info', 1234, 'a-revision-id')],
1107
client.finished_test()
1109
def test_unexpected_error(self):
1110
# If the server sends an error the client doesn't understand, it gets
1111
# turned into an UnknownErrorFromSmartServer, which is presented as a
1112
# non-internal error to the user.
1113
transport = MemoryTransport()
1114
transport.mkdir('branch')
1115
transport = transport.clone('branch')
1116
client = FakeClient(transport.base)
1117
# get_stacked_on_url
1118
client.add_error_response('NotStacked')
1120
client.add_success_response('ok', 'branch token', 'repo token')
1122
client.add_error_response('UnexpectedError')
1124
client.add_success_response('ok')
1126
branch = self.make_remote_branch(transport, client)
1127
# Lock the branch, reset the record of remote calls.
1131
err = self.assertRaises(
1132
errors.UnknownErrorFromSmartServer,
1133
branch.set_last_revision_info, 123, 'revid')
1134
self.assertEqual(('UnexpectedError',), err.error_tuple)
1137
def test_tip_change_rejected(self):
1138
"""TipChangeRejected responses cause a TipChangeRejected exception to
1141
transport = MemoryTransport()
1142
transport.mkdir('branch')
1143
transport = transport.clone('branch')
1144
client = FakeClient(transport.base)
1145
# get_stacked_on_url
1146
client.add_error_response('NotStacked')
1148
client.add_success_response('ok', 'branch token', 'repo token')
1150
client.add_error_response('TipChangeRejected', 'rejection message')
1152
client.add_success_response('ok')
1154
branch = self.make_remote_branch(transport, client)
1155
# Lock the branch, reset the record of remote calls.
1157
self.addCleanup(branch.unlock)
1160
# The 'TipChangeRejected' error response triggered by calling
1161
# set_last_revision_info causes a TipChangeRejected exception.
1162
err = self.assertRaises(
1163
errors.TipChangeRejected,
1164
branch.set_last_revision_info, 123, 'revid')
1165
self.assertEqual('rejection message', err.msg)
1168
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1169
"""Getting the branch configuration should use an abstract method not vfs.
1172
def test_get_branch_conf(self):
1173
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1174
## # We should see that branch.get_config() does a single rpc to get the
1175
## # remote configuration file, abstracting away where that is stored on
1176
## # the server. However at the moment it always falls back to using the
1177
## # vfs, and this would need some changes in config.py.
1179
## # in an empty branch we decode the response properly
1180
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1181
## # we need to make a real branch because the remote_branch.control_files
1182
## # will trigger _ensure_real.
1183
## branch = self.make_branch('quack')
1184
## transport = branch.bzrdir.root_transport
1185
## # we do not want bzrdir to make any remote calls
1186
## bzrdir = RemoteBzrDir(transport, _client=False)
1187
## branch = RemoteBranch(bzrdir, None, _client=client)
1188
## config = branch.get_config()
1189
## self.assertEqual(
1190
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1194
class TestBranchLockWrite(RemoteBranchTestCase):
1196
def test_lock_write_unlockable(self):
1197
transport = MemoryTransport()
1198
client = FakeClient(transport.base)
1199
client.add_expected_call(
1200
'Branch.get_stacked_on_url', ('quack/',),
1201
'error', ('NotStacked',),)
1202
client.add_expected_call(
1203
'Branch.lock_write', ('quack/', '', ''),
1204
'error', ('UnlockableTransport',))
1205
transport.mkdir('quack')
1206
transport = transport.clone('quack')
1207
branch = self.make_remote_branch(transport, client)
1208
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1209
client.finished_test()
1212
class TestTransportIsReadonly(tests.TestCase):
1214
def test_true(self):
1215
client = FakeClient()
1216
client.add_success_response('yes')
1217
transport = RemoteTransport('bzr://example.com/', medium=False,
1219
self.assertEqual(True, transport.is_readonly())
1221
[('call', 'Transport.is_readonly', ())],
1224
def test_false(self):
1225
client = FakeClient()
1226
client.add_success_response('no')
1227
transport = RemoteTransport('bzr://example.com/', medium=False,
1229
self.assertEqual(False, transport.is_readonly())
1231
[('call', 'Transport.is_readonly', ())],
1234
def test_error_from_old_server(self):
1235
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1237
Clients should treat it as a "no" response, because is_readonly is only
1238
advisory anyway (a transport could be read-write, but then the
1239
underlying filesystem could be readonly anyway).
1241
client = FakeClient()
1242
client.add_unknown_method_response('Transport.is_readonly')
1243
transport = RemoteTransport('bzr://example.com/', medium=False,
1245
self.assertEqual(False, transport.is_readonly())
1247
[('call', 'Transport.is_readonly', ())],
1251
class TestTransportMkdir(tests.TestCase):
1253
def test_permissiondenied(self):
1254
client = FakeClient()
1255
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1256
transport = RemoteTransport('bzr://example.com/', medium=False,
1258
exc = self.assertRaises(
1259
errors.PermissionDenied, transport.mkdir, 'client path')
1260
expected_error = errors.PermissionDenied('/client path', 'extra')
1261
self.assertEqual(expected_error, exc)
1264
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1266
def test_defaults_to_none(self):
1267
t = RemoteSSHTransport('bzr+ssh://example.com')
1268
self.assertIs(None, t._get_credentials()[0])
1270
def test_uses_authentication_config(self):
1271
conf = config.AuthenticationConfig()
1272
conf._get_config().update(
1273
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1276
t = RemoteSSHTransport('bzr+ssh://example.com')
1277
self.assertEqual('bar', t._get_credentials()[0])
1280
class TestRemoteRepository(TestRemote):
1281
"""Base for testing RemoteRepository protocol usage.
1283
These tests contain frozen requests and responses. We want any changes to
1284
what is sent or expected to be require a thoughtful update to these tests
1285
because they might break compatibility with different-versioned servers.
1288
def setup_fake_client_and_repository(self, transport_path):
1289
"""Create the fake client and repository for testing with.
1291
There's no real server here; we just have canned responses sent
1294
:param transport_path: Path below the root of the MemoryTransport
1295
where the repository will be created.
1297
transport = MemoryTransport()
1298
transport.mkdir(transport_path)
1299
client = FakeClient(transport.base)
1300
transport = transport.clone(transport_path)
1301
# we do not want bzrdir to make any remote calls
1302
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1304
repo = RemoteRepository(bzrdir, None, _client=client)
1308
class TestRepositoryGatherStats(TestRemoteRepository):
1310
def test_revid_none(self):
1311
# ('ok',), body with revisions and size
1312
transport_path = 'quack'
1313
repo, client = self.setup_fake_client_and_repository(transport_path)
1314
client.add_success_response_with_body(
1315
'revisions: 2\nsize: 18\n', 'ok')
1316
result = repo.gather_stats(None)
1318
[('call_expecting_body', 'Repository.gather_stats',
1319
('quack/','','no'))],
1321
self.assertEqual({'revisions': 2, 'size': 18}, result)
1323
def test_revid_no_committers(self):
1324
# ('ok',), body without committers
1325
body = ('firstrev: 123456.300 3600\n'
1326
'latestrev: 654231.400 0\n'
1329
transport_path = 'quick'
1330
revid = u'\xc8'.encode('utf8')
1331
repo, client = self.setup_fake_client_and_repository(transport_path)
1332
client.add_success_response_with_body(body, 'ok')
1333
result = repo.gather_stats(revid)
1335
[('call_expecting_body', 'Repository.gather_stats',
1336
('quick/', revid, 'no'))],
1338
self.assertEqual({'revisions': 2, 'size': 18,
1339
'firstrev': (123456.300, 3600),
1340
'latestrev': (654231.400, 0),},
1343
def test_revid_with_committers(self):
1344
# ('ok',), body with committers
1345
body = ('committers: 128\n'
1346
'firstrev: 123456.300 3600\n'
1347
'latestrev: 654231.400 0\n'
1350
transport_path = 'buick'
1351
revid = u'\xc8'.encode('utf8')
1352
repo, client = self.setup_fake_client_and_repository(transport_path)
1353
client.add_success_response_with_body(body, 'ok')
1354
result = repo.gather_stats(revid, True)
1356
[('call_expecting_body', 'Repository.gather_stats',
1357
('buick/', revid, 'yes'))],
1359
self.assertEqual({'revisions': 2, 'size': 18,
1361
'firstrev': (123456.300, 3600),
1362
'latestrev': (654231.400, 0),},
1366
class TestRepositoryGetGraph(TestRemoteRepository):
1368
def test_get_graph(self):
1369
# get_graph returns a graph with a custom parents provider.
1370
transport_path = 'quack'
1371
repo, client = self.setup_fake_client_and_repository(transport_path)
1372
graph = repo.get_graph()
1373
self.assertNotEqual(graph._parents_provider, repo)
1376
class TestRepositoryGetParentMap(TestRemoteRepository):
1378
def test_get_parent_map_caching(self):
1379
# get_parent_map returns from cache until unlock()
1380
# setup a reponse with two revisions
1381
r1 = u'\u0e33'.encode('utf8')
1382
r2 = u'\u0dab'.encode('utf8')
1383
lines = [' '.join([r2, r1]), r1]
1384
encoded_body = bz2.compress('\n'.join(lines))
1386
transport_path = 'quack'
1387
repo, client = self.setup_fake_client_and_repository(transport_path)
1388
client.add_success_response_with_body(encoded_body, 'ok')
1389
client.add_success_response_with_body(encoded_body, 'ok')
1391
graph = repo.get_graph()
1392
parents = graph.get_parent_map([r2])
1393
self.assertEqual({r2: (r1,)}, parents)
1394
# locking and unlocking deeper should not reset
1397
parents = graph.get_parent_map([r1])
1398
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1400
[('call_with_body_bytes_expecting_body',
1401
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
1404
# now we call again, and it should use the second response.
1406
graph = repo.get_graph()
1407
parents = graph.get_parent_map([r1])
1408
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1410
[('call_with_body_bytes_expecting_body',
1411
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
1412
('call_with_body_bytes_expecting_body',
1413
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
1418
def test_get_parent_map_reconnects_if_unknown_method(self):
1419
transport_path = 'quack'
1420
repo, client = self.setup_fake_client_and_repository(transport_path)
1421
client.add_unknown_method_response('Repository,get_parent_map')
1422
client.add_success_response_with_body('', 'ok')
1423
self.assertFalse(client._medium._is_remote_before((1, 2)))
1424
rev_id = 'revision-id'
1425
expected_deprecations = [
1426
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1428
parents = self.callDeprecated(
1429
expected_deprecations, repo.get_parent_map, [rev_id])
1431
[('call_with_body_bytes_expecting_body',
1432
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
1433
('disconnect medium',),
1434
('call_expecting_body', 'Repository.get_revision_graph',
1437
# The medium is now marked as being connected to an older server
1438
self.assertTrue(client._medium._is_remote_before((1, 2)))
1440
def test_get_parent_map_fallback_parentless_node(self):
1441
"""get_parent_map falls back to get_revision_graph on old servers. The
1442
results from get_revision_graph are tweaked to match the get_parent_map
1445
Specifically, a {key: ()} result from get_revision_graph means "no
1446
parents" for that key, which in get_parent_map results should be
1447
represented as {key: ('null:',)}.
1449
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1451
rev_id = 'revision-id'
1452
transport_path = 'quack'
1453
repo, client = self.setup_fake_client_and_repository(transport_path)
1454
client.add_success_response_with_body(rev_id, 'ok')
1455
client._medium._remember_remote_is_before((1, 2))
1456
expected_deprecations = [
1457
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1459
parents = self.callDeprecated(
1460
expected_deprecations, repo.get_parent_map, [rev_id])
1462
[('call_expecting_body', 'Repository.get_revision_graph',
1465
self.assertEqual({rev_id: ('null:',)}, parents)
1467
def test_get_parent_map_unexpected_response(self):
1468
repo, client = self.setup_fake_client_and_repository('path')
1469
client.add_success_response('something unexpected!')
1471
errors.UnexpectedSmartServerResponse,
1472
repo.get_parent_map, ['a-revision-id'])
1475
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1477
def test_allows_new_revisions(self):
1478
"""get_parent_map's results can be updated by commit."""
1479
smart_server = server.SmartTCPServer_for_testing()
1480
smart_server.setUp()
1481
self.addCleanup(smart_server.tearDown)
1482
self.make_branch('branch')
1483
branch = Branch.open(smart_server.get_url() + '/branch')
1484
tree = branch.create_checkout('tree', lightweight=True)
1486
self.addCleanup(tree.unlock)
1487
graph = tree.branch.repository.get_graph()
1488
# This provides an opportunity for the missing rev-id to be cached.
1489
self.assertEqual({}, graph.get_parent_map(['rev1']))
1490
tree.commit('message', rev_id='rev1')
1491
graph = tree.branch.repository.get_graph()
1492
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1495
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1497
def test_null_revision(self):
1498
# a null revision has the predictable result {}, we should have no wire
1499
# traffic when calling it with this argument
1500
transport_path = 'empty'
1501
repo, client = self.setup_fake_client_and_repository(transport_path)
1502
client.add_success_response('notused')
1503
result = self.applyDeprecated(one_four, repo.get_revision_graph,
1505
self.assertEqual([], client._calls)
1506
self.assertEqual({}, result)
1508
def test_none_revision(self):
1509
# with none we want the entire graph
1510
r1 = u'\u0e33'.encode('utf8')
1511
r2 = u'\u0dab'.encode('utf8')
1512
lines = [' '.join([r2, r1]), r1]
1513
encoded_body = '\n'.join(lines)
1515
transport_path = 'sinhala'
1516
repo, client = self.setup_fake_client_and_repository(transport_path)
1517
client.add_success_response_with_body(encoded_body, 'ok')
1518
result = self.applyDeprecated(one_four, repo.get_revision_graph)
1520
[('call_expecting_body', 'Repository.get_revision_graph',
1523
self.assertEqual({r1: (), r2: (r1, )}, result)
1525
def test_specific_revision(self):
1526
# with a specific revision we want the graph for that
1527
# with none we want the entire graph
1528
r11 = u'\u0e33'.encode('utf8')
1529
r12 = u'\xc9'.encode('utf8')
1530
r2 = u'\u0dab'.encode('utf8')
1531
lines = [' '.join([r2, r11, r12]), r11, r12]
1532
encoded_body = '\n'.join(lines)
1534
transport_path = 'sinhala'
1535
repo, client = self.setup_fake_client_and_repository(transport_path)
1536
client.add_success_response_with_body(encoded_body, 'ok')
1537
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
1539
[('call_expecting_body', 'Repository.get_revision_graph',
1542
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1544
def test_no_such_revision(self):
1546
transport_path = 'sinhala'
1547
repo, client = self.setup_fake_client_and_repository(transport_path)
1548
client.add_error_response('nosuchrevision', revid)
1549
# also check that the right revision is reported in the error
1550
self.assertRaises(errors.NoSuchRevision,
1551
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1553
[('call_expecting_body', 'Repository.get_revision_graph',
1554
('sinhala/', revid))],
1557
def test_unexpected_error(self):
1559
transport_path = 'sinhala'
1560
repo, client = self.setup_fake_client_and_repository(transport_path)
1561
client.add_error_response('AnUnexpectedError')
1562
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1563
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1564
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1567
class TestRepositoryIsShared(TestRemoteRepository):
1569
def test_is_shared(self):
1570
# ('yes', ) for Repository.is_shared -> 'True'.
1571
transport_path = 'quack'
1572
repo, client = self.setup_fake_client_and_repository(transport_path)
1573
client.add_success_response('yes')
1574
result = repo.is_shared()
1576
[('call', 'Repository.is_shared', ('quack/',))],
1578
self.assertEqual(True, result)
1580
def test_is_not_shared(self):
1581
# ('no', ) for Repository.is_shared -> 'False'.
1582
transport_path = 'qwack'
1583
repo, client = self.setup_fake_client_and_repository(transport_path)
1584
client.add_success_response('no')
1585
result = repo.is_shared()
1587
[('call', 'Repository.is_shared', ('qwack/',))],
1589
self.assertEqual(False, result)
1592
class TestRepositoryLockWrite(TestRemoteRepository):
1594
def test_lock_write(self):
1595
transport_path = 'quack'
1596
repo, client = self.setup_fake_client_and_repository(transport_path)
1597
client.add_success_response('ok', 'a token')
1598
result = repo.lock_write()
1600
[('call', 'Repository.lock_write', ('quack/', ''))],
1602
self.assertEqual('a token', result)
1604
def test_lock_write_already_locked(self):
1605
transport_path = 'quack'
1606
repo, client = self.setup_fake_client_and_repository(transport_path)
1607
client.add_error_response('LockContention')
1608
self.assertRaises(errors.LockContention, repo.lock_write)
1610
[('call', 'Repository.lock_write', ('quack/', ''))],
1613
def test_lock_write_unlockable(self):
1614
transport_path = 'quack'
1615
repo, client = self.setup_fake_client_and_repository(transport_path)
1616
client.add_error_response('UnlockableTransport')
1617
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1619
[('call', 'Repository.lock_write', ('quack/', ''))],
1623
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1625
def test_backwards_compat(self):
1626
self.setup_smart_server_with_call_log()
1627
repo = self.make_repository('.')
1628
self.reset_smart_call_log()
1629
verb = 'Repository.set_make_working_trees'
1630
self.disable_verb(verb)
1631
repo.set_make_working_trees(True)
1632
call_count = len([call for call in self.hpss_calls if
1633
call.call.method == verb])
1634
self.assertEqual(1, call_count)
1636
def test_current(self):
1637
transport_path = 'quack'
1638
repo, client = self.setup_fake_client_and_repository(transport_path)
1639
client.add_expected_call(
1640
'Repository.set_make_working_trees', ('quack/', 'True'),
1642
client.add_expected_call(
1643
'Repository.set_make_working_trees', ('quack/', 'False'),
1645
repo.set_make_working_trees(True)
1646
repo.set_make_working_trees(False)
1649
class TestRepositoryUnlock(TestRemoteRepository):
1651
def test_unlock(self):
1652
transport_path = 'quack'
1653
repo, client = self.setup_fake_client_and_repository(transport_path)
1654
client.add_success_response('ok', 'a token')
1655
client.add_success_response('ok')
1659
[('call', 'Repository.lock_write', ('quack/', '')),
1660
('call', 'Repository.unlock', ('quack/', 'a token'))],
1663
def test_unlock_wrong_token(self):
1664
# If somehow the token is wrong, unlock will raise TokenMismatch.
1665
transport_path = 'quack'
1666
repo, client = self.setup_fake_client_and_repository(transport_path)
1667
client.add_success_response('ok', 'a token')
1668
client.add_error_response('TokenMismatch')
1670
self.assertRaises(errors.TokenMismatch, repo.unlock)
1673
class TestRepositoryHasRevision(TestRemoteRepository):
1675
def test_none(self):
1676
# repo.has_revision(None) should not cause any traffic.
1677
transport_path = 'quack'
1678
repo, client = self.setup_fake_client_and_repository(transport_path)
1680
# The null revision is always there, so has_revision(None) == True.
1681
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1683
# The remote repo shouldn't be accessed.
1684
self.assertEqual([], client._calls)
1687
class TestRepositoryTarball(TestRemoteRepository):
1689
# This is a canned tarball reponse we can validate against
1691
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1692
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1693
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1694
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1695
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1696
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1697
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1698
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1699
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1700
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1701
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1702
'nWQ7QH/F3JFOFCQ0aSPfA='
1705
def test_repository_tarball(self):
1706
# Test that Repository.tarball generates the right operations
1707
transport_path = 'repo'
1708
expected_calls = [('call_expecting_body', 'Repository.tarball',
1709
('repo/', 'bz2',),),
1711
repo, client = self.setup_fake_client_and_repository(transport_path)
1712
client.add_success_response_with_body(self.tarball_content, 'ok')
1713
# Now actually ask for the tarball
1714
tarball_file = repo._get_tarball('bz2')
1716
self.assertEqual(expected_calls, client._calls)
1717
self.assertEqual(self.tarball_content, tarball_file.read())
1719
tarball_file.close()
1722
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
1723
"""RemoteRepository.copy_content_into optimizations"""
1725
def test_copy_content_remote_to_local(self):
1726
self.transport_server = server.SmartTCPServer_for_testing
1727
src_repo = self.make_repository('repo1')
1728
src_repo = repository.Repository.open(self.get_url('repo1'))
1729
# At the moment the tarball-based copy_content_into can't write back
1730
# into a smart server. It would be good if it could upload the
1731
# tarball; once that works we'd have to create repositories of
1732
# different formats. -- mbp 20070410
1733
dest_url = self.get_vfs_only_url('repo2')
1734
dest_bzrdir = BzrDir.create(dest_url)
1735
dest_repo = dest_bzrdir.create_repository()
1736
self.assertFalse(isinstance(dest_repo, RemoteRepository))
1737
self.assertTrue(isinstance(src_repo, RemoteRepository))
1738
src_repo.copy_content_into(dest_repo)
1741
class _StubRealPackRepository(object):
1743
def __init__(self, calls):
1744
self._pack_collection = _StubPackCollection(calls)
1747
class _StubPackCollection(object):
1749
def __init__(self, calls):
1753
self.calls.append(('pack collection autopack',))
1755
def reload_pack_names(self):
1756
self.calls.append(('pack collection reload_pack_names',))
1759
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1760
"""Tests for RemoteRepository.autopack implementation."""
1763
"""When the server returns 'ok' and there's no _real_repository, then
1764
nothing else happens: the autopack method is done.
1766
transport_path = 'quack'
1767
repo, client = self.setup_fake_client_and_repository(transport_path)
1768
client.add_expected_call(
1769
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
1771
client.finished_test()
1773
def test_ok_with_real_repo(self):
1774
"""When the server returns 'ok' and there is a _real_repository, then
1775
the _real_repository's reload_pack_name's method will be called.
1777
transport_path = 'quack'
1778
repo, client = self.setup_fake_client_and_repository(transport_path)
1779
client.add_expected_call(
1780
'PackRepository.autopack', ('quack/',),
1782
repo._real_repository = _StubRealPackRepository(client._calls)
1785
[('call', 'PackRepository.autopack', ('quack/',)),
1786
('pack collection reload_pack_names',)],
1789
def test_backwards_compatibility(self):
1790
"""If the server does not recognise the PackRepository.autopack verb,
1791
fallback to the real_repository's implementation.
1793
transport_path = 'quack'
1794
repo, client = self.setup_fake_client_and_repository(transport_path)
1795
client.add_unknown_method_response('PackRepository.autopack')
1796
def stub_ensure_real():
1797
client._calls.append(('_ensure_real',))
1798
repo._real_repository = _StubRealPackRepository(client._calls)
1799
repo._ensure_real = stub_ensure_real
1802
[('call', 'PackRepository.autopack', ('quack/',)),
1804
('pack collection autopack',)],
1808
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
1809
"""Base class for unit tests for bzrlib.remote._translate_error."""
1811
def translateTuple(self, error_tuple, **context):
1812
"""Call _translate_error with an ErrorFromSmartServer built from the
1815
:param error_tuple: A tuple of a smart server response, as would be
1816
passed to an ErrorFromSmartServer.
1817
:kwargs context: context items to call _translate_error with.
1819
:returns: The error raised by _translate_error.
1821
# Raise the ErrorFromSmartServer before passing it as an argument,
1822
# because _translate_error may need to re-raise it with a bare 'raise'
1824
server_error = errors.ErrorFromSmartServer(error_tuple)
1825
translated_error = self.translateErrorFromSmartServer(
1826
server_error, **context)
1827
return translated_error
1829
def translateErrorFromSmartServer(self, error_object, **context):
1830
"""Like translateTuple, but takes an already constructed
1831
ErrorFromSmartServer rather than a tuple.
1835
except errors.ErrorFromSmartServer, server_error:
1836
translated_error = self.assertRaises(
1837
errors.BzrError, remote._translate_error, server_error,
1839
return translated_error
1842
class TestErrorTranslationSuccess(TestErrorTranslationBase):
1843
"""Unit tests for bzrlib.remote._translate_error.
1845
Given an ErrorFromSmartServer (which has an error tuple from a smart
1846
server) and some context, _translate_error raises more specific errors from
1849
This test case covers the cases where _translate_error succeeds in
1850
translating an ErrorFromSmartServer to something better. See
1851
TestErrorTranslationRobustness for other cases.
1854
def test_NoSuchRevision(self):
1855
branch = self.make_branch('')
1857
translated_error = self.translateTuple(
1858
('NoSuchRevision', revid), branch=branch)
1859
expected_error = errors.NoSuchRevision(branch, revid)
1860
self.assertEqual(expected_error, translated_error)
1862
def test_nosuchrevision(self):
1863
repository = self.make_repository('')
1865
translated_error = self.translateTuple(
1866
('nosuchrevision', revid), repository=repository)
1867
expected_error = errors.NoSuchRevision(repository, revid)
1868
self.assertEqual(expected_error, translated_error)
1870
def test_nobranch(self):
1871
bzrdir = self.make_bzrdir('')
1872
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
1873
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
1874
self.assertEqual(expected_error, translated_error)
1876
def test_LockContention(self):
1877
translated_error = self.translateTuple(('LockContention',))
1878
expected_error = errors.LockContention('(remote lock)')
1879
self.assertEqual(expected_error, translated_error)
1881
def test_UnlockableTransport(self):
1882
bzrdir = self.make_bzrdir('')
1883
translated_error = self.translateTuple(
1884
('UnlockableTransport',), bzrdir=bzrdir)
1885
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
1886
self.assertEqual(expected_error, translated_error)
1888
def test_LockFailed(self):
1889
lock = 'str() of a server lock'
1890
why = 'str() of why'
1891
translated_error = self.translateTuple(('LockFailed', lock, why))
1892
expected_error = errors.LockFailed(lock, why)
1893
self.assertEqual(expected_error, translated_error)
1895
def test_TokenMismatch(self):
1896
token = 'a lock token'
1897
translated_error = self.translateTuple(('TokenMismatch',), token=token)
1898
expected_error = errors.TokenMismatch(token, '(remote token)')
1899
self.assertEqual(expected_error, translated_error)
1901
def test_Diverged(self):
1902
branch = self.make_branch('a')
1903
other_branch = self.make_branch('b')
1904
translated_error = self.translateTuple(
1905
('Diverged',), branch=branch, other_branch=other_branch)
1906
expected_error = errors.DivergedBranches(branch, other_branch)
1907
self.assertEqual(expected_error, translated_error)
1909
def test_ReadError_no_args(self):
1911
translated_error = self.translateTuple(('ReadError',), path=path)
1912
expected_error = errors.ReadError(path)
1913
self.assertEqual(expected_error, translated_error)
1915
def test_ReadError(self):
1917
translated_error = self.translateTuple(('ReadError', path))
1918
expected_error = errors.ReadError(path)
1919
self.assertEqual(expected_error, translated_error)
1921
def test_PermissionDenied_no_args(self):
1923
translated_error = self.translateTuple(('PermissionDenied',), path=path)
1924
expected_error = errors.PermissionDenied(path)
1925
self.assertEqual(expected_error, translated_error)
1927
def test_PermissionDenied_one_arg(self):
1929
translated_error = self.translateTuple(('PermissionDenied', path))
1930
expected_error = errors.PermissionDenied(path)
1931
self.assertEqual(expected_error, translated_error)
1933
def test_PermissionDenied_one_arg_and_context(self):
1934
"""Given a choice between a path from the local context and a path on
1935
the wire, _translate_error prefers the path from the local context.
1937
local_path = 'local path'
1938
remote_path = 'remote path'
1939
translated_error = self.translateTuple(
1940
('PermissionDenied', remote_path), path=local_path)
1941
expected_error = errors.PermissionDenied(local_path)
1942
self.assertEqual(expected_error, translated_error)
1944
def test_PermissionDenied_two_args(self):
1946
extra = 'a string with extra info'
1947
translated_error = self.translateTuple(
1948
('PermissionDenied', path, extra))
1949
expected_error = errors.PermissionDenied(path, extra)
1950
self.assertEqual(expected_error, translated_error)
1953
class TestErrorTranslationRobustness(TestErrorTranslationBase):
1954
"""Unit tests for bzrlib.remote._translate_error's robustness.
1956
TestErrorTranslationSuccess is for cases where _translate_error can
1957
translate successfully. This class about how _translate_err behaves when
1958
it fails to translate: it re-raises the original error.
1961
def test_unrecognised_server_error(self):
1962
"""If the error code from the server is not recognised, the original
1963
ErrorFromSmartServer is propagated unmodified.
1965
error_tuple = ('An unknown error tuple',)
1966
server_error = errors.ErrorFromSmartServer(error_tuple)
1967
translated_error = self.translateErrorFromSmartServer(server_error)
1968
expected_error = errors.UnknownErrorFromSmartServer(server_error)
1969
self.assertEqual(expected_error, translated_error)
1971
def test_context_missing_a_key(self):
1972
"""In case of a bug in the client, or perhaps an unexpected response
1973
from a server, _translate_error returns the original error tuple from
1974
the server and mutters a warning.
1976
# To translate a NoSuchRevision error _translate_error needs a 'branch'
1977
# in the context dict. So let's give it an empty context dict instead
1978
# to exercise its error recovery.
1980
error_tuple = ('NoSuchRevision', 'revid')
1981
server_error = errors.ErrorFromSmartServer(error_tuple)
1982
translated_error = self.translateErrorFromSmartServer(server_error)
1983
self.assertEqual(server_error, translated_error)
1984
# In addition to re-raising ErrorFromSmartServer, some debug info has
1985
# been muttered to the log file for developer to look at.
1986
self.assertContainsRe(
1987
self._get_log(keep_log_file=True),
1988
"Missing key 'branch' in context")
1990
def test_path_missing(self):
1991
"""Some translations (PermissionDenied, ReadError) can determine the
1992
'path' variable from either the wire or the local context. If neither
1993
has it, then an error is raised.
1995
error_tuple = ('ReadError',)
1996
server_error = errors.ErrorFromSmartServer(error_tuple)
1997
translated_error = self.translateErrorFromSmartServer(server_error)
1998
self.assertEqual(server_error, translated_error)
1999
# In addition to re-raising ErrorFromSmartServer, some debug info has
2000
# been muttered to the log file for developer to look at.
2001
self.assertContainsRe(
2002
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2005
class TestStacking(tests.TestCaseWithTransport):
2006
"""Tests for operations on stacked remote repositories.
2008
The underlying format type must support stacking.
2011
def test_access_stacked_remote(self):
2012
# based on <http://launchpad.net/bugs/261315>
2013
# make a branch stacked on another repository containing an empty
2014
# revision, then open it over hpss - we should be able to see that
2016
base_transport = self.get_transport()
2017
base_builder = self.make_branch_builder('base', format='1.6')
2018
base_builder.start_series()
2019
base_revid = base_builder.build_snapshot('rev-id', None,
2020
[('add', ('', None, 'directory', None))],
2022
base_builder.finish_series()
2023
stacked_branch = self.make_branch('stacked', format='1.6')
2024
stacked_branch.set_stacked_on_url('../base')
2025
# start a server looking at this
2026
smart_server = server.SmartTCPServer_for_testing()
2027
smart_server.setUp()
2028
self.addCleanup(smart_server.tearDown)
2029
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2030
# can get its branch and repository
2031
remote_branch = remote_bzrdir.open_branch()
2032
remote_repo = remote_branch.repository
2033
remote_repo.lock_read()
2035
# it should have an appropriate fallback repository, which should also
2036
# be a RemoteRepository
2037
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2038
self.assertIsInstance(remote_repo._fallback_repositories[0],
2040
# and it has the revision committed to the underlying repository;
2041
# these have varying implementations so we try several of them
2042
self.assertTrue(remote_repo.has_revisions([base_revid]))
2043
self.assertTrue(remote_repo.has_revision(base_revid))
2044
self.assertEqual(remote_repo.get_revision(base_revid).message,
2047
remote_repo.unlock()
2049
def prepare_stacked_remote_branch(self):
2050
smart_server = server.SmartTCPServer_for_testing()
2051
smart_server.setUp()
2052
self.addCleanup(smart_server.tearDown)
2053
tree1 = self.make_branch_and_tree('tree1')
2054
tree1.commit('rev1', rev_id='rev1')
2055
tree2 = self.make_branch_and_tree('tree2', format='1.6')
2056
tree2.branch.set_stacked_on_url(tree1.branch.base)
2057
branch2 = Branch.open(smart_server.get_url() + '/tree2')
2059
self.addCleanup(branch2.unlock)
2062
def test_stacked_get_parent_map(self):
2063
# the public implementation of get_parent_map obeys stacking
2064
branch = self.prepare_stacked_remote_branch()
2065
repo = branch.repository
2066
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2068
def test_unstacked_get_parent_map(self):
2069
# _unstacked_provider.get_parent_map ignores stacking
2070
branch = self.prepare_stacked_remote_branch()
2071
provider = branch.repository._unstacked_provider
2072
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2075
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2078
super(TestRemoteBranchEffort, self).setUp()
2079
# Create a smart server that publishes whatever the backing VFS server
2081
self.smart_server = server.SmartTCPServer_for_testing()
2082
self.smart_server.setUp(self.get_server())
2083
self.addCleanup(self.smart_server.tearDown)
2084
# Log all HPSS calls into self.hpss_calls.
2085
_SmartClient.hooks.install_named_hook(
2086
'call', self.capture_hpss_call, None)
2087
self.hpss_calls = []
2089
def capture_hpss_call(self, params):
2090
self.hpss_calls.append(params.method)
2092
def test_copy_content_into_avoids_revision_history(self):
2093
local = self.make_branch('local')
2094
remote_backing_tree = self.make_branch_and_tree('remote')
2095
remote_backing_tree.commit("Commit.")
2096
remote_branch_url = self.smart_server.get_url() + 'remote'
2097
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2098
local.repository.fetch(remote_branch.repository)
2099
self.hpss_calls = []
2100
remote_branch.copy_content_into(local)
2101
self.assertFalse('Branch.revision_history' in self.hpss_calls)