~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

Merge from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
45
45
from bzrlib.revision import NULL_REVISION
46
46
from bzrlib.smart import server, medium
47
47
from bzrlib.smart.client import _SmartClient
 
48
from bzrlib.symbol_versioning import one_four
 
49
from bzrlib.transport import get_transport
48
50
from bzrlib.transport.memory import MemoryTransport
49
51
from bzrlib.transport.remote import RemoteTransport
50
52
 
55
57
        self.transport_server = server.SmartTCPServer_for_testing
56
58
        super(BasicRemoteObjectTests, self).setUp()
57
59
        self.transport = self.get_transport()
58
 
        self.client = self.transport.get_smart_client()
59
60
        # make a branch that can be opened over the smart transport
60
61
        self.local_wt = BzrDir.create_standalone_workingtree('.')
61
62
 
135
136
        """Create a FakeClient.
136
137
 
137
138
        :param responses: A list of response-tuple, body-data pairs to be sent
138
 
            back to callers.
 
139
            back to callers.  A special case is if the response-tuple is
 
140
            'unknown verb', then a UnknownSmartMethod will be raised for that
 
141
            call, using the second element of the tuple as the verb in the
 
142
            exception.
139
143
        """
140
144
        self.responses = responses
141
145
        self._calls = []
142
146
        self.expecting_body = False
143
 
        _SmartClient.__init__(self, FakeMedium(fake_medium_base, self._calls))
 
147
        _SmartClient.__init__(self, FakeMedium(self._calls), fake_medium_base)
 
148
 
 
149
    def _get_next_response(self):
 
150
        response_tuple = self.responses.pop(0)
 
151
        if response_tuple[0][0] == 'unknown verb':
 
152
            raise errors.UnknownSmartMethod(response_tuple[0][1])
 
153
        return response_tuple
144
154
 
145
155
    def call(self, method, *args):
146
156
        self._calls.append(('call', method, args))
147
 
        return self.responses.pop(0)[0]
 
157
        return self._get_next_response()[0]
148
158
 
149
159
    def call_expecting_body(self, method, *args):
150
160
        self._calls.append(('call_expecting_body', method, args))
151
 
        result = self.responses.pop(0)
 
161
        result = self._get_next_response()
152
162
        self.expecting_body = True
153
163
        return result[0], FakeProtocol(result[1], self)
154
164
 
155
165
    def call_with_body_bytes_expecting_body(self, method, args, body):
156
166
        self._calls.append(('call_with_body_bytes_expecting_body', method,
157
167
            args, body))
158
 
        result = self.responses.pop(0)
 
168
        result = self._get_next_response()
159
169
        self.expecting_body = True
160
170
        return result[0], FakeProtocol(result[1], self)
161
171
 
162
172
 
163
173
class FakeMedium(object):
164
174
 
165
 
    def __init__(self, base, client_calls):
166
 
        self.base = base
167
 
        self.connection = FakeConnection(client_calls)
168
 
        self._client_calls = client_calls
169
 
 
170
 
 
171
 
class FakeConnection(object):
172
 
 
173
175
    def __init__(self, client_calls):
174
176
        self._remote_is_at_least_1_2 = True
175
177
        self._client_calls = client_calls
191
193
        self.assertTrue(result)
192
194
 
193
195
 
 
196
class Test_SmartClient_remote_path_from_transport(tests.TestCase):
 
197
    """Tests for the behaviour of _SmartClient.remote_path_from_transport."""
 
198
 
 
199
    def assertRemotePath(self, expected, client_base, transport_base):
 
200
        """Assert that the result of _SmartClient.remote_path_from_transport
 
201
        is the expected value for a given client_base and transport_base.
 
202
        """
 
203
        dummy_medium = 'dummy medium'
 
204
        client = _SmartClient(dummy_medium, client_base)
 
205
        transport = get_transport(transport_base)
 
206
        result = client.remote_path_from_transport(transport)
 
207
        self.assertEqual(expected, result)
 
208
        
 
209
    def test_remote_path_from_transport(self):
 
