/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

merge bzr.dev r3564

Show diffs side-by-side

added added

removed removed

Lines of Context:
46
46
from bzrlib.smart import server, medium
47
47
from bzrlib.smart.client import _SmartClient
48
48
from bzrlib.symbol_versioning import one_four
49
 
from bzrlib.transport import get_transport
 
49
from bzrlib.transport import get_transport, http
50
50
from bzrlib.transport.memory import MemoryTransport
51
 
from bzrlib.transport.remote import RemoteTransport
 
51
from bzrlib.transport.remote import RemoteTransport, RemoteTCPTransport
52
52
 
53
53
 
54
54
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
132
132
class FakeClient(_SmartClient):
133
133
    """Lookalike for _SmartClient allowing testing."""
134
134
    
135
 
    def __init__(self, responses, fake_medium_base='fake base'):
 
135
    def __init__(self, fake_medium_base='fake base'):
136
136
        """Create a FakeClient.
137
137
 
138
138
        :param responses: A list of response-tuple, body-data pairs to be sent
141
141
            call, using the second element of the tuple as the verb in the
142
142
            exception.
143
143
        """
144
 
        self.responses = responses
 
144
        self.responses = []
145
145
        self._calls = []
146
146
        self.expecting_body = False
147
 
        _SmartClient.__init__(self, FakeMedium(self._calls), fake_medium_base)
 
147
        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
 
148
 
 
149
    def add_success_response(self, *args):
 
150
        self.responses.append(('success', args, None))
 
151
 
 
152
    def add_success_response_with_body(self, body, *args):
 
153
        self.responses.append(('success', args, body))
 
154
 
 
155
    def add_error_response(self, *args):
 
156
        self.responses.append(('error', args))
 
157
 
 
158
    def add_unknown_method_response(self, verb):
 
159
        self.responses.append(('unknown', verb))
148
160
 
149
161
    def _get_next_response(self):
150
162
        response_tuple = self.responses.pop(0)
151
 
        if response_tuple[0][0] == 'unknown verb':
152
 
            raise errors.UnknownSmartMethod(response_tuple[0][1])
 
163
        if response_tuple[0] == 'unknown':
 
164
            raise errors.UnknownSmartMethod(response_tuple[1])
 
165
        elif response_tuple[0] == 'error':
 
166
            raise errors.ErrorFromSmartServer(response_tuple[1])
153
167
        return response_tuple
154
168
 
155
169
    def call(self, method, *args):
156
170
        self._calls.append(('call', method, args))
157
 
        return self._get_next_response()[0]
 
171
        return self._get_next_response()[1]
158
172
 
159
173
    def call_expecting_body(self, method, *args):
160
174
        self._calls.append(('call_expecting_body', method, args))
161
175
        result = self._get_next_response()
162
176
        self.expecting_body = True
163
 
        return result[0], FakeProtocol(result[1], self)
 
177
        return result[1], FakeProtocol(result[2], self)
164
178
 
165
179
    def call_with_body_bytes_expecting_body(self, method, args, body):
166
180
        self._calls.append(('call_with_body_bytes_expecting_body', method,
167
181
            args, body))
168
182
        result = self._get_next_response()
169
183
        self.expecting_body = True
170
 
        return result[0], FakeProtocol(result[1], self)
171
 
 
172
 
 
173
 
class FakeMedium(object):
174
 
 
175
 
    def __init__(self, client_calls):
176
 
        self._remote_is_at_least_1_2 = True
 
184
        return result[1], FakeProtocol(result[2], self)
 
185
 
 
186
 
 
187
class FakeMedium(medium.SmartClientMedium):
 
188
 
 
189
    def __init__(self, client_calls, base):
 
190
        medium.SmartClientMedium.__init__(self, base)
177
191
        self._client_calls = client_calls
178
192
 
179
193
    def disconnect(self):
183
197
class TestVfsHas(tests.TestCase):
184
198
 
185
199
    def test_unicode_path(self):
186
 
        client = FakeClient([(('yes',), )], '/')
 
200
        client = FakeClient('/')
 
201
        client.add_success_response('yes',)
187
202
        transport = RemoteTransport('bzr://localhost/', _client=client)
188
203
        filename = u'/hell\u00d8'.encode('utf8')
189
204
        result = transport.has(filename)
193
208
        self.assertTrue(result)
194
209
 
195
210
 
196
 
class Test_SmartClient_remote_path_from_transport(tests.TestCase):
197
 
    """Tests for the behaviour of _SmartClient.remote_path_from_transport."""
 
211
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
 
212
    """Tests for the behaviour of client_medium.remote_path_from_transport."""
198
213
 
199
214
    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.
 
215
        """Assert that the result of
 
216
        SmartClientMedium.remote_path_from_transport is the expected value for
 
217
        a given client_base and transport_base.
202
218
        """
203
 
        dummy_medium = 'dummy medium'
204
 
        client = _SmartClient(dummy_medium, client_base)
 
219
        client_medium = medium.SmartClientMedium(client_base)
205
220
        transport = get_transport(transport_base)
206
 
        result = client.remote_path_from_transport(transport)
 
221
        result = client_medium.remote_path_from_transport(transport)
207
222
        self.assertEqual(expected, result)
