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[0].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(reference_bzrdir_format.repository_format,
383
result._repository_format)
384
self.assertEqual(reference_bzrdir_format.get_branch_format(),
385
result._branch_format)
386
client.finished_test()
389
class TestBzrDirOpenBranch(TestRemote):
391
def test_branch_present(self):
392
reference_format = self.get_repo_format()
393
network_name = reference_format.network_name()
394
transport = MemoryTransport()
395
transport.mkdir('quack')
396
transport = transport.clone('quack')
397
client = FakeClient(transport.base)
398
client.add_expected_call(
399
'BzrDir.open_branch', ('quack/',),
400
'success', ('ok', ''))
401
client.add_expected_call(
402
'BzrDir.find_repositoryV3', ('quack/',),
403
'success', ('ok', '', 'no', 'no', 'no', network_name))
404
client.add_expected_call(
405
'Branch.get_stacked_on_url', ('quack/',),
406
'error', ('NotStacked',))
407
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
409
result = bzrdir.open_branch()
410
self.assertIsInstance(result, RemoteBranch)
411
self.assertEqual(bzrdir, result.bzrdir)
412
client.finished_test()
414
def test_branch_missing(self):
415
transport = MemoryTransport()
416
transport.mkdir('quack')
417
transport = transport.clone('quack')
418
client = FakeClient(transport.base)
419
client.add_error_response('nobranch')
420
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
422
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
424
[('call', 'BzrDir.open_branch', ('quack/',))],
427
def test__get_tree_branch(self):
428
# _get_tree_branch is a form of open_branch, but it should only ask for
429
# branch opening, not any other network requests.
432
calls.append("Called")
434
transport = MemoryTransport()
435
# no requests on the network - catches other api calls being made.
436
client = FakeClient(transport.base)
437
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
439
# patch the open_branch call to record that it was called.
440
bzrdir.open_branch = open_branch
441
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
442
self.assertEqual(["Called"], calls)
443
self.assertEqual([], client._calls)
445
def test_url_quoting_of_path(self):
446
# Relpaths on the wire should not be URL-escaped. So "~" should be
447
# transmitted as "~", not "%7E".
448
transport = RemoteTCPTransport('bzr://localhost/~hello/')
449
client = FakeClient(transport.base)
450
reference_format = self.get_repo_format()
451
network_name = reference_format.network_name()
452
client.add_expected_call(
453
'BzrDir.open_branch', ('~hello/',),
454
'success', ('ok', ''))
455
client.add_expected_call(
456
'BzrDir.find_repositoryV3', ('~hello/',),
457
'success', ('ok', '', 'no', 'no', 'no', network_name))
458
client.add_expected_call(
459
'Branch.get_stacked_on_url', ('~hello/',),
460
'error', ('NotStacked',))
461
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
463
result = bzrdir.open_branch()
464
client.finished_test()
466
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
467
reference_format = self.get_repo_format()
468
network_name = reference_format.network_name()
469
transport = MemoryTransport()
470
transport.mkdir('quack')
471
transport = transport.clone('quack')
473
rich_response = 'yes'
477
subtree_response = 'yes'
479
subtree_response = 'no'
480
client = FakeClient(transport.base)
481
client.add_success_response(
482
'ok', '', rich_response, subtree_response, external_lookup,
484
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
486
result = bzrdir.open_repository()
488
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
490
self.assertIsInstance(result, RemoteRepository)
491
self.assertEqual(bzrdir, result.bzrdir)
492
self.assertEqual(rich_root, result._format.rich_root_data)
493
self.assertEqual(subtrees, result._format.supports_tree_reference)
495
def test_open_repository_sets_format_attributes(self):
496
self.check_open_repository(True, True)
497
self.check_open_repository(False, True)
498
self.check_open_repository(True, False)
499
self.check_open_repository(False, False)
500
self.check_open_repository(False, False, 'yes')
502
def test_old_server(self):
503
"""RemoteBzrDirFormat should fail to probe if the server version is too
506
self.assertRaises(errors.NotBranchError,
507
RemoteBzrDirFormat.probe_transport, OldServerTransport())
510
class TestBzrDirCreateBranch(TestRemote):
512
def test_backwards_compat(self):
513
self.setup_smart_server_with_call_log()
514
repo = self.make_repository('.')
515
self.reset_smart_call_log()
516
self.disable_verb('BzrDir.create_branch')
517
branch = repo.bzrdir.create_branch()
518
create_branch_call_count = len([call for call in self.hpss_calls if
519
call[0].method == 'BzrDir.create_branch'])
520
self.assertEqual(1, create_branch_call_count)
522
def test_current_server(self):
523
transport = self.get_transport('.')
524
transport = transport.clone('quack')
525
self.make_repository('quack')
526
client = FakeClient(transport.base)
527
reference_bzrdir_format = bzrdir.format_registry.get('default')()
528
reference_format = reference_bzrdir_format.get_branch_format()
529
network_name = reference_format.network_name()
530
reference_repo_fmt = reference_bzrdir_format.repository_format
531
reference_repo_name = reference_repo_fmt.network_name()
532
client.add_expected_call(
533
'BzrDir.create_branch', ('quack/', network_name),
534
'success', ('ok', network_name, '', 'no', 'no', 'yes',
535
reference_repo_name))
536
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
538
branch = a_bzrdir.create_branch()
539
# We should have got a remote branch
540
self.assertIsInstance(branch, remote.RemoteBranch)
541
# its format should have the settings from the response
542
format = branch._format
543
self.assertEqual(network_name, format.network_name())
546
class TestBzrDirCreateRepository(TestRemote):
548
def test_backwards_compat(self):
549
self.setup_smart_server_with_call_log()
550
bzrdir = self.make_bzrdir('.')
551
self.reset_smart_call_log()
552
self.disable_verb('BzrDir.create_repository')
553
repo = bzrdir.create_repository()
554
create_repo_call_count = len([call for call in self.hpss_calls if
555
call[0].method == 'BzrDir.create_repository'])
556
self.assertEqual(1, create_repo_call_count)
558
def test_current_server(self):
559
transport = self.get_transport('.')
560
transport = transport.clone('quack')
561
self.make_bzrdir('quack')
562
client = FakeClient(transport.base)
563
reference_bzrdir_format = bzrdir.format_registry.get('default')()
564
reference_format = reference_bzrdir_format.repository_format
565
network_name = reference_format.network_name()
566
client.add_expected_call(
567
'BzrDir.create_repository', ('quack/',
568
'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
569
'success', ('ok', 'no', 'no', 'no', network_name))
570
a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
572
repo = a_bzrdir.create_repository()
573
# We should have got a remote repository
574
self.assertIsInstance(repo, remote.RemoteRepository)
575
# its format should have the settings from the response
576
format = repo._format
577
self.assertFalse(format.rich_root_data)
578
self.assertFalse(format.supports_tree_reference)
579
self.assertFalse(format.supports_external_lookups)
580
self.assertEqual(network_name, format.network_name())
583
class TestBzrDirOpenRepository(TestRemote):
585
def test_backwards_compat_1_2_3(self):
586
# fallback all the way to the first version.
587
reference_format = self.get_repo_format()
588
network_name = reference_format.network_name()
589
client = FakeClient('bzr://example.com/')
590
client.add_unknown_method_response('BzrDir.find_repositoryV3')
591
client.add_unknown_method_response('BzrDir.find_repositoryV2')
592
client.add_success_response('ok', '', 'no', 'no')
593
# A real repository instance will be created to determine the network
595
client.add_success_response_with_body(
596
"Bazaar-NG meta directory, format 1\n", 'ok')
597
client.add_success_response_with_body(
598
reference_format.get_format_string(), 'ok')
599
# PackRepository wants to do a stat
600
client.add_success_response('stat', '0', '65535')
601
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
603
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
605
repo = bzrdir.open_repository()
607
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
608
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
609
('call', 'BzrDir.find_repository', ('quack/',)),
610
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
611
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
612
('call', 'stat', ('/quack/.bzr/repository',)),
615
self.assertEqual(network_name, repo._format.network_name())
617
def test_backwards_compat_2(self):
618
# fallback to find_repositoryV2
619
reference_format = self.get_repo_format()
620
network_name = reference_format.network_name()
621
client = FakeClient('bzr://example.com/')
622
client.add_unknown_method_response('BzrDir.find_repositoryV3')
623
client.add_success_response('ok', '', 'no', 'no', 'no')
624
# A real repository instance will be created to determine the network
626
client.add_success_response_with_body(
627
"Bazaar-NG meta directory, format 1\n", 'ok')
628
client.add_success_response_with_body(
629
reference_format.get_format_string(), 'ok')
630
# PackRepository wants to do a stat
631
client.add_success_response('stat', '0', '65535')
632
remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
634
bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
636
repo = bzrdir.open_repository()
638
[('call', 'BzrDir.find_repositoryV3', ('quack/',)),
639
('call', 'BzrDir.find_repositoryV2', ('quack/',)),
640
('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
641
('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
642
('call', 'stat', ('/quack/.bzr/repository',)),
645
self.assertEqual(network_name, repo._format.network_name())
647
def test_current_server(self):
648
reference_format = self.get_repo_format()
649
network_name = reference_format.network_name()
650
transport = MemoryTransport()
651
transport.mkdir('quack')
652
transport = transport.clone('quack')
653
client = FakeClient(transport.base)
654
client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
655
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
657
repo = bzrdir.open_repository()
659
[('call', 'BzrDir.find_repositoryV3', ('quack/',))],
661
self.assertEqual(network_name, repo._format.network_name())
664
class OldSmartClient(object):
665
"""A fake smart client for test_old_version that just returns a version one
666
response to the 'hello' (query version) command.
669
def get_request(self):
670
input_file = StringIO('ok\x011\n')
671
output_file = StringIO()
672
client_medium = medium.SmartSimplePipesClientMedium(
673
input_file, output_file)
674
return medium.SmartClientStreamMediumRequest(client_medium)
676
def protocol_version(self):
680
class OldServerTransport(object):
681
"""A fake transport for test_old_server that reports it's smart server
682
protocol version as version one.
688
def get_smart_client(self):
689
return OldSmartClient()
692
class RemoteBranchTestCase(tests.TestCase):
694
def make_remote_branch(self, transport, client):
695
"""Make a RemoteBranch using 'client' as its _SmartClient.
697
A RemoteBzrDir and RemoteRepository will also be created to fill out
698
the RemoteBranch, albeit with stub values for some of their attributes.
700
# we do not want bzrdir to make any remote calls, so use False as its
701
# _client. If it tries to make a remote call, this will fail
703
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
705
repo = RemoteRepository(bzrdir, None, _client=client)
706
return RemoteBranch(bzrdir, repo, _client=client)
709
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
711
def test_empty_branch(self):
712
# in an empty branch we decode the response properly
713
transport = MemoryTransport()
714
client = FakeClient(transport.base)
715
client.add_expected_call(
716
'Branch.get_stacked_on_url', ('quack/',),
717
'error', ('NotStacked',))
718
client.add_expected_call(
719
'Branch.last_revision_info', ('quack/',),
720
'success', ('ok', '0', 'null:'))
721
transport.mkdir('quack')
722
transport = transport.clone('quack')
723
branch = self.make_remote_branch(transport, client)
724
result = branch.last_revision_info()
725
client.finished_test()
726
self.assertEqual((0, NULL_REVISION), result)
728
def test_non_empty_branch(self):
729
# in a non-empty branch we also decode the response properly
730
revid = u'\xc8'.encode('utf8')
731
transport = MemoryTransport()
732
client = FakeClient(transport.base)
733
client.add_expected_call(
734
'Branch.get_stacked_on_url', ('kwaak/',),
735
'error', ('NotStacked',))
736
client.add_expected_call(
737
'Branch.last_revision_info', ('kwaak/',),
738
'success', ('ok', '2', revid))
739
transport.mkdir('kwaak')
740
transport = transport.clone('kwaak')
741
branch = self.make_remote_branch(transport, client)
742
result = branch.last_revision_info()
743
self.assertEqual((2, revid), result)
746
class TestBranch_get_stacked_on_url(TestRemote):
747
"""Test Branch._get_stacked_on_url rpc"""
749
def test_get_stacked_on_invalid_url(self):
750
# test that asking for a stacked on url the server can't access works.
751
# This isn't perfect, but then as we're in the same process there
752
# really isn't anything we can do to be 100% sure that the server
753
# doesn't just open in - this test probably needs to be rewritten using
754
# a spawn()ed server.
755
stacked_branch = self.make_branch('stacked', format='1.9')
756
memory_branch = self.make_branch('base', format='1.9')
757
vfs_url = self.get_vfs_only_url('base')
758
stacked_branch.set_stacked_on_url(vfs_url)
759
transport = stacked_branch.bzrdir.root_transport
760
client = FakeClient(transport.base)
761
client.add_expected_call(
762
'Branch.get_stacked_on_url', ('stacked/',),
763
'success', ('ok', vfs_url))
764
# XXX: Multiple calls are bad, this second call documents what is
766
client.add_expected_call(
767
'Branch.get_stacked_on_url', ('stacked/',),
768
'success', ('ok', vfs_url))
769
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
771
branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, None),
773
result = branch.get_stacked_on_url()
774
self.assertEqual(vfs_url, result)
776
def test_backwards_compatible(self):
777
# like with bzr1.6 with no Branch.get_stacked_on_url rpc
778
base_branch = self.make_branch('base', format='1.6')
779
stacked_branch = self.make_branch('stacked', format='1.6')
780
stacked_branch.set_stacked_on_url('../base')
781
client = FakeClient(self.get_url())
782
client.add_expected_call(
783
'BzrDir.open_branch', ('stacked/',),
784
'success', ('ok', ''))
785
client.add_expected_call(
786
'BzrDir.find_repositoryV3', ('stacked/',),
787
'success', ('ok', '', 'no', 'no', 'no',
788
stacked_branch.repository._format.network_name()))
789
# called twice, once from constructor and then again by us
790
client.add_expected_call(
791
'Branch.get_stacked_on_url', ('stacked/',),
792
'unknown', ('Branch.get_stacked_on_url',))
793
client.add_expected_call(
794
'Branch.get_stacked_on_url', ('stacked/',),
795
'unknown', ('Branch.get_stacked_on_url',))
796
# this will also do vfs access, but that goes direct to the transport
797
# and isn't seen by the FakeClient.
798
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
799
remote.RemoteBzrDirFormat(), _client=client)
800
branch = bzrdir.open_branch()
801
result = branch.get_stacked_on_url()
802
self.assertEqual('../base', result)
803
client.finished_test()
804
# it's in the fallback list both for the RemoteRepository and its vfs
806
self.assertEqual(1, len(branch.repository._fallback_repositories))
808
len(branch.repository._real_repository._fallback_repositories))
810
def test_get_stacked_on_real_branch(self):
811
base_branch = self.make_branch('base', format='1.6')
812
stacked_branch = self.make_branch('stacked', format='1.6')
813
stacked_branch.set_stacked_on_url('../base')
814
reference_format = self.get_repo_format()
815
network_name = reference_format.network_name()
816
client = FakeClient(self.get_url())
817
client.add_expected_call(
818
'BzrDir.open_branch', ('stacked/',),
819
'success', ('ok', ''))
820
client.add_expected_call(
821
'BzrDir.find_repositoryV3', ('stacked/',),
822
'success', ('ok', '', 'no', 'no', 'no', network_name))
823
# called twice, once from constructor and then again by us
824
client.add_expected_call(
825
'Branch.get_stacked_on_url', ('stacked/',),
826
'success', ('ok', '../base'))
827
client.add_expected_call(
828
'Branch.get_stacked_on_url', ('stacked/',),
829
'success', ('ok', '../base'))
830
bzrdir = RemoteBzrDir(self.get_transport('stacked'),
831
remote.RemoteBzrDirFormat(), _client=client)
832
branch = bzrdir.open_branch()
833
result = branch.get_stacked_on_url()
834
self.assertEqual('../base', result)
835
client.finished_test()
836
# it's in the fallback list both for the RemoteRepository and its vfs
838
self.assertEqual(1, len(branch.repository._fallback_repositories))
840
len(branch.repository._real_repository._fallback_repositories))
843
class TestBranchSetLastRevision(RemoteBranchTestCase):
845
def test_set_empty(self):
846
# set_revision_history([]) is translated to calling
847
# Branch.set_last_revision(path, '') on the wire.
848
transport = MemoryTransport()
849
transport.mkdir('branch')
850
transport = transport.clone('branch')
852
client = FakeClient(transport.base)
853
client.add_expected_call(
854
'Branch.get_stacked_on_url', ('branch/',),
855
'error', ('NotStacked',))
856
client.add_expected_call(
857
'Branch.lock_write', ('branch/', '', ''),
858
'success', ('ok', 'branch token', 'repo token'))
859
client.add_expected_call(
860
'Branch.last_revision_info',
862
'success', ('ok', '0', 'null:'))
863
client.add_expected_call(
864
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
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
874
result = branch.set_revision_history([])
876
self.assertEqual(None, result)
877
client.finished_test()
879
def test_set_nonempty(self):
880
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
881
# Branch.set_last_revision(path, rev-idN) on the wire.
882
transport = MemoryTransport()
883
transport.mkdir('branch')
884
transport = transport.clone('branch')
886
client = FakeClient(transport.base)
887
client.add_expected_call(
888
'Branch.get_stacked_on_url', ('branch/',),
889
'error', ('NotStacked',))
890
client.add_expected_call(
891
'Branch.lock_write', ('branch/', '', ''),
892
'success', ('ok', 'branch token', 'repo token'))
893
client.add_expected_call(
894
'Branch.last_revision_info',
896
'success', ('ok', '0', 'null:'))
898
encoded_body = bz2.compress('\n'.join(lines))
899
client.add_success_response_with_body(encoded_body, 'ok')
900
client.add_expected_call(
901
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
903
client.add_expected_call(
904
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
906
branch = self.make_remote_branch(transport, client)
907
# This is a hack to work around the problem that RemoteBranch currently
908
# unnecessarily invokes _ensure_real upon a call to lock_write.
909
branch._ensure_real = lambda: None
910
# Lock the branch, reset the record of remote calls.
912
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
914
self.assertEqual(None, result)
915
client.finished_test()
917
def test_no_such_revision(self):
918
transport = MemoryTransport()
919
transport.mkdir('branch')
920
transport = transport.clone('branch')
921
# A response of 'NoSuchRevision' is translated into an exception.
922
client = FakeClient(transport.base)
923
client.add_expected_call(
924
'Branch.get_stacked_on_url', ('branch/',),
925
'error', ('NotStacked',))
926
client.add_expected_call(
927
'Branch.lock_write', ('branch/', '', ''),
928
'success', ('ok', 'branch token', 'repo token'))
929
client.add_expected_call(
930
'Branch.last_revision_info',
932
'success', ('ok', '0', 'null:'))
933
# get_graph calls to construct the revision history, for the set_rh
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', ('NoSuchRevision', 'rev-id'))
941
client.add_expected_call(
942
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
945
branch = self.make_remote_branch(transport, client)
948
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
950
client.finished_test()
952
def test_tip_change_rejected(self):
953
"""TipChangeRejected responses cause a TipChangeRejected exception to
956
transport = MemoryTransport()
957
transport.mkdir('branch')
958
transport = transport.clone('branch')
959
client = FakeClient(transport.base)
960
rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
961
rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
962
client.add_expected_call(
963
'Branch.get_stacked_on_url', ('branch/',),
964
'error', ('NotStacked',))
965
client.add_expected_call(
966
'Branch.lock_write', ('branch/', '', ''),
967
'success', ('ok', 'branch token', 'repo token'))
968
client.add_expected_call(
969
'Branch.last_revision_info',
971
'success', ('ok', '0', 'null:'))
973
encoded_body = bz2.compress('\n'.join(lines))
974
client.add_success_response_with_body(encoded_body, 'ok')
975
client.add_expected_call(
976
'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
977
'error', ('TipChangeRejected', rejection_msg_utf8))
978
client.add_expected_call(
979
'Branch.unlock', ('branch/', 'branch token', 'repo token'),
981
branch = self.make_remote_branch(transport, client)
982
branch._ensure_real = lambda: None
984
# The 'TipChangeRejected' error response triggered by calling
985
# set_revision_history causes a TipChangeRejected exception.
986
err = self.assertRaises(
987
errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
988
# The UTF-8 message from the response has been decoded into a unicode
990
self.assertIsInstance(err.msg, unicode)
991
self.assertEqual(rejection_msg_unicode, err.msg)
993
client.finished_test()
996
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
998
def test_set_last_revision_info(self):
999
# set_last_revision_info(num, 'rev-id') is translated to calling
1000
# Branch.set_last_revision_info(num, 'rev-id') on the wire.
1001
transport = MemoryTransport()
1002
transport.mkdir('branch')
1003
transport = transport.clone('branch')
1004
client = FakeClient(transport.base)
1005
# get_stacked_on_url
1006
client.add_error_response('NotStacked')
1008
client.add_success_response('ok', 'branch token', 'repo token')
1009
# query the current revision
1010
client.add_success_response('ok', '0', 'null:')
1012
client.add_success_response('ok')
1014
client.add_success_response('ok')
1016
branch = self.make_remote_branch(transport, client)
1017
# Lock the branch, reset the record of remote calls.
1020
result = branch.set_last_revision_info(1234, 'a-revision-id')
1022
[('call', 'Branch.last_revision_info', ('branch/',)),
1023
('call', 'Branch.set_last_revision_info',
1024
('branch/', 'branch token', 'repo token',
1025
'1234', 'a-revision-id'))],
1027
self.assertEqual(None, result)
1029
def test_no_such_revision(self):
1030
# A response of 'NoSuchRevision' is translated into an exception.
1031
transport = MemoryTransport()
1032
transport.mkdir('branch')
1033
transport = transport.clone('branch')
1034
client = FakeClient(transport.base)
1035
# get_stacked_on_url
1036
client.add_error_response('NotStacked')
1038
client.add_success_response('ok', 'branch token', 'repo token')
1040
client.add_error_response('NoSuchRevision', 'revid')
1042
client.add_success_response('ok')
1044
branch = self.make_remote_branch(transport, client)
1045
# Lock the branch, reset the record of remote calls.
1050
errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1053
def lock_remote_branch(self, branch):
1054
"""Trick a RemoteBranch into thinking it is locked."""
1055
branch._lock_mode = 'w'
1056
branch._lock_count = 2
1057
branch._lock_token = 'branch token'
1058
branch._repo_lock_token = 'repo token'
1059
branch.repository._lock_mode = 'w'
1060
branch.repository._lock_count = 2
1061
branch.repository._lock_token = 'repo token'
1063
def test_backwards_compatibility(self):
1064
"""If the server does not support the Branch.set_last_revision_info
1065
verb (which is new in 1.4), then the client falls back to VFS methods.
1067
# This test is a little messy. Unlike most tests in this file, it
1068
# doesn't purely test what a Remote* object sends over the wire, and
1069
# how it reacts to responses from the wire. It instead relies partly
1070
# on asserting that the RemoteBranch will call
1071
# self._real_branch.set_last_revision_info(...).
1073
# First, set up our RemoteBranch with a FakeClient that raises
1074
# UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1075
transport = MemoryTransport()
1076
transport.mkdir('branch')
1077
transport = transport.clone('branch')
1078
client = FakeClient(transport.base)
1079
client.add_expected_call(
1080
'Branch.get_stacked_on_url', ('branch/',),
1081
'error', ('NotStacked',))
1082
client.add_expected_call(
1083
'Branch.last_revision_info',
1085
'success', ('ok', '0', 'null:'))
1086
client.add_expected_call(
1087
'Branch.set_last_revision_info',
1088
('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1089
'unknown', 'Branch.set_last_revision_info')
1091
branch = self.make_remote_branch(transport, client)
1092
class StubRealBranch(object):
1095
def set_last_revision_info(self, revno, revision_id):
1097
('set_last_revision_info', revno, revision_id))
1098
def _clear_cached_state(self):
1100
real_branch = StubRealBranch()
1101
branch._real_branch = real_branch
1102
self.lock_remote_branch(branch)
1104
# Call set_last_revision_info, and verify it behaved as expected.
1105
result = branch.set_last_revision_info(1234, 'a-revision-id')
1107
[('set_last_revision_info', 1234, 'a-revision-id')],
1109
client.finished_test()
1111
def test_unexpected_error(self):
1112
# If the server sends an error the client doesn't understand, it gets
1113
# turned into an UnknownErrorFromSmartServer, which is presented as a
1114
# non-internal error to the user.
1115
transport = MemoryTransport()
1116
transport.mkdir('branch')
1117
transport = transport.clone('branch')
1118
client = FakeClient(transport.base)
1119
# get_stacked_on_url
1120
client.add_error_response('NotStacked')
1122
client.add_success_response('ok', 'branch token', 'repo token')
1124
client.add_error_response('UnexpectedError')
1126
client.add_success_response('ok')
1128
branch = self.make_remote_branch(transport, client)
1129
# Lock the branch, reset the record of remote calls.
1133
err = self.assertRaises(
1134
errors.UnknownErrorFromSmartServer,
1135
branch.set_last_revision_info, 123, 'revid')
1136
self.assertEqual(('UnexpectedError',), err.error_tuple)
1139
def test_tip_change_rejected(self):
1140
"""TipChangeRejected responses cause a TipChangeRejected exception to
1143
transport = MemoryTransport()
1144
transport.mkdir('branch')
1145
transport = transport.clone('branch')
1146
client = FakeClient(transport.base)
1147
# get_stacked_on_url
1148
client.add_error_response('NotStacked')
1150
client.add_success_response('ok', 'branch token', 'repo token')
1152
client.add_error_response('TipChangeRejected', 'rejection message')
1154
client.add_success_response('ok')
1156
branch = self.make_remote_branch(transport, client)
1157
# Lock the branch, reset the record of remote calls.
1159
self.addCleanup(branch.unlock)
1162
# The 'TipChangeRejected' error response triggered by calling
1163
# set_last_revision_info causes a TipChangeRejected exception.
1164
err = self.assertRaises(
1165
errors.TipChangeRejected,
1166
branch.set_last_revision_info, 123, 'revid')
1167
self.assertEqual('rejection message', err.msg)
1170
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1171
"""Getting the branch configuration should use an abstract method not vfs.
1174
def test_get_branch_conf(self):
1175
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1176
## # We should see that branch.get_config() does a single rpc to get the
1177
## # remote configuration file, abstracting away where that is stored on
1178
## # the server. However at the moment it always falls back to using the
1179
## # vfs, and this would need some changes in config.py.
1181
## # in an empty branch we decode the response properly
1182
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1183
## # we need to make a real branch because the remote_branch.control_files
1184
## # will trigger _ensure_real.
1185
## branch = self.make_branch('quack')
1186
## transport = branch.bzrdir.root_transport
1187
## # we do not want bzrdir to make any remote calls
1188
## bzrdir = RemoteBzrDir(transport, _client=False)
1189
## branch = RemoteBranch(bzrdir, None, _client=client)
1190
## config = branch.get_config()
1191
## self.assertEqual(
1192
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1196
class TestBranchLockWrite(RemoteBranchTestCase):
1198
def test_lock_write_unlockable(self):
1199
transport = MemoryTransport()
1200
client = FakeClient(transport.base)
1201
client.add_expected_call(
1202
'Branch.get_stacked_on_url', ('quack/',),
1203
'error', ('NotStacked',),)
1204
client.add_expected_call(
1205
'Branch.lock_write', ('quack/', '', ''),
1206
'error', ('UnlockableTransport',))
1207
transport.mkdir('quack')
1208
transport = transport.clone('quack')
1209
branch = self.make_remote_branch(transport, client)
1210
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1211
client.finished_test()
1214
class TestTransportIsReadonly(tests.TestCase):
1216
def test_true(self):
1217
client = FakeClient()
1218
client.add_success_response('yes')
1219
transport = RemoteTransport('bzr://example.com/', medium=False,
1221
self.assertEqual(True, transport.is_readonly())
1223
[('call', 'Transport.is_readonly', ())],
1226
def test_false(self):
1227
client = FakeClient()
1228
client.add_success_response('no')
1229
transport = RemoteTransport('bzr://example.com/', medium=False,
1231
self.assertEqual(False, transport.is_readonly())
1233
[('call', 'Transport.is_readonly', ())],
1236
def test_error_from_old_server(self):
1237
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1239
Clients should treat it as a "no" response, because is_readonly is only
1240
advisory anyway (a transport could be read-write, but then the
1241
underlying filesystem could be readonly anyway).
1243
client = FakeClient()
1244
client.add_unknown_method_response('Transport.is_readonly')
1245
transport = RemoteTransport('bzr://example.com/', medium=False,
1247
self.assertEqual(False, transport.is_readonly())
1249
[('call', 'Transport.is_readonly', ())],
1253
class TestTransportMkdir(tests.TestCase):
1255
def test_permissiondenied(self):
1256
client = FakeClient()
1257
client.add_error_response('PermissionDenied', 'remote path', 'extra')
1258
transport = RemoteTransport('bzr://example.com/', medium=False,
1260
exc = self.assertRaises(
1261
errors.PermissionDenied, transport.mkdir, 'client path')
1262
expected_error = errors.PermissionDenied('/client path', 'extra')
1263
self.assertEqual(expected_error, exc)
1266
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1268
def test_defaults_to_none(self):
1269
t = RemoteSSHTransport('bzr+ssh://example.com')
1270
self.assertIs(None, t._get_credentials()[0])
1272
def test_uses_authentication_config(self):
1273
conf = config.AuthenticationConfig()
1274
conf._get_config().update(
1275
{'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1278
t = RemoteSSHTransport('bzr+ssh://example.com')
1279
self.assertEqual('bar', t._get_credentials()[0])
1282
class TestRemoteRepository(TestRemote):
1283
"""Base for testing RemoteRepository protocol usage.
1285
These tests contain frozen requests and responses. We want any changes to
1286
what is sent or expected to be require a thoughtful update to these tests
1287
because they might break compatibility with different-versioned servers.
1290
def setup_fake_client_and_repository(self, transport_path):
1291
"""Create the fake client and repository for testing with.
1293
There's no real server here; we just have canned responses sent
1296
:param transport_path: Path below the root of the MemoryTransport
1297
where the repository will be created.
1299
transport = MemoryTransport()
1300
transport.mkdir(transport_path)
1301
client = FakeClient(transport.base)
1302
transport = transport.clone(transport_path)
1303
# we do not want bzrdir to make any remote calls
1304
bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1306
repo = RemoteRepository(bzrdir, None, _client=client)
1310
class TestRepositoryGatherStats(TestRemoteRepository):
1312
def test_revid_none(self):
1313
# ('ok',), body with revisions and size
1314
transport_path = 'quack'
1315
repo, client = self.setup_fake_client_and_repository(transport_path)
1316
client.add_success_response_with_body(
1317
'revisions: 2\nsize: 18\n', 'ok')
1318
result = repo.gather_stats(None)
1320
[('call_expecting_body', 'Repository.gather_stats',
1321
('quack/','','no'))],
1323
self.assertEqual({'revisions': 2, 'size': 18}, result)
1325
def test_revid_no_committers(self):
1326
# ('ok',), body without committers
1327
body = ('firstrev: 123456.300 3600\n'
1328
'latestrev: 654231.400 0\n'
1331
transport_path = 'quick'
1332
revid = u'\xc8'.encode('utf8')
1333
repo, client = self.setup_fake_client_and_repository(transport_path)
1334
client.add_success_response_with_body(body, 'ok')
1335
result = repo.gather_stats(revid)
1337
[('call_expecting_body', 'Repository.gather_stats',
1338
('quick/', revid, 'no'))],
1340
self.assertEqual({'revisions': 2, 'size': 18,
1341
'firstrev': (123456.300, 3600),
1342
'latestrev': (654231.400, 0),},
1345
def test_revid_with_committers(self):
1346
# ('ok',), body with committers
1347
body = ('committers: 128\n'
1348
'firstrev: 123456.300 3600\n'
1349
'latestrev: 654231.400 0\n'
1352
transport_path = 'buick'
1353
revid = u'\xc8'.encode('utf8')
1354
repo, client = self.setup_fake_client_and_repository(transport_path)
1355
client.add_success_response_with_body(body, 'ok')
1356
result = repo.gather_stats(revid, True)
1358
[('call_expecting_body', 'Repository.gather_stats',
1359
('buick/', revid, 'yes'))],
1361
self.assertEqual({'revisions': 2, 'size': 18,
1363
'firstrev': (123456.300, 3600),
1364
'latestrev': (654231.400, 0),},
1368
class TestRepositoryGetGraph(TestRemoteRepository):
1370
def test_get_graph(self):
1371
# get_graph returns a graph with a custom parents provider.
1372
transport_path = 'quack'
1373
repo, client = self.setup_fake_client_and_repository(transport_path)
1374
graph = repo.get_graph()
1375
self.assertNotEqual(graph._parents_provider, repo)
1378
class TestRepositoryGetParentMap(TestRemoteRepository):
1380
def test_get_parent_map_caching(self):
1381
# get_parent_map returns from cache until unlock()
1382
# setup a reponse with two revisions
1383
r1 = u'\u0e33'.encode('utf8')
1384
r2 = u'\u0dab'.encode('utf8')
1385
lines = [' '.join([r2, r1]), r1]
1386
encoded_body = bz2.compress('\n'.join(lines))
1388
transport_path = 'quack'
1389
repo, client = self.setup_fake_client_and_repository(transport_path)
1390
client.add_success_response_with_body(encoded_body, 'ok')
1391
client.add_success_response_with_body(encoded_body, 'ok')
1393
graph = repo.get_graph()
1394
parents = graph.get_parent_map([r2])
1395
self.assertEqual({r2: (r1,)}, parents)
1396
# locking and unlocking deeper should not reset
1399
parents = graph.get_parent_map([r1])
1400
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1402
[('call_with_body_bytes_expecting_body',
1403
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
1406
# now we call again, and it should use the second response.
1408
graph = repo.get_graph()
1409
parents = graph.get_parent_map([r1])
1410
self.assertEqual({r1: (NULL_REVISION,)}, parents)
1412
[('call_with_body_bytes_expecting_body',
1413
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
1414
('call_with_body_bytes_expecting_body',
1415
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
1420
def test_get_parent_map_reconnects_if_unknown_method(self):
1421
transport_path = 'quack'
1422
repo, client = self.setup_fake_client_and_repository(transport_path)
1423
client.add_unknown_method_response('Repository,get_parent_map')
1424
client.add_success_response_with_body('', 'ok')
1425
self.assertFalse(client._medium._is_remote_before((1, 2)))
1426
rev_id = 'revision-id'
1427
expected_deprecations = [
1428
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1430
parents = self.callDeprecated(
1431
expected_deprecations, repo.get_parent_map, [rev_id])
1433
[('call_with_body_bytes_expecting_body',
1434
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
1435
('disconnect medium',),
1436
('call_expecting_body', 'Repository.get_revision_graph',
1439
# The medium is now marked as being connected to an older server
1440
self.assertTrue(client._medium._is_remote_before((1, 2)))
1442
def test_get_parent_map_fallback_parentless_node(self):
1443
"""get_parent_map falls back to get_revision_graph on old servers. The
1444
results from get_revision_graph are tweaked to match the get_parent_map
1447
Specifically, a {key: ()} result from get_revision_graph means "no
1448
parents" for that key, which in get_parent_map results should be
1449
represented as {key: ('null:',)}.
1451
This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1453
rev_id = 'revision-id'
1454
transport_path = 'quack'
1455
repo, client = self.setup_fake_client_and_repository(transport_path)
1456
client.add_success_response_with_body(rev_id, 'ok')
1457
client._medium._remember_remote_is_before((1, 2))
1458
expected_deprecations = [
1459
'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1461
parents = self.callDeprecated(
1462
expected_deprecations, repo.get_parent_map, [rev_id])
1464
[('call_expecting_body', 'Repository.get_revision_graph',
1467
self.assertEqual({rev_id: ('null:',)}, parents)
1469
def test_get_parent_map_unexpected_response(self):
1470
repo, client = self.setup_fake_client_and_repository('path')
1471
client.add_success_response('something unexpected!')
1473
errors.UnexpectedSmartServerResponse,
1474
repo.get_parent_map, ['a-revision-id'])
1477
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1479
def test_allows_new_revisions(self):
1480
"""get_parent_map's results can be updated by commit."""
1481
smart_server = server.SmartTCPServer_for_testing()
1482
smart_server.setUp()
1483
self.addCleanup(smart_server.tearDown)
1484
self.make_branch('branch')
1485
branch = Branch.open(smart_server.get_url() + '/branch')
1486
tree = branch.create_checkout('tree', lightweight=True)
1488
self.addCleanup(tree.unlock)
1489
graph = tree.branch.repository.get_graph()
1490
# This provides an opportunity for the missing rev-id to be cached.
1491
self.assertEqual({}, graph.get_parent_map(['rev1']))
1492
tree.commit('message', rev_id='rev1')
1493
graph = tree.branch.repository.get_graph()
1494
self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1497
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1499
def test_null_revision(self):
1500
# a null revision has the predictable result {}, we should have no wire
1501
# traffic when calling it with this argument
1502
transport_path = 'empty'
1503
repo, client = self.setup_fake_client_and_repository(transport_path)
1504
client.add_success_response('notused')
1505
result = self.applyDeprecated(one_four, repo.get_revision_graph,
1507
self.assertEqual([], client._calls)
1508
self.assertEqual({}, result)
1510
def test_none_revision(self):
1511
# with none we want the entire graph
1512
r1 = u'\u0e33'.encode('utf8')
1513
r2 = u'\u0dab'.encode('utf8')
1514
lines = [' '.join([r2, r1]), r1]
1515
encoded_body = '\n'.join(lines)
1517
transport_path = 'sinhala'
1518
repo, client = self.setup_fake_client_and_repository(transport_path)
1519
client.add_success_response_with_body(encoded_body, 'ok')
1520
result = self.applyDeprecated(one_four, repo.get_revision_graph)
1522
[('call_expecting_body', 'Repository.get_revision_graph',
1525
self.assertEqual({r1: (), r2: (r1, )}, result)
1527
def test_specific_revision(self):
1528
# with a specific revision we want the graph for that
1529
# with none we want the entire graph
1530
r11 = u'\u0e33'.encode('utf8')
1531
r12 = u'\xc9'.encode('utf8')
1532
r2 = u'\u0dab'.encode('utf8')
1533
lines = [' '.join([r2, r11, r12]), r11, r12]
1534
encoded_body = '\n'.join(lines)
1536
transport_path = 'sinhala'
1537
repo, client = self.setup_fake_client_and_repository(transport_path)
1538
client.add_success_response_with_body(encoded_body, 'ok')
1539
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
1541
[('call_expecting_body', 'Repository.get_revision_graph',
1544
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1546
def test_no_such_revision(self):
1548
transport_path = 'sinhala'
1549
repo, client = self.setup_fake_client_and_repository(transport_path)
1550
client.add_error_response('nosuchrevision', revid)
1551
# also check that the right revision is reported in the error
1552
self.assertRaises(errors.NoSuchRevision,
1553
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1555
[('call_expecting_body', 'Repository.get_revision_graph',
1556
('sinhala/', revid))],
1559
def test_unexpected_error(self):
1561
transport_path = 'sinhala'
1562
repo, client = self.setup_fake_client_and_repository(transport_path)
1563
client.add_error_response('AnUnexpectedError')
1564
e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1565
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
1566
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1569
class TestRepositoryIsShared(TestRemoteRepository):
1571
def test_is_shared(self):
1572
# ('yes', ) for Repository.is_shared -> 'True'.
1573
transport_path = 'quack'
1574
repo, client = self.setup_fake_client_and_repository(transport_path)
1575
client.add_success_response('yes')
1576
result = repo.is_shared()
1578
[('call', 'Repository.is_shared', ('quack/',))],
1580
self.assertEqual(True, result)
1582
def test_is_not_shared(self):
1583
# ('no', ) for Repository.is_shared -> 'False'.
1584
transport_path = 'qwack'
1585
repo, client = self.setup_fake_client_and_repository(transport_path)
1586
client.add_success_response('no')
1587
result = repo.is_shared()
1589
[('call', 'Repository.is_shared', ('qwack/',))],
1591
self.assertEqual(False, result)
1594
class TestRepositoryLockWrite(TestRemoteRepository):
1596
def test_lock_write(self):
1597
transport_path = 'quack'
1598
repo, client = self.setup_fake_client_and_repository(transport_path)
1599
client.add_success_response('ok', 'a token')
1600
result = repo.lock_write()
1602
[('call', 'Repository.lock_write', ('quack/', ''))],
1604
self.assertEqual('a token', result)
1606
def test_lock_write_already_locked(self):
1607
transport_path = 'quack'
1608
repo, client = self.setup_fake_client_and_repository(transport_path)
1609
client.add_error_response('LockContention')
1610
self.assertRaises(errors.LockContention, repo.lock_write)
1612
[('call', 'Repository.lock_write', ('quack/', ''))],
1615
def test_lock_write_unlockable(self):
1616
transport_path = 'quack'
1617
repo, client = self.setup_fake_client_and_repository(transport_path)
1618
client.add_error_response('UnlockableTransport')
1619
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1621
[('call', 'Repository.lock_write', ('quack/', ''))],
1625
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1627
def test_backwards_compat(self):
1628
self.setup_smart_server_with_call_log()
1629
repo = self.make_repository('.')
1630
self.reset_smart_call_log()
1631
verb = 'Repository.set_make_working_trees'
1632
self.disable_verb(verb)
1633
repo.set_make_working_trees(True)
1634
call_count = len([call for call in self.hpss_calls if
1635
call[0].method == verb])
1636
self.assertEqual(1, call_count)
1638
def test_current(self):
1639
transport_path = 'quack'
1640
repo, client = self.setup_fake_client_and_repository(transport_path)
1641
client.add_expected_call(
1642
'Repository.set_make_working_trees', ('quack/', 'True'),
1644
client.add_expected_call(
1645
'Repository.set_make_working_trees', ('quack/', 'False'),
1647
repo.set_make_working_trees(True)
1648
repo.set_make_working_trees(False)
1651
class TestRepositoryUnlock(TestRemoteRepository):
1653
def test_unlock(self):
1654
transport_path = 'quack'
1655
repo, client = self.setup_fake_client_and_repository(transport_path)
1656
client.add_success_response('ok', 'a token')
1657
client.add_success_response('ok')
1661
[('call', 'Repository.lock_write', ('quack/', '')),
1662
('call', 'Repository.unlock', ('quack/', 'a token'))],
1665
def test_unlock_wrong_token(self):
1666
# If somehow the token is wrong, unlock will raise TokenMismatch.
1667
transport_path = 'quack'
1668
repo, client = self.setup_fake_client_and_repository(transport_path)
1669
client.add_success_response('ok', 'a token')
1670
client.add_error_response('TokenMismatch')
1672
self.assertRaises(errors.TokenMismatch, repo.unlock)
1675
class TestRepositoryHasRevision(TestRemoteRepository):
1677
def test_none(self):
1678
# repo.has_revision(None) should not cause any traffic.
1679
transport_path = 'quack'
1680
repo, client = self.setup_fake_client_and_repository(transport_path)
1682
# The null revision is always there, so has_revision(None) == True.
1683
self.assertEqual(True, repo.has_revision(NULL_REVISION))
1685
# The remote repo shouldn't be accessed.
1686
self.assertEqual([], client._calls)
1689
class TestRepositoryTarball(TestRemoteRepository):
1691
# This is a canned tarball reponse we can validate against
1693
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
1694
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
1695
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
1696
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
1697
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
1698
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
1699
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
1700
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
1701
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
1702
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
1703
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
1704
'nWQ7QH/F3JFOFCQ0aSPfA='
1707
def test_repository_tarball(self):
1708
# Test that Repository.tarball generates the right operations
1709
transport_path = 'repo'
1710
expected_calls = [('call_expecting_body', 'Repository.tarball',
1711
('repo/', 'bz2',),),
1713
repo, client = self.setup_fake_client_and_repository(transport_path)
1714
client.add_success_response_with_body(self.tarball_content, 'ok')
1715
# Now actually ask for the tarball
1716
tarball_file = repo._get_tarball('bz2')
1718
self.assertEqual(expected_calls, client._calls)
1719
self.assertEqual(self.tarball_content, tarball_file.read())
1721
tarball_file.close()
1724
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
1725
"""RemoteRepository.copy_content_into optimizations"""
1727
def test_copy_content_remote_to_local(self):
1728
self.transport_server = server.SmartTCPServer_for_testing
1729
src_repo = self.make_repository('repo1')
1730
src_repo = repository.Repository.open(self.get_url('repo1'))
1731
# At the moment the tarball-based copy_content_into can't write back
1732
# into a smart server. It would be good if it could upload the
1733
# tarball; once that works we'd have to create repositories of
1734
# different formats. -- mbp 20070410
1735
dest_url = self.get_vfs_only_url('repo2')
1736
dest_bzrdir = BzrDir.create(dest_url)
1737
dest_repo = dest_bzrdir.create_repository()
1738
self.assertFalse(isinstance(dest_repo, RemoteRepository))
1739
self.assertTrue(isinstance(src_repo, RemoteRepository))
1740
src_repo.copy_content_into(dest_repo)
1743
class _StubRealPackRepository(object):
1745
def __init__(self, calls):
1746
self._pack_collection = _StubPackCollection(calls)
1749
class _StubPackCollection(object):
1751
def __init__(self, calls):
1755
self.calls.append(('pack collection autopack',))
1757
def reload_pack_names(self):
1758
self.calls.append(('pack collection reload_pack_names',))
1761
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1762
"""Tests for RemoteRepository.autopack implementation."""
1765
"""When the server returns 'ok' and there's no _real_repository, then
1766
nothing else happens: the autopack method is done.
1768
transport_path = 'quack'
1769
repo, client = self.setup_fake_client_and_repository(transport_path)
1770
client.add_expected_call(
1771
'PackRepository.autopack', ('quack/',), 'success', ('ok',))
1773
client.finished_test()
1775
def test_ok_with_real_repo(self):
1776
"""When the server returns 'ok' and there is a _real_repository, then
1777
the _real_repository's reload_pack_name's method will be called.
1779
transport_path = 'quack'
1780
repo, client = self.setup_fake_client_and_repository(transport_path)
1781
client.add_expected_call(
1782
'PackRepository.autopack', ('quack/',),
1784
repo._real_repository = _StubRealPackRepository(client._calls)
1787
[('call', 'PackRepository.autopack', ('quack/',)),
1788
('pack collection reload_pack_names',)],
1791
def test_backwards_compatibility(self):
1792
"""If the server does not recognise the PackRepository.autopack verb,
1793
fallback to the real_repository's implementation.
1795
transport_path = 'quack'
1796
repo, client = self.setup_fake_client_and_repository(transport_path)
1797
client.add_unknown_method_response('PackRepository.autopack')
1798
def stub_ensure_real():
1799
client._calls.append(('_ensure_real',))
1800
repo._real_repository = _StubRealPackRepository(client._calls)
1801
repo._ensure_real = stub_ensure_real
1804
[('call', 'PackRepository.autopack', ('quack/',)),
1806
('pack collection autopack',)],
1810
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
1811
"""Base class for unit tests for bzrlib.remote._translate_error."""
1813
def translateTuple(self, error_tuple, **context):
1814
"""Call _translate_error with an ErrorFromSmartServer built from the
1817
:param error_tuple: A tuple of a smart server response, as would be
1818
passed to an ErrorFromSmartServer.
1819
:kwargs context: context items to call _translate_error with.
1821
:returns: The error raised by _translate_error.
1823
# Raise the ErrorFromSmartServer before passing it as an argument,
1824
# because _translate_error may need to re-raise it with a bare 'raise'
1826
server_error = errors.ErrorFromSmartServer(error_tuple)
1827
translated_error = self.translateErrorFromSmartServer(
1828
server_error, **context)
1829
return translated_error
1831
def translateErrorFromSmartServer(self, error_object, **context):
1832
"""Like translateTuple, but takes an already constructed
1833
ErrorFromSmartServer rather than a tuple.
1837
except errors.ErrorFromSmartServer, server_error:
1838
translated_error = self.assertRaises(
1839
errors.BzrError, remote._translate_error, server_error,
1841
return translated_error
1844
class TestErrorTranslationSuccess(TestErrorTranslationBase):
1845
"""Unit tests for bzrlib.remote._translate_error.
1847
Given an ErrorFromSmartServer (which has an error tuple from a smart
1848
server) and some context, _translate_error raises more specific errors from
1851
This test case covers the cases where _translate_error succeeds in
1852
translating an ErrorFromSmartServer to something better. See
1853
TestErrorTranslationRobustness for other cases.
1856
def test_NoSuchRevision(self):
1857
branch = self.make_branch('')
1859
translated_error = self.translateTuple(
1860
('NoSuchRevision', revid), branch=branch)
1861
expected_error = errors.NoSuchRevision(branch, revid)
1862
self.assertEqual(expected_error, translated_error)
1864
def test_nosuchrevision(self):
1865
repository = self.make_repository('')
1867
translated_error = self.translateTuple(
1868
('nosuchrevision', revid), repository=repository)
1869
expected_error = errors.NoSuchRevision(repository, revid)
1870
self.assertEqual(expected_error, translated_error)
1872
def test_nobranch(self):
1873
bzrdir = self.make_bzrdir('')
1874
translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
1875
expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
1876
self.assertEqual(expected_error, translated_error)
1878
def test_LockContention(self):
1879
translated_error = self.translateTuple(('LockContention',))
1880
expected_error = errors.LockContention('(remote lock)')
1881
self.assertEqual(expected_error, translated_error)
1883
def test_UnlockableTransport(self):
1884
bzrdir = self.make_bzrdir('')
1885
translated_error = self.translateTuple(
1886
('UnlockableTransport',), bzrdir=bzrdir)
1887
expected_error = errors.UnlockableTransport(bzrdir.root_transport)
1888
self.assertEqual(expected_error, translated_error)
1890
def test_LockFailed(self):
1891
lock = 'str() of a server lock'
1892
why = 'str() of why'
1893
translated_error = self.translateTuple(('LockFailed', lock, why))
1894
expected_error = errors.LockFailed(lock, why)
1895
self.assertEqual(expected_error, translated_error)
1897
def test_TokenMismatch(self):
1898
token = 'a lock token'
1899
translated_error = self.translateTuple(('TokenMismatch',), token=token)
1900
expected_error = errors.TokenMismatch(token, '(remote token)')
1901
self.assertEqual(expected_error, translated_error)
1903
def test_Diverged(self):
1904
branch = self.make_branch('a')
1905
other_branch = self.make_branch('b')
1906
translated_error = self.translateTuple(
1907
('Diverged',), branch=branch, other_branch=other_branch)
1908
expected_error = errors.DivergedBranches(branch, other_branch)
1909
self.assertEqual(expected_error, translated_error)
1911
def test_ReadError_no_args(self):
1913
translated_error = self.translateTuple(('ReadError',), path=path)
1914
expected_error = errors.ReadError(path)
1915
self.assertEqual(expected_error, translated_error)
1917
def test_ReadError(self):
1919
translated_error = self.translateTuple(('ReadError', path))
1920
expected_error = errors.ReadError(path)
1921
self.assertEqual(expected_error, translated_error)
1923
def test_PermissionDenied_no_args(self):
1925
translated_error = self.translateTuple(('PermissionDenied',), path=path)
1926
expected_error = errors.PermissionDenied(path)
1927
self.assertEqual(expected_error, translated_error)
1929
def test_PermissionDenied_one_arg(self):
1931
translated_error = self.translateTuple(('PermissionDenied', path))
1932
expected_error = errors.PermissionDenied(path)
1933
self.assertEqual(expected_error, translated_error)
1935
def test_PermissionDenied_one_arg_and_context(self):
1936
"""Given a choice between a path from the local context and a path on
1937
the wire, _translate_error prefers the path from the local context.
1939
local_path = 'local path'
1940
remote_path = 'remote path'
1941
translated_error = self.translateTuple(
1942
('PermissionDenied', remote_path), path=local_path)
1943
expected_error = errors.PermissionDenied(local_path)
1944
self.assertEqual(expected_error, translated_error)
1946
def test_PermissionDenied_two_args(self):
1948
extra = 'a string with extra info'
1949
translated_error = self.translateTuple(
1950
('PermissionDenied', path, extra))
1951
expected_error = errors.PermissionDenied(path, extra)
1952
self.assertEqual(expected_error, translated_error)
1955
class TestErrorTranslationRobustness(TestErrorTranslationBase):
1956
"""Unit tests for bzrlib.remote._translate_error's robustness.
1958
TestErrorTranslationSuccess is for cases where _translate_error can
1959
translate successfully. This class about how _translate_err behaves when
1960
it fails to translate: it re-raises the original error.
1963
def test_unrecognised_server_error(self):
1964
"""If the error code from the server is not recognised, the original
1965
ErrorFromSmartServer is propagated unmodified.
1967
error_tuple = ('An unknown error tuple',)
1968
server_error = errors.ErrorFromSmartServer(error_tuple)
1969
translated_error = self.translateErrorFromSmartServer(server_error)
1970
expected_error = errors.UnknownErrorFromSmartServer(server_error)
1971
self.assertEqual(expected_error, translated_error)
1973
def test_context_missing_a_key(self):
1974
"""In case of a bug in the client, or perhaps an unexpected response
1975
from a server, _translate_error returns the original error tuple from
1976
the server and mutters a warning.
1978
# To translate a NoSuchRevision error _translate_error needs a 'branch'
1979
# in the context dict. So let's give it an empty context dict instead
1980
# to exercise its error recovery.
1982
error_tuple = ('NoSuchRevision', 'revid')
1983
server_error = errors.ErrorFromSmartServer(error_tuple)
1984
translated_error = self.translateErrorFromSmartServer(server_error)
1985
self.assertEqual(server_error, translated_error)
1986
# In addition to re-raising ErrorFromSmartServer, some debug info has
1987
# been muttered to the log file for developer to look at.
1988
self.assertContainsRe(
1989
self._get_log(keep_log_file=True),
1990
"Missing key 'branch' in context")
1992
def test_path_missing(self):
1993
"""Some translations (PermissionDenied, ReadError) can determine the
1994
'path' variable from either the wire or the local context. If neither
1995
has it, then an error is raised.
1997
error_tuple = ('ReadError',)
1998
server_error = errors.ErrorFromSmartServer(error_tuple)
1999
translated_error = self.translateErrorFromSmartServer(server_error)
2000
self.assertEqual(server_error, translated_error)
2001
# In addition to re-raising ErrorFromSmartServer, some debug info has
2002
# been muttered to the log file for developer to look at.
2003
self.assertContainsRe(
2004
self._get_log(keep_log_file=True), "Missing key 'path' in context")
2007
class TestStacking(tests.TestCaseWithTransport):
2008
"""Tests for operations on stacked remote repositories.
2010
The underlying format type must support stacking.
2013
def test_access_stacked_remote(self):
2014
# based on <http://launchpad.net/bugs/261315>
2015
# make a branch stacked on another repository containing an empty
2016
# revision, then open it over hpss - we should be able to see that
2018
base_transport = self.get_transport()
2019
base_builder = self.make_branch_builder('base', format='1.6')
2020
base_builder.start_series()
2021
base_revid = base_builder.build_snapshot('rev-id', None,
2022
[('add', ('', None, 'directory', None))],
2024
base_builder.finish_series()
2025
stacked_branch = self.make_branch('stacked', format='1.6')
2026
stacked_branch.set_stacked_on_url('../base')
2027
# start a server looking at this
2028
smart_server = server.SmartTCPServer_for_testing()
2029
smart_server.setUp()
2030
self.addCleanup(smart_server.tearDown)
2031
remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2032
# can get its branch and repository
2033
remote_branch = remote_bzrdir.open_branch()
2034
remote_repo = remote_branch.repository
2035
remote_repo.lock_read()
2037
# it should have an appropriate fallback repository, which should also
2038
# be a RemoteRepository
2039
self.assertEquals(len(remote_repo._fallback_repositories), 1)
2040
self.assertIsInstance(remote_repo._fallback_repositories[0],
2042
# and it has the revision committed to the underlying repository;
2043
# these have varying implementations so we try several of them
2044
self.assertTrue(remote_repo.has_revisions([base_revid]))
2045
self.assertTrue(remote_repo.has_revision(base_revid))
2046
self.assertEqual(remote_repo.get_revision(base_revid).message,
2049
remote_repo.unlock()
2051
def prepare_stacked_remote_branch(self):
2052
smart_server = server.SmartTCPServer_for_testing()
2053
smart_server.setUp()
2054
self.addCleanup(smart_server.tearDown)
2055
tree1 = self.make_branch_and_tree('tree1')
2056
tree1.commit('rev1', rev_id='rev1')
2057
tree2 = self.make_branch_and_tree('tree2', format='1.6')
2058
tree2.branch.set_stacked_on_url(tree1.branch.base)
2059
branch2 = Branch.open(smart_server.get_url() + '/tree2')
2061
self.addCleanup(branch2.unlock)
2064
def test_stacked_get_parent_map(self):
2065
# the public implementation of get_parent_map obeys stacking
2066
branch = self.prepare_stacked_remote_branch()
2067
repo = branch.repository
2068
self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2070
def test_unstacked_get_parent_map(self):
2071
# _unstacked_provider.get_parent_map ignores stacking
2072
branch = self.prepare_stacked_remote_branch()
2073
provider = branch.repository._unstacked_provider
2074
self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2077
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2080
super(TestRemoteBranchEffort, self).setUp()
2081
# Create a smart server that publishes whatever the backing VFS server
2083
self.smart_server = server.SmartTCPServer_for_testing()
2084
self.smart_server.setUp(self.get_server())
2085
self.addCleanup(self.smart_server.tearDown)
2086
# Log all HPSS calls into self.hpss_calls.
2087
_SmartClient.hooks.install_named_hook(
2088
'call', self.capture_hpss_call, None)
2089
self.hpss_calls = []
2091
def capture_hpss_call(self, params):
2092
self.hpss_calls.append(params.method)
2094
def test_copy_content_into_avoids_revision_history(self):
2095
local = self.make_branch('local')
2096
remote_backing_tree = self.make_branch_and_tree('remote')
2097
remote_backing_tree.commit("Commit.")
2098
remote_branch_url = self.smart_server.get_url() + 'remote'
2099
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2100
local.repository.fetch(remote_branch.repository)
2101
self.hpss_calls = []
2102
remote_branch.copy_content_into(local)
2103
self.assertFalse('Branch.revision_history' in self.hpss_calls)