210
        """_SmartClient.remote_path_from_transport calculates a URL for the
 
211
        given transport relative to the root of the client base URL.
 
212
        """
 
213
        self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
 
214
        self.assertRemotePath(
 
215
            'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
 
216
 
 
217
    def test_remote_path_from_transport_http(self):
 
218
        """Remote paths for HTTP transports are calculated differently to other
 
219
        transports.  They are just relative to the client base, not the root
 
220
        directory of the host.
 
221
        """
 
222
        for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
 
223
            self.assertRemotePath(
 
224
                '../xyz/', scheme + '//host/path', scheme + '//host/xyz')
 
225
            self.assertRemotePath(
 
226
                'xyz/', scheme + '//host/path', scheme + '//host/path/xyz')
 
227
 
 
228
 
194
229
class TestBzrDirOpenBranch(tests.TestCase):
195
230
 
196
231
    def test_branch_present(self):
289
324
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
290
325
 
291
326
 
 
327
class TestBzrDirOpenRepository(tests.TestCase):
 
328
 
 
329
    def test_backwards_compat_1_2(self):
 
330
        transport = MemoryTransport()
 
331
        transport.mkdir('quack')
 
332
        transport = transport.clone('quack')
 
333
        client = FakeClient([
 
334
            (('unknown verb', 'RemoteRepository.find_repositoryV2'), ''),
 
335
            (('ok', '', 'no', 'no'), ''),],
 
336
            transport.base)
 
337
        bzrdir = RemoteBzrDir(transport, _client=client)
 
338
        repo = bzrdir.open_repository()
 
339
        self.assertEqual(
 
340
            [('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
341
             ('call', 'BzrDir.find_repository', ('quack/',))],
 
342
            client._calls)
 
343
 
 
344
 
292
345
class OldSmartClient(object):
293
346
    """A fake smart client for test_old_version that just returns a version one
294
347
    response to the 'hello' (query version) command.
441
494
        branch.unlock()
442
495
 
443
496
 
 
497
class TestBranchSetLastRevisionInfo(tests.TestCase):
 
498
 
 
499
    def test_set_last_revision_info(self):
 
500
        # set_last_revision_info(num, 'rev-id') is translated to calling
 
501
        # Branch.set_last_revision_info(num, 'rev-id') on the wire.
 
502
        transport = MemoryTransport()
 
503
        transport.mkdir('branch')
 
504
        transport = transport.clone('branch')
 
505
        client = FakeClient([
 
506
            # lock_write
 
507
            (('ok', 'branch token', 'repo token'), ),
 
508
            # set_last_revision_info
 
509
            (('ok',), ),
 
510
            # unlock
 
511
            (('ok',), )], transport.base)
 
512
 
 
513
        bzrdir = RemoteBzrDir(transport, _client=False)
 
514
        branch = RemoteBranch(bzrdir, None, _client=client)
 
515
        # This is a hack to work around the problem that RemoteBranch currently
 
516
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
517
        branch._ensure_real = lambda: None
 
518
        # Lock the branch, reset the record of remote calls.
 
519
        branch.lock_write()
 
520
        client._calls = []
 
521
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
522
        self.assertEqual(
 
523
            [('call', 'Branch.set_last_revision_info',
 
524
                ('branch/', 'branch token', 'repo token',
 
525
                 '1234', 'a-revision-id'))],
 
526
            client._calls)
 
527
        self.assertEqual(None, result)
 
528
 
 
529
    def test_no_such_revision(self):
 
530
        # A response of 'NoSuchRevision' is translated into an exception.
 
531
        client = FakeClient([
 
532
            # lock_write
 
533
            (('ok', 'branch token', 'repo token'), ),
 
534
            # set_last_revision_info
 
535
            (('NoSuchRevision', 'revid'), ),
 
536
            # unlock
 
537
            (('ok',), ),
 
538
            ])
 
539
        transport = MemoryTransport()
 
540
        transport.mkdir('branch')
 
541
        transport = transport.clone('branch')
 
542
 
 
543
        bzrdir = RemoteBzrDir(transport, _client=False)
 
544
        branch = RemoteBranch(bzrdir, None, _client=client)
 
545
        # This is a hack to work around the problem that RemoteBranch currently
 
546
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
547
        branch._ensure_real = lambda: None
 
548
        # Lock the branch, reset the record of remote calls.
 
549
        branch.lock_write()
 
550
        client._calls = []
 
551
 
 
552
        self.assertRaises(
 
553
            errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
 
554
        branch.unlock()
 
555
 
 
556
    def lock_remote_branch(self, branch):
 
557
        """Trick a RemoteBranch into thinking it is locked."""
 
558
        branch._lock_mode = 'w'
 
559
        branch._lock_count = 2
 
560
        branch._lock_token = 'branch token'
 
561
        branch._repo_lock_token = 'repo token'
 
562
 
 
563
    def test_backwards_compatibility(self):
 
564
        """If the server does not support the Branch.set_last_revision_info
 
565
        verb (which is new in 1.4), then the client falls back to VFS methods.
 
566
        """
 
567
        # This test is a little messy.  Unlike most tests in this file, it
 
568
        # doesn't purely test what a Remote* object sends over the wire, and
 
569
        # how it reacts to responses from the wire.  It instead relies partly
 
570
        # on asserting that the RemoteBranch will call
 
571
        # self._real_branch.set_last_revision_info(...).
 
572
 
 
573
        # First, set up our RemoteBranch with a FakeClient that raises
 
574
        # UnknownSmartMethod, and a StubRealBranch that logs how it is called.
 
575
        transport = MemoryTransport()
 
576
        transport.mkdir('branch')
 
577
        transport = transport.clone('branch')
 
578
        client = FakeClient(
 
579
            [(('unknown verb', 'Branch.set_last_revision_info',), ),],
 
580
            transport.base)
 
581
        bzrdir = RemoteBzrDir(transport, _client=False)
 
582
        branch = RemoteBranch(bzrdir, None, _client=client)
 
583
        class StubRealBranch(object):
 
584
            def __init__(self):
 
585
                self.calls = []
 
586
            def set_last_revision_info(self, revno, revision_id):
 
587
                self.calls.append(
 
588
                    ('set_last_revision_info', revno, revision_id))
 
589
        real_branch = StubRealBranch()
 
590
        branch._real_branch = real_branch
 
591
        self.lock_remote_branch(branch)
 
592
 
 
593
        # Call set_last_revision_info, and verify it behaved as expected.
 
594
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
595
        self.assertEqual(
 
596
            [('call', 'Branch.set_last_revision_info',
 
597
                ('branch/', 'branch token', 'repo token',
 
598
                 '1234', 'a-revision-id')),],
 
599
            client._calls)
 
600
        self.assertEqual(
 
601
            [('set_last_revision_info', 1234, 'a-revision-id')],
 
602
            real_branch.calls)
 
603
 
 
604
 
444
605
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
445
606
    """Test branch.control_files api munging...
446
607
 
510
671
        advisory anyway (a transport could be read-write, but then the
511
672
        underlying filesystem could be readonly anyway).
512
673
        """
513
 
        client = FakeClient([(
514
 
            ('error', "Generic bzr smart protocol error: "
515
 
                      "bad request 'Transport.is_readonly'"), '')])
516
 
        transport = RemoteTransport('bzr://example.com/', medium=False,
517
 
                                    _client=client)
518
 
        self.assertEqual(False, transport.is_readonly())
519
 
        self.assertEqual(
520
 
            [('call', 'Transport.is_readonly', ())],
521
 
            client._calls)
522
 
 
523
 
    def test_error_from_old_0_11_server(self):
524
 
        """Same as test_error_from_old_server, but with the slightly different
525
 
        error message from bzr 0.11 servers.
526
 
        """
527
 
        client = FakeClient([(
528
 
            ('error', "Generic bzr smart protocol error: "
529
 
                      "bad request u'Transport.is_readonly'"), '')])
 
674
        client = FakeClient([(('unknown verb', 'Transport.is_readonly'), '')])
530
675
        transport = RemoteTransport('bzr://example.com/', medium=False,
531
676
                                    _client=client)
532
677
        self.assertEqual(False, transport.is_readonly())
678
823
        repo.unlock()
679
824
 
680
825
    def test_get_parent_map_reconnects_if_unknown_method(self):
681
 
        error_msg = (
682
 
            "Generic bzr smart protocol error: "
683
 
            "bad request 'Repository.get_parent_map'")
684
826
        responses = [
685
 
            (('error', error_msg), ''),
 
827
            (('unknown verb', 'Repository.get_parent_map'), ''),
686
828
            (('ok',), '')]
687
829
        transport_path = 'quack'
688
830
        repo, client = self.setup_fake_client_and_repository(
689
831
            responses, transport_path)
690
832
        rev_id = 'revision-id'
691
 
        parents = repo.get_parent_map([rev_id])
 
833
        expected_deprecations = [
 
834
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
 
835
            'in version 1.4.']
 
836
        parents = self.callDeprecated(
 
837
            expected_deprecations, repo.get_parent_map, [rev_id])
692
838
        self.assertEqual(
693
839
            [('call_with_body_bytes_expecting_body',
694
840
              'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
697
843
              ('quack/', ''))],
698
844
            client._calls)
699
845
 
 
846
    def test_get_parent_map_unexpected_response(self):
 
847
        responses = [
 
848
            (('something unexpected!',), '')]
 
849
        repo, client = self.setup_fake_client_and_repository(responses, 'path')
 
850
        self.assertRaises(
 
851
            errors.UnexpectedSmartServerResponse,
 
852
            repo.get_parent_map, ['a-revision-id'])
700
853
 
701
854
 
702
855
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
708
861
        transport_path = 'empty'
709
862
        repo, client = self.setup_fake_client_and_repository(
710
863
            responses, transport_path)
711
 
        result = repo.get_revision_graph(NULL_REVISION)
 
864
        result = self.applyDeprecated(one_four, repo.get_revision_graph,
 
865
            NULL_REVISION)
712
866
        self.assertEqual([], client._calls)
713
867
        self.assertEqual({}, result)
714
868
 
723
877
        transport_path = 'sinhala'
724
878
        repo, client = self.setup_fake_client_and_repository(
725
879
            responses, transport_path)
726
 
        result = repo.get_revision_graph()
 
880
        result = self.applyDeprecated(one_four, repo.get_revision_graph)
727
881
        self.assertEqual(
728
882
            [('call_expecting_body', 'Repository.get_revision_graph',
729
883
             ('sinhala/', ''))],
743
897
        transport_path = 'sinhala'
744
898
        repo, client = self.setup_fake_client_and_repository(
745
899
            responses, transport_path)
746
 
        result = repo.get_revision_graph(r2)
 
900
        result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
747
901
        self.assertEqual(
748
902
            [('call_expecting_body', 'Repository.get_revision_graph',
749
903
             ('sinhala/', r2))],
758
912
            responses, transport_path)
759
913
        # also check that the right revision is reported in the error
760
914
        self.assertRaises(errors.NoSuchRevision,
761
 
            repo.get_revision_graph, revid)
 
915
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
762
916
        self.assertEqual(
763
917
            [('call_expecting_body', 'Repository.get_revision_graph',
764
918
             ('sinhala/', revid))],
962
1116
    
963
1117
    def test_backwards_compatibility(self):
964
1118
        """If the server doesn't recognise this request, fallback to VFS."""
965
 
        error_msg = (
966
 
            "Generic bzr smart protocol error: "
967
 
            "bad request 'Repository.stream_revisions_chunked'")
968
1119
        responses = [
969
 
            (('error', error_msg), '')]
 
1120
            (('unknown verb', 'Repository.stream_revisions_chunked'), '')]
970
1121
        repo, client = self.setup_fake_client_and_repository(
971
1122
            responses, 'path')
972
1123
        self.mock_called = False