208
 
        
 
223
 
209
224
    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.
 
225
        """SmartClientMedium.remote_path_from_transport calculates a URL for
 
226
        the given transport relative to the root of the client base URL.
212
227
        """
213
228
        self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
214
229
        self.assertRemotePath(
215
230
            'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
216
231
 
 
232
    def assertRemotePathHTTP(self, expected, transport_base, relpath):
 
233
        """Assert that the result of
 
234
        HttpTransportBase.remote_path_from_transport is the expected value for
 
235
        a given transport_base and relpath of that transport.  (Note that
 
236
        HttpTransportBase is a subclass of SmartClientMedium)
 
237
        """
 
238
        base_transport = get_transport(transport_base)
 
239
        client_medium = base_transport.get_smart_medium()
 
240
        cloned_transport = base_transport.clone(relpath)
 
241
        result = client_medium.remote_path_from_transport(cloned_transport)
 
242
        self.assertEqual(expected, result)
 
243
        
217
244
    def test_remote_path_from_transport_http(self):
218
245
        """Remote paths for HTTP transports are calculated differently to other
219
246
        transports.  They are just relative to the client base, not the root
220
247
        directory of the host.
221
248
        """
222
249
        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')
 
250
            self.assertRemotePathHTTP(
 
251
                '../xyz/', scheme + '//host/path', '../xyz/')
 
252
            self.assertRemotePathHTTP(
 
253
                'xyz/', scheme + '//host/path', 'xyz/')
 
254
 
 
255
 
 
256
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
 
257
    """Tests for the behaviour of client_medium.remote_is_at_least."""
 
258
 
 
259
    def test_initially_unlimited(self):
 
260
        """A fresh medium assumes that the remote side supports all
 
261
        versions.
 
262
        """
 
263
        client_medium = medium.SmartClientMedium('dummy base')
 
264
        self.assertFalse(client_medium._is_remote_before((99, 99)))
 
265
    
 
266
    def test__remember_remote_is_before(self):
 
267
        """Calling _remember_remote_is_before ratchets down the known remote
 
268
        version.
 
269
        """
 
270
        client_medium = medium.SmartClientMedium('dummy base')
 
271
        # Mark the remote side as being less than 1.6.  The remote side may
 
272
        # still be 1.5.
 
273
        client_medium._remember_remote_is_before((1, 6))
 
274
        self.assertTrue(client_medium._is_remote_before((1, 6)))
 
275
        self.assertFalse(client_medium._is_remote_before((1, 5)))
 
276
        # Calling _remember_remote_is_before again with a lower value works.
 
277
        client_medium._remember_remote_is_before((1, 5))
 
278
        self.assertTrue(client_medium._is_remote_before((1, 5)))
 
279
        # You cannot call _remember_remote_is_before with a larger value.
 
280
        self.assertRaises(
 
281
            AssertionError, client_medium._remember_remote_is_before, (1, 9))
227
282
 
228
283
 
229
284
class TestBzrDirOpenBranch(tests.TestCase):
232
287
        transport = MemoryTransport()
233
288
        transport.mkdir('quack')
234
289
        transport = transport.clone('quack')
235
 
        client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no', 'no'), )],
236
 
                            transport.base)
 
290
        client = FakeClient(transport.base)
 
291
        client.add_success_response('ok', '')
 
292
        client.add_success_response('ok', '', 'no', 'no', 'no')
237
293
        bzrdir = RemoteBzrDir(transport, _client=client)
238
294
        result = bzrdir.open_branch()
