1# Copyright (C) 2006-2013, 2016 Canonical Ltd 2# 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. 7# 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. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software 15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 17"""Tests for remote bzrdir/branch/repo/etc 18 19These are proxy objects which act on remote objects by sending messages 20through a smart client. The proxies are to be created when attempting to open 21the object given a transport that supports smartserver rpc operations. 22 23These tests correspond to tests.test_smart, which exercises the server side. 24""" 25 26import base64 27import bz2 28from io import BytesIO 29import tarfile 30import zlib 31 32from ... import ( 33 bencode, 34 branch, 35 config, 36 controldir, 37 errors, 38 repository, 39 tests, 40 transport, 41 treebuilder, 42 ) 43from ...branch import Branch 44from .. import ( 45 bzrdir, 46 inventory, 47 inventory_delta, 48 remote, 49 versionedfile, 50 vf_search, 51 ) 52from ..bzrdir import ( 53 BzrDir, 54 BzrDirFormat, 55 ) 56from .. import ( 57 RemoteBzrProber, 58 ) 59from ..chk_serializer import chk_bencode_serializer 60from ..remote import ( 61 RemoteBranch, 62 RemoteBranchFormat, 63 RemoteBzrDir, 64 RemoteBzrDirFormat, 65 RemoteRepository, 66 RemoteRepositoryFormat, 67 ) 68from .. import groupcompress_repo, knitpack_repo 69from ...revision import ( 70 NULL_REVISION, 71 Revision, 72 ) 73from ..smart import medium, request 74from ..smart.client import _SmartClient 75from ..smart.repository import ( 76 SmartServerRepositoryGetParentMap, 77 SmartServerRepositoryGetStream_1_19, 78 _stream_to_byte_stream, 79 ) 80from ...tests import ( 81 test_server, 82 ) 83from ...tests.scenarios import load_tests_apply_scenarios 84from ...transport.memory import MemoryTransport 85from ...transport.remote import ( 86 RemoteTransport, 87 RemoteSSHTransport, 88 RemoteTCPTransport, 89 ) 90 91 92load_tests = load_tests_apply_scenarios 93 94 95class BasicRemoteObjectTests(tests.TestCaseWithTransport): 96 97 scenarios = [ 98 ('HPSS-v2', 99 {'transport_server': 100 test_server.SmartTCPServer_for_testing_v2_only}), 101 ('HPSS-v3', 102 {'transport_server': test_server.SmartTCPServer_for_testing})] 103 104 def setUp(self): 105 super(BasicRemoteObjectTests, self).setUp() 106 self.transport = self.get_transport() 107 # make a branch that can be opened over the smart transport 108 self.local_wt = BzrDir.create_standalone_workingtree('.') 109 self.addCleanup(self.transport.disconnect) 110 111 def test_create_remote_bzrdir(self): 112 b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat()) 113 self.assertIsInstance(b, BzrDir) 114 115 def test_open_remote_branch(self): 116 # open a standalone branch in the working directory 117 b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat()) 118 branch = b.open_branch() 119 self.assertIsInstance(branch, Branch) 120 121 def test_remote_repository(self): 122 b = BzrDir.open_from_transport(self.transport) 123 repo = b.open_repository() 124 revid = u'\xc823123123'.encode('utf8') 125 self.assertFalse(repo.has_revision(revid)) 126 self.local_wt.commit(message='test commit', rev_id=revid) 127 self.assertTrue(repo.has_revision(revid)) 128 129 def test_find_correct_format(self): 130 """Should open a RemoteBzrDir over a RemoteTransport""" 131 fmt = BzrDirFormat.find_format(self.transport) 132 self.assertIn(RemoteBzrProber, controldir.ControlDirFormat._probers) 133 self.assertIsInstance(fmt, RemoteBzrDirFormat) 134 135 def test_open_detected_smart_format(self): 136 fmt = BzrDirFormat.find_format(self.transport) 137 d = fmt.open(self.transport) 138 self.assertIsInstance(d, BzrDir) 139 140 def test_remote_branch_repr(self): 141 b = BzrDir.open_from_transport(self.transport).open_branch() 142 self.assertStartsWith(str(b), 'RemoteBranch(') 143 144 def test_remote_bzrdir_repr(self): 145 b = BzrDir.open_from_transport(self.transport) 146 self.assertStartsWith(str(b), 'RemoteBzrDir(') 147 148 def test_remote_branch_format_supports_stacking(self): 149 t = self.transport 150 self.make_branch('unstackable', format='pack-0.92') 151 b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch() 152 self.assertFalse(b._format.supports_stacking()) 153 self.make_branch('stackable', format='1.9') 154 b = BzrDir.open_from_transport(t.clone('stackable')).open_branch() 155 self.assertTrue(b._format.supports_stacking()) 156 157 def test_remote_repo_format_supports_external_references(self): 158 t = self.transport 159 bd = self.make_controldir('unstackable', format='pack-0.92') 160 r = bd.create_repository() 161 self.assertFalse(r._format.supports_external_lookups) 162 r = BzrDir.open_from_transport( 163 t.clone('unstackable')).open_repository() 164 self.assertFalse(r._format.supports_external_lookups) 165 bd = self.make_controldir('stackable', format='1.9') 166 r = bd.create_repository() 167 self.assertTrue(r._format.supports_external_lookups) 168 r = BzrDir.open_from_transport(t.clone('stackable')).open_repository() 169 self.assertTrue(r._format.supports_external_lookups) 170 171 def test_remote_branch_set_append_revisions_only(self): 172 # Make a format 1.9 branch, which supports append_revisions_only 173 branch = self.make_branch('branch', format='1.9') 174 branch.set_append_revisions_only(True) 175 config = branch.get_config_stack() 176 self.assertEqual( 177 True, config.get('append_revisions_only')) 178 branch.set_append_revisions_only(False) 179 config = branch.get_config_stack() 180 self.assertEqual( 181 False, config.get('append_revisions_only')) 182 183 def test_remote_branch_set_append_revisions_only_upgrade_reqd(self): 184 branch = self.make_branch('branch', format='knit') 185 self.assertRaises( 186 errors.UpgradeRequired, branch.set_append_revisions_only, True) 187 188 189class FakeProtocol(object): 190 """Lookalike SmartClientRequestProtocolOne allowing body reading tests.""" 191 192 def __init__(self, body, fake_client): 193 self.body = body 194 self._body_buffer = None 195 self._fake_client = fake_client 196 197 def read_body_bytes(self, count=-1): 198 if self._body_buffer is None: 199 self._body_buffer = BytesIO(self.body) 200 bytes = self._body_buffer.read(count) 201 if self._body_buffer.tell() == len(self._body_buffer.getvalue()): 202 self._fake_client.expecting_body = False 203 return bytes 204 205 def cancel_read_body(self): 206 self._fake_client.expecting_body = False 207 208 def read_streamed_body(self): 209 return self.body 210 211 212class FakeClient(_SmartClient): 213 """Lookalike for _SmartClient allowing testing.""" 214 215 def __init__(self, fake_medium_base='fake base'): 216 """Create a FakeClient.""" 217 self.responses = [] 218 self._calls = [] 219 self.expecting_body = False 220 # if non-None, this is the list of expected calls, with only the 221 # method name and arguments included. the body might be hard to 222 # compute so is not included. If a call is None, that call can 223 # be anything. 224 self._expected_calls = None 225 _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base)) 226 227 def add_expected_call(self, call_name, call_args, response_type, 228 response_args, response_body=None): 229 if self._expected_calls is None: 230 self._expected_calls = [] 231 self._expected_calls.append((call_name, call_args)) 232 self.responses.append((response_type, response_args, response_body)) 233 234 def add_success_response(self, *args): 235 self.responses.append((b'success', args, None)) 236 237 def add_success_response_with_body(self, body, *args): 238 self.responses.append((b'success', args, body)) 239 if self._expected_calls is not None: 240 self._expected_calls.append(None) 241 242 def add_error_response(self, *args): 243 self.responses.append((b'error', args)) 244 245 def add_unknown_method_response(self, verb): 246 self.responses.append((b'unknown', verb)) 247 248 def finished_test(self): 249 if self._expected_calls: 250 raise AssertionError("%r finished but was still expecting %r" 251 % (self, self._expected_calls[0])) 252 253 def _get_next_response(self): 254 try: 255 response_tuple = self.responses.pop(0) 256 except IndexError: 257 raise AssertionError("%r didn't expect any more calls" % (self,)) 258 if response_tuple[0] == b'unknown': 259 raise errors.UnknownSmartMethod(response_tuple[1]) 260 elif response_tuple[0] == b'error': 261 raise errors.ErrorFromSmartServer(response_tuple[1]) 262 return response_tuple 263 264 def _check_call(self, method, args): 265 if self._expected_calls is None: 266 # the test should be updated to say what it expects 267 return 268 try: 269 next_call = self._expected_calls.pop(0) 270 except IndexError: 271 raise AssertionError("%r didn't expect any more calls " 272 "but got %r%r" 273 % (self, method, args,)) 274 if next_call is None: 275 return 276 if method != next_call[0] or args != next_call[1]: 277 raise AssertionError( 278 "%r expected %r%r but got %r%r" % 279 (self, next_call[0], next_call[1], method, args,)) 280 281 def call(self, method, *args): 282 self._check_call(method, args) 283 self._calls.append(('call', method, args)) 284 return self._get_next_response()[1] 285 286 def call_expecting_body(self, method, *args): 287 self._check_call(method, args) 288 self._calls.append(('call_expecting_body', method, args)) 289 result = self._get_next_response() 290 self.expecting_body = True 291 return result[1], FakeProtocol(result[2], self) 292 293 def call_with_body_bytes(self, method, args, body): 294 self._check_call(method, args) 295 self._calls.append(('call_with_body_bytes', method, args, body)) 296 result = self._get_next_response() 297 return result[1], FakeProtocol(result[2], self) 298 299 def call_with_body_bytes_expecting_body(self, method, args, body): 300 self._check_call(method, args) 301 self._calls.append(('call_with_body_bytes_expecting_body', method, 302 args, body)) 303 result = self._get_next_response() 304 self.expecting_body = True 305 return result[1], FakeProtocol(result[2], self) 306 307 def call_with_body_stream(self, args, stream): 308 # Explicitly consume the stream before checking for an error, because 309 # that's what happens a real medium. 310 stream = list(stream) 311 self._check_call(args[0], args[1:]) 312 self._calls.append( 313 ('call_with_body_stream', args[0], args[1:], stream)) 314 result = self._get_next_response() 315 # The second value returned from call_with_body_stream is supposed to 316 # be a response_handler object, but so far no tests depend on that. 317 response_handler = None 318 return result[1], response_handler 319 320 321class FakeMedium(medium.SmartClientMedium): 322 323 def __init__(self, client_calls, base): 324 medium.SmartClientMedium.__init__(self, base) 325 self._client_calls = client_calls 326 327 def disconnect(self): 328 self._client_calls.append(('disconnect medium',)) 329 330 331class TestVfsHas(tests.TestCase): 332 333 def test_unicode_path(self): 334 client = FakeClient('/') 335 client.add_success_response(b'yes',) 336 transport = RemoteTransport('bzr://localhost/', _client=client) 337 filename = u'/hell\u00d8' 338 result = transport.has(filename) 339 self.assertEqual( 340 [('call', b'has', (filename.encode('utf-8'),))], 341 client._calls) 342 self.assertTrue(result) 343 344 345class TestRemote(tests.TestCaseWithMemoryTransport): 346 347 def get_branch_format(self): 348 reference_bzrdir_format = controldir.format_registry.get('default')() 349 return reference_bzrdir_format.get_branch_format() 350 351 def get_repo_format(self): 352 reference_bzrdir_format = controldir.format_registry.get('default')() 353 return reference_bzrdir_format.repository_format 354 355 def assertFinished(self, fake_client): 356 """Assert that all of a FakeClient's expected calls have occurred.""" 357 fake_client.finished_test() 358 359 360class Test_ClientMedium_remote_path_from_transport(tests.TestCase): 361 """Tests for the behaviour of client_medium.remote_path_from_transport.""" 362 363 def assertRemotePath(self, expected, client_base, transport_base): 364 """Assert that the result of 365 SmartClientMedium.remote_path_from_transport is the expected value for 366 a given client_base and transport_base. 367 """ 368 client_medium = medium.SmartClientMedium(client_base) 369 t = transport.get_transport(transport_base) 370 result = client_medium.remote_path_from_transport(t) 371 self.assertEqual(expected, result) 372 373 def test_remote_path_from_transport(self): 374 """SmartClientMedium.remote_path_from_transport calculates a URL for 375 the given transport relative to the root of the client base URL. 376 """ 377 self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz') 378 self.assertRemotePath( 379 'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz') 380 381 def assertRemotePathHTTP(self, expected, transport_base, relpath): 382 """Assert that the result of 383 HttpTransportBase.remote_path_from_transport is the expected value for 384 a given transport_base and relpath of that transport. (Note that 385 HttpTransportBase is a subclass of SmartClientMedium) 386 """ 387 base_transport = transport.get_transport(transport_base) 388 client_medium = base_transport.get_smart_medium() 389 cloned_transport = base_transport.clone(relpath) 390 result = client_medium.remote_path_from_transport(cloned_transport) 391 self.assertEqual(expected, result) 392 393 def test_remote_path_from_transport_http(self): 394 """Remote paths for HTTP transports are calculated differently to other 395 transports. They are just relative to the client base, not the root 396 directory of the host. 397 """ 398 for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']: 399 self.assertRemotePathHTTP( 400 '../xyz/', scheme + '//host/path', '../xyz/') 401 self.assertRemotePathHTTP( 402 'xyz/', scheme + '//host/path', 'xyz/') 403 404 405class Test_ClientMedium_remote_is_at_least(tests.TestCase): 406 """Tests for the behaviour of client_medium.remote_is_at_least.""" 407 408 def test_initially_unlimited(self): 409 """A fresh medium assumes that the remote side supports all 410 versions. 411 """ 412 client_medium = medium.SmartClientMedium('dummy base') 413 self.assertFalse(client_medium._is_remote_before((99, 99))) 414 415 def test__remember_remote_is_before(self): 416 """Calling _remember_remote_is_before ratchets down the known remote 417 version. 418 """ 419 client_medium = medium.SmartClientMedium('dummy base') 420 # Mark the remote side as being less than 1.6. The remote side may 421 # still be 1.5. 422 client_medium._remember_remote_is_before((1, 6)) 423 self.assertTrue(client_medium._is_remote_before((1, 6))) 424 self.assertFalse(client_medium._is_remote_before((1, 5))) 425 # Calling _remember_remote_is_before again with a lower value works. 426 client_medium._remember_remote_is_before((1, 5)) 427 self.assertTrue(client_medium._is_remote_before((1, 5))) 428 # If you call _remember_remote_is_before with a higher value it logs a 429 # warning, and continues to remember the lower value. 430 self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before') 431 client_medium._remember_remote_is_before((1, 9)) 432 self.assertContainsRe(self.get_log(), '_remember_remote_is_before') 433 self.assertTrue(client_medium._is_remote_before((1, 5))) 434 435 436class TestBzrDirCloningMetaDir(TestRemote): 437 438 def test_backwards_compat(self): 439 self.setup_smart_server_with_call_log() 440 a_dir = self.make_controldir('.') 441 self.reset_smart_call_log() 442 verb = b'BzrDir.cloning_metadir' 443 self.disable_verb(verb) 444 a_dir.cloning_metadir() 445 call_count = len([call for call in self.hpss_calls if 446 call.call.method == verb]) 447 self.assertEqual(1, call_count) 448 449 def test_branch_reference(self): 450 transport = self.get_transport('quack') 451 referenced = self.make_branch('referenced') 452 expected = referenced.controldir.cloning_metadir() 453 client = FakeClient(transport.base) 454 client.add_expected_call( 455 b'BzrDir.cloning_metadir', (b'quack/', b'False'), 456 b'error', (b'BranchReference',)), 457 client.add_expected_call( 458 b'BzrDir.open_branchV3', (b'quack/',), 459 b'success', (b'ref', self.get_url('referenced').encode('utf-8'))), 460 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 461 _client=client) 462 result = a_controldir.cloning_metadir() 463 # We should have got a control dir matching the referenced branch. 464 self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result)) 465 self.assertEqual(expected._repository_format, 466 result._repository_format) 467 self.assertEqual(expected._branch_format, result._branch_format) 468 self.assertFinished(client) 469 470 def test_current_server(self): 471 transport = self.get_transport('.') 472 transport = transport.clone('quack') 473 self.make_controldir('quack') 474 client = FakeClient(transport.base) 475 reference_bzrdir_format = controldir.format_registry.get('default')() 476 control_name = reference_bzrdir_format.network_name() 477 client.add_expected_call( 478 b'BzrDir.cloning_metadir', (b'quack/', b'False'), 479 b'success', (control_name, b'', (b'branch', b''))), 480 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 481 _client=client) 482 result = a_controldir.cloning_metadir() 483 # We should have got a reference control dir with default branch and 484 # repository formats. 485 # This pokes a little, just to be sure. 486 self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result)) 487 self.assertEqual(None, result._repository_format) 488 self.assertEqual(None, result._branch_format) 489 self.assertFinished(client) 490 491 def test_unknown(self): 492 transport = self.get_transport('quack') 493 referenced = self.make_branch('referenced') 494 referenced.controldir.cloning_metadir() 495 client = FakeClient(transport.base) 496 client.add_expected_call( 497 b'BzrDir.cloning_metadir', (b'quack/', b'False'), 498 b'success', (b'unknown', b'unknown', (b'branch', b''))), 499 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 500 _client=client) 501 self.assertRaises(errors.UnknownFormatError, 502 a_controldir.cloning_metadir) 503 504 505class TestBzrDirCheckoutMetaDir(TestRemote): 506 507 def test__get_checkout_format(self): 508 transport = MemoryTransport() 509 client = FakeClient(transport.base) 510 reference_bzrdir_format = controldir.format_registry.get('default')() 511 control_name = reference_bzrdir_format.network_name() 512 client.add_expected_call( 513 b'BzrDir.checkout_metadir', (b'quack/', ), 514 b'success', (control_name, b'', b'')) 515 transport.mkdir('quack') 516 transport = transport.clone('quack') 517 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 518 _client=client) 519 result = a_controldir.checkout_metadir() 520 # We should have got a reference control dir with default branch and 521 # repository formats. 522 self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result)) 523 self.assertEqual(None, result._repository_format) 524 self.assertEqual(None, result._branch_format) 525 self.assertFinished(client) 526 527 def test_unknown_format(self): 528 transport = MemoryTransport() 529 client = FakeClient(transport.base) 530 client.add_expected_call( 531 b'BzrDir.checkout_metadir', (b'quack/',), 532 b'success', (b'dontknow', b'', b'')) 533 transport.mkdir('quack') 534 transport = transport.clone('quack') 535 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 536 _client=client) 537 self.assertRaises(errors.UnknownFormatError, 538 a_controldir.checkout_metadir) 539 self.assertFinished(client) 540 541 542class TestBzrDirGetBranches(TestRemote): 543 544 def test_get_branches(self): 545 transport = MemoryTransport() 546 client = FakeClient(transport.base) 547 reference_bzrdir_format = controldir.format_registry.get('default')() 548 branch_name = reference_bzrdir_format.get_branch_format().network_name() 549 client.add_success_response_with_body( 550 bencode.bencode({ 551 b"foo": (b"branch", branch_name), 552 b"": (b"branch", branch_name)}), b"success") 553 client.add_success_response( 554 b'ok', b'', b'no', b'no', b'no', 555 reference_bzrdir_format.repository_format.network_name()) 556 client.add_error_response(b'NotStacked') 557 client.add_success_response( 558 b'ok', b'', b'no', b'no', b'no', 559 reference_bzrdir_format.repository_format.network_name()) 560 client.add_error_response(b'NotStacked') 561 transport.mkdir('quack') 562 transport = transport.clone('quack') 563 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 564 _client=client) 565 result = a_controldir.get_branches() 566 self.assertEqual({"", "foo"}, set(result.keys())) 567 self.assertEqual( 568 [('call_expecting_body', b'BzrDir.get_branches', (b'quack/',)), 569 ('call', b'BzrDir.find_repositoryV3', (b'quack/', )), 570 ('call', b'Branch.get_stacked_on_url', (b'quack/', )), 571 ('call', b'BzrDir.find_repositoryV3', (b'quack/', )), 572 ('call', b'Branch.get_stacked_on_url', (b'quack/', ))], 573 client._calls) 574 575 576class TestBzrDirDestroyBranch(TestRemote): 577 578 def test_destroy_default(self): 579 transport = self.get_transport('quack') 580 self.make_branch('referenced') 581 client = FakeClient(transport.base) 582 client.add_expected_call( 583 b'BzrDir.destroy_branch', (b'quack/', ), 584 b'success', (b'ok',)), 585 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 586 _client=client) 587 a_controldir.destroy_branch() 588 self.assertFinished(client) 589 590 591class TestBzrDirHasWorkingTree(TestRemote): 592 593 def test_has_workingtree(self): 594 transport = self.get_transport('quack') 595 client = FakeClient(transport.base) 596 client.add_expected_call( 597 b'BzrDir.has_workingtree', (b'quack/',), 598 b'success', (b'yes',)), 599 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 600 _client=client) 601 self.assertTrue(a_controldir.has_workingtree()) 602 self.assertFinished(client) 603 604 def test_no_workingtree(self): 605 transport = self.get_transport('quack') 606 client = FakeClient(transport.base) 607 client.add_expected_call( 608 b'BzrDir.has_workingtree', (b'quack/',), 609 b'success', (b'no',)), 610 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 611 _client=client) 612 self.assertFalse(a_controldir.has_workingtree()) 613 self.assertFinished(client) 614 615 616class TestBzrDirDestroyRepository(TestRemote): 617 618 def test_destroy_repository(self): 619 transport = self.get_transport('quack') 620 client = FakeClient(transport.base) 621 client.add_expected_call( 622 b'BzrDir.destroy_repository', (b'quack/',), 623 b'success', (b'ok',)), 624 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 625 _client=client) 626 a_controldir.destroy_repository() 627 self.assertFinished(client) 628 629 630class TestBzrDirOpen(TestRemote): 631 632 def make_fake_client_and_transport(self, path='quack'): 633 transport = MemoryTransport() 634 transport.mkdir(path) 635 transport = transport.clone(path) 636 client = FakeClient(transport.base) 637 return client, transport 638 639 def test_absent(self): 640 client, transport = self.make_fake_client_and_transport() 641 client.add_expected_call( 642 b'BzrDir.open_2.1', (b'quack/',), b'success', (b'no',)) 643 self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport, 644 RemoteBzrDirFormat(), _client=client, 645 _force_probe=True) 646 self.assertFinished(client) 647 648 def test_present_without_workingtree(self): 649 client, transport = self.make_fake_client_and_transport() 650 client.add_expected_call( 651 b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'no')) 652 bd = RemoteBzrDir(transport, RemoteBzrDirFormat(), 653 _client=client, _force_probe=True) 654 self.assertIsInstance(bd, RemoteBzrDir) 655 self.assertFalse(bd.has_workingtree()) 656 self.assertRaises(errors.NoWorkingTree, bd.open_workingtree) 657 self.assertFinished(client) 658 659 def test_present_with_workingtree(self): 660 client, transport = self.make_fake_client_and_transport() 661 client.add_expected_call( 662 b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'yes')) 663 bd = RemoteBzrDir(transport, RemoteBzrDirFormat(), 664 _client=client, _force_probe=True) 665 self.assertIsInstance(bd, RemoteBzrDir) 666 self.assertTrue(bd.has_workingtree()) 667 self.assertRaises(errors.NotLocalUrl, bd.open_workingtree) 668 self.assertFinished(client) 669 670 def test_backwards_compat(self): 671 client, transport = self.make_fake_client_and_transport() 672 client.add_expected_call( 673 b'BzrDir.open_2.1', (b'quack/',), b'unknown', 674 (b'BzrDir.open_2.1',)) 675 client.add_expected_call( 676 b'BzrDir.open', (b'quack/',), b'success', (b'yes',)) 677 bd = RemoteBzrDir(transport, RemoteBzrDirFormat(), 678 _client=client, _force_probe=True) 679 self.assertIsInstance(bd, RemoteBzrDir) 680 self.assertFinished(client) 681 682 def test_backwards_compat_hpss_v2(self): 683 client, transport = self.make_fake_client_and_transport() 684 # Monkey-patch fake client to simulate real-world behaviour with v2 685 # server: upon first RPC call detect the protocol version, and because 686 # the version is 2 also do _remember_remote_is_before((1, 6)) before 687 # continuing with the RPC. 688 orig_check_call = client._check_call 689 690 def check_call(method, args): 691 client._medium._protocol_version = 2 692 client._medium._remember_remote_is_before((1, 6)) 693 client._check_call = orig_check_call 694 client._check_call(method, args) 695 client._check_call = check_call 696 client.add_expected_call( 697 b'BzrDir.open_2.1', (b'quack/',), b'unknown', 698 (b'BzrDir.open_2.1',)) 699 client.add_expected_call( 700 b'BzrDir.open', (b'quack/',), b'success', (b'yes',)) 701 bd = RemoteBzrDir(transport, RemoteBzrDirFormat(), 702 _client=client, _force_probe=True) 703 self.assertIsInstance(bd, RemoteBzrDir) 704 self.assertFinished(client) 705 706 707class TestBzrDirOpenBranch(TestRemote): 708 709 def test_backwards_compat(self): 710 self.setup_smart_server_with_call_log() 711 self.make_branch('.') 712 a_dir = BzrDir.open(self.get_url('.')) 713 self.reset_smart_call_log() 714 verb = b'BzrDir.open_branchV3' 715 self.disable_verb(verb) 716 a_dir.open_branch() 717 call_count = len([call for call in self.hpss_calls if 718 call.call.method == verb]) 719 self.assertEqual(1, call_count) 720 721 def test_branch_present(self): 722 reference_format = self.get_repo_format() 723 network_name = reference_format.network_name() 724 branch_network_name = self.get_branch_format().network_name() 725 transport = MemoryTransport() 726 transport.mkdir('quack') 727 transport = transport.clone('quack') 728 client = FakeClient(transport.base) 729 client.add_expected_call( 730 b'BzrDir.open_branchV3', (b'quack/',), 731 b'success', (b'branch', branch_network_name)) 732 client.add_expected_call( 733 b'BzrDir.find_repositoryV3', (b'quack/',), 734 b'success', (b'ok', b'', b'no', b'no', b'no', network_name)) 735 client.add_expected_call( 736 b'Branch.get_stacked_on_url', (b'quack/',), 737 b'error', (b'NotStacked',)) 738 bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 739 _client=client) 740 result = bzrdir.open_branch() 741 self.assertIsInstance(result, RemoteBranch) 742 self.assertEqual(bzrdir, result.controldir) 743 self.assertFinished(client) 744 745 def test_branch_missing(self): 746 transport = MemoryTransport() 747 transport.mkdir('quack') 748 transport = transport.clone('quack') 749 client = FakeClient(transport.base) 750 client.add_error_response(b'nobranch') 751 bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 752 _client=client) 753 self.assertRaises(errors.NotBranchError, bzrdir.open_branch) 754 self.assertEqual( 755 [('call', b'BzrDir.open_branchV3', (b'quack/',))], 756 client._calls) 757 758 def test__get_tree_branch(self): 759 # _get_tree_branch is a form of open_branch, but it should only ask for 760 # branch opening, not any other network requests. 761 calls = [] 762 763 def open_branch(name=None, possible_transports=None): 764 calls.append("Called") 765 return "a-branch" 766 transport = MemoryTransport() 767 # no requests on the network - catches other api calls being made. 768 client = FakeClient(transport.base) 769 bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 770 _client=client) 771 # patch the open_branch call to record that it was called. 772 bzrdir.open_branch = open_branch 773 self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch()) 774 self.assertEqual(["Called"], calls) 775 self.assertEqual([], client._calls) 776 777 def test_url_quoting_of_path(self): 778 # Relpaths on the wire should not be URL-escaped. So "~" should be 779 # transmitted as "~", not "%7E". 780 transport = RemoteTCPTransport('bzr://localhost/~hello/') 781 client = FakeClient(transport.base) 782 reference_format = self.get_repo_format() 783 network_name = reference_format.network_name() 784 branch_network_name = self.get_branch_format().network_name() 785 client.add_expected_call( 786 b'BzrDir.open_branchV3', (b'~hello/',), 787 b'success', (b'branch', branch_network_name)) 788 client.add_expected_call( 789 b'BzrDir.find_repositoryV3', (b'~hello/',), 790 b'success', (b'ok', b'', b'no', b'no', b'no', network_name)) 791 client.add_expected_call( 792 b'Branch.get_stacked_on_url', (b'~hello/',), 793 b'error', (b'NotStacked',)) 794 bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 795 _client=client) 796 bzrdir.open_branch() 797 self.assertFinished(client) 798 799 def check_open_repository(self, rich_root, subtrees, 800 external_lookup=b'no'): 801 reference_format = self.get_repo_format() 802 network_name = reference_format.network_name() 803 transport = MemoryTransport() 804 transport.mkdir('quack') 805 transport = transport.clone('quack') 806 if rich_root: 807 rich_response = b'yes' 808 else: 809 rich_response = b'no' 810 if subtrees: 811 subtree_response = b'yes' 812 else: 813 subtree_response = b'no' 814 client = FakeClient(transport.base) 815 client.add_success_response( 816 b'ok', b'', rich_response, subtree_response, external_lookup, 817 network_name) 818 bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 819 _client=client) 820 result = bzrdir.open_repository() 821 self.assertEqual( 822 [('call', b'BzrDir.find_repositoryV3', (b'quack/',))], 823 client._calls) 824 self.assertIsInstance(result, RemoteRepository) 825 self.assertEqual(bzrdir, result.controldir) 826 self.assertEqual(rich_root, result._format.rich_root_data) 827 self.assertEqual(subtrees, result._format.supports_tree_reference) 828 829 def test_open_repository_sets_format_attributes(self): 830 self.check_open_repository(True, True) 831 self.check_open_repository(False, True) 832 self.check_open_repository(True, False) 833 self.check_open_repository(False, False) 834 self.check_open_repository(False, False, b'yes') 835 836 def test_old_server(self): 837 """RemoteBzrDirFormat should fail to probe if the server version is too 838 old. 839 """ 840 self.assertRaises( 841 errors.NotBranchError, 842 RemoteBzrProber.probe_transport, OldServerTransport()) 843 844 845class TestBzrDirCreateBranch(TestRemote): 846 847 def test_backwards_compat(self): 848 self.setup_smart_server_with_call_log() 849 repo = self.make_repository('.') 850 self.reset_smart_call_log() 851 self.disable_verb(b'BzrDir.create_branch') 852 repo.controldir.create_branch() 853 create_branch_call_count = len( 854 [call for call in self.hpss_calls 855 if call.call.method == b'BzrDir.create_branch']) 856 self.assertEqual(1, create_branch_call_count) 857 858 def test_current_server(self): 859 transport = self.get_transport('.') 860 transport = transport.clone('quack') 861 self.make_repository('quack') 862 client = FakeClient(transport.base) 863 reference_bzrdir_format = controldir.format_registry.get('default')() 864 reference_format = reference_bzrdir_format.get_branch_format() 865 network_name = reference_format.network_name() 866 reference_repo_fmt = reference_bzrdir_format.repository_format 867 reference_repo_name = reference_repo_fmt.network_name() 868 client.add_expected_call( 869 b'BzrDir.create_branch', (b'quack/', network_name), 870 b'success', (b'ok', network_name, b'', b'no', b'no', b'yes', 871 reference_repo_name)) 872 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 873 _client=client) 874 branch = a_controldir.create_branch() 875 # We should have got a remote branch 876 self.assertIsInstance(branch, remote.RemoteBranch) 877 # its format should have the settings from the response 878 format = branch._format 879 self.assertEqual(network_name, format.network_name()) 880 881 def test_already_open_repo_and_reused_medium(self): 882 """Bug 726584: create_branch(..., repository=repo) should work 883 regardless of what the smart medium's base URL is. 884 """ 885 self.transport_server = test_server.SmartTCPServer_for_testing 886 transport = self.get_transport('.') 887 repo = self.make_repository('quack') 888 # Client's medium rooted a transport root (not at the bzrdir) 889 client = FakeClient(transport.base) 890 transport = transport.clone('quack') 891 reference_bzrdir_format = controldir.format_registry.get('default')() 892 reference_format = reference_bzrdir_format.get_branch_format() 893 network_name = reference_format.network_name() 894 reference_repo_fmt = reference_bzrdir_format.repository_format 895 reference_repo_name = reference_repo_fmt.network_name() 896 client.add_expected_call( 897 b'BzrDir.create_branch', (b'extra/quack/', network_name), 898 b'success', (b'ok', network_name, b'', b'no', b'no', b'yes', 899 reference_repo_name)) 900 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 901 _client=client) 902 branch = a_controldir.create_branch(repository=repo) 903 # We should have got a remote branch 904 self.assertIsInstance(branch, remote.RemoteBranch) 905 # its format should have the settings from the response 906 format = branch._format 907 self.assertEqual(network_name, format.network_name()) 908 909 910class TestBzrDirCreateRepository(TestRemote): 911 912 def test_backwards_compat(self): 913 self.setup_smart_server_with_call_log() 914 bzrdir = self.make_controldir('.') 915 self.reset_smart_call_log() 916 self.disable_verb(b'BzrDir.create_repository') 917 bzrdir.create_repository() 918 create_repo_call_count = len([call for call in self.hpss_calls if 919 call.call.method == b'BzrDir.create_repository']) 920 self.assertEqual(1, create_repo_call_count) 921 922 def test_current_server(self): 923 transport = self.get_transport('.') 924 transport = transport.clone('quack') 925 self.make_controldir('quack') 926 client = FakeClient(transport.base) 927 reference_bzrdir_format = controldir.format_registry.get('default')() 928 reference_format = reference_bzrdir_format.repository_format 929 network_name = reference_format.network_name() 930 client.add_expected_call( 931 b'BzrDir.create_repository', (b'quack/', 932 b'Bazaar repository format 2a (needs bzr 1.16 or later)\n', 933 b'False'), 934 b'success', (b'ok', b'yes', b'yes', b'yes', network_name)) 935 a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 936 _client=client) 937 repo = a_controldir.create_repository() 938 # We should have got a remote repository 939 self.assertIsInstance(repo, remote.RemoteRepository) 940 # its format should have the settings from the response 941 format = repo._format 942 self.assertTrue(format.rich_root_data) 943 self.assertTrue(format.supports_tree_reference) 944 self.assertTrue(format.supports_external_lookups) 945 self.assertEqual(network_name, format.network_name()) 946 947 948class TestBzrDirOpenRepository(TestRemote): 949 950 def test_backwards_compat_1_2_3(self): 951 # fallback all the way to the first version. 952 reference_format = self.get_repo_format() 953 network_name = reference_format.network_name() 954 server_url = 'bzr://example.com/' 955 self.permit_url(server_url) 956 client = FakeClient(server_url) 957 client.add_unknown_method_response(b'BzrDir.find_repositoryV3') 958 client.add_unknown_method_response(b'BzrDir.find_repositoryV2') 959 client.add_success_response(b'ok', b'', b'no', b'no') 960 # A real repository instance will be created to determine the network 961 # name. 962 client.add_success_response_with_body( 963 b"Bazaar-NG meta directory, format 1\n", b'ok') 964 client.add_success_response(b'stat', b'0', b'65535') 965 client.add_success_response_with_body( 966 reference_format.get_format_string(), b'ok') 967 # PackRepository wants to do a stat 968 client.add_success_response(b'stat', b'0', b'65535') 969 remote_transport = RemoteTransport(server_url + 'quack/', medium=False, 970 _client=client) 971 bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(), 972 _client=client) 973 repo = bzrdir.open_repository() 974 self.assertEqual( 975 [('call', b'BzrDir.find_repositoryV3', (b'quack/',)), 976 ('call', b'BzrDir.find_repositoryV2', (b'quack/',)), 977 ('call', b'BzrDir.find_repository', (b'quack/',)), 978 ('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)), 979 ('call', b'stat', (b'/quack/.bzr',)), 980 ('call_expecting_body', b'get', (b'/quack/.bzr/repository/format',)), 981 ('call', b'stat', (b'/quack/.bzr/repository',)), 982 ], 983 client._calls) 984 self.assertEqual(network_name, repo._format.network_name()) 985 986 def test_backwards_compat_2(self): 987 # fallback to find_repositoryV2 988 reference_format = self.get_repo_format() 989 network_name = reference_format.network_name() 990 server_url = 'bzr://example.com/' 991 self.permit_url(server_url) 992 client = FakeClient(server_url) 993 client.add_unknown_method_response(b'BzrDir.find_repositoryV3') 994 client.add_success_response(b'ok', b'', b'no', b'no', b'no') 995 # A real repository instance will be created to determine the network 996 # name. 997 client.add_success_response_with_body( 998 b"Bazaar-NG meta directory, format 1\n", b'ok') 999 client.add_success_response(b'stat', b'0', b'65535') 1000 client.add_success_response_with_body( 1001 reference_format.get_format_string(), b'ok') 1002 # PackRepository wants to do a stat 1003 client.add_success_response(b'stat', b'0', b'65535') 1004 remote_transport = RemoteTransport(server_url + 'quack/', medium=False, 1005 _client=client) 1006 bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(), 1007 _client=client) 1008 repo = bzrdir.open_repository() 1009 self.assertEqual( 1010 [('call', b'BzrDir.find_repositoryV3', (b'quack/',)), 1011 ('call', b'BzrDir.find_repositoryV2', (b'quack/',)), 1012 ('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)), 1013 ('call', b'stat', (b'/quack/.bzr',)), 1014 ('call_expecting_body', b'get', 1015 (b'/quack/.bzr/repository/format',)), 1016 ('call', b'stat', (b'/quack/.bzr/repository',)), 1017 ], 1018 client._calls) 1019 self.assertEqual(network_name, repo._format.network_name()) 1020 1021 def test_current_server(self): 1022 reference_format = self.get_repo_format() 1023 network_name = reference_format.network_name() 1024 transport = MemoryTransport() 1025 transport.mkdir('quack') 1026 transport = transport.clone('quack') 1027 client = FakeClient(transport.base) 1028 client.add_success_response( 1029 b'ok', b'', b'no', b'no', b'no', network_name) 1030 bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 1031 _client=client) 1032 repo = bzrdir.open_repository() 1033 self.assertEqual( 1034 [('call', b'BzrDir.find_repositoryV3', (b'quack/',))], 1035 client._calls) 1036 self.assertEqual(network_name, repo._format.network_name()) 1037 1038 1039class TestBzrDirFormatInitializeEx(TestRemote): 1040 1041 def test_success(self): 1042 """Simple test for typical successful call.""" 1043 fmt = RemoteBzrDirFormat() 1044 default_format_name = BzrDirFormat.get_default_format().network_name() 1045 transport = self.get_transport() 1046 client = FakeClient(transport.base) 1047 client.add_expected_call( 1048 b'BzrDirFormat.initialize_ex_1.16', 1049 (default_format_name, b'path', b'False', b'False', b'False', b'', 1050 b'', b'', b'', b'False'), 1051 b'success', 1052 (b'.', b'no', b'no', b'yes', b'repo fmt', b'repo bzrdir fmt', 1053 b'bzrdir fmt', b'False', b'', b'', b'repo lock token')) 1054 # XXX: It would be better to call fmt.initialize_on_transport_ex, but 1055 # it's currently hard to test that without supplying a real remote 1056 # transport connected to a real server. 1057 fmt._initialize_on_transport_ex_rpc( 1058 client, b'path', transport, False, False, False, None, None, None, 1059 None, False) 1060 self.assertFinished(client) 1061 1062 def test_error(self): 1063 """Error responses are translated, e.g. 'PermissionDenied' raises the 1064 corresponding error from the client. 1065 """ 1066 fmt = RemoteBzrDirFormat() 1067 default_format_name = BzrDirFormat.get_default_format().network_name() 1068 transport = self.get_transport() 1069 client = FakeClient(transport.base) 1070 client.add_expected_call( 1071 b'BzrDirFormat.initialize_ex_1.16', 1072 (default_format_name, b'path', b'False', b'False', b'False', b'', 1073 b'', b'', b'', b'False'), 1074 b'error', 1075 (b'PermissionDenied', b'path', b'extra info')) 1076 # XXX: It would be better to call fmt.initialize_on_transport_ex, but 1077 # it's currently hard to test that without supplying a real remote 1078 # transport connected to a real server. 1079 err = self.assertRaises( 1080 errors.PermissionDenied, 1081 fmt._initialize_on_transport_ex_rpc, client, b'path', transport, 1082 False, False, False, None, None, None, None, False) 1083 self.assertEqual('path', err.path) 1084 self.assertEqual(': extra info', err.extra) 1085 self.assertFinished(client) 1086 1087 def test_error_from_real_server(self): 1088 """Integration test for error translation.""" 1089 transport = self.make_smart_server('foo') 1090 transport = transport.clone('no-such-path') 1091 fmt = RemoteBzrDirFormat() 1092 self.assertRaises( 1093 errors.NoSuchFile, fmt.initialize_on_transport_ex, transport, 1094 create_prefix=False) 1095 1096 1097class OldSmartClient(object): 1098 """A fake smart client for test_old_version that just returns a version one 1099 response to the 'hello' (query version) command. 1100 """ 1101 1102 def get_request(self): 1103 input_file = BytesIO(b'ok\x011\n') 1104 output_file = BytesIO() 1105 client_medium = medium.SmartSimplePipesClientMedium( 1106 input_file, output_file) 1107 return medium.SmartClientStreamMediumRequest(client_medium) 1108 1109 def protocol_version(self): 1110 return 1 1111 1112 1113class OldServerTransport(object): 1114 """A fake transport for test_old_server that reports it's smart server 1115 protocol version as version one. 1116 """ 1117 1118 def __init__(self): 1119 self.base = 'fake:' 1120 1121 def get_smart_client(self): 1122 return OldSmartClient() 1123 1124 1125class RemoteBzrDirTestCase(TestRemote): 1126 1127 def make_remote_bzrdir(self, transport, client): 1128 """Make a RemotebzrDir using 'client' as the _client.""" 1129 return RemoteBzrDir(transport, RemoteBzrDirFormat(), 1130 _client=client) 1131 1132 1133class RemoteBranchTestCase(RemoteBzrDirTestCase): 1134 1135 def lock_remote_branch(self, branch): 1136 """Trick a RemoteBranch into thinking it is locked.""" 1137 branch._lock_mode = 'w' 1138 branch._lock_count = 2 1139 branch._lock_token = b'branch token' 1140 branch._repo_lock_token = b'repo token' 1141 branch.repository._lock_mode = 'w' 1142 branch.repository._lock_count = 2 1143 branch.repository._lock_token = b'repo token' 1144 1145 def make_remote_branch(self, transport, client): 1146 """Make a RemoteBranch using 'client' as its _SmartClient. 1147 1148 A RemoteBzrDir and RemoteRepository will also be created to fill out 1149 the RemoteBranch, albeit with stub values for some of their attributes. 1150 """ 1151 # we do not want bzrdir to make any remote calls, so use False as its 1152 # _client. If it tries to make a remote call, this will fail 1153 # immediately. 1154 bzrdir = self.make_remote_bzrdir(transport, False) 1155 repo = RemoteRepository(bzrdir, None, _client=client) 1156 branch_format = self.get_branch_format() 1157 format = RemoteBranchFormat(network_name=branch_format.network_name()) 1158 return RemoteBranch(bzrdir, repo, _client=client, format=format) 1159 1160 1161class TestBranchBreakLock(RemoteBranchTestCase): 1162 1163 def test_break_lock(self): 1164 transport = MemoryTransport() 1165 client = FakeClient(transport.base) 1166 client.add_expected_call( 1167 b'Branch.get_stacked_on_url', (b'quack/',), 1168 b'error', (b'NotStacked',)) 1169 client.add_expected_call( 1170 b'Branch.break_lock', (b'quack/',), 1171 b'success', (b'ok',)) 1172 transport.mkdir('quack') 1173 transport = transport.clone('quack') 1174 branch = self.make_remote_branch(transport, client) 1175 branch.break_lock() 1176 self.assertFinished(client) 1177 1178 1179class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase): 1180 1181 def test_get_physical_lock_status_yes(self): 1182 transport = MemoryTransport() 1183 client = FakeClient(transport.base) 1184 client.add_expected_call( 1185 b'Branch.get_stacked_on_url', (b'quack/',), 1186 b'error', (b'NotStacked',)) 1187 client.add_expected_call( 1188 b'Branch.get_physical_lock_status', (b'quack/',), 1189 b'success', (b'yes',)) 1190 transport.mkdir('quack') 1191 transport = transport.clone('quack') 1192 branch = self.make_remote_branch(transport, client) 1193 result = branch.get_physical_lock_status() 1194 self.assertFinished(client) 1195 self.assertEqual(True, result) 1196 1197 def test_get_physical_lock_status_no(self): 1198 transport = MemoryTransport() 1199 client = FakeClient(transport.base) 1200 client.add_expected_call( 1201 b'Branch.get_stacked_on_url', (b'quack/',), 1202 b'error', (b'NotStacked',)) 1203 client.add_expected_call( 1204 b'Branch.get_physical_lock_status', (b'quack/',), 1205 b'success', (b'no',)) 1206 transport.mkdir('quack') 1207 transport = transport.clone('quack') 1208 branch = self.make_remote_branch(transport, client) 1209 result = branch.get_physical_lock_status() 1210 self.assertFinished(client) 1211 self.assertEqual(False, result) 1212 1213 1214class TestBranchGetParent(RemoteBranchTestCase): 1215 1216 def test_no_parent(self): 1217 # in an empty branch we decode the response properly 1218 transport = MemoryTransport() 1219 client = FakeClient(transport.base) 1220 client.add_expected_call( 1221 b'Branch.get_stacked_on_url', (b'quack/',), 1222 b'error', (b'NotStacked',)) 1223 client.add_expected_call( 1224 b'Branch.get_parent', (b'quack/',), 1225 b'success', (b'',)) 1226 transport.mkdir('quack') 1227 transport = transport.clone('quack') 1228 branch = self.make_remote_branch(transport, client) 1229 result = branch.get_parent() 1230 self.assertFinished(client) 1231 self.assertEqual(None, result) 1232 1233 def test_parent_relative(self): 1234 transport = MemoryTransport() 1235 client = FakeClient(transport.base) 1236 client.add_expected_call( 1237 b'Branch.get_stacked_on_url', (b'kwaak/',), 1238 b'error', (b'NotStacked',)) 1239 client.add_expected_call( 1240 b'Branch.get_parent', (b'kwaak/',), 1241 b'success', (b'../foo/',)) 1242 transport.mkdir('kwaak') 1243 transport = transport.clone('kwaak') 1244 branch = self.make_remote_branch(transport, client) 1245 result = branch.get_parent() 1246 self.assertEqual(transport.clone('../foo').base, result) 1247 1248 def test_parent_absolute(self): 1249 transport = MemoryTransport() 1250 client = FakeClient(transport.base) 1251 client.add_expected_call( 1252 b'Branch.get_stacked_on_url', (b'kwaak/',), 1253 b'error', (b'NotStacked',)) 1254 client.add_expected_call( 1255 b'Branch.get_parent', (b'kwaak/',), 1256 b'success', (b'http://foo/',)) 1257 transport.mkdir('kwaak') 1258 transport = transport.clone('kwaak') 1259 branch = self.make_remote_branch(transport, client) 1260 result = branch.get_parent() 1261 self.assertEqual('http://foo/', result) 1262 self.assertFinished(client) 1263 1264 1265class TestBranchSetParentLocation(RemoteBranchTestCase): 1266 1267 def test_no_parent(self): 1268 # We call the verb when setting parent to None 1269 transport = MemoryTransport() 1270 client = FakeClient(transport.base) 1271 client.add_expected_call( 1272 b'Branch.get_stacked_on_url', (b'quack/',), 1273 b'error', (b'NotStacked',)) 1274 client.add_expected_call( 1275 b'Branch.set_parent_location', (b'quack/', b'b', b'r', b''), 1276 b'success', ()) 1277 transport.mkdir('quack') 1278 transport = transport.clone('quack') 1279 branch = self.make_remote_branch(transport, client) 1280 branch._lock_token = b'b' 1281 branch._repo_lock_token = b'r' 1282 branch._set_parent_location(None) 1283 self.assertFinished(client) 1284 1285 def test_parent(self): 1286 transport = MemoryTransport() 1287 client = FakeClient(transport.base) 1288 client.add_expected_call( 1289 b'Branch.get_stacked_on_url', (b'kwaak/',), 1290 b'error', (b'NotStacked',)) 1291 client.add_expected_call( 1292 b'Branch.set_parent_location', (b'kwaak/', b'b', b'r', b'foo'), 1293 b'success', ()) 1294 transport.mkdir('kwaak') 1295 transport = transport.clone('kwaak') 1296 branch = self.make_remote_branch(transport, client) 1297 branch._lock_token = b'b' 1298 branch._repo_lock_token = b'r' 1299 branch._set_parent_location('foo') 1300 self.assertFinished(client) 1301 1302 def test_backwards_compat(self): 1303 self.setup_smart_server_with_call_log() 1304 branch = self.make_branch('.') 1305 self.reset_smart_call_log() 1306 verb = b'Branch.set_parent_location' 1307 self.disable_verb(verb) 1308 branch.set_parent('http://foo/') 1309 self.assertLength(14, self.hpss_calls) 1310 1311 1312class TestBranchGetTagsBytes(RemoteBranchTestCase): 1313 1314 def test_backwards_compat(self): 1315 self.setup_smart_server_with_call_log() 1316 branch = self.make_branch('.') 1317 self.reset_smart_call_log() 1318 verb = b'Branch.get_tags_bytes' 1319 self.disable_verb(verb) 1320 branch.tags.get_tag_dict() 1321 call_count = len([call for call in self.hpss_calls if 1322 call.call.method == verb]) 1323 self.assertEqual(1, call_count) 1324 1325 def test_trivial(self): 1326 transport = MemoryTransport() 1327 client = FakeClient(transport.base) 1328 client.add_expected_call( 1329 b'Branch.get_stacked_on_url', (b'quack/',), 1330 b'error', (b'NotStacked',)) 1331 client.add_expected_call( 1332 b'Branch.get_tags_bytes', (b'quack/',), 1333 b'success', (b'',)) 1334 transport.mkdir('quack') 1335 transport = transport.clone('quack') 1336 branch = self.make_remote_branch(transport, client) 1337 result = branch.tags.get_tag_dict() 1338 self.assertFinished(client) 1339 self.assertEqual({}, result) 1340 1341 1342class TestBranchSetTagsBytes(RemoteBranchTestCase): 1343 1344 def test_trivial(self): 1345 transport = MemoryTransport() 1346 client = FakeClient(transport.base) 1347 client.add_expected_call( 1348 b'Branch.get_stacked_on_url', (b'quack/',), 1349 b'error', (b'NotStacked',)) 1350 client.add_expected_call( 1351 b'Branch.set_tags_bytes', (b'quack/', 1352 b'branch token', b'repo token'), 1353 b'success', ('',)) 1354 transport.mkdir('quack') 1355 transport = transport.clone('quack') 1356 branch = self.make_remote_branch(transport, client) 1357 self.lock_remote_branch(branch) 1358 branch._set_tags_bytes(b'tags bytes') 1359 self.assertFinished(client) 1360 self.assertEqual(b'tags bytes', client._calls[-1][-1]) 1361 1362 def test_backwards_compatible(self): 1363 transport = MemoryTransport() 1364 client = FakeClient(transport.base) 1365 client.add_expected_call( 1366 b'Branch.get_stacked_on_url', (b'quack/',), 1367 b'error', (b'NotStacked',)) 1368 client.add_expected_call( 1369 b'Branch.set_tags_bytes', (b'quack/', 1370 b'branch token', b'repo token'), 1371 b'unknown', (b'Branch.set_tags_bytes',)) 1372 transport.mkdir('quack') 1373 transport = transport.clone('quack') 1374 branch = self.make_remote_branch(transport, client) 1375 self.lock_remote_branch(branch) 1376 1377 class StubRealBranch(object): 1378 def __init__(self): 1379 self.calls = [] 1380 1381 def _set_tags_bytes(self, bytes): 1382 self.calls.append(('set_tags_bytes', bytes)) 1383 real_branch = StubRealBranch() 1384 branch._real_branch = real_branch 1385 branch._set_tags_bytes(b'tags bytes') 1386 # Call a second time, to exercise the 'remote version already inferred' 1387 # code path. 1388 branch._set_tags_bytes(b'tags bytes') 1389 self.assertFinished(client) 1390 self.assertEqual( 1391 [('set_tags_bytes', b'tags bytes')] * 2, real_branch.calls) 1392 1393 1394class TestBranchHeadsToFetch(RemoteBranchTestCase): 1395 1396 def test_uses_last_revision_info_and_tags_by_default(self): 1397 transport = MemoryTransport() 1398 client = FakeClient(transport.base) 1399 client.add_expected_call( 1400 b'Branch.get_stacked_on_url', (b'quack/',), 1401 b'error', (b'NotStacked',)) 1402 client.add_expected_call( 1403 b'Branch.last_revision_info', (b'quack/',), 1404 b'success', (b'ok', b'1', b'rev-tip')) 1405 client.add_expected_call( 1406 b'Branch.get_config_file', (b'quack/',), 1407 b'success', (b'ok',), b'') 1408 transport.mkdir('quack') 1409 transport = transport.clone('quack') 1410 branch = self.make_remote_branch(transport, client) 1411 result = branch.heads_to_fetch() 1412 self.assertFinished(client) 1413 self.assertEqual(({b'rev-tip'}, set()), result) 1414 1415 def test_uses_last_revision_info_and_tags_when_set(self): 1416 transport = MemoryTransport() 1417 client = FakeClient(transport.base) 1418 client.add_expected_call( 1419 b'Branch.get_stacked_on_url', (b'quack/',), 1420 b'error', (b'NotStacked',)) 1421 client.add_expected_call( 1422 b'Branch.last_revision_info', (b'quack/',), 1423 b'success', (b'ok', b'1', b'rev-tip')) 1424 client.add_expected_call( 1425 b'Branch.get_config_file', (b'quack/',), 1426 b'success', (b'ok',), b'branch.fetch_tags = True') 1427 # XXX: this will break if the default format's serialization of tags 1428 # changes, or if the RPC for fetching tags changes from get_tags_bytes. 1429 client.add_expected_call( 1430 b'Branch.get_tags_bytes', (b'quack/',), 1431 b'success', (b'd5:tag-17:rev-foo5:tag-27:rev-bare',)) 1432 transport.mkdir('quack') 1433 transport = transport.clone('quack') 1434 branch = self.make_remote_branch(transport, client) 1435 result = branch.heads_to_fetch() 1436 self.assertFinished(client) 1437 self.assertEqual( 1438 ({b'rev-tip'}, {b'rev-foo', b'rev-bar'}), result) 1439 1440 def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self): 1441 transport = MemoryTransport() 1442 client = FakeClient(transport.base) 1443 client.add_expected_call( 1444 b'Branch.get_stacked_on_url', (b'quack/',), 1445 b'error', (b'NotStacked',)) 1446 client.add_expected_call( 1447 b'Branch.heads_to_fetch', (b'quack/',), 1448 b'success', ([b'tip'], [b'tagged-1', b'tagged-2'])) 1449 transport.mkdir('quack') 1450 transport = transport.clone('quack') 1451 branch = self.make_remote_branch(transport, client) 1452 branch._format._use_default_local_heads_to_fetch = lambda: False 1453 result = branch.heads_to_fetch() 1454 self.assertFinished(client) 1455 self.assertEqual(({b'tip'}, {b'tagged-1', b'tagged-2'}), result) 1456 1457 def make_branch_with_tags(self): 1458 self.setup_smart_server_with_call_log() 1459 # Make a branch with a single revision. 1460 builder = self.make_branch_builder('foo') 1461 builder.start_series() 1462 builder.build_snapshot(None, [ 1463 ('add', ('', b'root-id', 'directory', ''))], 1464 revision_id=b'tip') 1465 builder.finish_series() 1466 branch = builder.get_branch() 1467 # Add two tags to that branch 1468 branch.tags.set_tag('tag-1', b'rev-1') 1469 branch.tags.set_tag('tag-2', b'rev-2') 1470 return branch 1471 1472 def test_backwards_compatible(self): 1473 br = self.make_branch_with_tags() 1474 br.get_config_stack().set('branch.fetch_tags', True) 1475 self.addCleanup(br.lock_read().unlock) 1476 # Disable the heads_to_fetch verb 1477 verb = b'Branch.heads_to_fetch' 1478 self.disable_verb(verb) 1479 self.reset_smart_call_log() 1480 result = br.heads_to_fetch() 1481 self.assertEqual(({b'tip'}, {b'rev-1', b'rev-2'}), result) 1482 self.assertEqual( 1483 [b'Branch.last_revision_info', b'Branch.get_tags_bytes'], 1484 [call.call.method for call in self.hpss_calls]) 1485 1486 def test_backwards_compatible_no_tags(self): 1487 br = self.make_branch_with_tags() 1488 br.get_config_stack().set('branch.fetch_tags', False) 1489 self.addCleanup(br.lock_read().unlock) 1490 # Disable the heads_to_fetch verb 1491 verb = b'Branch.heads_to_fetch' 1492 self.disable_verb(verb) 1493 self.reset_smart_call_log() 1494 result = br.heads_to_fetch() 1495 self.assertEqual(({b'tip'}, set()), result) 1496 self.assertEqual( 1497 [b'Branch.last_revision_info'], 1498 [call.call.method for call in self.hpss_calls]) 1499 1500 1501class TestBranchLastRevisionInfo(RemoteBranchTestCase): 1502 1503 def test_empty_branch(self): 1504 # in an empty branch we decode the response properly 1505 transport = MemoryTransport() 1506 client = FakeClient(transport.base) 1507 client.add_expected_call( 1508 b'Branch.get_stacked_on_url', (b'quack/',), 1509 b'error', (b'NotStacked',)) 1510 client.add_expected_call( 1511 b'Branch.last_revision_info', (b'quack/',), 1512 b'success', (b'ok', b'0', b'null:')) 1513 transport.mkdir('quack') 1514 transport = transport.clone('quack') 1515 branch = self.make_remote_branch(transport, client) 1516 result = branch.last_revision_info() 1517 self.assertFinished(client) 1518 self.assertEqual((0, NULL_REVISION), result) 1519 1520 def test_non_empty_branch(self): 1521 # in a non-empty branch we also decode the response properly 1522 revid = u'\xc8'.encode('utf8') 1523 transport = MemoryTransport() 1524 client = FakeClient(transport.base) 1525 client.add_expected_call( 1526 b'Branch.get_stacked_on_url', (b'kwaak/',), 1527 b'error', (b'NotStacked',)) 1528 client.add_expected_call( 1529 b'Branch.last_revision_info', (b'kwaak/',), 1530 b'success', (b'ok', b'2', revid)) 1531 transport.mkdir('kwaak') 1532 transport = transport.clone('kwaak') 1533 branch = self.make_remote_branch(transport, client) 1534 result = branch.last_revision_info() 1535 self.assertEqual((2, revid), result) 1536 1537 1538class TestBranch_get_stacked_on_url(TestRemote): 1539 """Test Branch._get_stacked_on_url rpc""" 1540 1541 def test_get_stacked_on_invalid_url(self): 1542 # test that asking for a stacked on url the server can't access works. 1543 # This isn't perfect, but then as we're in the same process there 1544 # really isn't anything we can do to be 100% sure that the server 1545 # doesn't just open in - this test probably needs to be rewritten using 1546 # a spawn()ed server. 1547 stacked_branch = self.make_branch('stacked', format='1.9') 1548 self.make_branch('base', format='1.9') 1549 vfs_url = self.get_vfs_only_url('base') 1550 stacked_branch.set_stacked_on_url(vfs_url) 1551 transport = stacked_branch.controldir.root_transport 1552 client = FakeClient(transport.base) 1553 client.add_expected_call( 1554 b'Branch.get_stacked_on_url', (b'stacked/',), 1555 b'success', (b'ok', vfs_url.encode('utf-8'))) 1556 # XXX: Multiple calls are bad, this second call documents what is 1557 # today. 1558 client.add_expected_call( 1559 b'Branch.get_stacked_on_url', (b'stacked/',), 1560 b'success', (b'ok', vfs_url.encode('utf-8'))) 1561 bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 1562 _client=client) 1563 repo_fmt = remote.RemoteRepositoryFormat() 1564 repo_fmt._custom_format = stacked_branch.repository._format 1565 branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt), 1566 _client=client) 1567 result = branch.get_stacked_on_url() 1568 self.assertEqual(vfs_url, result) 1569 1570 def test_backwards_compatible(self): 1571 # like with bzr1.6 with no Branch.get_stacked_on_url rpc 1572 self.make_branch('base', format='1.6') 1573 stacked_branch = self.make_branch('stacked', format='1.6') 1574 stacked_branch.set_stacked_on_url('../base') 1575 client = FakeClient(self.get_url()) 1576 branch_network_name = self.get_branch_format().network_name() 1577 client.add_expected_call( 1578 b'BzrDir.open_branchV3', (b'stacked/',), 1579 b'success', (b'branch', branch_network_name)) 1580 client.add_expected_call( 1581 b'BzrDir.find_repositoryV3', (b'stacked/',), 1582 b'success', (b'ok', b'', b'no', b'no', b'yes', 1583 stacked_branch.repository._format.network_name())) 1584 # called twice, once from constructor and then again by us 1585 client.add_expected_call( 1586 b'Branch.get_stacked_on_url', (b'stacked/',), 1587 b'unknown', (b'Branch.get_stacked_on_url',)) 1588 client.add_expected_call( 1589 b'Branch.get_stacked_on_url', (b'stacked/',), 1590 b'unknown', (b'Branch.get_stacked_on_url',)) 1591 # this will also do vfs access, but that goes direct to the transport 1592 # and isn't seen by the FakeClient. 1593 bzrdir = RemoteBzrDir(self.get_transport('stacked'), 1594 RemoteBzrDirFormat(), _client=client) 1595 branch = bzrdir.open_branch() 1596 result = branch.get_stacked_on_url() 1597 self.assertEqual('../base', result) 1598 self.assertFinished(client) 1599 # it's in the fallback list both for the RemoteRepository and its vfs 1600 # repository 1601 self.assertEqual(1, len(branch.repository._fallback_repositories)) 1602 self.assertEqual(1, 1603 len(branch.repository._real_repository._fallback_repositories)) 1604 1605 def test_get_stacked_on_real_branch(self): 1606 self.make_branch('base') 1607 stacked_branch = self.make_branch('stacked') 1608 stacked_branch.set_stacked_on_url('../base') 1609 reference_format = self.get_repo_format() 1610 network_name = reference_format.network_name() 1611 client = FakeClient(self.get_url()) 1612 branch_network_name = self.get_branch_format().network_name() 1613 client.add_expected_call( 1614 b'BzrDir.open_branchV3', (b'stacked/',), 1615 b'success', (b'branch', branch_network_name)) 1616 client.add_expected_call( 1617 b'BzrDir.find_repositoryV3', (b'stacked/',), 1618 b'success', (b'ok', b'', b'yes', b'no', b'yes', network_name)) 1619 # called twice, once from constructor and then again by us 1620 client.add_expected_call( 1621 b'Branch.get_stacked_on_url', (b'stacked/',), 1622 b'success', (b'ok', b'../base')) 1623 client.add_expected_call( 1624 b'Branch.get_stacked_on_url', (b'stacked/',), 1625 b'success', (b'ok', b'../base')) 1626 bzrdir = RemoteBzrDir(self.get_transport('stacked'), 1627 RemoteBzrDirFormat(), _client=client) 1628 branch = bzrdir.open_branch() 1629 result = branch.get_stacked_on_url() 1630 self.assertEqual('../base', result) 1631 self.assertFinished(client) 1632 # it's in the fallback list both for the RemoteRepository. 1633 self.assertEqual(1, len(branch.repository._fallback_repositories)) 1634 # And we haven't had to construct a real repository. 1635 self.assertEqual(None, branch.repository._real_repository) 1636 1637 1638class TestBranchSetLastRevision(RemoteBranchTestCase): 1639 1640 def test_set_empty(self): 1641 # _set_last_revision_info('null:') is translated to calling 1642 # Branch.set_last_revision(path, '') on the wire. 1643 transport = MemoryTransport() 1644 transport.mkdir('branch') 1645 transport = transport.clone('branch') 1646 1647 client = FakeClient(transport.base) 1648 client.add_expected_call( 1649 b'Branch.get_stacked_on_url', (b'branch/',), 1650 b'error', (b'NotStacked',)) 1651 client.add_expected_call( 1652 b'Branch.lock_write', (b'branch/', b'', b''), 1653 b'success', (b'ok', b'branch token', b'repo token')) 1654 client.add_expected_call( 1655 b'Branch.last_revision_info', 1656 (b'branch/',), 1657 b'success', (b'ok', b'0', b'null:')) 1658 client.add_expected_call( 1659 b'Branch.set_last_revision', (b'branch/', 1660 b'branch token', b'repo token', b'null:',), 1661 b'success', (b'ok',)) 1662 client.add_expected_call( 1663 b'Branch.unlock', (b'branch/', b'branch token', b'repo token'), 1664 b'success', (b'ok',)) 1665 branch = self.make_remote_branch(transport, client) 1666 branch.lock_write() 1667 result = branch._set_last_revision(NULL_REVISION) 1668 branch.unlock() 1669 self.assertEqual(None, result) 1670 self.assertFinished(client) 1671 1672 def test_set_nonempty(self): 1673 # set_last_revision_info(N, rev-idN) is translated to calling 1674 # Branch.set_last_revision(path, rev-idN) on the wire. 1675 transport = MemoryTransport() 1676 transport.mkdir('branch') 1677 transport = transport.clone('branch') 1678 1679 client = FakeClient(transport.base) 1680 client.add_expected_call( 1681 b'Branch.get_stacked_on_url', (b'branch/',), 1682 b'error', (b'NotStacked',)) 1683 client.add_expected_call( 1684 b'Branch.lock_write', (b'branch/', b'', b''), 1685 b'success', (b'ok', b'branch token', b'repo token')) 1686 client.add_expected_call( 1687 b'Branch.last_revision_info', 1688 (b'branch/',), 1689 b'success', (b'ok', b'0', b'null:')) 1690 lines = [b'rev-id2'] 1691 encoded_body = bz2.compress(b'\n'.join(lines)) 1692 client.add_success_response_with_body(encoded_body, b'ok') 1693 client.add_expected_call( 1694 b'Branch.set_last_revision', (b'branch/', 1695 b'branch token', b'repo token', b'rev-id2',), 1696 b'success', (b'ok',)) 1697 client.add_expected_call( 1698 b'Branch.unlock', (b'branch/', b'branch token', b'repo token'), 1699 b'success', (b'ok',)) 1700 branch = self.make_remote_branch(transport, client) 1701 # Lock the branch, reset the record of remote calls. 1702 branch.lock_write() 1703 result = branch._set_last_revision(b'rev-id2') 1704 branch.unlock() 1705 self.assertEqual(None, result) 1706 self.assertFinished(client) 1707 1708 def test_no_such_revision(self): 1709 transport = MemoryTransport() 1710 transport.mkdir('branch') 1711 transport = transport.clone('branch') 1712 # A response of 'NoSuchRevision' is translated into an exception. 1713 client = FakeClient(transport.base) 1714 client.add_expected_call( 1715 b'Branch.get_stacked_on_url', (b'branch/',), 1716 b'error', (b'NotStacked',)) 1717 client.add_expected_call( 1718 b'Branch.lock_write', (b'branch/', b'', b''), 1719 b'success', (b'ok', b'branch token', b'repo token')) 1720 client.add_expected_call( 1721 b'Branch.last_revision_info', 1722 (b'branch/',), 1723 b'success', (b'ok', b'0', b'null:')) 1724 # get_graph calls to construct the revision history, for the set_rh 1725 # hook 1726 lines = [b'rev-id'] 1727 encoded_body = bz2.compress(b'\n'.join(lines)) 1728 client.add_success_response_with_body(encoded_body, b'ok') 1729 client.add_expected_call( 1730 b'Branch.set_last_revision', (b'branch/', 1731 b'branch token', b'repo token', b'rev-id',), 1732 b'error', (b'NoSuchRevision', b'rev-id')) 1733 client.add_expected_call( 1734 b'Branch.unlock', (b'branch/', b'branch token', b'repo token'), 1735 b'success', (b'ok',)) 1736 1737 branch = self.make_remote_branch(transport, client) 1738 branch.lock_write() 1739 self.assertRaises( 1740 errors.NoSuchRevision, branch._set_last_revision, b'rev-id') 1741 branch.unlock() 1742 self.assertFinished(client) 1743 1744 def test_tip_change_rejected(self): 1745 """TipChangeRejected responses cause a TipChangeRejected exception to 1746 be raised. 1747 """ 1748 transport = MemoryTransport() 1749 transport.mkdir('branch') 1750 transport = transport.clone('branch') 1751 client = FakeClient(transport.base) 1752 rejection_msg_unicode = u'rejection message\N{INTERROBANG}' 1753 rejection_msg_utf8 = rejection_msg_unicode.encode('utf8') 1754 client.add_expected_call( 1755 b'Branch.get_stacked_on_url', (b'branch/',), 1756 b'error', (b'NotStacked',)) 1757 client.add_expected_call( 1758 b'Branch.lock_write', (b'branch/', b'', b''), 1759 b'success', (b'ok', b'branch token', b'repo token')) 1760 client.add_expected_call( 1761 b'Branch.last_revision_info', 1762 (b'branch/',), 1763 b'success', (b'ok', b'0', b'null:')) 1764 lines = [b'rev-id'] 1765 encoded_body = bz2.compress(b'\n'.join(lines)) 1766 client.add_success_response_with_body(encoded_body, b'ok') 1767 client.add_expected_call( 1768 b'Branch.set_last_revision', (b'branch/', 1769 b'branch token', b'repo token', b'rev-id',), 1770 b'error', (b'TipChangeRejected', rejection_msg_utf8)) 1771 client.add_expected_call( 1772 b'Branch.unlock', (b'branch/', b'branch token', b'repo token'), 1773 b'success', (b'ok',)) 1774 branch = self.make_remote_branch(transport, client) 1775 branch.lock_write() 1776 # The 'TipChangeRejected' error response triggered by calling 1777 # set_last_revision_info causes a TipChangeRejected exception. 1778 err = self.assertRaises( 1779 errors.TipChangeRejected, 1780 branch._set_last_revision, b'rev-id') 1781 # The UTF-8 message from the response has been decoded into a unicode 1782 # object. 1783 self.assertIsInstance(err.msg, str) 1784 self.assertEqual(rejection_msg_unicode, err.msg) 1785 branch.unlock() 1786 self.assertFinished(client) 1787 1788 1789class TestBranchSetLastRevisionInfo(RemoteBranchTestCase): 1790 1791 def test_set_last_revision_info(self): 1792 # set_last_revision_info(num, b'rev-id') is translated to calling 1793 # Branch.set_last_revision_info(num, 'rev-id') on the wire. 1794 transport = MemoryTransport() 1795 transport.mkdir('branch') 1796 transport = transport.clone('branch') 1797 client = FakeClient(transport.base) 1798 # get_stacked_on_url 1799 client.add_error_response(b'NotStacked') 1800 # lock_write 1801 client.add_success_response(b'ok', b'branch token', b'repo token') 1802 # query the current revision 1803 client.add_success_response(b'ok', b'0', b'null:') 1804 # set_last_revision 1805 client.add_success_response(b'ok') 1806 # unlock 1807 client.add_success_response(b'ok') 1808 1809 branch = self.make_remote_branch(transport, client) 1810 # Lock the branch, reset the record of remote calls. 1811 branch.lock_write() 1812 client._calls = [] 1813 result = branch.set_last_revision_info(1234, b'a-revision-id') 1814 self.assertEqual( 1815 [('call', b'Branch.last_revision_info', (b'branch/',)), 1816 ('call', b'Branch.set_last_revision_info', 1817 (b'branch/', b'branch token', b'repo token', 1818 b'1234', b'a-revision-id'))], 1819 client._calls) 1820 self.assertEqual(None, result) 1821 1822 def test_no_such_revision(self): 1823 # A response of 'NoSuchRevision' is translated into an exception. 1824 transport = MemoryTransport() 1825 transport.mkdir('branch') 1826 transport = transport.clone('branch') 1827 client = FakeClient(transport.base) 1828 # get_stacked_on_url 1829 client.add_error_response(b'NotStacked') 1830 # lock_write 1831 client.add_success_response(b'ok', b'branch token', b'repo token') 1832 # set_last_revision 1833 client.add_error_response(b'NoSuchRevision', b'revid') 1834 # unlock 1835 client.add_success_response(b'ok') 1836 1837 branch = self.make_remote_branch(transport, client) 1838 # Lock the branch, reset the record of remote calls. 1839 branch.lock_write() 1840 client._calls = [] 1841 1842 self.assertRaises( 1843 errors.NoSuchRevision, branch.set_last_revision_info, 123, b'revid') 1844 branch.unlock() 1845 1846 def test_backwards_compatibility(self): 1847 """If the server does not support the Branch.set_last_revision_info 1848 verb (which is new in 1.4), then the client falls back to VFS methods. 1849 """ 1850 # This test is a little messy. Unlike most tests in this file, it 1851 # doesn't purely test what a Remote* object sends over the wire, and 1852 # how it reacts to responses from the wire. It instead relies partly 1853 # on asserting that the RemoteBranch will call 1854 # self._real_branch.set_last_revision_info(...). 1855 1856 # First, set up our RemoteBranch with a FakeClient that raises 1857 # UnknownSmartMethod, and a StubRealBranch that logs how it is called. 1858 transport = MemoryTransport() 1859 transport.mkdir('branch') 1860 transport = transport.clone('branch') 1861 client = FakeClient(transport.base) 1862 client.add_expected_call( 1863 b'Branch.get_stacked_on_url', (b'branch/',), 1864 b'error', (b'NotStacked',)) 1865 client.add_expected_call( 1866 b'Branch.last_revision_info', 1867 (b'branch/',), 1868 b'success', (b'ok', b'0', b'null:')) 1869 client.add_expected_call( 1870 b'Branch.set_last_revision_info', 1871 (b'branch/', b'branch token', b'repo token', b'1234', b'a-revision-id',), 1872 b'unknown', b'Branch.set_last_revision_info') 1873 1874 branch = self.make_remote_branch(transport, client) 1875 1876 class StubRealBranch(object): 1877 def __init__(self): 1878 self.calls = [] 1879 1880 def set_last_revision_info(self, revno, revision_id): 1881 self.calls.append( 1882 ('set_last_revision_info', revno, revision_id)) 1883 1884 def _clear_cached_state(self): 1885 pass 1886 real_branch = StubRealBranch() 1887 branch._real_branch = real_branch 1888 self.lock_remote_branch(branch) 1889 1890 # Call set_last_revision_info, and verify it behaved as expected. 1891 branch.set_last_revision_info(1234, b'a-revision-id') 1892 self.assertEqual( 1893 [('set_last_revision_info', 1234, b'a-revision-id')], 1894 real_branch.calls) 1895 self.assertFinished(client) 1896 1897 def test_unexpected_error(self): 1898 # If the server sends an error the client doesn't understand, it gets 1899 # turned into an UnknownErrorFromSmartServer, which is presented as a 1900 # non-internal error to the user. 1901 transport = MemoryTransport() 1902 transport.mkdir('branch') 1903 transport = transport.clone('branch') 1904 client = FakeClient(transport.base) 1905 # get_stacked_on_url 1906 client.add_error_response(b'NotStacked') 1907 # lock_write 1908 client.add_success_response(b'ok', b'branch token', b'repo token') 1909 # set_last_revision 1910 client.add_error_response(b'UnexpectedError') 1911 # unlock 1912 client.add_success_response(b'ok') 1913 1914 branch = self.make_remote_branch(transport, client) 1915 # Lock the branch, reset the record of remote calls. 1916 branch.lock_write() 1917 client._calls = [] 1918 1919 err = self.assertRaises( 1920 errors.UnknownErrorFromSmartServer, 1921 branch.set_last_revision_info, 123, b'revid') 1922 self.assertEqual((b'UnexpectedError',), err.error_tuple) 1923 branch.unlock() 1924 1925 def test_tip_change_rejected(self): 1926 """TipChangeRejected responses cause a TipChangeRejected exception to 1927 be raised. 1928 """ 1929 transport = MemoryTransport() 1930 transport.mkdir('branch') 1931 transport = transport.clone('branch') 1932 client = FakeClient(transport.base) 1933 # get_stacked_on_url 1934 client.add_error_response(b'NotStacked') 1935 # lock_write 1936 client.add_success_response(b'ok', b'branch token', b'repo token') 1937 # set_last_revision 1938 client.add_error_response(b'TipChangeRejected', b'rejection message') 1939 # unlock 1940 client.add_success_response(b'ok') 1941 1942 branch = self.make_remote_branch(transport, client) 1943 # Lock the branch, reset the record of remote calls. 1944 branch.lock_write() 1945 self.addCleanup(branch.unlock) 1946 client._calls = [] 1947 1948 # The 'TipChangeRejected' error response triggered by calling 1949 # set_last_revision_info causes a TipChangeRejected exception. 1950 err = self.assertRaises( 1951 errors.TipChangeRejected, 1952 branch.set_last_revision_info, 123, b'revid') 1953 self.assertEqual('rejection message', err.msg) 1954 1955 1956class TestBranchGetSetConfig(RemoteBranchTestCase): 1957 1958 def test_get_branch_conf(self): 1959 # in an empty branch we decode the response properly 1960 client = FakeClient() 1961 client.add_expected_call( 1962 b'Branch.get_stacked_on_url', (b'memory:///',), 1963 b'error', (b'NotStacked',),) 1964 client.add_success_response_with_body(b'# config file body', b'ok') 1965 transport = MemoryTransport() 1966 branch = self.make_remote_branch(transport, client) 1967 config = branch.get_config() 1968 config.has_explicit_nickname() 1969 self.assertEqual( 1970 [('call', b'Branch.get_stacked_on_url', (b'memory:///',)), 1971 ('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))], 1972 client._calls) 1973 1974 def test_get_multi_line_branch_conf(self): 1975 # Make sure that multiple-line branch.conf files are supported 1976 # 1977 # https://bugs.launchpad.net/bzr/+bug/354075 1978 client = FakeClient() 1979 client.add_expected_call( 1980 b'Branch.get_stacked_on_url', (b'memory:///',), 1981 b'error', (b'NotStacked',),) 1982 client.add_success_response_with_body(b'a = 1\nb = 2\nc = 3\n', b'ok') 1983 transport = MemoryTransport() 1984 branch = self.make_remote_branch(transport, client) 1985 config = branch.get_config() 1986 self.assertEqual(u'2', config.get_user_option('b')) 1987 1988 def test_set_option(self): 1989 client = FakeClient() 1990 client.add_expected_call( 1991 b'Branch.get_stacked_on_url', (b'memory:///',), 1992 b'error', (b'NotStacked',),) 1993 client.add_expected_call( 1994 b'Branch.lock_write', (b'memory:///', b'', b''), 1995 b'success', (b'ok', b'branch token', b'repo token')) 1996 client.add_expected_call( 1997 b'Branch.set_config_option', (b'memory:///', b'branch token', 1998 b'repo token', b'foo', b'bar', b''), 1999 b'success', ()) 2000 client.add_expected_call( 2001 b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'), 2002 b'success', (b'ok',)) 2003 transport = MemoryTransport() 2004 branch = self.make_remote_branch(transport, client) 2005 branch.lock_write() 2006 config = branch._get_config() 2007 config.set_option('foo', 'bar') 2008 branch.unlock() 2009 self.assertFinished(client) 2010 2011 def test_set_option_with_dict(self): 2012 client = FakeClient() 2013 client.add_expected_call( 2014 b'Branch.get_stacked_on_url', (b'memory:///',), 2015 b'error', (b'NotStacked',),) 2016 client.add_expected_call( 2017 b'Branch.lock_write', (b'memory:///', b'', b''), 2018 b'success', (b'ok', b'branch token', b'repo token')) 2019 encoded_dict_value = b'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde' 2020 client.add_expected_call( 2021 b'Branch.set_config_option_dict', (b'memory:///', b'branch token', 2022 b'repo token', encoded_dict_value, b'foo', b''), 2023 b'success', ()) 2024 client.add_expected_call( 2025 b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'), 2026 b'success', (b'ok',)) 2027 transport = MemoryTransport() 2028 branch = self.make_remote_branch(transport, client) 2029 branch.lock_write() 2030 config = branch._get_config() 2031 config.set_option( 2032 {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}, 2033 'foo') 2034 branch.unlock() 2035 self.assertFinished(client) 2036 2037 def test_set_option_with_bool(self): 2038 client = FakeClient() 2039 client.add_expected_call( 2040 b'Branch.get_stacked_on_url', (b'memory:///',), 2041 b'error', (b'NotStacked',),) 2042 client.add_expected_call( 2043 b'Branch.lock_write', (b'memory:///', b'', b''), 2044 b'success', (b'ok', b'branch token', b'repo token')) 2045 client.add_expected_call( 2046 b'Branch.set_config_option', (b'memory:///', b'branch token', 2047 b'repo token', b'True', b'foo', b''), 2048 b'success', ()) 2049 client.add_expected_call( 2050 b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'), 2051 b'success', (b'ok',)) 2052 transport = MemoryTransport() 2053 branch = self.make_remote_branch(transport, client) 2054 branch.lock_write() 2055 config = branch._get_config() 2056 config.set_option(True, 'foo') 2057 branch.unlock() 2058 self.assertFinished(client) 2059 2060 def test_backwards_compat_set_option(self): 2061 self.setup_smart_server_with_call_log() 2062 branch = self.make_branch('.') 2063 verb = b'Branch.set_config_option' 2064 self.disable_verb(verb) 2065 branch.lock_write() 2066 self.addCleanup(branch.unlock) 2067 self.reset_smart_call_log() 2068 branch._get_config().set_option('value', 'name') 2069 self.assertLength(11, self.hpss_calls) 2070 self.assertEqual('value', branch._get_config().get_option('name')) 2071 2072 def test_backwards_compat_set_option_with_dict(self): 2073 self.setup_smart_server_with_call_log() 2074 branch = self.make_branch('.') 2075 verb = b'Branch.set_config_option_dict' 2076 self.disable_verb(verb) 2077 branch.lock_write() 2078 self.addCleanup(branch.unlock) 2079 self.reset_smart_call_log() 2080 config = branch._get_config() 2081 value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'} 2082 config.set_option(value_dict, 'name') 2083 self.assertLength(11, self.hpss_calls) 2084 self.assertEqual(value_dict, branch._get_config().get_option('name')) 2085 2086 2087class TestBranchGetPutConfigStore(RemoteBranchTestCase): 2088 2089 def test_get_branch_conf(self): 2090 # in an empty branch we decode the response properly 2091 client = FakeClient() 2092 client.add_expected_call( 2093 b'Branch.get_stacked_on_url', (b'memory:///',), 2094 b'error', (b'NotStacked',),) 2095 client.add_success_response_with_body(b'# config file body', b'ok') 2096 transport = MemoryTransport() 2097 branch = self.make_remote_branch(transport, client) 2098 config = branch.get_config_stack() 2099 config.get("email") 2100 config.get("log_format") 2101 self.assertEqual( 2102 [('call', b'Branch.get_stacked_on_url', (b'memory:///',)), 2103 ('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))], 2104 client._calls) 2105 2106 def test_set_branch_conf(self): 2107 client = FakeClient() 2108 client.add_expected_call( 2109 b'Branch.get_stacked_on_url', (b'memory:///',), 2110 b'error', (b'NotStacked',),) 2111 client.add_expected_call( 2112 b'Branch.lock_write', (b'memory:///', b'', b''), 2113 b'success', (b'ok', b'branch token', b'repo token')) 2114 client.add_expected_call( 2115 b'Branch.get_config_file', (b'memory:///', ), 2116 b'success', (b'ok', ), b"# line 1\n") 2117 client.add_expected_call( 2118 b'Branch.get_config_file', (b'memory:///', ), 2119 b'success', (b'ok', ), b"# line 1\n") 2120 client.add_expected_call( 2121 b'Branch.put_config_file', (b'memory:///', b'branch token', 2122 b'repo token'), 2123 b'success', (b'ok',)) 2124 client.add_expected_call( 2125 b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'), 2126 b'success', (b'ok',)) 2127 transport = MemoryTransport() 2128 branch = self.make_remote_branch(transport, client) 2129 branch.lock_write() 2130 config = branch.get_config_stack() 2131 config.set('email', 'The Dude <lebowski@example.com>') 2132 branch.unlock() 2133 self.assertFinished(client) 2134 self.assertEqual( 2135 [('call', b'Branch.get_stacked_on_url', (b'memory:///',)), 2136 ('call', b'Branch.lock_write', (b'memory:///', b'', b'')), 2137 ('call_expecting_body', b'Branch.get_config_file', 2138 (b'memory:///',)), 2139 ('call_expecting_body', b'Branch.get_config_file', 2140 (b'memory:///',)), 2141 ('call_with_body_bytes_expecting_body', b'Branch.put_config_file', 2142 (b'memory:///', b'branch token', b'repo token'), 2143 b'# line 1\nemail = The Dude <lebowski@example.com>\n'), 2144 ('call', b'Branch.unlock', 2145 (b'memory:///', b'branch token', b'repo token'))], 2146 client._calls) 2147 2148 2149class TestBranchLockWrite(RemoteBranchTestCase): 2150 2151 def test_lock_write_unlockable(self): 2152 transport = MemoryTransport() 2153 client = FakeClient(transport.base) 2154 client.add_expected_call( 2155 b'Branch.get_stacked_on_url', (b'quack/',), 2156 b'error', (b'NotStacked',),) 2157 client.add_expected_call( 2158 b'Branch.lock_write', (b'quack/', b'', b''), 2159 b'error', (b'UnlockableTransport',)) 2160 transport.mkdir('quack') 2161 transport = transport.clone('quack') 2162 branch = self.make_remote_branch(transport, client) 2163 self.assertRaises(errors.UnlockableTransport, branch.lock_write) 2164 self.assertFinished(client) 2165 2166 2167class TestBranchRevisionIdToRevno(RemoteBranchTestCase): 2168 2169 def test_simple(self): 2170 transport = MemoryTransport() 2171 client = FakeClient(transport.base) 2172 client.add_expected_call( 2173 b'Branch.get_stacked_on_url', (b'quack/',), 2174 b'error', (b'NotStacked',),) 2175 client.add_expected_call( 2176 b'Branch.revision_id_to_revno', (b'quack/', b'null:'), 2177 b'success', (b'ok', b'0',),) 2178 client.add_expected_call( 2179 b'Branch.revision_id_to_revno', (b'quack/', b'unknown'), 2180 b'error', (b'NoSuchRevision', b'unknown',),) 2181 transport.mkdir('quack') 2182 transport = transport.clone('quack') 2183 branch = self.make_remote_branch(transport, client) 2184 self.assertEqual(0, branch.revision_id_to_revno(b'null:')) 2185 self.assertRaises(errors.NoSuchRevision, 2186 branch.revision_id_to_revno, b'unknown') 2187 self.assertFinished(client) 2188 2189 def test_dotted(self): 2190 transport = MemoryTransport() 2191 client = FakeClient(transport.base) 2192 client.add_expected_call( 2193 b'Branch.get_stacked_on_url', (b'quack/',), 2194 b'error', (b'NotStacked',),) 2195 client.add_expected_call( 2196 b'Branch.revision_id_to_revno', (b'quack/', b'null:'), 2197 b'success', (b'ok', b'0',),) 2198 client.add_expected_call( 2199 b'Branch.revision_id_to_revno', (b'quack/', b'unknown'), 2200 b'error', (b'NoSuchRevision', b'unknown',),) 2201 transport.mkdir('quack') 2202 transport = transport.clone('quack') 2203 branch = self.make_remote_branch(transport, client) 2204 self.assertEqual((0, ), branch.revision_id_to_dotted_revno(b'null:')) 2205 self.assertRaises(errors.NoSuchRevision, 2206 branch.revision_id_to_dotted_revno, b'unknown') 2207 self.assertFinished(client) 2208 2209 def test_ghost_revid(self): 2210 transport = MemoryTransport() 2211 client = FakeClient(transport.base) 2212 client.add_expected_call( 2213 b'Branch.get_stacked_on_url', (b'quack/',), 2214 b'error', (b'NotStacked',),) 2215 # Some older versions of bzr/brz didn't explicitly return 2216 # GhostRevisionsHaveNoRevno 2217 client.add_expected_call( 2218 b'Branch.revision_id_to_revno', (b'quack/', b'revid'), 2219 b'error', (b'error', b'GhostRevisionsHaveNoRevno', 2220 b'The reivison {revid} was not found because there was ' 2221 b'a ghost at {ghost-revid}')) 2222 client.add_expected_call( 2223 b'Branch.revision_id_to_revno', (b'quack/', b'revid'), 2224 b'error', (b'GhostRevisionsHaveNoRevno', b'revid', b'ghost-revid',)) 2225 transport.mkdir('quack') 2226 transport = transport.clone('quack') 2227 branch = self.make_remote_branch(transport, client) 2228 self.assertRaises(errors.GhostRevisionsHaveNoRevno, 2229 branch.revision_id_to_dotted_revno, b'revid') 2230 self.assertRaises(errors.GhostRevisionsHaveNoRevno, 2231 branch.revision_id_to_dotted_revno, b'revid') 2232 self.assertFinished(client) 2233 2234 def test_dotted_no_smart_verb(self): 2235 self.setup_smart_server_with_call_log() 2236 branch = self.make_branch('.') 2237 self.disable_verb(b'Branch.revision_id_to_revno') 2238 self.reset_smart_call_log() 2239 self.assertEqual((0, ), 2240 branch.revision_id_to_dotted_revno(b'null:')) 2241 self.assertLength(8, self.hpss_calls) 2242 2243 2244class TestBzrDirGetSetConfig(RemoteBzrDirTestCase): 2245 2246 def test__get_config(self): 2247 client = FakeClient() 2248 client.add_success_response_with_body(b'default_stack_on = /\n', b'ok') 2249 transport = MemoryTransport() 2250 bzrdir = self.make_remote_bzrdir(transport, client) 2251 config = bzrdir.get_config() 2252 self.assertEqual('/', config.get_default_stack_on()) 2253 self.assertEqual( 2254 [('call_expecting_body', b'BzrDir.get_config_file', 2255 (b'memory:///',))], 2256 client._calls) 2257 2258 def test_set_option_uses_vfs(self): 2259 self.setup_smart_server_with_call_log() 2260 bzrdir = self.make_controldir('.') 2261 self.reset_smart_call_log() 2262 config = bzrdir.get_config() 2263 config.set_default_stack_on('/') 2264 self.assertLength(4, self.hpss_calls) 2265 2266 def test_backwards_compat_get_option(self): 2267 self.setup_smart_server_with_call_log() 2268 bzrdir = self.make_controldir('.') 2269 verb = b'BzrDir.get_config_file' 2270 self.disable_verb(verb) 2271 self.reset_smart_call_log() 2272 self.assertEqual(None, 2273 bzrdir._get_config().get_option('default_stack_on')) 2274 self.assertLength(4, self.hpss_calls) 2275 2276 2277class TestTransportIsReadonly(tests.TestCase): 2278 2279 def test_true(self): 2280 client = FakeClient() 2281 client.add_success_response(b'yes') 2282 transport = RemoteTransport('bzr://example.com/', medium=False, 2283 _client=client) 2284 self.assertEqual(True, transport.is_readonly()) 2285 self.assertEqual( 2286 [('call', b'Transport.is_readonly', ())], 2287 client._calls) 2288 2289 def test_false(self): 2290 client = FakeClient() 2291 client.add_success_response(b'no') 2292 transport = RemoteTransport('bzr://example.com/', medium=False, 2293 _client=client) 2294 self.assertEqual(False, transport.is_readonly()) 2295 self.assertEqual( 2296 [('call', b'Transport.is_readonly', ())], 2297 client._calls) 2298 2299 def test_error_from_old_server(self): 2300 """bzr 0.15 and earlier servers don't recognise the is_readonly verb. 2301 2302 Clients should treat it as a "no" response, because is_readonly is only 2303 advisory anyway (a transport could be read-write, but then the 2304 underlying filesystem could be readonly anyway). 2305 """ 2306 client = FakeClient() 2307 client.add_unknown_method_response(b'Transport.is_readonly') 2308 transport = RemoteTransport('bzr://example.com/', medium=False, 2309 _client=client) 2310 self.assertEqual(False, transport.is_readonly()) 2311 self.assertEqual( 2312 [('call', b'Transport.is_readonly', ())], 2313 client._calls) 2314 2315 2316class TestTransportMkdir(tests.TestCase): 2317 2318 def test_permissiondenied(self): 2319 client = FakeClient() 2320 client.add_error_response( 2321 b'PermissionDenied', b'remote path', b'extra') 2322 transport = RemoteTransport('bzr://example.com/', medium=False, 2323 _client=client) 2324 exc = self.assertRaises( 2325 errors.PermissionDenied, transport.mkdir, 'client path') 2326 expected_error = errors.PermissionDenied('/client path', 'extra') 2327 self.assertEqual(expected_error, exc) 2328 2329 2330class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir): 2331 2332 def test_defaults_to_none(self): 2333 t = RemoteSSHTransport('bzr+ssh://example.com') 2334 self.assertIs(None, t._get_credentials()[0]) 2335 2336 def test_uses_authentication_config(self): 2337 conf = config.AuthenticationConfig() 2338 conf._get_config().update( 2339 {'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host': 2340 'example.com'}}) 2341 conf._save() 2342 t = RemoteSSHTransport('bzr+ssh://example.com') 2343 self.assertEqual('bar', t._get_credentials()[0]) 2344 2345 2346class TestRemoteRepository(TestRemote): 2347 """Base for testing RemoteRepository protocol usage. 2348 2349 These tests contain frozen requests and responses. We want any changes to 2350 what is sent or expected to be require a thoughtful update to these tests 2351 because they might break compatibility with different-versioned servers. 2352 """ 2353 2354 def setup_fake_client_and_repository(self, transport_path): 2355 """Create the fake client and repository for testing with. 2356 2357 There's no real server here; we just have canned responses sent 2358 back one by one. 2359 2360 :param transport_path: Path below the root of the MemoryTransport 2361 where the repository will be created. 2362 """ 2363 transport = MemoryTransport() 2364 transport.mkdir(transport_path) 2365 client = FakeClient(transport.base) 2366 transport = transport.clone(transport_path) 2367 # we do not want bzrdir to make any remote calls 2368 bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(), 2369 _client=False) 2370 repo = RemoteRepository(bzrdir, None, _client=client) 2371 return repo, client 2372 2373 2374def remoted_description(format): 2375 return 'Remote: ' + format.get_format_description() 2376 2377 2378class TestBranchFormat(tests.TestCase): 2379 2380 def test_get_format_description(self): 2381 remote_format = RemoteBranchFormat() 2382 real_format = branch.format_registry.get_default() 2383 remote_format._network_name = real_format.network_name() 2384 self.assertEqual(remoted_description(real_format), 2385 remote_format.get_format_description()) 2386 2387 2388class TestRepositoryFormat(TestRemoteRepository): 2389 2390 def test_fast_delta(self): 2391 true_name = groupcompress_repo.RepositoryFormat2a().network_name() 2392 true_format = RemoteRepositoryFormat() 2393 true_format._network_name = true_name 2394 self.assertEqual(True, true_format.fast_deltas) 2395 false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name() 2396 false_format = RemoteRepositoryFormat() 2397 false_format._network_name = false_name 2398 self.assertEqual(False, false_format.fast_deltas) 2399 2400 def test_get_format_description(self): 2401 remote_repo_format = RemoteRepositoryFormat() 2402 real_format = repository.format_registry.get_default() 2403 remote_repo_format._network_name = real_format.network_name() 2404 self.assertEqual(remoted_description(real_format), 2405 remote_repo_format.get_format_description()) 2406 2407 2408class TestRepositoryAllRevisionIds(TestRemoteRepository): 2409 2410 def test_empty(self): 2411 transport_path = 'quack' 2412 repo, client = self.setup_fake_client_and_repository(transport_path) 2413 client.add_success_response_with_body(b'', b'ok') 2414 self.assertEqual([], repo.all_revision_ids()) 2415 self.assertEqual( 2416 [('call_expecting_body', b'Repository.all_revision_ids', 2417 (b'quack/',))], 2418 client._calls) 2419 2420 def test_with_some_content(self): 2421 transport_path = 'quack' 2422 repo, client = self.setup_fake_client_and_repository(transport_path) 2423 client.add_success_response_with_body( 2424 b'rev1\nrev2\nanotherrev\n', b'ok') 2425 self.assertEqual( 2426 set([b"rev1", b"rev2", b"anotherrev"]), 2427 set(repo.all_revision_ids())) 2428 self.assertEqual( 2429 [('call_expecting_body', b'Repository.all_revision_ids', 2430 (b'quack/',))], 2431 client._calls) 2432 2433 2434class TestRepositoryGatherStats(TestRemoteRepository): 2435 2436 def test_revid_none(self): 2437 # ('ok',), body with revisions and size 2438 transport_path = 'quack' 2439 repo, client = self.setup_fake_client_and_repository(transport_path) 2440 client.add_success_response_with_body( 2441 b'revisions: 2\nsize: 18\n', b'ok') 2442 result = repo.gather_stats(None) 2443 self.assertEqual( 2444 [('call_expecting_body', b'Repository.gather_stats', 2445 (b'quack/', b'', b'no'))], 2446 client._calls) 2447 self.assertEqual({'revisions': 2, 'size': 18}, result) 2448 2449 def test_revid_no_committers(self): 2450 # ('ok',), body without committers 2451 body = (b'firstrev: 123456.300 3600\n' 2452 b'latestrev: 654231.400 0\n' 2453 b'revisions: 2\n' 2454 b'size: 18\n') 2455 transport_path = 'quick' 2456 revid = u'\xc8'.encode('utf8') 2457 repo, client = self.setup_fake_client_and_repository(transport_path) 2458 client.add_success_response_with_body(body, b'ok') 2459 result = repo.gather_stats(revid) 2460 self.assertEqual( 2461 [('call_expecting_body', b'Repository.gather_stats', 2462 (b'quick/', revid, b'no'))], 2463 client._calls) 2464 self.assertEqual({'revisions': 2, 'size': 18, 2465 'firstrev': (123456.300, 3600), 2466 'latestrev': (654231.400, 0), }, 2467 result) 2468 2469 def test_revid_with_committers(self): 2470 # ('ok',), body with committers 2471 body = (b'committers: 128\n' 2472 b'firstrev: 123456.300 3600\n' 2473 b'latestrev: 654231.400 0\n' 2474 b'revisions: 2\n' 2475 b'size: 18\n') 2476 transport_path = 'buick' 2477 revid = u'\xc8'.encode('utf8') 2478 repo, client = self.setup_fake_client_and_repository(transport_path) 2479 client.add_success_response_with_body(body, b'ok') 2480 result = repo.gather_stats(revid, True) 2481 self.assertEqual( 2482 [('call_expecting_body', b'Repository.gather_stats', 2483 (b'buick/', revid, b'yes'))], 2484 client._calls) 2485 self.assertEqual({'revisions': 2, 'size': 18, 2486 'committers': 128, 2487 'firstrev': (123456.300, 3600), 2488 'latestrev': (654231.400, 0), }, 2489 result) 2490 2491 2492class TestRepositoryBreakLock(TestRemoteRepository): 2493 2494 def test_break_lock(self): 2495 transport_path = 'quack' 2496 repo, client = self.setup_fake_client_and_repository(transport_path) 2497 client.add_success_response(b'ok') 2498 repo.break_lock() 2499 self.assertEqual( 2500 [('call', b'Repository.break_lock', (b'quack/',))], 2501 client._calls) 2502 2503 2504class TestRepositoryGetSerializerFormat(TestRemoteRepository): 2505 2506 def test_get_serializer_format(self): 2507 transport_path = 'hill' 2508 repo, client = self.setup_fake_client_and_repository(transport_path) 2509 client.add_success_response(b'ok', b'7') 2510 self.assertEqual(b'7', repo.get_serializer_format()) 2511 self.assertEqual( 2512 [('call', b'VersionedFileRepository.get_serializer_format', 2513 (b'hill/', ))], 2514 client._calls) 2515 2516 2517class TestRepositoryReconcile(TestRemoteRepository): 2518 2519 def test_reconcile(self): 2520 transport_path = 'hill' 2521 repo, client = self.setup_fake_client_and_repository(transport_path) 2522 body = (b"garbage_inventories: 2\n" 2523 b"inconsistent_parents: 3\n") 2524 client.add_expected_call( 2525 b'Repository.lock_write', (b'hill/', b''), 2526 b'success', (b'ok', b'a token')) 2527 client.add_success_response_with_body(body, b'ok') 2528 reconciler = repo.reconcile() 2529 self.assertEqual( 2530 [('call', b'Repository.lock_write', (b'hill/', b'')), 2531 ('call_expecting_body', b'Repository.reconcile', 2532 (b'hill/', b'a token'))], 2533 client._calls) 2534 self.assertEqual(2, reconciler.garbage_inventories) 2535 self.assertEqual(3, reconciler.inconsistent_parents) 2536 2537 2538class TestRepositoryGetRevisionSignatureText(TestRemoteRepository): 2539 2540 def test_text(self): 2541 # ('ok',), body with signature text 2542 transport_path = 'quack' 2543 repo, client = self.setup_fake_client_and_repository(transport_path) 2544 client.add_success_response_with_body( 2545 b'THETEXT', b'ok') 2546 self.assertEqual(b"THETEXT", repo.get_signature_text(b"revid")) 2547 self.assertEqual( 2548 [('call_expecting_body', b'Repository.get_revision_signature_text', 2549 (b'quack/', b'revid'))], 2550 client._calls) 2551 2552 def test_no_signature(self): 2553 transport_path = 'quick' 2554 repo, client = self.setup_fake_client_and_repository(transport_path) 2555 client.add_error_response(b'nosuchrevision', b'unknown') 2556 self.assertRaises(errors.NoSuchRevision, repo.get_signature_text, 2557 b"unknown") 2558 self.assertEqual( 2559 [('call_expecting_body', b'Repository.get_revision_signature_text', 2560 (b'quick/', b'unknown'))], 2561 client._calls) 2562 2563 2564class TestRepositoryGetGraph(TestRemoteRepository): 2565 2566 def test_get_graph(self): 2567 # get_graph returns a graph with a custom parents provider. 2568 transport_path = 'quack' 2569 repo, client = self.setup_fake_client_and_repository(transport_path) 2570 graph = repo.get_graph() 2571 self.assertNotEqual(graph._parents_provider, repo) 2572 2573 2574class TestRepositoryAddSignatureText(TestRemoteRepository): 2575 2576 def test_add_signature_text(self): 2577 transport_path = 'quack' 2578 repo, client = self.setup_fake_client_and_repository(transport_path) 2579 client.add_expected_call( 2580 b'Repository.lock_write', (b'quack/', b''), 2581 b'success', (b'ok', b'a token')) 2582 client.add_expected_call( 2583 b'Repository.start_write_group', (b'quack/', b'a token'), 2584 b'success', (b'ok', (b'token1', ))) 2585 client.add_expected_call( 2586 b'Repository.add_signature_text', (b'quack/', b'a token', b'rev1', 2587 b'token1'), 2588 b'success', (b'ok', ), None) 2589 repo.lock_write() 2590 repo.start_write_group() 2591 self.assertIs( 2592 None, repo.add_signature_text(b"rev1", b"every bloody emperor")) 2593 self.assertEqual( 2594 ('call_with_body_bytes_expecting_body', 2595 b'Repository.add_signature_text', 2596 (b'quack/', b'a token', b'rev1', b'token1'), 2597 b'every bloody emperor'), 2598 client._calls[-1]) 2599 2600 2601class TestRepositoryGetParentMap(TestRemoteRepository): 2602 2603 def test_get_parent_map_caching(self): 2604 # get_parent_map returns from cache until unlock() 2605 # setup a reponse with two revisions 2606 r1 = u'\u0e33'.encode('utf8') 2607 r2 = u'\u0dab'.encode('utf8') 2608 lines = [b' '.join([r2, r1]), r1] 2609 encoded_body = bz2.compress(b'\n'.join(lines)) 2610 2611 transport_path = 'quack' 2612 repo, client = self.setup_fake_client_and_repository(transport_path) 2613 client.add_success_response_with_body(encoded_body, b'ok') 2614 client.add_success_response_with_body(encoded_body, b'ok') 2615 repo.lock_read() 2616 graph = repo.get_graph() 2617 parents = graph.get_parent_map([r2]) 2618 self.assertEqual({r2: (r1,)}, parents) 2619 # locking and unlocking deeper should not reset 2620 repo.lock_read() 2621 repo.unlock() 2622 parents = graph.get_parent_map([r1]) 2623 self.assertEqual({r1: (NULL_REVISION,)}, parents) 2624 self.assertEqual( 2625 [('call_with_body_bytes_expecting_body', 2626 b'Repository.get_parent_map', (b'quack/', 2627 b'include-missing:', r2), 2628 b'\n\n0')], 2629 client._calls) 2630 repo.unlock() 2631 # now we call again, and it should use the second response. 2632 repo.lock_read() 2633 graph = repo.get_graph() 2634 parents = graph.get_parent_map([r1]) 2635 self.assertEqual({r1: (NULL_REVISION,)}, parents) 2636 self.assertEqual( 2637 [('call_with_body_bytes_expecting_body', 2638 b'Repository.get_parent_map', (b'quack/', 2639 b'include-missing:', r2), 2640 b'\n\n0'), 2641 ('call_with_body_bytes_expecting_body', 2642 b'Repository.get_parent_map', (b'quack/', 2643 b'include-missing:', r1), 2644 b'\n\n0'), 2645 ], 2646 client._calls) 2647 repo.unlock() 2648 2649 def test_get_parent_map_reconnects_if_unknown_method(self): 2650 transport_path = 'quack' 2651 rev_id = b'revision-id' 2652 repo, client = self.setup_fake_client_and_repository(transport_path) 2653 client.add_unknown_method_response(b'Repository.get_parent_map') 2654 client.add_success_response_with_body(rev_id, b'ok') 2655 self.assertFalse(client._medium._is_remote_before((1, 2))) 2656 parents = repo.get_parent_map([rev_id]) 2657 self.assertEqual( 2658 [('call_with_body_bytes_expecting_body', 2659 b'Repository.get_parent_map', 2660 (b'quack/', b'include-missing:', rev_id), b'\n\n0'), 2661 ('disconnect medium',), 2662 ('call_expecting_body', b'Repository.get_revision_graph', 2663 (b'quack/', b''))], 2664 client._calls) 2665 # The medium is now marked as being connected to an older server 2666 self.assertTrue(client._medium._is_remote_before((1, 2))) 2667 self.assertEqual({rev_id: (b'null:',)}, parents) 2668 2669 def test_get_parent_map_fallback_parentless_node(self): 2670 """get_parent_map falls back to get_revision_graph on old servers. The 2671 results from get_revision_graph are tweaked to match the get_parent_map 2672 API. 2673 2674 Specifically, a {key: ()} result from get_revision_graph means "no 2675 parents" for that key, which in get_parent_map results should be 2676 represented as {key: ('null:',)}. 2677 2678 This is the test for https://bugs.launchpad.net/bzr/+bug/214894 2679 """ 2680 rev_id = b'revision-id' 2681 transport_path = 'quack' 2682 repo, client = self.setup_fake_client_and_repository(transport_path) 2683 client.add_success_response_with_body(rev_id, b'ok') 2684 client._medium._remember_remote_is_before((1, 2)) 2685 parents = repo.get_parent_map([rev_id]) 2686 self.assertEqual( 2687 [('call_expecting_body', b'Repository.get_revision_graph', 2688 (b'quack/', b''))], 2689 client._calls) 2690 self.assertEqual({rev_id: (b'null:',)}, parents) 2691 2692 def test_get_parent_map_unexpected_response(self): 2693 repo, client = self.setup_fake_client_and_repository('path') 2694 client.add_success_response(b'something unexpected!') 2695 self.assertRaises( 2696 errors.UnexpectedSmartServerResponse, 2697 repo.get_parent_map, [b'a-revision-id']) 2698 2699 def test_get_parent_map_negative_caches_missing_keys(self): 2700 self.setup_smart_server_with_call_log() 2701 repo = self.make_repository('foo') 2702 self.assertIsInstance(repo, RemoteRepository) 2703 repo.lock_read() 2704 self.addCleanup(repo.unlock) 2705 self.reset_smart_call_log() 2706 graph = repo.get_graph() 2707 self.assertEqual( 2708 {}, graph.get_parent_map([b'some-missing', b'other-missing'])) 2709 self.assertLength(1, self.hpss_calls) 2710 # No call if we repeat this 2711 self.reset_smart_call_log() 2712 graph = repo.get_graph() 2713 self.assertEqual( 2714 {}, graph.get_parent_map([b'some-missing', b'other-missing'])) 2715 self.assertLength(0, self.hpss_calls) 2716 # Asking for more unknown keys makes a request. 2717 self.reset_smart_call_log() 2718 graph = repo.get_graph() 2719 self.assertEqual( 2720 {}, graph.get_parent_map([b'some-missing', b'other-missing', 2721 b'more-missing'])) 2722 self.assertLength(1, self.hpss_calls) 2723 2724 def disableExtraResults(self): 2725 self.overrideAttr(SmartServerRepositoryGetParentMap, 2726 'no_extra_results', True) 2727 2728 def test_null_cached_missing_and_stop_key(self): 2729 self.setup_smart_server_with_call_log() 2730 # Make a branch with a single revision. 2731 builder = self.make_branch_builder('foo') 2732 builder.start_series() 2733 builder.build_snapshot(None, [ 2734 ('add', ('', b'root-id', 'directory', ''))], 2735 revision_id=b'first') 2736 builder.finish_series() 2737 branch = builder.get_branch() 2738 repo = branch.repository 2739 self.assertIsInstance(repo, RemoteRepository) 2740 # Stop the server from sending extra results. 2741 self.disableExtraResults() 2742 repo.lock_read() 2743 self.addCleanup(repo.unlock) 2744 self.reset_smart_call_log() 2745 graph = repo.get_graph() 2746 # Query for b'first' and b'null:'. Because b'null:' is a parent of 2747 # 'first' it will be a candidate for the stop_keys of subsequent 2748 # requests, and because b'null:' was queried but not returned it will 2749 # be cached as missing. 2750 self.assertEqual({b'first': (b'null:',)}, 2751 graph.get_parent_map([b'first', b'null:'])) 2752 # Now query for another key. This request will pass along a recipe of 2753 # start and stop keys describing the already cached results, and this 2754 # recipe's revision count must be correct (or else it will trigger an 2755 # error from the server). 2756 self.assertEqual({}, graph.get_parent_map([b'another-key'])) 2757 # This assertion guards against disableExtraResults silently failing to 2758 # work, thus invalidating the test. 2759 self.assertLength(2, self.hpss_calls) 2760 2761 def test_get_parent_map_gets_ghosts_from_result(self): 2762 # asking for a revision should negatively cache close ghosts in its 2763 # ancestry. 2764 self.setup_smart_server_with_call_log() 2765 tree = self.make_branch_and_memory_tree('foo') 2766 with tree.lock_write(): 2767 builder = treebuilder.TreeBuilder() 2768 builder.start_tree(tree) 2769 builder.build([]) 2770 builder.finish_tree() 2771 tree.set_parent_ids([b'non-existant'], 2772 allow_leftmost_as_ghost=True) 2773 rev_id = tree.commit('') 2774 tree.lock_read() 2775 self.addCleanup(tree.unlock) 2776 repo = tree.branch.repository 2777 self.assertIsInstance(repo, RemoteRepository) 2778 # ask for rev_id 2779 repo.get_parent_map([rev_id]) 2780 self.reset_smart_call_log() 2781 # Now asking for rev_id's ghost parent should not make calls 2782 self.assertEqual({}, repo.get_parent_map([b'non-existant'])) 2783 self.assertLength(0, self.hpss_calls) 2784 2785 def test_exposes_get_cached_parent_map(self): 2786 """RemoteRepository exposes get_cached_parent_map from 2787 _unstacked_provider 2788 """ 2789 r1 = u'\u0e33'.encode('utf8') 2790 r2 = u'\u0dab'.encode('utf8') 2791 lines = [b' '.join([r2, r1]), r1] 2792 encoded_body = bz2.compress(b'\n'.join(lines)) 2793 2794 transport_path = 'quack' 2795 repo, client = self.setup_fake_client_and_repository(transport_path) 2796 client.add_success_response_with_body(encoded_body, b'ok') 2797 repo.lock_read() 2798 # get_cached_parent_map should *not* trigger an RPC 2799 self.assertEqual({}, repo.get_cached_parent_map([r1])) 2800 self.assertEqual([], client._calls) 2801 self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2])) 2802 self.assertEqual({r1: (NULL_REVISION,)}, 2803 repo.get_cached_parent_map([r1])) 2804 self.assertEqual( 2805 [('call_with_body_bytes_expecting_body', 2806 b'Repository.get_parent_map', (b'quack/', 2807 b'include-missing:', r2), 2808 b'\n\n0')], 2809 client._calls) 2810 repo.unlock() 2811 2812 2813class TestGetParentMapAllowsNew(tests.TestCaseWithTransport): 2814 2815 def test_allows_new_revisions(self): 2816 """get_parent_map's results can be updated by commit.""" 2817 smart_server = test_server.SmartTCPServer_for_testing() 2818 self.start_server(smart_server) 2819 self.make_branch('branch') 2820 branch = Branch.open(smart_server.get_url() + '/branch') 2821 tree = branch.create_checkout('tree', lightweight=True) 2822 tree.lock_write() 2823 self.addCleanup(tree.unlock) 2824 graph = tree.branch.repository.get_graph() 2825 # This provides an opportunity for the missing rev-id to be cached. 2826 self.assertEqual({}, graph.get_parent_map([b'rev1'])) 2827 tree.commit('message', rev_id=b'rev1') 2828 graph = tree.branch.repository.get_graph() 2829 self.assertEqual({b'rev1': (b'null:',)}, 2830 graph.get_parent_map([b'rev1'])) 2831 2832 2833class TestRepositoryGetRevisions(TestRemoteRepository): 2834 2835 def test_hpss_missing_revision(self): 2836 transport_path = 'quack' 2837 repo, client = self.setup_fake_client_and_repository(transport_path) 2838 client.add_success_response_with_body( 2839 b'', b'ok', b'10') 2840 self.assertRaises(errors.NoSuchRevision, repo.get_revisions, 2841 [b'somerev1', b'anotherrev2']) 2842 self.assertEqual( 2843 [('call_with_body_bytes_expecting_body', 2844 b'Repository.iter_revisions', (b'quack/', ), 2845 b"somerev1\nanotherrev2")], 2846 client._calls) 2847 2848 def test_hpss_get_single_revision(self): 2849 transport_path = 'quack' 2850 repo, client = self.setup_fake_client_and_repository(transport_path) 2851 somerev1 = Revision(b"somerev1") 2852 somerev1.committer = "Joe Committer <joe@example.com>" 2853 somerev1.timestamp = 1321828927 2854 somerev1.timezone = -60 2855 somerev1.inventory_sha1 = b"691b39be74c67b1212a75fcb19c433aaed903c2b" 2856 somerev1.message = "Message" 2857 body = zlib.compress(b''.join(chk_bencode_serializer.write_revision_to_lines( 2858 somerev1))) 2859 # Split up body into two bits to make sure the zlib compression object 2860 # gets data fed twice. 2861 client.add_success_response_with_body( 2862 [body[:10], body[10:]], b'ok', b'10') 2863 revs = repo.get_revisions([b'somerev1']) 2864 self.assertEqual(revs, [somerev1]) 2865 self.assertEqual( 2866 [('call_with_body_bytes_expecting_body', 2867 b'Repository.iter_revisions', 2868 (b'quack/', ), b"somerev1")], 2869 client._calls) 2870 2871 2872class TestRepositoryGetRevisionGraph(TestRemoteRepository): 2873 2874 def test_null_revision(self): 2875 # a null revision has the predictable result {}, we should have no wire 2876 # traffic when calling it with this argument 2877 transport_path = 'empty' 2878 repo, client = self.setup_fake_client_and_repository(transport_path) 2879 client.add_success_response(b'notused') 2880 # actual RemoteRepository.get_revision_graph is gone, but there's an 2881 # equivalent private method for testing 2882 result = repo._get_revision_graph(NULL_REVISION) 2883 self.assertEqual([], client._calls) 2884 self.assertEqual({}, result) 2885 2886 def test_none_revision(self): 2887 # with none we want the entire graph 2888 r1 = u'\u0e33'.encode('utf8') 2889 r2 = u'\u0dab'.encode('utf8') 2890 lines = [b' '.join([r2, r1]), r1] 2891 encoded_body = b'\n'.join(lines) 2892 2893 transport_path = 'sinhala' 2894 repo, client = self.setup_fake_client_and_repository(transport_path) 2895 client.add_success_response_with_body(encoded_body, b'ok') 2896 # actual RemoteRepository.get_revision_graph is gone, but there's an 2897 # equivalent private method for testing 2898 result = repo._get_revision_graph(None) 2899 self.assertEqual( 2900 [('call_expecting_body', b'Repository.get_revision_graph', 2901 (b'sinhala/', b''))], 2902 client._calls) 2903 self.assertEqual({r1: (), r2: (r1, )}, result) 2904 2905 def test_specific_revision(self): 2906 # with a specific revision we want the graph for that 2907 # with none we want the entire graph 2908 r11 = u'\u0e33'.encode('utf8') 2909 r12 = u'\xc9'.encode('utf8') 2910 r2 = u'\u0dab'.encode('utf8') 2911 lines = [b' '.join([r2, r11, r12]), r11, r12] 2912 encoded_body = b'\n'.join(lines) 2913 2914 transport_path = 'sinhala' 2915 repo, client = self.setup_fake_client_and_repository(transport_path) 2916 client.add_success_response_with_body(encoded_body, b'ok') 2917 result = repo._get_revision_graph(r2) 2918 self.assertEqual( 2919 [('call_expecting_body', b'Repository.get_revision_graph', 2920 (b'sinhala/', r2))], 2921 client._calls) 2922 self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result) 2923 2924 def test_no_such_revision(self): 2925 revid = b'123' 2926 transport_path = 'sinhala' 2927 repo, client = self.setup_fake_client_and_repository(transport_path) 2928 client.add_error_response(b'nosuchrevision', revid) 2929 # also check that the right revision is reported in the error 2930 self.assertRaises(errors.NoSuchRevision, 2931 repo._get_revision_graph, revid) 2932 self.assertEqual( 2933 [('call_expecting_body', b'Repository.get_revision_graph', 2934 (b'sinhala/', revid))], 2935 client._calls) 2936 2937 def test_unexpected_error(self): 2938 revid = '123' 2939 transport_path = 'sinhala' 2940 repo, client = self.setup_fake_client_and_repository(transport_path) 2941 client.add_error_response(b'AnUnexpectedError') 2942 e = self.assertRaises(errors.UnknownErrorFromSmartServer, 2943 repo._get_revision_graph, revid) 2944 self.assertEqual((b'AnUnexpectedError',), e.error_tuple) 2945 2946 2947class TestRepositoryGetRevIdForRevno(TestRemoteRepository): 2948 2949 def test_ok(self): 2950 repo, client = self.setup_fake_client_and_repository('quack') 2951 client.add_expected_call( 2952 b'Repository.get_rev_id_for_revno', (b'quack/', 2953 5, (42, b'rev-foo')), 2954 b'success', (b'ok', b'rev-five')) 2955 result = repo.get_rev_id_for_revno(5, (42, b'rev-foo')) 2956 self.assertEqual((True, b'rev-five'), result) 2957 self.assertFinished(client) 2958 2959 def test_history_incomplete(self): 2960 repo, client = self.setup_fake_client_and_repository('quack') 2961 client.add_expected_call( 2962 b'Repository.get_rev_id_for_revno', (b'quack/', 2963 5, (42, b'rev-foo')), 2964 b'success', (b'history-incomplete', 10, b'rev-ten')) 2965 result = repo.get_rev_id_for_revno(5, (42, b'rev-foo')) 2966 self.assertEqual((False, (10, b'rev-ten')), result) 2967 self.assertFinished(client) 2968 2969 def test_history_incomplete_with_fallback(self): 2970 """A 'history-incomplete' response causes the fallback repository to be 2971 queried too, if one is set. 2972 """ 2973 # Make a repo with a fallback repo, both using a FakeClient. 2974 format = remote.response_tuple_to_repo_format( 2975 (b'yes', b'no', b'yes', self.get_repo_format().network_name())) 2976 repo, client = self.setup_fake_client_and_repository('quack') 2977 repo._format = format 2978 fallback_repo, ignored = self.setup_fake_client_and_repository( 2979 'fallback') 2980 fallback_repo._client = client 2981 fallback_repo._format = format 2982 repo.add_fallback_repository(fallback_repo) 2983 # First the client should ask the primary repo 2984 client.add_expected_call( 2985 b'Repository.get_rev_id_for_revno', (b'quack/', 2986 1, (42, b'rev-foo')), 2987 b'success', (b'history-incomplete', 2, b'rev-two')) 2988 # Then it should ask the fallback, using revno/revid from the 2989 # history-incomplete response as the known revno/revid. 2990 client.add_expected_call( 2991 b'Repository.get_rev_id_for_revno', ( 2992 b'fallback/', 1, (2, b'rev-two')), 2993 b'success', (b'ok', b'rev-one')) 2994 result = repo.get_rev_id_for_revno(1, (42, b'rev-foo')) 2995 self.assertEqual((True, b'rev-one'), result) 2996 self.assertFinished(client) 2997 2998 def test_nosuchrevision(self): 2999 # 'nosuchrevision' is returned when the known-revid is not found in the 3000 # remote repo. The client translates that response to NoSuchRevision. 3001 repo, client = self.setup_fake_client_and_repository('quack') 3002 client.add_expected_call( 3003 b'Repository.get_rev_id_for_revno', (b'quack/', 3004 5, (42, b'rev-foo')), 3005 b'error', (b'nosuchrevision', b'rev-foo')) 3006 self.assertRaises( 3007 errors.NoSuchRevision, 3008 repo.get_rev_id_for_revno, 5, (42, b'rev-foo')) 3009 self.assertFinished(client) 3010 3011 def test_outofbounds(self): 3012 repo, client = self.setup_fake_client_and_repository('quack') 3013 client.add_expected_call( 3014 b'Repository.get_rev_id_for_revno', (b'quack/', 3015 43, (42, b'rev-foo')), 3016 b'error', (b'revno-outofbounds', 43, 0, 42)) 3017 self.assertRaises( 3018 errors.RevnoOutOfBounds, 3019 repo.get_rev_id_for_revno, 43, (42, b'rev-foo')) 3020 self.assertFinished(client) 3021 3022 def test_outofbounds_old(self): 3023 # Older versions of bzr didn't support RevnoOutOfBounds 3024 repo, client = self.setup_fake_client_and_repository('quack') 3025 client.add_expected_call( 3026 b'Repository.get_rev_id_for_revno', (b'quack/', 3027 43, (42, b'rev-foo')), 3028 b'error', ( 3029 b'error', b'ValueError', 3030 b'requested revno (43) is later than given known revno (42)')) 3031 self.assertRaises( 3032 errors.RevnoOutOfBounds, 3033 repo.get_rev_id_for_revno, 43, (42, b'rev-foo')) 3034 self.assertFinished(client) 3035 3036 def test_branch_fallback_locking(self): 3037 """RemoteBranch.get_rev_id takes a read lock, and tries to call the 3038 get_rev_id_for_revno verb. If the verb is unknown the VFS fallback 3039 will be invoked, which will fail if the repo is unlocked. 3040 """ 3041 self.setup_smart_server_with_call_log() 3042 tree = self.make_branch_and_memory_tree('.') 3043 tree.lock_write() 3044 tree.add('') 3045 rev1 = tree.commit('First') 3046 tree.commit('Second') 3047 tree.unlock() 3048 branch = tree.branch 3049 self.assertFalse(branch.is_locked()) 3050 self.reset_smart_call_log() 3051 verb = b'Repository.get_rev_id_for_revno' 3052 self.disable_verb(verb) 3053 self.assertEqual(rev1, branch.get_rev_id(1)) 3054 self.assertLength(1, [call for call in self.hpss_calls if 3055 call.call.method == verb]) 3056 3057 3058class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository): 3059 3060 def test_has_signature_for_revision_id(self): 3061 # ('yes', ) for Repository.has_signature_for_revision_id -> 'True'. 3062 transport_path = 'quack' 3063 repo, client = self.setup_fake_client_and_repository(transport_path) 3064 client.add_success_response(b'yes') 3065 result = repo.has_signature_for_revision_id(b'A') 3066 self.assertEqual( 3067 [('call', b'Repository.has_signature_for_revision_id', 3068 (b'quack/', b'A'))], 3069 client._calls) 3070 self.assertEqual(True, result) 3071 3072 def test_is_not_shared(self): 3073 # ('no', ) for Repository.has_signature_for_revision_id -> 'False'. 3074 transport_path = 'qwack' 3075 repo, client = self.setup_fake_client_and_repository(transport_path) 3076 client.add_success_response(b'no') 3077 result = repo.has_signature_for_revision_id(b'A') 3078 self.assertEqual( 3079 [('call', b'Repository.has_signature_for_revision_id', 3080 (b'qwack/', b'A'))], 3081 client._calls) 3082 self.assertEqual(False, result) 3083 3084 3085class TestRepositoryPhysicalLockStatus(TestRemoteRepository): 3086 3087 def test_get_physical_lock_status_yes(self): 3088 transport_path = 'qwack' 3089 repo, client = self.setup_fake_client_and_repository(transport_path) 3090 client.add_success_response(b'yes') 3091 result = repo.get_physical_lock_status() 3092 self.assertEqual( 3093 [('call', b'Repository.get_physical_lock_status', 3094 (b'qwack/', ))], 3095 client._calls) 3096 self.assertEqual(True, result) 3097 3098 def test_get_physical_lock_status_no(self): 3099 transport_path = 'qwack' 3100 repo, client = self.setup_fake_client_and_repository(transport_path) 3101 client.add_success_response(b'no') 3102 result = repo.get_physical_lock_status() 3103 self.assertEqual( 3104 [('call', b'Repository.get_physical_lock_status', 3105 (b'qwack/', ))], 3106 client._calls) 3107 self.assertEqual(False, result) 3108 3109 3110class TestRepositoryIsShared(TestRemoteRepository): 3111 3112 def test_is_shared(self): 3113 # ('yes', ) for Repository.is_shared -> 'True'. 3114 transport_path = 'quack' 3115 repo, client = self.setup_fake_client_and_repository(transport_path) 3116 client.add_success_response(b'yes') 3117 result = repo.is_shared() 3118 self.assertEqual( 3119 [('call', b'Repository.is_shared', (b'quack/',))], 3120 client._calls) 3121 self.assertEqual(True, result) 3122 3123 def test_is_not_shared(self): 3124 # ('no', ) for Repository.is_shared -> 'False'. 3125 transport_path = 'qwack' 3126 repo, client = self.setup_fake_client_and_repository(transport_path) 3127 client.add_success_response(b'no') 3128 result = repo.is_shared() 3129 self.assertEqual( 3130 [('call', b'Repository.is_shared', (b'qwack/',))], 3131 client._calls) 3132 self.assertEqual(False, result) 3133 3134 3135class TestRepositoryMakeWorkingTrees(TestRemoteRepository): 3136 3137 def test_make_working_trees(self): 3138 # ('yes', ) for Repository.make_working_trees -> 'True'. 3139 transport_path = 'quack' 3140 repo, client = self.setup_fake_client_and_repository(transport_path) 3141 client.add_success_response(b'yes') 3142 result = repo.make_working_trees() 3143 self.assertEqual( 3144 [('call', b'Repository.make_working_trees', (b'quack/',))], 3145 client._calls) 3146 self.assertEqual(True, result) 3147 3148 def test_no_working_trees(self): 3149 # ('no', ) for Repository.make_working_trees -> 'False'. 3150 transport_path = 'qwack' 3151 repo, client = self.setup_fake_client_and_repository(transport_path) 3152 client.add_success_response(b'no') 3153 result = repo.make_working_trees() 3154 self.assertEqual( 3155 [('call', b'Repository.make_working_trees', (b'qwack/',))], 3156 client._calls) 3157 self.assertEqual(False, result) 3158 3159 3160class TestRepositoryLockWrite(TestRemoteRepository): 3161 3162 def test_lock_write(self): 3163 transport_path = 'quack' 3164 repo, client = self.setup_fake_client_and_repository(transport_path) 3165 client.add_success_response(b'ok', b'a token') 3166 token = repo.lock_write().repository_token 3167 self.assertEqual( 3168 [('call', b'Repository.lock_write', (b'quack/', b''))], 3169 client._calls) 3170 self.assertEqual(b'a token', token) 3171 3172 def test_lock_write_already_locked(self): 3173 transport_path = 'quack' 3174 repo, client = self.setup_fake_client_and_repository(transport_path) 3175 client.add_error_response(b'LockContention') 3176 self.assertRaises(errors.LockContention, repo.lock_write) 3177 self.assertEqual( 3178 [('call', b'Repository.lock_write', (b'quack/', b''))], 3179 client._calls) 3180 3181 def test_lock_write_unlockable(self): 3182 transport_path = 'quack' 3183 repo, client = self.setup_fake_client_and_repository(transport_path) 3184 client.add_error_response(b'UnlockableTransport') 3185 self.assertRaises(errors.UnlockableTransport, repo.lock_write) 3186 self.assertEqual( 3187 [('call', b'Repository.lock_write', (b'quack/', b''))], 3188 client._calls) 3189 3190 3191class TestRepositoryWriteGroups(TestRemoteRepository): 3192 3193 def test_start_write_group(self): 3194 transport_path = 'quack' 3195 repo, client = self.setup_fake_client_and_repository(transport_path) 3196 client.add_expected_call( 3197 b'Repository.lock_write', (b'quack/', b''), 3198 b'success', (b'ok', b'a token')) 3199 client.add_expected_call( 3200 b'Repository.start_write_group', (b'quack/', b'a token'), 3201 b'success', (b'ok', (b'token1', ))) 3202 repo.lock_write() 3203 repo.start_write_group() 3204 3205 def test_start_write_group_unsuspendable(self): 3206 # Some repositories do not support suspending write 3207 # groups. For those, fall back to the "real" repository. 3208 transport_path = 'quack' 3209 repo, client = self.setup_fake_client_and_repository(transport_path) 3210 3211 def stub_ensure_real(): 3212 client._calls.append(('_ensure_real',)) 3213 repo._real_repository = _StubRealPackRepository(client._calls) 3214 repo._ensure_real = stub_ensure_real 3215 client.add_expected_call( 3216 b'Repository.lock_write', (b'quack/', b''), 3217 b'success', (b'ok', b'a token')) 3218 client.add_expected_call( 3219 b'Repository.start_write_group', (b'quack/', b'a token'), 3220 b'error', (b'UnsuspendableWriteGroup',)) 3221 repo.lock_write() 3222 repo.start_write_group() 3223 self.assertEqual(client._calls[-2:], [ 3224 ('_ensure_real',), 3225 ('start_write_group',)]) 3226 3227 def test_commit_write_group(self): 3228 transport_path = 'quack' 3229 repo, client = self.setup_fake_client_and_repository(transport_path) 3230 client.add_expected_call( 3231 b'Repository.lock_write', (b'quack/', b''), 3232 b'success', (b'ok', b'a token')) 3233 client.add_expected_call( 3234 b'Repository.start_write_group', (b'quack/', b'a token'), 3235 b'success', (b'ok', [b'token1'])) 3236 client.add_expected_call( 3237 b'Repository.commit_write_group', (b'quack/', 3238 b'a token', [b'token1']), 3239 b'success', (b'ok',)) 3240 repo.lock_write() 3241 repo.start_write_group() 3242 repo.commit_write_group() 3243 3244 def test_abort_write_group(self): 3245 transport_path = 'quack' 3246 repo, client = self.setup_fake_client_and_repository(transport_path) 3247 client.add_expected_call( 3248 b'Repository.lock_write', (b'quack/', b''), 3249 b'success', (b'ok', b'a token')) 3250 client.add_expected_call( 3251 b'Repository.start_write_group', (b'quack/', b'a token'), 3252 b'success', (b'ok', [b'token1'])) 3253 client.add_expected_call( 3254 b'Repository.abort_write_group', (b'quack/', 3255 b'a token', [b'token1']), 3256 b'success', (b'ok',)) 3257 repo.lock_write() 3258 repo.start_write_group() 3259 repo.abort_write_group(False) 3260 3261 def test_suspend_write_group(self): 3262 transport_path = 'quack' 3263 repo, client = self.setup_fake_client_and_repository(transport_path) 3264 self.assertEqual([], repo.suspend_write_group()) 3265 3266 def test_resume_write_group(self): 3267 transport_path = 'quack' 3268 repo, client = self.setup_fake_client_and_repository(transport_path) 3269 client.add_expected_call( 3270 b'Repository.lock_write', (b'quack/', b''), 3271 b'success', (b'ok', b'a token')) 3272 client.add_expected_call( 3273 b'Repository.check_write_group', (b'quack/', 3274 b'a token', [b'token1']), 3275 b'success', (b'ok',)) 3276 repo.lock_write() 3277 repo.resume_write_group(['token1']) 3278 3279 3280class TestRepositorySetMakeWorkingTrees(TestRemoteRepository): 3281 3282 def test_backwards_compat(self): 3283 self.setup_smart_server_with_call_log() 3284 repo = self.make_repository('.') 3285 self.reset_smart_call_log() 3286 verb = b'Repository.set_make_working_trees' 3287 self.disable_verb(verb) 3288 repo.set_make_working_trees(True) 3289 call_count = len([call for call in self.hpss_calls if 3290 call.call.method == verb]) 3291 self.assertEqual(1, call_count) 3292 3293 def test_current(self): 3294 transport_path = 'quack' 3295 repo, client = self.setup_fake_client_and_repository(transport_path) 3296 client.add_expected_call( 3297 b'Repository.set_make_working_trees', (b'quack/', b'True'), 3298 b'success', (b'ok',)) 3299 client.add_expected_call( 3300 b'Repository.set_make_working_trees', (b'quack/', b'False'), 3301 b'success', (b'ok',)) 3302 repo.set_make_working_trees(True) 3303 repo.set_make_working_trees(False) 3304 3305 3306class TestRepositoryUnlock(TestRemoteRepository): 3307 3308 def test_unlock(self): 3309 transport_path = 'quack' 3310 repo, client = self.setup_fake_client_and_repository(transport_path) 3311 client.add_success_response(b'ok', b'a token') 3312 client.add_success_response(b'ok') 3313 repo.lock_write() 3314 repo.unlock() 3315 self.assertEqual( 3316 [('call', b'Repository.lock_write', (b'quack/', b'')), 3317 ('call', b'Repository.unlock', (b'quack/', b'a token'))], 3318 client._calls) 3319 3320 def test_unlock_wrong_token(self): 3321 # If somehow the token is wrong, unlock will raise TokenMismatch. 3322 transport_path = 'quack' 3323 repo, client = self.setup_fake_client_and_repository(transport_path) 3324 client.add_success_response(b'ok', b'a token') 3325 client.add_error_response(b'TokenMismatch') 3326 repo.lock_write() 3327 self.assertRaises(errors.TokenMismatch, repo.unlock) 3328 3329 3330class TestRepositoryHasRevision(TestRemoteRepository): 3331 3332 def test_none(self): 3333 # repo.has_revision(None) should not cause any traffic. 3334 transport_path = 'quack' 3335 repo, client = self.setup_fake_client_and_repository(transport_path) 3336 3337 # The null revision is always there, so has_revision(None) == True. 3338 self.assertEqual(True, repo.has_revision(NULL_REVISION)) 3339 3340 # The remote repo shouldn't be accessed. 3341 self.assertEqual([], client._calls) 3342 3343 3344class TestRepositoryIterFilesBytes(TestRemoteRepository): 3345 """Test Repository.iter_file_bytes.""" 3346 3347 def test_single(self): 3348 transport_path = 'quack' 3349 repo, client = self.setup_fake_client_and_repository(transport_path) 3350 client.add_expected_call( 3351 b'Repository.iter_files_bytes', (b'quack/', ), 3352 b'success', (b'ok',), iter([b"ok\x000", b"\n", zlib.compress(b"mydata" * 10)])) 3353 for (identifier, byte_stream) in repo.iter_files_bytes([(b"somefile", 3354 b"somerev", b"myid")]): 3355 self.assertEqual(b"myid", identifier) 3356 self.assertEqual(b"".join(byte_stream), b"mydata" * 10) 3357 3358 def test_missing(self): 3359 transport_path = 'quack' 3360 repo, client = self.setup_fake_client_and_repository(transport_path) 3361 client.add_expected_call( 3362 b'Repository.iter_files_bytes', 3363 (b'quack/', ), 3364 b'error', (b'RevisionNotPresent', b'somefile', b'somerev'), 3365 iter([b"absent\0somefile\0somerev\n"])) 3366 self.assertRaises(errors.RevisionNotPresent, list, 3367 repo.iter_files_bytes( 3368 [(b"somefile", b"somerev", b"myid")])) 3369 3370 3371class TestRepositoryInsertStreamBase(TestRemoteRepository): 3372 """Base class for Repository.insert_stream and .insert_stream_1.19 3373 tests. 3374 """ 3375 3376 def checkInsertEmptyStream(self, repo, client): 3377 """Insert an empty stream, checking the result. 3378 3379 This checks that there are no resume_tokens or missing_keys, and that 3380 the client is finished. 3381 """ 3382 sink = repo._get_sink() 3383 fmt = repository.format_registry.get_default() 3384 resume_tokens, missing_keys = sink.insert_stream([], fmt, []) 3385 self.assertEqual([], resume_tokens) 3386 self.assertEqual(set(), missing_keys) 3387 self.assertFinished(client) 3388 3389 3390class TestRepositoryInsertStream(TestRepositoryInsertStreamBase): 3391 """Tests for using Repository.insert_stream verb when the _1.19 variant is 3392 not available. 3393 3394 This test case is very similar to TestRepositoryInsertStream_1_19. 3395 """ 3396 3397 def setUp(self): 3398 super(TestRepositoryInsertStream, self).setUp() 3399 self.disable_verb(b'Repository.insert_stream_1.19') 3400 3401 def test_unlocked_repo(self): 3402 transport_path = 'quack' 3403 repo, client = self.setup_fake_client_and_repository(transport_path) 3404 client.add_expected_call( 3405 b'Repository.insert_stream_1.19', (b'quack/', b''), 3406 b'unknown', (b'Repository.insert_stream_1.19',)) 3407 client.add_expected_call( 3408 b'Repository.insert_stream', (b'quack/', b''), 3409 b'success', (b'ok',)) 3410 client.add_expected_call( 3411 b'Repository.insert_stream', (b'quack/', b''), 3412 b'success', (b'ok',)) 3413 self.checkInsertEmptyStream(repo, client) 3414 3415 def test_locked_repo_with_no_lock_token(self): 3416 transport_path = 'quack' 3417 repo, client = self.setup_fake_client_and_repository(transport_path) 3418 client.add_expected_call( 3419 b'Repository.lock_write', (b'quack/', b''), 3420 b'success', (b'ok', b'')) 3421 client.add_expected_call( 3422 b'Repository.insert_stream_1.19', (b'quack/', b''), 3423 b'unknown', (b'Repository.insert_stream_1.19',)) 3424 client.add_expected_call( 3425 b'Repository.insert_stream', (b'quack/', b''), 3426 b'success', (b'ok',)) 3427 client.add_expected_call( 3428 b'Repository.insert_stream', (b'quack/', b''), 3429 b'success', (b'ok',)) 3430 repo.lock_write() 3431 self.checkInsertEmptyStream(repo, client) 3432 3433 def test_locked_repo_with_lock_token(self): 3434 transport_path = 'quack' 3435 repo, client = self.setup_fake_client_and_repository(transport_path) 3436 client.add_expected_call( 3437 b'Repository.lock_write', (b'quack/', b''), 3438 b'success', (b'ok', b'a token')) 3439 client.add_expected_call( 3440 b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'), 3441 b'unknown', (b'Repository.insert_stream_1.19',)) 3442 client.add_expected_call( 3443 b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'), 3444 b'success', (b'ok',)) 3445 client.add_expected_call( 3446 b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'), 3447 b'success', (b'ok',)) 3448 repo.lock_write() 3449 self.checkInsertEmptyStream(repo, client) 3450 3451 def test_stream_with_inventory_deltas(self): 3452 """'inventory-deltas' substreams cannot be sent to the 3453 Repository.insert_stream verb, because not all servers that implement 3454 that verb will accept them. So when one is encountered the RemoteSink 3455 immediately stops using that verb and falls back to VFS insert_stream. 3456 """ 3457 transport_path = 'quack' 3458 repo, client = self.setup_fake_client_and_repository(transport_path) 3459 client.add_expected_call( 3460 b'Repository.insert_stream_1.19', (b'quack/', b''), 3461 b'unknown', (b'Repository.insert_stream_1.19',)) 3462 client.add_expected_call( 3463 b'Repository.insert_stream', (b'quack/', b''), 3464 b'success', (b'ok',)) 3465 client.add_expected_call( 3466 b'Repository.insert_stream', (b'quack/', b''), 3467 b'success', (b'ok',)) 3468 # Create a fake real repository for insert_stream to fall back on, so 3469 # that we can directly see the records the RemoteSink passes to the 3470 # real sink. 3471 3472 class FakeRealSink: 3473 def __init__(self): 3474 self.records = [] 3475 3476 def insert_stream(self, stream, src_format, resume_tokens): 3477 for substream_kind, substream in stream: 3478 self.records.append( 3479 (substream_kind, [record.key for record in substream])) 3480 return [b'fake tokens'], [b'fake missing keys'] 3481 fake_real_sink = FakeRealSink() 3482 3483 class FakeRealRepository: 3484 def _get_sink(self): 3485 return fake_real_sink 3486 3487 def is_in_write_group(self): 3488 return False 3489 3490 def refresh_data(self): 3491 return True 3492 repo._real_repository = FakeRealRepository() 3493 sink = repo._get_sink() 3494 fmt = repository.format_registry.get_default() 3495 stream = self.make_stream_with_inv_deltas(fmt) 3496 resume_tokens, missing_keys = sink.insert_stream(stream, fmt, []) 3497 # Every record from the first inventory delta should have been sent to 3498 # the VFS sink. 3499 expected_records = [ 3500 ('inventory-deltas', [(b'rev2',), (b'rev3',)]), 3501 ('texts', [(b'some-rev', b'some-file')])] 3502 self.assertEqual(expected_records, fake_real_sink.records) 3503 # The return values from the real sink's insert_stream are propagated 3504 # back to the original caller. 3505 self.assertEqual([b'fake tokens'], resume_tokens) 3506 self.assertEqual([b'fake missing keys'], missing_keys) 3507 self.assertFinished(client) 3508 3509 def make_stream_with_inv_deltas(self, fmt): 3510 """Make a simple stream with an inventory delta followed by more 3511 records and more substreams to test that all records and substreams 3512 from that point on are used. 3513 3514 This sends, in order: 3515 * inventories substream: rev1, rev2, rev3. rev2 and rev3 are 3516 inventory-deltas. 3517 * texts substream: (some-rev, some-file) 3518 """ 3519 # Define a stream using generators so that it isn't rewindable. 3520 inv = inventory.Inventory(revision_id=b'rev1') 3521 inv.root.revision = b'rev1' 3522 3523 def stream_with_inv_delta(): 3524 yield ('inventories', inventories_substream()) 3525 yield ('inventory-deltas', inventory_delta_substream()) 3526 yield ('texts', [ 3527 versionedfile.FulltextContentFactory( 3528 (b'some-rev', b'some-file'), (), None, b'content')]) 3529 3530 def inventories_substream(): 3531 # An empty inventory fulltext. This will be streamed normally. 3532 chunks = fmt._serializer.write_inventory_to_lines(inv) 3533 yield versionedfile.ChunkedContentFactory( 3534 (b'rev1',), (), None, chunks, chunks_are_lines=True) 3535 3536 def inventory_delta_substream(): 3537 # An inventory delta. This can't be streamed via this verb, so it 3538 # will trigger a fallback to VFS insert_stream. 3539 entry = inv.make_entry( 3540 'directory', 'newdir', inv.root.file_id, b'newdir-id') 3541 entry.revision = b'ghost' 3542 delta = [(None, 'newdir', b'newdir-id', entry)] 3543 serializer = inventory_delta.InventoryDeltaSerializer( 3544 versioned_root=True, tree_references=False) 3545 lines = serializer.delta_to_lines(b'rev1', b'rev2', delta) 3546 yield versionedfile.ChunkedContentFactory( 3547 (b'rev2',), ((b'rev1',)), None, lines) 3548 # Another delta. 3549 lines = serializer.delta_to_lines(b'rev1', b'rev3', delta) 3550 yield versionedfile.ChunkedContentFactory( 3551 (b'rev3',), ((b'rev1',)), None, lines) 3552 return stream_with_inv_delta() 3553 3554 3555class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase): 3556 3557 def test_unlocked_repo(self): 3558 transport_path = 'quack' 3559 repo, client = self.setup_fake_client_and_repository(transport_path) 3560 client.add_expected_call( 3561 b'Repository.insert_stream_1.19', (b'quack/', b''), 3562 b'success', (b'ok',)) 3563 client.add_expected_call( 3564 b'Repository.insert_stream_1.19', (b'quack/', b''), 3565 b'success', (b'ok',)) 3566 self.checkInsertEmptyStream(repo, client) 3567 3568 def test_locked_repo_with_no_lock_token(self): 3569 transport_path = 'quack' 3570 repo, client = self.setup_fake_client_and_repository(transport_path) 3571 client.add_expected_call( 3572 b'Repository.lock_write', (b'quack/', b''), 3573 b'success', (b'ok', b'')) 3574 client.add_expected_call( 3575 b'Repository.insert_stream_1.19', (b'quack/', b''), 3576 b'success', (b'ok',)) 3577 client.add_expected_call( 3578 b'Repository.insert_stream_1.19', (b'quack/', b''), 3579 b'success', (b'ok',)) 3580 repo.lock_write() 3581 self.checkInsertEmptyStream(repo, client) 3582 3583 def test_locked_repo_with_lock_token(self): 3584 transport_path = 'quack' 3585 repo, client = self.setup_fake_client_and_repository(transport_path) 3586 client.add_expected_call( 3587 b'Repository.lock_write', (b'quack/', b''), 3588 b'success', (b'ok', b'a token')) 3589 client.add_expected_call( 3590 b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'), 3591 b'success', (b'ok',)) 3592 client.add_expected_call( 3593 b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'), 3594 b'success', (b'ok',)) 3595 repo.lock_write() 3596 self.checkInsertEmptyStream(repo, client) 3597 3598 3599class TestRepositoryTarball(TestRemoteRepository): 3600 3601 # This is a canned tarball reponse we can validate against 3602 tarball_content = base64.b64decode( 3603 'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz' 3604 'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY' 3605 'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk' 3606 'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA' 3607 'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB' 3608 'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs' 3609 'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt' 3610 'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n' 3611 '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC' 3612 'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV' 3613 'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy' 3614 'nWQ7QH/F3JFOFCQ0aSPfA=' 3615 ) 3616 3617 def test_repository_tarball(self): 3618 # Test that Repository.tarball generates the right operations 3619 transport_path = 'repo' 3620 expected_calls = [('call_expecting_body', b'Repository.tarball', 3621 (b'repo/', b'bz2',),), 3622 ] 3623 repo, client = self.setup_fake_client_and_repository(transport_path) 3624 client.add_success_response_with_body(self.tarball_content, b'ok') 3625 # Now actually ask for the tarball 3626 tarball_file = repo._get_tarball('bz2') 3627 try: 3628 self.assertEqual(expected_calls, client._calls) 3629 self.assertEqual(self.tarball_content, tarball_file.read()) 3630 finally: 3631 tarball_file.close() 3632 3633 3634class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport): 3635 """RemoteRepository.copy_content_into optimizations""" 3636 3637 def test_copy_content_remote_to_local(self): 3638 self.transport_server = test_server.SmartTCPServer_for_testing 3639 src_repo = self.make_repository('repo1') 3640 src_repo = repository.Repository.open(self.get_url('repo1')) 3641 # At the moment the tarball-based copy_content_into can't write back 3642 # into a smart server. It would be good if it could upload the 3643 # tarball; once that works we'd have to create repositories of 3644 # different formats. -- mbp 20070410 3645 dest_url = self.get_vfs_only_url('repo2') 3646 dest_bzrdir = BzrDir.create(dest_url) 3647 dest_repo = dest_bzrdir.create_repository() 3648 self.assertFalse(isinstance(dest_repo, RemoteRepository)) 3649 self.assertTrue(isinstance(src_repo, RemoteRepository)) 3650 src_repo.copy_content_into(dest_repo) 3651 3652 3653class _StubRealPackRepository(object): 3654 3655 def __init__(self, calls): 3656 self.calls = calls 3657 self._pack_collection = _StubPackCollection(calls) 3658 3659 def start_write_group(self): 3660 self.calls.append(('start_write_group',)) 3661 3662 def is_in_write_group(self): 3663 return False 3664 3665 def refresh_data(self): 3666 self.calls.append(('pack collection reload_pack_names',)) 3667 3668 3669class _StubPackCollection(object): 3670 3671 def __init__(self, calls): 3672 self.calls = calls 3673 3674 def autopack(self): 3675 self.calls.append(('pack collection autopack',)) 3676 3677 3678class TestRemotePackRepositoryAutoPack(TestRemoteRepository): 3679 """Tests for RemoteRepository.autopack implementation.""" 3680 3681 def test_ok(self): 3682 """When the server returns 'ok' and there's no _real_repository, then 3683 nothing else happens: the autopack method is done. 3684 """ 3685 transport_path = 'quack' 3686 repo, client = self.setup_fake_client_and_repository(transport_path) 3687 client.add_expected_call( 3688 b'PackRepository.autopack', (b'quack/',), b'success', (b'ok',)) 3689 repo.autopack() 3690 self.assertFinished(client) 3691 3692 def test_ok_with_real_repo(self): 3693 """When the server returns 'ok' and there is a _real_repository, then 3694 the _real_repository's reload_pack_name's method will be called. 3695 """ 3696 transport_path = 'quack' 3697 repo, client = self.setup_fake_client_and_repository(transport_path) 3698 client.add_expected_call( 3699 b'PackRepository.autopack', (b'quack/',), 3700 b'success', (b'ok',)) 3701 repo._real_repository = _StubRealPackRepository(client._calls) 3702 repo.autopack() 3703 self.assertEqual( 3704 [('call', b'PackRepository.autopack', (b'quack/',)), 3705 ('pack collection reload_pack_names',)], 3706 client._calls) 3707 3708 def test_backwards_compatibility(self): 3709 """If the server does not recognise the PackRepository.autopack verb, 3710 fallback to the real_repository's implementation. 3711 """ 3712 transport_path = 'quack' 3713 repo, client = self.setup_fake_client_and_repository(transport_path) 3714 client.add_unknown_method_response(b'PackRepository.autopack') 3715 3716 def stub_ensure_real(): 3717 client._calls.append(('_ensure_real',)) 3718 repo._real_repository = _StubRealPackRepository(client._calls) 3719 repo._ensure_real = stub_ensure_real 3720 repo.autopack() 3721 self.assertEqual( 3722 [('call', b'PackRepository.autopack', (b'quack/',)), 3723 ('_ensure_real',), 3724 ('pack collection autopack',)], 3725 client._calls) 3726 3727 def test_oom_error_reporting(self): 3728 """An out-of-memory condition on the server is reported clearly""" 3729 transport_path = 'quack' 3730 repo, client = self.setup_fake_client_and_repository(transport_path) 3731 client.add_expected_call( 3732 b'PackRepository.autopack', (b'quack/',), 3733 b'error', (b'MemoryError',)) 3734 err = self.assertRaises(errors.BzrError, repo.autopack) 3735 self.assertContainsRe(str(err), "^remote server out of mem") 3736 3737 3738class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport): 3739 """Base class for unit tests for breezy.bzr.remote._translate_error.""" 3740 3741 def translateTuple(self, error_tuple, **context): 3742 """Call _translate_error with an ErrorFromSmartServer built from the 3743 given error_tuple. 3744 3745 :param error_tuple: A tuple of a smart server response, as would be 3746 passed to an ErrorFromSmartServer. 3747 :kwargs context: context items to call _translate_error with. 3748 3749 :returns: The error raised by _translate_error. 3750 """ 3751 # Raise the ErrorFromSmartServer before passing it as an argument, 3752 # because _translate_error may need to re-raise it with a bare 'raise' 3753 # statement. 3754 server_error = errors.ErrorFromSmartServer(error_tuple) 3755 translated_error = self.translateErrorFromSmartServer( 3756 server_error, **context) 3757 return translated_error 3758 3759 def translateErrorFromSmartServer(self, error_object, **context): 3760 """Like translateTuple, but takes an already constructed 3761 ErrorFromSmartServer rather than a tuple. 3762 """ 3763 try: 3764 raise error_object 3765 except errors.ErrorFromSmartServer as server_error: 3766 translated_error = self.assertRaises( 3767 errors.BzrError, remote._translate_error, server_error, 3768 **context) 3769 return translated_error 3770 3771 3772class TestErrorTranslationSuccess(TestErrorTranslationBase): 3773 """Unit tests for breezy.bzr.remote._translate_error. 3774 3775 Given an ErrorFromSmartServer (which has an error tuple from a smart 3776 server) and some context, _translate_error raises more specific errors from 3777 breezy.errors. 3778 3779 This test case covers the cases where _translate_error succeeds in 3780 translating an ErrorFromSmartServer to something better. See 3781 TestErrorTranslationRobustness for other cases. 3782 """ 3783 3784 def test_NoSuchRevision(self): 3785 branch = self.make_branch('') 3786 revid = b'revid' 3787 translated_error = self.translateTuple( 3788 (b'NoSuchRevision', revid), branch=branch) 3789 expected_error = errors.NoSuchRevision(branch, revid) 3790 self.assertEqual(expected_error, translated_error) 3791 3792 def test_nosuchrevision(self): 3793 repository = self.make_repository('') 3794 revid = b'revid' 3795 translated_error = self.translateTuple( 3796 (b'nosuchrevision', revid), repository=repository) 3797 expected_error = errors.NoSuchRevision(repository, revid) 3798 self.assertEqual(expected_error, translated_error) 3799 3800 def test_nobranch(self): 3801 bzrdir = self.make_controldir('') 3802 translated_error = self.translateTuple((b'nobranch',), bzrdir=bzrdir) 3803 expected_error = errors.NotBranchError(path=bzrdir.root_transport.base) 3804 self.assertEqual(expected_error, translated_error) 3805 3806 def test_nobranch_one_arg(self): 3807 bzrdir = self.make_controldir('') 3808 translated_error = self.translateTuple( 3809 (b'nobranch', b'extra detail'), bzrdir=bzrdir) 3810 expected_error = errors.NotBranchError( 3811 path=bzrdir.root_transport.base, 3812 detail='extra detail') 3813 self.assertEqual(expected_error, translated_error) 3814 3815 def test_norepository(self): 3816 bzrdir = self.make_controldir('') 3817 translated_error = self.translateTuple((b'norepository',), 3818 bzrdir=bzrdir) 3819 expected_error = errors.NoRepositoryPresent(bzrdir) 3820 self.assertEqual(expected_error, translated_error) 3821 3822 def test_LockContention(self): 3823 translated_error = self.translateTuple((b'LockContention',)) 3824 expected_error = errors.LockContention('(remote lock)') 3825 self.assertEqual(expected_error, translated_error) 3826 3827 def test_UnlockableTransport(self): 3828 bzrdir = self.make_controldir('') 3829 translated_error = self.translateTuple( 3830 (b'UnlockableTransport',), bzrdir=bzrdir) 3831 expected_error = errors.UnlockableTransport(bzrdir.root_transport) 3832 self.assertEqual(expected_error, translated_error) 3833 3834 def test_LockFailed(self): 3835 lock = 'str() of a server lock' 3836 why = 'str() of why' 3837 translated_error = self.translateTuple( 3838 (b'LockFailed', lock.encode('ascii'), why.encode('ascii'))) 3839 expected_error = errors.LockFailed(lock, why) 3840 self.assertEqual(expected_error, translated_error) 3841 3842 def test_TokenMismatch(self): 3843 token = 'a lock token' 3844 translated_error = self.translateTuple( 3845 (b'TokenMismatch',), token=token) 3846 expected_error = errors.TokenMismatch(token, '(remote token)') 3847 self.assertEqual(expected_error, translated_error) 3848 3849 def test_Diverged(self): 3850 branch = self.make_branch('a') 3851 other_branch = self.make_branch('b') 3852 translated_error = self.translateTuple( 3853 (b'Diverged',), branch=branch, other_branch=other_branch) 3854 expected_error = errors.DivergedBranches(branch, other_branch) 3855 self.assertEqual(expected_error, translated_error) 3856 3857 def test_NotStacked(self): 3858 branch = self.make_branch('') 3859 translated_error = self.translateTuple((b'NotStacked',), branch=branch) 3860 expected_error = errors.NotStacked(branch) 3861 self.assertEqual(expected_error, translated_error) 3862 3863 def test_ReadError_no_args(self): 3864 path = 'a path' 3865 translated_error = self.translateTuple((b'ReadError',), path=path) 3866 expected_error = errors.ReadError(path) 3867 self.assertEqual(expected_error, translated_error) 3868 3869 def test_ReadError(self): 3870 path = 'a path' 3871 translated_error = self.translateTuple( 3872 (b'ReadError', path.encode('utf-8'))) 3873 expected_error = errors.ReadError(path) 3874 self.assertEqual(expected_error, translated_error) 3875 3876 def test_IncompatibleRepositories(self): 3877 translated_error = self.translateTuple((b'IncompatibleRepositories', 3878 b"repo1", b"repo2", b"details here")) 3879 expected_error = errors.IncompatibleRepositories("repo1", "repo2", 3880 "details here") 3881 self.assertEqual(expected_error, translated_error) 3882 3883 def test_GhostRevisionsHaveNoRevno(self): 3884 translated_error = self.translateTuple((b'GhostRevisionsHaveNoRevno', 3885 b"revid1", b"revid2")) 3886 expected_error = errors.GhostRevisionsHaveNoRevno(b"revid1", b"revid2") 3887 self.assertEqual(expected_error, translated_error) 3888 3889 def test_PermissionDenied_no_args(self): 3890 path = 'a path' 3891 translated_error = self.translateTuple((b'PermissionDenied',), 3892 path=path) 3893 expected_error = errors.PermissionDenied(path) 3894 self.assertEqual(expected_error, translated_error) 3895 3896 def test_PermissionDenied_one_arg(self): 3897 path = 'a path' 3898 translated_error = self.translateTuple( 3899 (b'PermissionDenied', path.encode('utf-8'))) 3900 expected_error = errors.PermissionDenied(path) 3901 self.assertEqual(expected_error, translated_error) 3902 3903 def test_PermissionDenied_one_arg_and_context(self): 3904 """Given a choice between a path from the local context and a path on 3905 the wire, _translate_error prefers the path from the local context. 3906 """ 3907 local_path = 'local path' 3908 remote_path = 'remote path' 3909 translated_error = self.translateTuple( 3910 (b'PermissionDenied', remote_path.encode('utf-8')), path=local_path) 3911 expected_error = errors.PermissionDenied(local_path) 3912 self.assertEqual(expected_error, translated_error) 3913 3914 def test_PermissionDenied_two_args(self): 3915 path = 'a path' 3916 extra = 'a string with extra info' 3917 translated_error = self.translateTuple( 3918 (b'PermissionDenied', path.encode('utf-8'), extra.encode('utf-8'))) 3919 expected_error = errors.PermissionDenied(path, extra) 3920 self.assertEqual(expected_error, translated_error) 3921 3922 # GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra' 3923 3924 def test_NoSuchFile_context_path(self): 3925 local_path = "local path" 3926 translated_error = self.translateTuple((b'ReadError', b"remote path"), 3927 path=local_path) 3928 expected_error = errors.ReadError(local_path) 3929 self.assertEqual(expected_error, translated_error) 3930 3931 def test_NoSuchFile_without_context(self): 3932 remote_path = "remote path" 3933 translated_error = self.translateTuple( 3934 (b'ReadError', remote_path.encode('utf-8'))) 3935 expected_error = errors.ReadError(remote_path) 3936 self.assertEqual(expected_error, translated_error) 3937 3938 def test_ReadOnlyError(self): 3939 translated_error = self.translateTuple((b'ReadOnlyError',)) 3940 expected_error = errors.TransportNotPossible("readonly transport") 3941 self.assertEqual(expected_error, translated_error) 3942 3943 def test_MemoryError(self): 3944 translated_error = self.translateTuple((b'MemoryError',)) 3945 self.assertStartsWith(str(translated_error), 3946 "remote server out of memory") 3947 3948 def test_generic_IndexError_no_classname(self): 3949 err = errors.ErrorFromSmartServer( 3950 (b'error', b"list index out of range")) 3951 translated_error = self.translateErrorFromSmartServer(err) 3952 expected_error = errors.UnknownErrorFromSmartServer(err) 3953 self.assertEqual(expected_error, translated_error) 3954 3955 # GZ 2011-03-02: TODO test generic non-ascii error string 3956 3957 def test_generic_KeyError(self): 3958 err = errors.ErrorFromSmartServer((b'error', b'KeyError', b"1")) 3959 translated_error = self.translateErrorFromSmartServer(err) 3960 expected_error = errors.UnknownErrorFromSmartServer(err) 3961 self.assertEqual(expected_error, translated_error) 3962 3963 def test_RevnoOutOfBounds(self): 3964 translated_error = self.translateTuple( 3965 ((b'revno-outofbounds', 5, 0, 3)), path=b'path') 3966 expected_error = errors.RevnoOutOfBounds(5, (0, 3)) 3967 self.assertEqual(expected_error, translated_error) 3968 3969 3970class TestErrorTranslationRobustness(TestErrorTranslationBase): 3971 """Unit tests for breezy.bzr.remote._translate_error's robustness. 3972 3973 TestErrorTranslationSuccess is for cases where _translate_error can 3974 translate successfully. This class about how _translate_err behaves when 3975 it fails to translate: it re-raises the original error. 3976 """ 3977 3978 def test_unrecognised_server_error(self): 3979 """If the error code from the server is not recognised, the original 3980 ErrorFromSmartServer is propagated unmodified. 3981 """ 3982 error_tuple = (b'An unknown error tuple',) 3983 server_error = errors.ErrorFromSmartServer(error_tuple) 3984 translated_error = self.translateErrorFromSmartServer(server_error) 3985 expected_error = errors.UnknownErrorFromSmartServer(server_error) 3986 self.assertEqual(expected_error, translated_error) 3987 3988 def test_context_missing_a_key(self): 3989 """In case of a bug in the client, or perhaps an unexpected response 3990 from a server, _translate_error returns the original error tuple from 3991 the server and mutters a warning. 3992 """ 3993 # To translate a NoSuchRevision error _translate_error needs a 'branch' 3994 # in the context dict. So let's give it an empty context dict instead 3995 # to exercise its error recovery. 3996 error_tuple = (b'NoSuchRevision', b'revid') 3997 server_error = errors.ErrorFromSmartServer(error_tuple) 3998 translated_error = self.translateErrorFromSmartServer(server_error) 3999 self.assertEqual(server_error, translated_error) 4000 # In addition to re-raising ErrorFromSmartServer, some debug info has 4001 # been muttered to the log file for developer to look at. 4002 self.assertContainsRe( 4003 self.get_log(), 4004 "Missing key 'branch' in context") 4005 4006 def test_path_missing(self): 4007 """Some translations (PermissionDenied, ReadError) can determine the 4008 'path' variable from either the wire or the local context. If neither 4009 has it, then an error is raised. 4010 """ 4011 error_tuple = (b'ReadError',) 4012 server_error = errors.ErrorFromSmartServer(error_tuple) 4013 translated_error = self.translateErrorFromSmartServer(server_error) 4014 self.assertEqual(server_error, translated_error) 4015 # In addition to re-raising ErrorFromSmartServer, some debug info has 4016 # been muttered to the log file for developer to look at. 4017 self.assertContainsRe(self.get_log(), "Missing key 'path' in context") 4018 4019 4020class TestStacking(tests.TestCaseWithTransport): 4021 """Tests for operations on stacked remote repositories. 4022 4023 The underlying format type must support stacking. 4024 """ 4025 4026 def test_access_stacked_remote(self): 4027 # based on <http://launchpad.net/bugs/261315> 4028 # make a branch stacked on another repository containing an empty 4029 # revision, then open it over hpss - we should be able to see that 4030 # revision. 4031 base_builder = self.make_branch_builder('base', format='1.9') 4032 base_builder.start_series() 4033 base_revid = base_builder.build_snapshot(None, 4034 [('add', ('', None, 'directory', None))], 4035 'message', revision_id=b'rev-id') 4036 base_builder.finish_series() 4037 stacked_branch = self.make_branch('stacked', format='1.9') 4038 stacked_branch.set_stacked_on_url('../base') 4039 # start a server looking at this 4040 smart_server = test_server.SmartTCPServer_for_testing() 4041 self.start_server(smart_server) 4042 remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked') 4043 # can get its branch and repository 4044 remote_branch = remote_bzrdir.open_branch() 4045 remote_repo = remote_branch.repository 4046 remote_repo.lock_read() 4047 try: 4048 # it should have an appropriate fallback repository, which should also 4049 # be a RemoteRepository 4050 self.assertLength(1, remote_repo._fallback_repositories) 4051 self.assertIsInstance(remote_repo._fallback_repositories[0], 4052 RemoteRepository) 4053 # and it has the revision committed to the underlying repository; 4054 # these have varying implementations so we try several of them 4055 self.assertTrue(remote_repo.has_revisions([base_revid])) 4056 self.assertTrue(remote_repo.has_revision(base_revid)) 4057 self.assertEqual(remote_repo.get_revision(base_revid).message, 4058 'message') 4059 finally: 4060 remote_repo.unlock() 4061 4062 def prepare_stacked_remote_branch(self): 4063 """Get stacked_upon and stacked branches with content in each.""" 4064 self.setup_smart_server_with_call_log() 4065 tree1 = self.make_branch_and_tree('tree1', format='1.9') 4066 tree1.commit('rev1', rev_id=b'rev1') 4067 tree2 = tree1.branch.controldir.sprout('tree2', stacked=True 4068 ).open_workingtree() 4069 local_tree = tree2.branch.create_checkout('local') 4070 local_tree.commit('local changes make me feel good.') 4071 branch2 = Branch.open(self.get_url('tree2')) 4072 branch2.lock_read() 4073 self.addCleanup(branch2.unlock) 4074 return tree1.branch, branch2 4075 4076 def test_stacked_get_parent_map(self): 4077 # the public implementation of get_parent_map obeys stacking 4078 _, branch = self.prepare_stacked_remote_branch() 4079 repo = branch.repository 4080 self.assertEqual({b'rev1'}, set(repo.get_parent_map([b'rev1']))) 4081 4082 def test_unstacked_get_parent_map(self): 4083 # _unstacked_provider.get_parent_map ignores stacking 4084 _, branch = self.prepare_stacked_remote_branch() 4085 provider = branch.repository._unstacked_provider 4086 self.assertEqual(set(), set(provider.get_parent_map([b'rev1']))) 4087 4088 def fetch_stream_to_rev_order(self, stream): 4089 result = [] 4090 for kind, substream in stream: 4091 if not kind == 'revisions': 4092 list(substream) 4093 else: 4094 for content in substream: 4095 result.append(content.key[-1]) 4096 return result 4097 4098 def get_ordered_revs(self, format, order, branch_factory=None): 4099 """Get a list of the revisions in a stream to format format. 4100 4101 :param format: The format of the target. 4102 :param order: the order that target should have requested. 4103 :param branch_factory: A callable to create a trunk and stacked branch 4104 to fetch from. If none, self.prepare_stacked_remote_branch is used. 4105 :result: The revision ids in the stream, in the order seen, 4106 the topological order of revisions in the source. 4107 """ 4108 unordered_format = controldir.format_registry.get(format)() 4109 target_repository_format = unordered_format.repository_format 4110 # Cross check 4111 self.assertEqual(order, target_repository_format._fetch_order) 4112 if branch_factory is None: 4113 branch_factory = self.prepare_stacked_remote_branch 4114 _, stacked = branch_factory() 4115 source = stacked.repository._get_source(target_repository_format) 4116 tip = stacked.last_revision() 4117 stacked.repository._ensure_real() 4118 graph = stacked.repository.get_graph() 4119 revs = [r for (r, ps) in graph.iter_ancestry([tip]) 4120 if r != NULL_REVISION] 4121 revs.reverse() 4122 search = vf_search.PendingAncestryResult([tip], stacked.repository) 4123 self.reset_smart_call_log() 4124 stream = source.get_stream(search) 4125 # We trust that if a revision is in the stream the rest of the new 4126 # content for it is too, as per our main fetch tests; here we are 4127 # checking that the revisions are actually included at all, and their 4128 # order. 4129 return self.fetch_stream_to_rev_order(stream), revs 4130 4131 def test_stacked_get_stream_unordered(self): 4132 # Repository._get_source.get_stream() from a stacked repository with 4133 # unordered yields the full data from both stacked and stacked upon 4134 # sources. 4135 rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered') 4136 self.assertEqual(set(expected_revs), set(rev_ord)) 4137 # Getting unordered results should have made a streaming data request 4138 # from the server, then one from the backing branch. 4139 self.assertLength(2, self.hpss_calls) 4140 4141 def test_stacked_on_stacked_get_stream_unordered(self): 4142 # Repository._get_source.get_stream() from a stacked repository which 4143 # is itself stacked yields the full data from all three sources. 4144 def make_stacked_stacked(): 4145 _, stacked = self.prepare_stacked_remote_branch() 4146 tree = stacked.controldir.sprout('tree3', stacked=True 4147 ).open_workingtree() 4148 local_tree = tree.branch.create_checkout('local-tree3') 4149 local_tree.commit('more local changes are better') 4150 branch = Branch.open(self.get_url('tree3')) 4151 branch.lock_read() 4152 self.addCleanup(branch.unlock) 4153 return None, branch 4154 rev_ord, expected_revs = self.get_ordered_revs( 4155 '1.9', 'unordered', branch_factory=make_stacked_stacked) 4156 self.assertEqual(set(expected_revs), set(rev_ord)) 4157 # Getting unordered results should have made a streaming data request 4158 # from the server, and one from each backing repo 4159 self.assertLength(3, self.hpss_calls) 4160 4161 def test_stacked_get_stream_topological(self): 4162 # Repository._get_source.get_stream() from a stacked repository with 4163 # topological sorting yields the full data from both stacked and 4164 # stacked upon sources in topological order. 4165 rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological') 4166 self.assertEqual(expected_revs, rev_ord) 4167 # Getting topological sort requires VFS calls still - one of which is 4168 # pushing up from the bound branch. 4169 self.assertLength(14, self.hpss_calls) 4170 4171 def test_stacked_get_stream_groupcompress(self): 4172 # Repository._get_source.get_stream() from a stacked repository with 4173 # groupcompress sorting yields the full data from both stacked and 4174 # stacked upon sources in groupcompress order. 4175 raise tests.TestSkipped('No groupcompress ordered format available') 4176 rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress') 4177 self.assertEqual(expected_revs, reversed(rev_ord)) 4178 # Getting unordered results should have made a streaming data request 4179 # from the backing branch, and one from the stacked on branch. 4180 self.assertLength(2, self.hpss_calls) 4181 4182 def test_stacked_pull_more_than_stacking_has_bug_360791(self): 4183 # When pulling some fixed amount of content that is more than the 4184 # source has (because some is coming from a fallback branch, no error 4185 # should be received. This was reported as bug 360791. 4186 # Need three branches: a trunk, a stacked branch, and a preexisting 4187 # branch pulling content from stacked and trunk. 4188 self.setup_smart_server_with_call_log() 4189 trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root") 4190 trunk.commit('start') 4191 stacked_branch = trunk.branch.create_clone_on_transport( 4192 self.get_transport('stacked'), stacked_on=trunk.branch.base) 4193 local = self.make_branch('local', format='1.9-rich-root') 4194 local.repository.fetch(stacked_branch.repository, 4195 stacked_branch.last_revision()) 4196 4197 4198class TestRemoteBranchEffort(tests.TestCaseWithTransport): 4199 4200 def setUp(self): 4201 super(TestRemoteBranchEffort, self).setUp() 4202 # Create a smart server that publishes whatever the backing VFS server 4203 # does. 4204 self.smart_server = test_server.SmartTCPServer_for_testing() 4205 self.start_server(self.smart_server, self.get_server()) 4206 # Log all HPSS calls into self.hpss_calls. 4207 _SmartClient.hooks.install_named_hook( 4208 'call', self.capture_hpss_call, None) 4209 self.hpss_calls = [] 4210 4211 def capture_hpss_call(self, params): 4212 self.hpss_calls.append(params.method) 4213 4214 def test_copy_content_into_avoids_revision_history(self): 4215 local = self.make_branch('local') 4216 builder = self.make_branch_builder('remote') 4217 builder.build_commit(message="Commit.") 4218 remote_branch_url = self.smart_server.get_url() + 'remote' 4219 remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch() 4220 local.repository.fetch(remote_branch.repository) 4221 self.hpss_calls = [] 4222 remote_branch.copy_content_into(local) 4223 self.assertFalse(b'Branch.revision_history' in self.hpss_calls) 4224 4225 def test_fetch_everything_needs_just_one_call(self): 4226 local = self.make_branch('local') 4227 builder = self.make_branch_builder('remote') 4228 builder.build_commit(message="Commit.") 4229 remote_branch_url = self.smart_server.get_url() + 'remote' 4230 remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch() 4231 self.hpss_calls = [] 4232 local.repository.fetch( 4233 remote_branch.repository, 4234 fetch_spec=vf_search.EverythingResult(remote_branch.repository)) 4235 self.assertEqual([b'Repository.get_stream_1.19'], self.hpss_calls) 4236 4237 def override_verb(self, verb_name, verb): 4238 request_handlers = request.request_handlers 4239 orig_verb = request_handlers.get(verb_name) 4240 orig_info = request_handlers.get_info(verb_name) 4241 request_handlers.register(verb_name, verb, override_existing=True) 4242 self.addCleanup(request_handlers.register, verb_name, orig_verb, 4243 override_existing=True, info=orig_info) 4244 4245 def test_fetch_everything_backwards_compat(self): 4246 """Can fetch with EverythingResult even with pre 2.4 servers. 4247 4248 Pre-2.4 do not support 'everything' searches with the 4249 Repository.get_stream_1.19 verb. 4250 """ 4251 verb_log = [] 4252 4253 class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19): 4254 """A version of the Repository.get_stream_1.19 verb patched to 4255 reject 'everything' searches the way 2.3 and earlier do. 4256 """ 4257 4258 def recreate_search(self, repository, search_bytes, 4259 discard_excess=False): 4260 verb_log.append(search_bytes.split(b'\n', 1)[0]) 4261 if search_bytes == b'everything': 4262 return (None, 4263 request.FailedSmartServerResponse((b'BadSearch',))) 4264 return super(OldGetStreamVerb, 4265 self).recreate_search(repository, search_bytes, 4266 discard_excess=discard_excess) 4267 self.override_verb(b'Repository.get_stream_1.19', OldGetStreamVerb) 4268 local = self.make_branch('local') 4269 builder = self.make_branch_builder('remote') 4270 builder.build_commit(message="Commit.") 4271 remote_branch_url = self.smart_server.get_url() + 'remote' 4272 remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch() 4273 self.hpss_calls = [] 4274 local.repository.fetch( 4275 remote_branch.repository, 4276 fetch_spec=vf_search.EverythingResult(remote_branch.repository)) 4277 # make sure the overridden verb was used 4278 self.assertLength(1, verb_log) 4279 # more than one HPSS call is needed, but because it's a VFS callback 4280 # its hard to predict exactly how many. 4281 self.assertTrue(len(self.hpss_calls) > 1) 4282 4283 4284class TestUpdateBoundBranchWithModifiedBoundLocation( 4285 tests.TestCaseWithTransport): 4286 """Ensure correct handling of bound_location modifications. 4287 4288 This is tested against a smart server as http://pad.lv/786980 was about a 4289 ReadOnlyError (write attempt during a read-only transaction) which can only 4290 happen in this context. 4291 """ 4292 4293 def setUp(self): 4294 super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp() 4295 self.transport_server = test_server.SmartTCPServer_for_testing 4296 4297 def make_master_and_checkout(self, master_name, checkout_name): 4298 # Create the master branch and its associated checkout 4299 self.master = self.make_branch_and_tree(master_name) 4300 self.checkout = self.master.branch.create_checkout(checkout_name) 4301 # Modify the master branch so there is something to update 4302 self.master.commit('add stuff') 4303 self.last_revid = self.master.commit('even more stuff') 4304 self.bound_location = self.checkout.branch.get_bound_location() 4305 4306 def assertUpdateSucceeds(self, new_location): 4307 self.checkout.branch.set_bound_location(new_location) 4308 self.checkout.update() 4309 self.assertEqual(self.last_revid, self.checkout.last_revision()) 4310 4311 def test_without_final_slash(self): 4312 self.make_master_and_checkout('master', 'checkout') 4313 # For unclear reasons some users have a bound_location without a final 4314 # '/', simulate that by forcing such a value 4315 self.assertEndsWith(self.bound_location, '/') 4316 self.assertUpdateSucceeds(self.bound_location.rstrip('/')) 4317 4318 def test_plus_sign(self): 4319 self.make_master_and_checkout('+master', 'checkout') 4320 self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1)) 4321 4322 def test_tilda(self): 4323 # Embed ~ in the middle of the path just to avoid any $HOME 4324 # interpretation 4325 self.make_master_and_checkout('mas~ter', 'checkout') 4326 self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1)) 4327 4328 4329class TestWithCustomErrorHandler(RemoteBranchTestCase): 4330 4331 def test_no_context(self): 4332 class OutOfCoffee(errors.BzrError): 4333 """A dummy exception for testing.""" 4334 4335 def __init__(self, urgency): 4336 self.urgency = urgency 4337 remote.no_context_error_translators.register(b"OutOfCoffee", 4338 lambda err: OutOfCoffee(err.error_args[0])) 4339 transport = MemoryTransport() 4340 client = FakeClient(transport.base) 4341 client.add_expected_call( 4342 b'Branch.get_stacked_on_url', (b'quack/',), 4343 b'error', (b'NotStacked',)) 4344 client.add_expected_call( 4345 b'Branch.last_revision_info', 4346 (b'quack/',), 4347 b'error', (b'OutOfCoffee', b'low')) 4348 transport.mkdir('quack') 4349 transport = transport.clone('quack') 4350 branch = self.make_remote_branch(transport, client) 4351 self.assertRaises(OutOfCoffee, branch.last_revision_info) 4352 self.assertFinished(client) 4353 4354 def test_with_context(self): 4355 class OutOfTea(errors.BzrError): 4356 def __init__(self, branch, urgency): 4357 self.branch = branch 4358 self.urgency = urgency 4359 remote.error_translators.register(b"OutOfTea", 4360 lambda err, find, path: OutOfTea( 4361 err.error_args[0].decode( 4362 'utf-8'), 4363 find("branch"))) 4364 transport = MemoryTransport() 4365 client = FakeClient(transport.base) 4366 client.add_expected_call( 4367 b'Branch.get_stacked_on_url', (b'quack/',), 4368 b'error', (b'NotStacked',)) 4369 client.add_expected_call( 4370 b'Branch.last_revision_info', 4371 (b'quack/',), 4372 b'error', (b'OutOfTea', b'low')) 4373 transport.mkdir('quack') 4374 transport = transport.clone('quack') 4375 branch = self.make_remote_branch(transport, client) 4376 self.assertRaises(OutOfTea, branch.last_revision_info) 4377 self.assertFinished(client) 4378 4379 4380class TestRepositoryPack(TestRemoteRepository): 4381 4382 def test_pack(self): 4383 transport_path = 'quack' 4384 repo, client = self.setup_fake_client_and_repository(transport_path) 4385 client.add_expected_call( 4386 b'Repository.lock_write', (b'quack/', b''), 4387 b'success', (b'ok', b'token')) 4388 client.add_expected_call( 4389 b'Repository.pack', (b'quack/', b'token', b'False'), 4390 b'success', (b'ok',), ) 4391 client.add_expected_call( 4392 b'Repository.unlock', (b'quack/', b'token'), 4393 b'success', (b'ok', )) 4394 repo.pack() 4395 4396 def test_pack_with_hint(self): 4397 transport_path = 'quack' 4398 repo, client = self.setup_fake_client_and_repository(transport_path) 4399 client.add_expected_call( 4400 b'Repository.lock_write', (b'quack/', b''), 4401 b'success', (b'ok', b'token')) 4402 client.add_expected_call( 4403 b'Repository.pack', (b'quack/', b'token', b'False'), 4404 b'success', (b'ok',), ) 4405 client.add_expected_call( 4406 b'Repository.unlock', (b'quack/', b'token', b'False'), 4407 b'success', (b'ok', )) 4408 repo.pack(['hinta', 'hintb']) 4409 4410 4411class TestRepositoryIterInventories(TestRemoteRepository): 4412 """Test Repository.iter_inventories.""" 4413 4414 def _serialize_inv_delta(self, old_name, new_name, delta): 4415 serializer = inventory_delta.InventoryDeltaSerializer(True, False) 4416 return b"".join(serializer.delta_to_lines(old_name, new_name, delta)) 4417 4418 def test_single_empty(self): 4419 transport_path = 'quack' 4420 repo, client = self.setup_fake_client_and_repository(transport_path) 4421 fmt = controldir.format_registry.get('2a')().repository_format 4422 repo._format = fmt 4423 stream = [('inventory-deltas', [ 4424 versionedfile.FulltextContentFactory(b'somerevid', None, None, 4425 self._serialize_inv_delta(b'null:', b'somerevid', []))])] 4426 client.add_expected_call( 4427 b'VersionedFileRepository.get_inventories', ( 4428 b'quack/', b'unordered'), 4429 b'success', (b'ok', ), 4430 _stream_to_byte_stream(stream, fmt)) 4431 ret = list(repo.iter_inventories([b"somerevid"])) 4432 self.assertLength(1, ret) 4433 inv = ret[0] 4434 self.assertEqual(b"somerevid", inv.revision_id) 4435 4436 def test_empty(self): 4437 transport_path = 'quack' 4438 repo, client = self.setup_fake_client_and_repository(transport_path) 4439 ret = list(repo.iter_inventories([])) 4440 self.assertEqual(ret, []) 4441 4442 def test_missing(self): 4443 transport_path = 'quack' 4444 repo, client = self.setup_fake_client_and_repository(transport_path) 4445 client.add_expected_call( 4446 b'VersionedFileRepository.get_inventories', ( 4447 b'quack/', b'unordered'), 4448 b'success', (b'ok', ), iter([])) 4449 self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories( 4450 [b"somerevid"])) 4451 4452 4453class TestRepositoryRevisionTreeArchive(TestRemoteRepository): 4454 """Test Repository.iter_inventories.""" 4455 4456 def _serialize_inv_delta(self, old_name, new_name, delta): 4457 serializer = inventory_delta.InventoryDeltaSerializer(True, False) 4458 return b"".join(serializer.delta_to_lines(old_name, new_name, delta)) 4459 4460 def test_simple(self): 4461 transport_path = 'quack' 4462 repo, client = self.setup_fake_client_and_repository(transport_path) 4463 fmt = controldir.format_registry.get('2a')().repository_format 4464 repo._format = fmt 4465 stream = [('inventory-deltas', [ 4466 versionedfile.FulltextContentFactory(b'somerevid', None, None, 4467 self._serialize_inv_delta(b'null:', b'somerevid', []))])] 4468 client.add_expected_call( 4469 b'VersionedFileRepository.get_inventories', ( 4470 b'quack/', b'unordered'), 4471 b'success', (b'ok', ), 4472 _stream_to_byte_stream(stream, fmt)) 4473 f = BytesIO() 4474 with tarfile.open(mode='w', fileobj=f) as tf: 4475 info = tarfile.TarInfo('somefile') 4476 info.mtime = 432432 4477 contents = b'some data' 4478 info.type = tarfile.REGTYPE 4479 info.mode = 0o644 4480 info.size = len(contents) 4481 tf.addfile(info, BytesIO(contents)) 4482 client.add_expected_call( 4483 b'Repository.revision_archive', (b'quack/', 4484 b'somerevid', b'tar', b'foo.tar', b'', b'', None), 4485 b'success', (b'ok', ), 4486 f.getvalue()) 4487 tree = repo.revision_tree(b'somerevid') 4488 self.assertEqual(f.getvalue(), b''.join( 4489 tree.archive('tar', 'foo.tar'))) 4490 4491 4492class TestRepositoryAnnotate(TestRemoteRepository): 4493 """Test RemoteRevisionTree.annotate..""" 4494 4495 def _serialize_inv_delta(self, old_name, new_name, delta): 4496 serializer = inventory_delta.InventoryDeltaSerializer(True, False) 4497 return b"".join(serializer.delta_to_lines(old_name, new_name, delta)) 4498 4499 def test_simple(self): 4500 transport_path = 'quack' 4501 repo, client = self.setup_fake_client_and_repository(transport_path) 4502 fmt = controldir.format_registry.get('2a')().repository_format 4503 repo._format = fmt 4504 stream = [ 4505 ('inventory-deltas', [ 4506 versionedfile.FulltextContentFactory( 4507 b'somerevid', None, None, 4508 self._serialize_inv_delta(b'null:', b'somerevid', []))])] 4509 client.add_expected_call( 4510 b'VersionedFileRepository.get_inventories', ( 4511 b'quack/', b'unordered'), 4512 b'success', (b'ok', ), 4513 _stream_to_byte_stream(stream, fmt)) 4514 client.add_expected_call( 4515 b'Repository.annotate_file_revision', 4516 (b'quack/', b'somerevid', b'filename', b'', b'current:'), 4517 b'success', (b'ok', ), 4518 bencode.bencode([[b'baserevid', b'line 1\n'], 4519 [b'somerevid', b'line2\n']])) 4520 tree = repo.revision_tree(b'somerevid') 4521 self.assertEqual([ 4522 (b'baserevid', b'line 1\n'), 4523 (b'somerevid', b'line2\n')], 4524 list(tree.annotate_iter('filename'))) 4525 4526 4527class TestBranchGetAllReferenceInfo(RemoteBranchTestCase): 4528 4529 def test_get_all_reference_info(self): 4530 transport = MemoryTransport() 4531 client = FakeClient(transport.base) 4532 client.add_expected_call( 4533 b'Branch.get_stacked_on_url', (b'quack/',), 4534 b'error', (b'NotStacked',)) 4535 client.add_expected_call( 4536 b'Branch.get_all_reference_info', (b'quack/',), 4537 b'success', (b'ok',), bencode.bencode([ 4538 (b'file-id', b'https://www.example.com/', b'')])) 4539 transport.mkdir('quack') 4540 transport = transport.clone('quack') 4541 branch = self.make_remote_branch(transport, client) 4542 result = branch._get_all_reference_info() 4543 self.assertFinished(client) 4544 self.assertEqual({b'file-id': ('https://www.example.com/', None)}, result) 4545