239
295
        self.assertEqual(
247
303
        transport = MemoryTransport()
248
304
        transport.mkdir('quack')
249
305
        transport = transport.clone('quack')
250
 
        client = FakeClient([(('nobranch',), )], transport.base)
 
306
        client = FakeClient(transport.base)
 
307
        client.add_error_response('nobranch')
251
308
        bzrdir = RemoteBzrDir(transport, _client=client)
252
309
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
253
310
        self.assertEqual(
263
320
            return "a-branch"
264
321
        transport = MemoryTransport()
265
322
        # no requests on the network - catches other api calls being made.
266
 
        client = FakeClient([], transport.base)
 
323
        client = FakeClient(transport.base)
267
324
        bzrdir = RemoteBzrDir(transport, _client=client)
268
325
        # patch the open_branch call to record that it was called.
269
326
        bzrdir.open_branch = open_branch
274
331
    def test_url_quoting_of_path(self):
275
332
        # Relpaths on the wire should not be URL-escaped.  So "~" should be
276
333
        # transmitted as "~", not "%7E".
277
 
        transport = RemoteTransport('bzr://localhost/~hello/')
278
 
        client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no', 'no'), )],
279
 
                            transport.base)
 
334
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
 
335
        client = FakeClient(transport.base)
 
336
        client.add_success_response('ok', '')
 
337
        client.add_success_response('ok', '', 'no', 'no', 'no')
280
338
        bzrdir = RemoteBzrDir(transport, _client=client)
281
339
        result = bzrdir.open_branch()
282
340
        self.assertEqual(
296
354
            subtree_response = 'yes'
297
355
        else:
298
356
            subtree_response = 'no'
299
 
        client = FakeClient(
300
 
            [(('ok', '', rich_response, subtree_response, external_lookup), ),],
301
 
            transport.base)
 
357
        client = FakeClient(transport.base)
 
358
        client.add_success_response(
 
359
            'ok', '', rich_response, subtree_response, external_lookup)
302
360
        bzrdir = RemoteBzrDir(transport, _client=client)
303
361
        result = bzrdir.open_repository()
304
362
        self.assertEqual(
330
388
        transport = MemoryTransport()
331
389
        transport.mkdir('quack')
332
390
        transport = transport.clone('quack')
333
 
        client = FakeClient([
334
 
            (('unknown verb', 'RemoteRepository.find_repositoryV2'), ''),
335
 
            (('ok', '', 'no', 'no'), ''),],
336
 
            transport.base)
 
391
        client = FakeClient(transport.base)
 
392
        client.add_unknown_method_response('RemoteRepository.find_repositoryV2')
 
393
        client.add_success_response('ok', '', 'no', 'no')
337
394
        bzrdir = RemoteBzrDir(transport, _client=client)
338
395
        repo = bzrdir.open_repository()
339
396
        self.assertEqual(
375
432
    def test_empty_branch(self):
376
433
        # in an empty branch we decode the response properly
377
434
        transport = MemoryTransport()
378
 
        client = FakeClient([(('ok', '0', 'null:'), )], transport.base)
 
435
        client = FakeClient(transport.base)
 
436
        client.add_success_response('ok', '0', 'null:')
379
437
        transport.mkdir('quack')
380
438
        transport = transport.clone('quack')
381
439
        # we do not want bzrdir to make any remote calls
392
450
        # in a non-empty branch we also decode the response properly
393
451
        revid = u'\xc8'.encode('utf8')
394
452
        transport = MemoryTransport()
395
 
        client = FakeClient([(('ok', '2', revid), )], transport.base)
 
453
        client = FakeClient(transport.base)
 
454
        client.add_success_response('ok', '2', revid)
396
455
        transport.mkdir('kwaak')
397
456
        transport = transport.clone('kwaak')
398
457
        # we do not want bzrdir to make any remote calls
415
474
        transport.mkdir('branch')
416
475
        transport = transport.clone('branch')
417
476
 
418
 
        client = FakeClient([
419
 
            # lock_write
420
 
            (('ok', 'branch token', 'repo token'), ),
421
 
            # set_last_revision
422
 
            (('ok',), ),
423
 
            # unlock
424
 
            (('ok',), )],
425
 
            transport.base)
 
477
        client = FakeClient(transport.base)
 
478
        # lock_write
 
479
        client.add_success_response('ok', 'branch token', 'repo token')
 
480
        # set_last_revision
 
481
        client.add_success_response('ok')
 
482
        # unlock
 
483
        client.add_success_response('ok')
426
484
        bzrdir = RemoteBzrDir(transport, _client=False)
427
485
        branch = RemoteBranch(bzrdir, None, _client=client)
428
486
        # This is a hack to work around the problem that RemoteBranch currently
445
503
        transport.mkdir('branch')
446
504
        transport = transport.clone('branch')
447
505
 
448
 
        client = FakeClient([
449
 
            # lock_write
450
 
            (('ok', 'branch token', 'repo token'), ),
451
 
            # set_last_revision
452
 
            (('ok',), ),
453
 
            # unlock
454
 
            (('ok',), )],
455
 
            transport.base)
 
506
        client = FakeClient(transport.base)
 
507
        # lock_write
 
508
        client.add_success_response('ok', 'branch token', 'repo token')
 
509
        # set_last_revision
 
510
        client.add_success_response('ok')
 
511
        # unlock
 
512
        client.add_success_response('ok')
456
513
        bzrdir = RemoteBzrDir(transport, _client=False)
457
514
        branch = RemoteBranch(bzrdir, None, _client=client)
458
515
        # This is a hack to work around the problem that RemoteBranch currently
471
528
        self.assertEqual(None, result)
472
529
 
473
530
    def test_no_such_revision(self):
474
 
        # A response of 'NoSuchRevision' is translated into an exception.
475
 
        client = FakeClient([
476
 
            # lock_write
477
 
            (('ok', 'branch token', 'repo token'), ),
478
 
            # set_last_revision
479
 
            (('NoSuchRevision', 'rev-id'), ),
480
 
            # unlock
481
 
            (('ok',), )])
482
531
        transport = MemoryTransport()
483
532
        transport.mkdir('branch')
484
533
        transport = transport.clone('branch')
 
534
        # A response of 'NoSuchRevision' is translated into an exception.
 
535
        client = FakeClient(transport.base)
 
536
        # lock_write
 
537
        client.add_success_response('ok', 'branch token', 'repo token')
 
538
        # set_last_revision
 
539
        client.add_error_response('NoSuchRevision', 'rev-id')
 
540
        # unlock
 
541
        client.add_success_response('ok')
485
542
 
486
543
        bzrdir = RemoteBzrDir(transport, _client=False)
487
544
        branch = RemoteBranch(bzrdir, None, _client=client)
502
559
        transport = MemoryTransport()
503
560
        transport.mkdir('branch')
504
561
        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)
 
562
        client = FakeClient(transport.base)
 
563
        # lock_write
 
564
        client.add_success_response('ok', 'branch token', 'repo token')
 
565
        # set_last_revision
 
566
        client.add_success_response('ok')
 
567
        # unlock
 
568
        client.add_success_response('ok')
512
569
 
513
570
        bzrdir = RemoteBzrDir(transport, _client=False)
514
571
        branch = RemoteBranch(bzrdir, None, _client=client)
528
585
 
529
586
    def test_no_such_revision(self):
530
587
        # 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
588
        transport = MemoryTransport()
540
589
        transport.mkdir('branch')
541
590
        transport = transport.clone('branch')
 
591
        client = FakeClient(transport.base)
 
592
        # lock_write
 
593
        client.add_success_response('ok', 'branch token', 'repo token')
 
594
        # set_last_revision
 
595
        client.add_error_response('NoSuchRevision', 'revid')
 
596
        # unlock
 
597
        client.add_success_response('ok')
542
598
 
543
599
        bzrdir = RemoteBzrDir(transport, _client=False)
544
600
        branch = RemoteBranch(bzrdir, None, _client=client)
575
631
        transport = MemoryTransport()
576
632
        transport.mkdir('branch')
577
633
        transport = transport.clone('branch')
578
 
        client = FakeClient(
579
 
            [(('unknown verb', 'Branch.set_last_revision_info',), ),],
580
 
            transport.base)
 
634
        client = FakeClient(transport.base)
 
635
        client.add_unknown_method_response('Branch.set_last_revision_info')
581
636
        bzrdir = RemoteBzrDir(transport, _client=False)
582
637
        branch = RemoteBranch(bzrdir, None, _client=client)
583
638
        class StubRealBranch(object):
586
641
            def set_last_revision_info(self, revno, revision_id):
587
642
                self.calls.append(
588
643
                    ('set_last_revision_info', revno, revision_id))
 
644
            def _clear_cached_state(self):
 
645
                pass
589
646
        real_branch = StubRealBranch()
590
647
        branch._real_branch = real_branch
591
648
        self.lock_remote_branch(branch)
601
658
            [('set_last_revision_info', 1234, 'a-revision-id')],
602
659
            real_branch.calls)
603
660
 
 
661
    def test_unexpected_error(self):
 
662
        # A response of 'NoSuchRevision' is translated into an exception.
 
663
        transport = MemoryTransport()
 
664
        transport.mkdir('branch')
 
665
        transport = transport.clone('branch')
 
666
        client = FakeClient(transport.base)
 
667
        # lock_write
 
668
        client.add_success_response('ok', 'branch token', 'repo token')
 
669
        # set_last_revision
 
670
        client.add_error_response('UnexpectedError')
 
671
        # unlock
 
672
        client.add_success_response('ok')
 
673
 
 
674
        bzrdir = RemoteBzrDir(transport, _client=False)
 
675
        branch = RemoteBranch(bzrdir, None, _client=client)
 
676
        # This is a hack to work around the problem that RemoteBranch currently
 
677
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
678
        branch._ensure_real = lambda: None
 
679
        # Lock the branch, reset the record of remote calls.
 
680
        branch.lock_write()
 
681
        client._calls = []
 
682
 
 
683
        err = self.assertRaises(
 
684
            errors.ErrorFromSmartServer,
 
685
            branch.set_last_revision_info, 123, 'revid')
 
686
        self.assertEqual(('UnexpectedError',), err.error_tuple)
 
687
        branch.unlock()
 
688
 
604
689
 
605
690
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
606
691
    """Getting the branch configuration should use an abstract method not vfs.
608
693
 
609
694
    def test_get_branch_conf(self):
610
695
        raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
611
 
        # We should see that branch.get_config() does a single rpc to get the
612
 
        # remote configuration file, abstracting away where that is stored on
613
 
        # the server.  However at the moment it always falls back to using the
614
 
        # vfs, and this would need some changes in config.py.
 
696
        ## # We should see that branch.get_config() does a single rpc to get the
 
697
        ## # remote configuration file, abstracting away where that is stored on
 
698
        ## # the server.  However at the moment it always falls back to using the
 
699
        ## # vfs, and this would need some changes in config.py.
615
700
 
616
 
        # in an empty branch we decode the response properly
617
 
        client = FakeClient([(('ok', ), '# config file body')], self.get_url())
618
 
        # we need to make a real branch because the remote_branch.control_files
619
 
        # will trigger _ensure_real.
620
 
        branch = self.make_branch('quack')
621
 
        transport = branch.bzrdir.root_transport
622
 
        # we do not want bzrdir to make any remote calls
623
 
        bzrdir = RemoteBzrDir(transport, _client=False)
624
 
        branch = RemoteBranch(bzrdir, None, _client=client)
625
 
        config = branch.get_config()
626
 
        self.assertEqual(
627
 
            [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
628
 
            client._calls)
 
701
        ## # in an empty branch we decode the response properly
 
702
        ## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
 
703
        ## # we need to make a real branch because the remote_branch.control_files
 
704
        ## # will trigger _ensure_real.
 
705
        ## branch = self.make_branch('quack')
 
706
        ## transport = branch.bzrdir.root_transport
 
707
        ## # we do not want bzrdir to make any remote calls
 
708
        ## bzrdir = RemoteBzrDir(transport, _client=False)
 
709
        ## branch = RemoteBranch(bzrdir, None, _client=client)
 
710
        ## config = branch.get_config()
 
711
        ## self.assertEqual(
 
712
        ##     [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
 
713
        ##     client._calls)
629
714
 
630
715
 
631
716
class TestBranchLockWrite(tests.TestCase):
632
717
 
633
718
    def test_lock_write_unlockable(self):
634
719
        transport = MemoryTransport()
635
 
        client = FakeClient([(('UnlockableTransport', ), '')], transport.base)
 
720
        client = FakeClient(transport.base)
 
721
        client.add_error_response('UnlockableTransport')
636
722
        transport.mkdir('quack')
637
723
        transport = transport.clone('quack')
638
724
        # we do not want bzrdir to make any remote calls
647
733
class TestTransportIsReadonly(tests.TestCase):
648
734
 
649
735
    def test_true(self):
650
 
        client = FakeClient([(('yes',), '')])
 
736
        client = FakeClient()
 
737
        client.add_success_response('yes')
651
738
        transport = RemoteTransport('bzr://example.com/', medium=False,
652
739
                                    _client=client)
653
740
        self.assertEqual(True, transport.is_readonly())
656
743
            client._calls)
657
744
 
658
745
    def test_false(self):
659
 
        client = FakeClient([(('no',), '')])
 
746
        client = FakeClient()
 
747
        client.add_success_response('no')
660
748
        transport = RemoteTransport('bzr://example.com/', medium=False,
661
749
                                    _client=client)
662
750
        self.assertEqual(False, transport.is_readonly())
671
759
        advisory anyway (a transport could be read-write, but then the
672
760
        underlying filesystem could be readonly anyway).
673
761
        """
674
 
        client = FakeClient([(('unknown verb', 'Transport.is_readonly'), '')])
 
762
        client = FakeClient()
 
763
        client.add_unknown_method_response('Transport.is_readonly')
675
764
        transport = RemoteTransport('bzr://example.com/', medium=False,
676
765
                                    _client=client)
677
766
        self.assertEqual(False, transport.is_readonly())
688
777
    because they might break compatibility with different-versioned servers.
689
778
    """
690
779
 
691
 
    def setup_fake_client_and_repository(self, responses, transport_path):
 
780
    def setup_fake_client_and_repository(self, transport_path):
692
781
        """Create the fake client and repository for testing with.
693
782
        
694
783
        There's no real server here; we just have canned responses sent
699
788
        """
700
789
        transport = MemoryTransport()
701
790
        transport.mkdir(transport_path)
702
 
        client = FakeClient(responses, transport.base)
 
791
        client = FakeClient(transport.base)
703
792
        transport = transport.clone(transport_path)
704
793
        # we do not want bzrdir to make any remote calls
705
794
        bzrdir = RemoteBzrDir(transport, _client=False)
711
800
 
712
801
    def test_revid_none(self):
713
802
        # ('ok',), body with revisions and size
714
 
        responses = [(('ok', ), 'revisions: 2\nsize: 18\n')]
715
803
        transport_path = 'quack'
716
 
        repo, client = self.setup_fake_client_and_repository(
717
 
            responses, transport_path)
 
804
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
805
        client.add_success_response_with_body(
 
806
            'revisions: 2\nsize: 18\n', 'ok')
718
807
        result = repo.gather_stats(None)
719
808
        self.assertEqual(
720
809
            [('call_expecting_body', 'Repository.gather_stats',
724
813
 
725
814
    def test_revid_no_committers(self):
726
815
        # ('ok',), body without committers
727
 
        responses = [(('ok', ),
728
 
                      'firstrev: 123456.300 3600\n'
729
 
                      'latestrev: 654231.400 0\n'
730
 
                      'revisions: 2\n'
731
 
                      'size: 18\n')]
 
816
        body = ('firstrev: 123456.300 3600\n'
 
817
                'latestrev: 654231.400 0\n'
 
818
                'revisions: 2\n'
 
819
                'size: 18\n')
732
820
        transport_path = 'quick'
733
821
        revid = u'\xc8'.encode('utf8')
734
 
        repo, client = self.setup_fake_client_and_repository(
735
 
            responses, transport_path)
 
822
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
823
        client.add_success_response_with_body(body, 'ok')
736
824
        result = repo.gather_stats(revid)
737
825
        self.assertEqual(
738
826
            [('call_expecting_body', 'Repository.gather_stats',
745
833
 
746
834
    def test_revid_with_committers(self):
747
835
        # ('ok',), body with committers
748
 
        responses = [(('ok', ),
749
 
                      'committers: 128\n'
750
 
                      'firstrev: 123456.300 3600\n'
751
 
                      'latestrev: 654231.400 0\n'
752
 
                      'revisions: 2\n'
753
 
                      'size: 18\n')]
 
836
        body = ('committers: 128\n'
 
837
                'firstrev: 123456.300 3600\n'
 
838
                'latestrev: 654231.400 0\n'
 
839
                'revisions: 2\n'
 
840
                'size: 18\n')
754
841
        transport_path = 'buick'
755
842
        revid = u'\xc8'.encode('utf8')
756
 
        repo, client = self.setup_fake_client_and_repository(
757
 
            responses, transport_path)
 
843
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
844
        client.add_success_response_with_body(body, 'ok')
758
845
        result = repo.gather_stats(revid, True)
759
846
        self.assertEqual(
760
847
            [('call_expecting_body', 'Repository.gather_stats',
772
859
    def test_get_graph(self):
773
860
        # get_graph returns a graph with the repository as the
774
861
        # parents_provider.
775
 
        responses = []
776
862
        transport_path = 'quack'
777
 
        repo, client = self.setup_fake_client_and_repository(
778
 
            responses, transport_path)
 
863
        repo, client = self.setup_fake_client_and_repository(transport_path)
779
864
        graph = repo.get_graph()
780
865
        self.assertEqual(graph._parents_provider, repo)
781
866
 
789
874
        r2 = u'\u0dab'.encode('utf8')
790
875
        lines = [' '.join([r2, r1]), r1]
791
876
        encoded_body = bz2.compress('\n'.join(lines))
792
 
        responses = [(('ok', ), encoded_body), (('ok', ), encoded_body)]
793
877
 
794
878
        transport_path = 'quack'
795
 
        repo, client = self.setup_fake_client_and_repository(
796
 
            responses, transport_path)
 
879
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
880
        client.add_success_response_with_body(encoded_body, 'ok')
 
881
        client.add_success_response_with_body(encoded_body, 'ok')
797
882
        repo.lock_read()
798
883
        graph = repo.get_graph()
799
884
        parents = graph.get_parent_map([r2])
823
908
        repo.unlock()
824
909
 
825
910
    def test_get_parent_map_reconnects_if_unknown_method(self):
826
 
        responses = [
827
 
            (('unknown verb', 'Repository.get_parent_map'), ''),
828
 
            (('ok',), '')]
829
911
        transport_path = 'quack'
830
 
        repo, client = self.setup_fake_client_and_repository(
831
 
            responses, transport_path)
832
 
        self.assertTrue(client._medium._remote_is_at_least_1_2)
 
912
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
913
        client.add_unknown_method_response('Repository,get_parent_map')
 
914
        client.add_success_response_with_body('', 'ok')
 
915
        self.assertFalse(client._medium._is_remote_before((1, 2)))
833
916
        rev_id = 'revision-id'
834
917
        expected_deprecations = [
835
918
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
844
927
              ('quack/', ''))],
845
928
            client._calls)
846
929
        # The medium is now marked as being connected to an older server
847
 
        self.assertFalse(client._medium._remote_is_at_least_1_2)
 
930
        self.assertTrue(client._medium._is_remote_before((1, 2)))
848
931
 
849
932
    def test_get_parent_map_fallback_parentless_node(self):
850
933
        """get_parent_map falls back to get_revision_graph on old servers.  The
858
941
        This is the test for https://bugs.launchpad.net/bzr/+bug/214894
859
942
        """
860
943
        rev_id = 'revision-id'
861
 
        responses = [(('ok',), rev_id)]
862
944
        transport_path = 'quack'
863
 
        repo, client = self.setup_fake_client_and_repository(
864
 
            responses, transport_path)
865
 
        client._medium._remote_is_at_least_1_2 = False
 
945
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
946
        client.add_success_response_with_body(rev_id, 'ok')
 
947
        client._medium._remember_remote_is_before((1, 2))
866
948
        expected_deprecations = [
867
949
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
868
950
            'in version 1.4.']
875
957
        self.assertEqual({rev_id: ('null:',)}, parents)
876
958
 
877
959
    def test_get_parent_map_unexpected_response(self):
878
 
        responses = [
879
 
            (('something unexpected!',), '')]
880
 
        repo, client = self.setup_fake_client_and_repository(responses, 'path')
 
960
        repo, client = self.setup_fake_client_and_repository('path')
 
961
        client.add_success_response('something unexpected!')
881
962
        self.assertRaises(
882
963
            errors.UnexpectedSmartServerResponse,
883
964
            repo.get_parent_map, ['a-revision-id'])
888
969
    def test_null_revision(self):
889
970
        # a null revision has the predictable result {}, we should have no wire
890
971
        # traffic when calling it with this argument
891
 
        responses = [(('notused', ), '')]
892
972
        transport_path = 'empty'
893
 
        repo, client = self.setup_fake_client_and_repository(
894
 
            responses, transport_path)
 
973
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
974
        client.add_success_response('notused')
895
975
        result = self.applyDeprecated(one_four, repo.get_revision_graph,
896
976
            NULL_REVISION)
897
977
        self.assertEqual([], client._calls)
904
984
        lines = [' '.join([r2, r1]), r1]
905
985
        encoded_body = '\n'.join(lines)
906
986
 
907
 
        responses = [(('ok', ), encoded_body)]
908
987
        transport_path = 'sinhala'
909
 
        repo, client = self.setup_fake_client_and_repository(
910
 
            responses, transport_path)
 
988
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
989
        client.add_success_response_with_body(encoded_body, 'ok')
911
990
        result = self.applyDeprecated(one_four, repo.get_revision_graph)
912
991
        self.assertEqual(
913
992
            [('call_expecting_body', 'Repository.get_revision_graph',
924
1003
        lines = [' '.join([r2, r11, r12]), r11, r12]
925
1004
        encoded_body = '\n'.join(lines)
926
1005
 
927
 
        responses = [(('ok', ), encoded_body)]
928
1006
        transport_path = 'sinhala'
929
 
        repo, client = self.setup_fake_client_and_repository(
930
 
            responses, transport_path)
 
1007
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1008
        client.add_success_response_with_body(encoded_body, 'ok')
931
1009
        result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
932
1010
        self.assertEqual(
933
1011
            [('call_expecting_body', 'Repository.get_revision_graph',
937
1015
 
938
1016
    def test_no_such_revision(self):
939
1017
        revid = '123'
940
 
        responses = [(('nosuchrevision', revid), '')]
941
1018
        transport_path = 'sinhala'
942
 
        repo, client = self.setup_fake_client_and_repository(
943
 
            responses, transport_path)
 
1019
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1020
        client.add_error_response('nosuchrevision', revid)
944
1021
        # also check that the right revision is reported in the error
945
1022
        self.assertRaises(errors.NoSuchRevision,
946
1023
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
949
1026
             ('sinhala/', revid))],
950
1027
            client._calls)
951
1028
 
 
1029
    def test_unexpected_error(self):
 
1030
        revid = '123'
 
1031
        transport_path = 'sinhala'
 
1032
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1033
        client.add_error_response('AnUnexpectedError')
 
1034
        e = self.assertRaises(errors.ErrorFromSmartServer,
 
1035
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
1036
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
 
1037
 
952
1038
        
953
1039
class TestRepositoryIsShared(TestRemoteRepository):
954
1040
 
955
1041
    def test_is_shared(self):
956
1042
        # ('yes', ) for Repository.is_shared -> 'True'.
957
 
        responses = [(('yes', ), )]
958
1043
        transport_path = 'quack'
959
 
        repo, client = self.setup_fake_client_and_repository(
960
 
            responses, transport_path)
 
1044
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1045
        client.add_success_response('yes')
961
1046
        result = repo.is_shared()
962
1047
        self.assertEqual(
963
1048
            [('call', 'Repository.is_shared', ('quack/',))],
966
1051
 
967
1052
    def test_is_not_shared(self):
968
1053
        # ('no', ) for Repository.is_shared -> 'False'.
969
 
        responses = [(('no', ), )]
970
1054
        transport_path = 'qwack'
971
 
        repo, client = self.setup_fake_client_and_repository(
972
 
            responses, transport_path)
 
1055
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1056
        client.add_success_response('no')
973
1057
        result = repo.is_shared()
974
1058
        self.assertEqual(
975
1059
            [('call', 'Repository.is_shared', ('qwack/',))],
980
1064
class TestRepositoryLockWrite(TestRemoteRepository):
981
1065
 
982
1066
    def test_lock_write(self):
983
 
        responses = [(('ok', 'a token'), '')]
984
1067
        transport_path = 'quack'
985
 
        repo, client = self.setup_fake_client_and_repository(
986
 
            responses, transport_path)
 
1068
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1069
        client.add_success_response('ok', 'a token')
987
1070
        result = repo.lock_write()
988
1071
        self.assertEqual(
989
1072
            [('call', 'Repository.lock_write', ('quack/', ''))],
991
1074
        self.assertEqual('a token', result)
992
1075
 
993
1076
    def test_lock_write_already_locked(self):
994
 
        responses = [(('LockContention', ), '')]
995
1077
        transport_path = 'quack'
996
 
        repo, client = self.setup_fake_client_and_repository(
997
 
            responses, transport_path)
 
1078
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1079
        client.add_error_response('LockContention')
998
1080
        self.assertRaises(errors.LockContention, repo.lock_write)
999
1081
        self.assertEqual(
1000
1082
            [('call', 'Repository.lock_write', ('quack/', ''))],
1001
1083
            client._calls)
1002
1084
 
1003
1085
    def test_lock_write_unlockable(self):
1004
 
        responses = [(('UnlockableTransport', ), '')]
1005
1086
        transport_path = 'quack'
1006
 
        repo, client = self.setup_fake_client_and_repository(
1007
 
            responses, transport_path)
 
1087
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1088
        client.add_error_response('UnlockableTransport')
1008
1089
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1009
1090
        self.assertEqual(
1010
1091
            [('call', 'Repository.lock_write', ('quack/', ''))],
1014
1095
class TestRepositoryUnlock(TestRemoteRepository):
1015
1096
 
1016
1097
    def test_unlock(self):
1017
 
        responses = [(('ok', 'a token'), ''),
1018
 
                     (('ok',), '')]
1019
1098
        transport_path = 'quack'
1020
 
        repo, client = self.setup_fake_client_and_repository(
1021
 
            responses, transport_path)
 
1099
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1100
        client.add_success_response('ok', 'a token')
 
1101
        client.add_success_response('ok')
1022
1102
        repo.lock_write()
1023
1103
        repo.unlock()
1024
1104
        self.assertEqual(
1028
1108
 
1029
1109
    def test_unlock_wrong_token(self):
1030
1110
        # If somehow the token is wrong, unlock will raise TokenMismatch.
1031
 
        responses = [(('ok', 'a token'), ''),
1032
 
                     (('TokenMismatch',), '')]
1033
1111
        transport_path = 'quack'
1034
 
        repo, client = self.setup_fake_client_and_repository(
1035
 
            responses, transport_path)
 
1112
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1113
        client.add_success_response('ok', 'a token')
 
1114
        client.add_error_response('TokenMismatch')
1036
1115
        repo.lock_write()
1037
1116
        self.assertRaises(errors.TokenMismatch, repo.unlock)
1038
1117
 
1042
1121
    def test_none(self):
1043
1122
        # repo.has_revision(None) should not cause any traffic.
1044
1123
        transport_path = 'quack'
1045
 
        responses = None
1046
 
        repo, client = self.setup_fake_client_and_repository(
1047
 
            responses, transport_path)
 
1124
        repo, client = self.setup_fake_client_and_repository(transport_path)
1048
1125
 
1049
1126
        # The null revision is always there, so has_revision(None) == True.
1050
1127
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
1074
1151
    def test_repository_tarball(self):
1075
1152
        # Test that Repository.tarball generates the right operations
1076
1153
        transport_path = 'repo'
1077
 
        expected_responses = [(('ok',), self.tarball_content),
1078
 
            ]
1079
1154
        expected_calls = [('call_expecting_body', 'Repository.tarball',
1080
1155
                           ('repo/', 'bz2',),),
1081
1156
            ]
1082
 
        remote_repo, client = self.setup_fake_client_and_repository(
1083
 
            expected_responses, transport_path)
 
1157
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1158
        client.add_success_response_with_body(self.tarball_content, 'ok')
1084
1159
        # Now actually ask for the tarball
1085
 
        tarball_file = remote_repo._get_tarball('bz2')
 
1160
        tarball_file = repo._get_tarball('bz2')
1086
1161
        try:
1087
1162
            self.assertEqual(expected_calls, client._calls)
1088
1163
            self.assertEqual(self.tarball_content, tarball_file.read())
1107
1182
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
1108
1183
        self.assertTrue(isinstance(src_repo, RemoteRepository))
1109
1184
        src_repo.copy_content_into(dest_repo)
1110
 
 
1111
 
 
1112
 
class TestRepositoryStreamKnitData(TestRemoteRepository):
1113
 
 
1114
 
    def make_pack_file(self, records):
1115
 
        pack_file = StringIO()
1116
 
        pack_writer = pack.ContainerWriter(pack_file.write)
1117
 
        pack_writer.begin()
1118
 
        for bytes, names in records:
1119
 
            pack_writer.add_bytes_record(bytes, names)
1120
 
        pack_writer.end()
1121
 
        pack_file.seek(0)
1122
 
        return pack_file
1123
 
 
1124
 
    def make_pack_stream(self, records):
1125
 
        pack_serialiser = pack.ContainerSerialiser()
1126
 
        yield pack_serialiser.begin()
1127
 
        for bytes, names in records:
1128
 
            yield pack_serialiser.bytes_record(bytes, names)
1129
 
        yield pack_serialiser.end()
1130
 
 
1131
 
    def test_bad_pack_from_server(self):
1132
 
        """A response with invalid data (e.g. it has a record with multiple
1133
 
        names) triggers an exception.
1134
 
        
1135
 
        Not all possible errors will be caught at this stage, but obviously
1136
 
        malformed data should be.
1137
 
        """
1138
 
        record = ('bytes', [('name1',), ('name2',)])
1139
 
        pack_stream = self.make_pack_stream([record])
1140
 
        responses = [(('ok',), pack_stream), ]
1141
 
        transport_path = 'quack'
1142
 
        repo, client = self.setup_fake_client_and_repository(
1143
 
            responses, transport_path)
1144
 
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
1145
 
        stream = repo.get_data_stream_for_search(search)
1146
 
        self.assertRaises(errors.SmartProtocolError, list, stream)
1147
 
    
1148
 
    def test_backwards_compatibility(self):
1149
 
        """If the server doesn't recognise this request, fallback to VFS."""
1150
 
        responses = [
1151
 
            (('unknown verb', 'Repository.stream_revisions_chunked'), '')]
1152
 
        repo, client = self.setup_fake_client_and_repository(
1153
 
            responses, 'path')
1154
 
        self.mock_called = False
1155
 
        repo._real_repository = MockRealRepository(self)
1156
 
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
1157
 
        repo.get_data_stream_for_search(search)
1158
 
        self.assertTrue(self.mock_called)
1159
 
        self.failIf(client.expecting_body,
1160
 
            "The protocol has been left in an unclean state that will cause "
1161
 
            "TooManyConcurrentRequests errors.")
1162
 
 
1163
 
 
1164
 
class MockRealRepository(object):
1165
 
    """Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
1166
 
 
1167
 
    def __init__(self, test):
1168
 
        self.test = test
1169
 
 
1170
 
    def get_data_stream_for_search(self, search):
1171
 
        self.test.assertEqual(set(['revid']), search.get_keys())
1172
 
        self.test.mock_called = True
1173
 
 
1174