/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

  • Committer: John Arbash Meinel
  • Date: 2008-06-05 16:27:16 UTC
  • mfrom: (3475 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3476.
  • Revision ID: john@arbash-meinel.com-20080605162716-a3hn238tnctbfd8j
merge bzr.dev, resolve NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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, http
48
50
from bzrlib.transport.memory import MemoryTransport
49
 
from bzrlib.transport.remote import RemoteTransport
 
51
from bzrlib.transport.remote import RemoteTransport, RemoteTCPTransport
50
52
 
51
53
 
52
54
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
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
 
131
132
class FakeClient(_SmartClient):
132
133
    """Lookalike for _SmartClient allowing testing."""
133
134
    
134
 
    def __init__(self, responses, fake_medium_base='fake base'):
 
135
    def __init__(self, fake_medium_base='fake base'):
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
 
        self.responses = responses
 
144
        self.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 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))
 
160
 
 
161
    def _get_next_response(self):
 
162
        response_tuple = self.responses.pop(0)
 
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])
 
167
        return response_tuple
144
168
 
145
169
    def call(self, method, *args):
146
170
        self._calls.append(('call', method, args))
147
 
        return self.responses.pop(0)[0]
 
171
        return self._get_next_response()[1]
148
172
 
149
173
    def call_expecting_body(self, method, *args):
150
174
        self._calls.append(('call_expecting_body', method, args))
151
 
        result = self.responses.pop(0)
 
175
        result = self._get_next_response()
152
176
        self.expecting_body = True
153
 
        return result[0], FakeProtocol(result[1], self)
 
177
        return result[1], FakeProtocol(result[2], self)
154
178
 
155
179
    def call_with_body_bytes_expecting_body(self, method, args, body):
156
180
        self._calls.append(('call_with_body_bytes_expecting_body', method,
157
181
            args, body))
158
 
        result = self.responses.pop(0)
 
182
        result = self._get_next_response()
159
183
        self.expecting_body = True
160
 
        return result[0], FakeProtocol(result[1], self)
161
 
 
162
 
 
163
 
class FakeMedium(object):
164
 
 
165
 
    def __init__(self, base, client_calls):
 
184
        return result[1], FakeProtocol(result[2], self)
 
185
 
 
186
 
 
187
class FakeMedium(medium.SmartClientMedium):
 
188
 
 
189
    def __init__(self, client_calls, base):
 
190
        self._remote_is_at_least_1_2 = True
 
191
        self._client_calls = client_calls
166
192
        self.base = base
167
 
        self.connection = FakeConnection(client_calls)
168
 
        self._client_calls = client_calls
169
 
 
170
 
 
171
 
class FakeConnection(object):
172
 
 
173
 
    def __init__(self, client_calls):
174
 
        self._remote_is_at_least_1_2 = True
175
 
        self._client_calls = client_calls
176
193
 
177
194
    def disconnect(self):
178
195
        self._client_calls.append(('disconnect medium',))
181
198
class TestVfsHas(tests.TestCase):
182
199
 
183
200
    def test_unicode_path(self):
184
 
        client = FakeClient([(('yes',), )], '/')
 
201
        client = FakeClient('/')
 
202
        client.add_success_response('yes',)
185
203
        transport = RemoteTransport('bzr://localhost/', _client=client)
186
204
        filename = u'/hell\u00d8'.encode('utf8')
187
205
        result = transport.has(filename)
191
209
        self.assertTrue(result)
192
210
 
193
211
 
 
212
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
 
213
    """Tests for the behaviour of client_medium.remote_path_from_transport."""
 
214
 
 
215
    def assertRemotePath(self, expected, client_base, transport_base):
 
216
        """Assert that the result of
 
217
        SmartClientMedium.remote_path_from_transport is the expected value for
 
218
        a given client_base and transport_base.
 
219
        """
 
220
        client_medium = medium.SmartClientMedium(client_base)
 
221
        transport = get_transport(transport_base)
 
222
        result = client_medium.remote_path_from_transport(transport)
 
223
        self.assertEqual(expected, result)
 
224
 
 
225
    def test_remote_path_from_transport(self):
 
226
        """SmartClientMedium.remote_path_from_transport calculates a URL for
 
227
        the given transport relative to the root of the client base URL.
 
228
        """
 
229
        self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
 
230
        self.assertRemotePath(
 
231
            'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
 
232
 
 
233
    def assertRemotePathHTTP(self, expected, transport_base, relpath):
 
234
        """Assert that the result of
 
235
        HttpTransportBase.remote_path_from_transport is the expected value for
 
236
        a given transport_base and relpath of that transport.  (Note that
 
237
        HttpTransportBase is a subclass of SmartClientMedium)
 
238
        """
 
239
        base_transport = get_transport(transport_base)
 
240
        client_medium = base_transport.get_smart_medium()
 
241
        cloned_transport = base_transport.clone(relpath)
 
242
        result = client_medium.remote_path_from_transport(cloned_transport)
 
243
        self.assertEqual(expected, result)
 
244
        
 
245
    def test_remote_path_from_transport_http(self):
 
246
        """Remote paths for HTTP transports are calculated differently to other
 
247
        transports.  They are just relative to the client base, not the root
 
248
        directory of the host.
 
249
        """
 
250
        for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
 
251
            self.assertRemotePathHTTP(
 
252
                '../xyz/', scheme + '//host/path', '../xyz/')
 
253
            self.assertRemotePathHTTP(
 
254
                'xyz/', scheme + '//host/path', 'xyz/')
 
255
 
 
256
 
194
257
class TestBzrDirOpenBranch(tests.TestCase):
195
258
 
196
259
    def test_branch_present(self):
197
260
        transport = MemoryTransport()
198
261
        transport.mkdir('quack')
199
262
        transport = transport.clone('quack')
200
 
        client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no', 'no'), )],
201
 
                            transport.base)
 
263
        client = FakeClient(transport.base)
 
264
        client.add_success_response('ok', '')
 
265
        client.add_success_response('ok', '', 'no', 'no', 'no')
202
266
        bzrdir = RemoteBzrDir(transport, _client=client)
203
267
        result = bzrdir.open_branch()
204
268
        self.assertEqual(
212
276
        transport = MemoryTransport()
213
277
        transport.mkdir('quack')
214
278
        transport = transport.clone('quack')
215
 
        client = FakeClient([(('nobranch',), )], transport.base)
 
279
        client = FakeClient(transport.base)
 
280
        client.add_error_response('nobranch')
216
281
        bzrdir = RemoteBzrDir(transport, _client=client)
217
282
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
218
283
        self.assertEqual(
228
293
            return "a-branch"
229
294
        transport = MemoryTransport()
230
295
        # no requests on the network - catches other api calls being made.
231
 
        client = FakeClient([], transport.base)
 
296
        client = FakeClient(transport.base)
232
297
        bzrdir = RemoteBzrDir(transport, _client=client)
233
298
        # patch the open_branch call to record that it was called.
234
299
        bzrdir.open_branch = open_branch
239
304
    def test_url_quoting_of_path(self):
240
305
        # Relpaths on the wire should not be URL-escaped.  So "~" should be
241
306
        # transmitted as "~", not "%7E".
242
 
        transport = RemoteTransport('bzr://localhost/~hello/')
243
 
        client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no', 'no'), )],
244
 
                            transport.base)
 
307
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
 
308
        client = FakeClient(transport.base)
 
309
        client.add_success_response('ok', '')
 
310
        client.add_success_response('ok', '', 'no', 'no', 'no')
245
311
        bzrdir = RemoteBzrDir(transport, _client=client)
246
312
        result = bzrdir.open_branch()
247
313
        self.assertEqual(
261
327
            subtree_response = 'yes'
262
328
        else:
263
329
            subtree_response = 'no'
264
 
        client = FakeClient(
265
 
            [(('ok', '', rich_response, subtree_response, external_lookup), ),],
266
 
            transport.base)
 
330
        client = FakeClient(transport.base)
 
331
        client.add_success_response(
 
332
            'ok', '', rich_response, subtree_response, external_lookup)
267
333
        bzrdir = RemoteBzrDir(transport, _client=client)
268
334
        result = bzrdir.open_repository()
269
335
        self.assertEqual(
289
355
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
290
356
 
291
357
 
 
358
class TestBzrDirOpenRepository(tests.TestCase):
 
359
 
 
360
    def test_backwards_compat_1_2(self):
 
361
        transport = MemoryTransport()
 
362
        transport.mkdir('quack')
 
363
        transport = transport.clone('quack')
 
364
        client = FakeClient(transport.base)
 
365
        client.add_unknown_method_response('RemoteRepository.find_repositoryV2')
 
366
        client.add_success_response('ok', '', 'no', 'no')
 
367
        bzrdir = RemoteBzrDir(transport, _client=client)
 
368
        repo = bzrdir.open_repository()
 
369
        self.assertEqual(
 
370
            [('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
371
             ('call', 'BzrDir.find_repository', ('quack/',))],
 
372
            client._calls)
 
373
 
 
374
 
292
375
class OldSmartClient(object):
293
376
    """A fake smart client for test_old_version that just returns a version one
294
377
    response to the 'hello' (query version) command.
301
384
            input_file, output_file)
302
385
        return medium.SmartClientStreamMediumRequest(client_medium)
303
386
 
 
387
    def protocol_version(self):
 
388
        return 1
 
389
 
304
390
 
305
391
class OldServerTransport(object):
306
392
    """A fake transport for test_old_server that reports it's smart server
319
405
    def test_empty_branch(self):
320
406
        # in an empty branch we decode the response properly
321
407
        transport = MemoryTransport()
322
 
        client = FakeClient([(('ok', '0', 'null:'), )], transport.base)
 
408
        client = FakeClient(transport.base)
 
409
        client.add_success_response('ok', '0', 'null:')
323
410
        transport.mkdir('quack')
324
411
        transport = transport.clone('quack')
325
412
        # we do not want bzrdir to make any remote calls
336
423
        # in a non-empty branch we also decode the response properly
337
424
        revid = u'\xc8'.encode('utf8')
338
425
        transport = MemoryTransport()
339
 
        client = FakeClient([(('ok', '2', revid), )], transport.base)
 
426
        client = FakeClient(transport.base)
 
427
        client.add_success_response('ok', '2', revid)
340
428
        transport.mkdir('kwaak')
341
429
        transport = transport.clone('kwaak')
342
430
        # we do not want bzrdir to make any remote calls
359
447
        transport.mkdir('branch')
360
448
        transport = transport.clone('branch')
361
449
 
362
 
        client = FakeClient([
363
 
            # lock_write
364
 
            (('ok', 'branch token', 'repo token'), ),
365
 
            # set_last_revision
366
 
            (('ok',), ),
367
 
            # unlock
368
 
            (('ok',), )],
369
 
            transport.base)
 
450
        client = FakeClient(transport.base)
 
451
        # lock_write
 
452
        client.add_success_response('ok', 'branch token', 'repo token')
 
453
        # set_last_revision
 
454
        client.add_success_response('ok')
 
455
        # unlock
 
456
        client.add_success_response('ok')
370
457
        bzrdir = RemoteBzrDir(transport, _client=False)
371
458
        branch = RemoteBranch(bzrdir, None, _client=client)
372
459
        # This is a hack to work around the problem that RemoteBranch currently
389
476
        transport.mkdir('branch')
390
477
        transport = transport.clone('branch')
391
478
 
392
 
        client = FakeClient([
393
 
            # lock_write
394
 
            (('ok', 'branch token', 'repo token'), ),
395
 
            # set_last_revision
396
 
            (('ok',), ),
397
 
            # unlock
398
 
            (('ok',), )],
399
 
            transport.base)
 
479
        client = FakeClient(transport.base)
 
480
        # lock_write
 
481
        client.add_success_response('ok', 'branch token', 'repo token')
 
482
        # set_last_revision
 
483
        client.add_success_response('ok')
 
484
        # unlock
 
485
        client.add_success_response('ok')
400
486
        bzrdir = RemoteBzrDir(transport, _client=False)
401
487
        branch = RemoteBranch(bzrdir, None, _client=client)
402
488
        # This is a hack to work around the problem that RemoteBranch currently
415
501
        self.assertEqual(None, result)
416
502
 
417
503
    def test_no_such_revision(self):
418
 
        # A response of 'NoSuchRevision' is translated into an exception.
419
 
        client = FakeClient([
420
 
            # lock_write
421
 
            (('ok', 'branch token', 'repo token'), ),
422
 
            # set_last_revision
423
 
            (('NoSuchRevision', 'rev-id'), ),
424
 
            # unlock
425
 
            (('ok',), )])
426
504
        transport = MemoryTransport()
427
505
        transport.mkdir('branch')
428
506
        transport = transport.clone('branch')
 
507
        # A response of 'NoSuchRevision' is translated into an exception.
 
508
        client = FakeClient(transport.base)
 
509
        # lock_write
 
510
        client.add_success_response('ok', 'branch token', 'repo token')
 
511
        # set_last_revision
 
512
        client.add_error_response('NoSuchRevision', 'rev-id')
 
513
        # unlock
 
514
        client.add_success_response('ok')
429
515
 
430
516
        bzrdir = RemoteBzrDir(transport, _client=False)
431
517
        branch = RemoteBranch(bzrdir, None, _client=client)
438
524
        branch.unlock()
439
525
 
440
526
 
 
527
class TestBranchSetLastRevisionInfo(tests.TestCase):
 
528
 
 
529
    def test_set_last_revision_info(self):
 
530
        # set_last_revision_info(num, 'rev-id') is translated to calling
 
531
        # Branch.set_last_revision_info(num, 'rev-id') on the wire.
 
532
        transport = MemoryTransport()
 
533
        transport.mkdir('branch')
 
534
        transport = transport.clone('branch')
 
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_success_response('ok')
 
540
        # unlock
 
541
        client.add_success_response('ok')
 
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
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
552
        self.assertEqual(
 
553
            [('call', 'Branch.set_last_revision_info',
 
554
                ('branch/', 'branch token', 'repo token',
 
555
                 '1234', 'a-revision-id'))],
 
556
            client._calls)
 
557
        self.assertEqual(None, result)
 
558
 
 
559
    def test_no_such_revision(self):
 
560
        # A response of 'NoSuchRevision' is translated into an exception.
 
561
        transport = MemoryTransport()
 
562
        transport.mkdir('branch')
 
563
        transport = transport.clone('branch')
 
564
        client = FakeClient(transport.base)
 
565
        # lock_write
 
566
        client.add_success_response('ok', 'branch token', 'repo token')
 
567
        # set_last_revision
 
568
        client.add_error_response('NoSuchRevision', 'revid')
 
569
        # unlock
 
570
        client.add_success_response('ok')
 
571
 
 
572
        bzrdir = RemoteBzrDir(transport, _client=False)
 
573
        branch = RemoteBranch(bzrdir, None, _client=client)
 
574
        # This is a hack to work around the problem that RemoteBranch currently
 
575
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
576
        branch._ensure_real = lambda: None
 
577
        # Lock the branch, reset the record of remote calls.
 
578
        branch.lock_write()
 
579
        client._calls = []
 
580
 
 
581
        self.assertRaises(
 
582
            errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
 
583
        branch.unlock()
 
584
 
 
585
    def lock_remote_branch(self, branch):
 
586
        """Trick a RemoteBranch into thinking it is locked."""
 
587
        branch._lock_mode = 'w'
 
588
        branch._lock_count = 2
 
589
        branch._lock_token = 'branch token'
 
590
        branch._repo_lock_token = 'repo token'
 
591
 
 
592
    def test_backwards_compatibility(self):
 
593
        """If the server does not support the Branch.set_last_revision_info
 
594
        verb (which is new in 1.4), then the client falls back to VFS methods.
 
595
        """
 
596
        # This test is a little messy.  Unlike most tests in this file, it
 
597
        # doesn't purely test what a Remote* object sends over the wire, and
 
598
        # how it reacts to responses from the wire.  It instead relies partly
 
599
        # on asserting that the RemoteBranch will call
 
600
        # self._real_branch.set_last_revision_info(...).
 
601
 
 
602
        # First, set up our RemoteBranch with a FakeClient that raises
 
603
        # UnknownSmartMethod, and a StubRealBranch that logs how it is called.
 
604
        transport = MemoryTransport()
 
605
        transport.mkdir('branch')
 
606
        transport = transport.clone('branch')
 
607
        client = FakeClient(transport.base)
 
608
        client.add_unknown_method_response('Branch.set_last_revision_info')
 
609
        bzrdir = RemoteBzrDir(transport, _client=False)
 
610
        branch = RemoteBranch(bzrdir, None, _client=client)
 
611
        class StubRealBranch(object):
 
612
            def __init__(self):
 
613
                self.calls = []
 
614
            def set_last_revision_info(self, revno, revision_id):
 
615
                self.calls.append(
 
616
                    ('set_last_revision_info', revno, revision_id))
 
617
        real_branch = StubRealBranch()
 
618
        branch._real_branch = real_branch
 
619
        self.lock_remote_branch(branch)
 
620
 
 
621
        # Call set_last_revision_info, and verify it behaved as expected.
 
622
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
623
        self.assertEqual(
 
624
            [('call', 'Branch.set_last_revision_info',
 
625
                ('branch/', 'branch token', 'repo token',
 
626
                 '1234', 'a-revision-id')),],
 
627
            client._calls)
 
628
        self.assertEqual(
 
629
            [('set_last_revision_info', 1234, 'a-revision-id')],
 
630
            real_branch.calls)
 
631
 
 
632
    def test_unexpected_error(self):
 
633
        # A response of 'NoSuchRevision' is translated into an exception.
 
634
        transport = MemoryTransport()
 
635
        transport.mkdir('branch')
 
636
        transport = transport.clone('branch')
 
637
        client = FakeClient(transport.base)
 
638
        # lock_write
 
639
        client.add_success_response('ok', 'branch token', 'repo token')
 
640
        # set_last_revision
 
641
        client.add_error_response('UnexpectedError')
 
642
        # unlock
 
643
        client.add_success_response('ok')
 
644
 
 
645
        bzrdir = RemoteBzrDir(transport, _client=False)
 
646
        branch = RemoteBranch(bzrdir, None, _client=client)
 
647
        # This is a hack to work around the problem that RemoteBranch currently
 
648
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
649
        branch._ensure_real = lambda: None
 
650
        # Lock the branch, reset the record of remote calls.
 
651
        branch.lock_write()
 
652
        client._calls = []
 
653
 
 
654
        err = self.assertRaises(
 
655
            errors.ErrorFromSmartServer,
 
656
            branch.set_last_revision_info, 123, 'revid')
 
657
        self.assertEqual(('UnexpectedError',), err.error_tuple)
 
658
        branch.unlock()
 
659
 
 
660
 
441
661
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
442
 
    """Test branch.control_files api munging...
443
 
 
444
 
    We special case RemoteBranch.control_files.get('branch.conf') to
445
 
    call a specific API so that RemoteBranch's can intercept configuration
446
 
    file reading, allowing them to signal to the client about things like
447
 
    'email is configured for commits'.
 
662
    """Getting the branch configuration should use an abstract method not vfs.
448
663
    """
449
664
 
450
665
    def test_get_branch_conf(self):
451
 
        # in an empty branch we decode the response properly
452
 
        client = FakeClient([(('ok', ), 'config file body')], self.get_url())
453
 
        # we need to make a real branch because the remote_branch.control_files
454
 
        # will trigger _ensure_real.
455
 
        branch = self.make_branch('quack')
456
 
        transport = branch.bzrdir.root_transport
457
 
        # we do not want bzrdir to make any remote calls
458
 
        bzrdir = RemoteBzrDir(transport, _client=False)
459
 
        branch = RemoteBranch(bzrdir, None, _client=client)
460
 
        result = branch.control_files.get('branch.conf')
461
 
        self.assertEqual(
462
 
            [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
463
 
            client._calls)
464
 
        self.assertEqual('config file body', result.read())
 
666
        raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
 
667
        ## # We should see that branch.get_config() does a single rpc to get the
 
668
        ## # remote configuration file, abstracting away where that is stored on
 
669
        ## # the server.  However at the moment it always falls back to using the
 
670
        ## # vfs, and this would need some changes in config.py.
 
671
 
 
672
        ## # in an empty branch we decode the response properly
 
673
        ## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
 
674
        ## # we need to make a real branch because the remote_branch.control_files
 
675
        ## # will trigger _ensure_real.
 
676
        ## branch = self.make_branch('quack')
 
677
        ## transport = branch.bzrdir.root_transport
 
678
        ## # we do not want bzrdir to make any remote calls
 
679
        ## bzrdir = RemoteBzrDir(transport, _client=False)
 
680
        ## branch = RemoteBranch(bzrdir, None, _client=client)
 
681
        ## config = branch.get_config()
 
682
        ## self.assertEqual(
 
683
        ##     [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
 
684
        ##     client._calls)
465
685
 
466
686
 
467
687
class TestBranchLockWrite(tests.TestCase):
468
688
 
469
689
    def test_lock_write_unlockable(self):
470
690
        transport = MemoryTransport()
471
 
        client = FakeClient([(('UnlockableTransport', ), '')], transport.base)
 
691
        client = FakeClient(transport.base)
 
692
        client.add_error_response('UnlockableTransport')
472
693
        transport.mkdir('quack')
473
694
        transport = transport.clone('quack')
474
695
        # we do not want bzrdir to make any remote calls
483
704
class TestTransportIsReadonly(tests.TestCase):
484
705
 
485
706
    def test_true(self):
486
 
        client = FakeClient([(('yes',), '')])
 
707
        client = FakeClient()
 
708
        client.add_success_response('yes')
487
709
        transport = RemoteTransport('bzr://example.com/', medium=False,
488
710
                                    _client=client)
489
711
        self.assertEqual(True, transport.is_readonly())
492
714
            client._calls)
493
715
 
494
716
    def test_false(self):
495
 
        client = FakeClient([(('no',), '')])
 
717
        client = FakeClient()
 
718
        client.add_success_response('no')
496
719
        transport = RemoteTransport('bzr://example.com/', medium=False,
497
720
                                    _client=client)
498
721
        self.assertEqual(False, transport.is_readonly())
507
730
        advisory anyway (a transport could be read-write, but then the
508
731
        underlying filesystem could be readonly anyway).
509
732
        """
510
 
        client = FakeClient([(
511
 
            ('error', "Generic bzr smart protocol error: "
512
 
                      "bad request 'Transport.is_readonly'"), '')])
513
 
        transport = RemoteTransport('bzr://example.com/', medium=False,
514
 
                                    _client=client)
515
 
        self.assertEqual(False, transport.is_readonly())
516
 
        self.assertEqual(
517
 
            [('call', 'Transport.is_readonly', ())],
518
 
            client._calls)
519
 
 
520
 
    def test_error_from_old_0_11_server(self):
521
 
        """Same as test_error_from_old_server, but with the slightly different
522
 
        error message from bzr 0.11 servers.
523
 
        """
524
 
        client = FakeClient([(
525
 
            ('error', "Generic bzr smart protocol error: "
526
 
                      "bad request u'Transport.is_readonly'"), '')])
 
733
        client = FakeClient()
 
734
        client.add_unknown_method_response('Transport.is_readonly')
527
735
        transport = RemoteTransport('bzr://example.com/', medium=False,
528
736
                                    _client=client)
529
737
        self.assertEqual(False, transport.is_readonly())
540
748
    because they might break compatibility with different-versioned servers.
541
749
    """
542
750
 
543
 
    def setup_fake_client_and_repository(self, responses, transport_path):
 
751
    def setup_fake_client_and_repository(self, transport_path):
544
752
        """Create the fake client and repository for testing with.
545
753
        
546
754
        There's no real server here; we just have canned responses sent
551
759
        """
552
760
        transport = MemoryTransport()
553
761
        transport.mkdir(transport_path)
554
 
        client = FakeClient(responses, transport.base)
 
762
        client = FakeClient(transport.base)
555
763
        transport = transport.clone(transport_path)
556
764
        # we do not want bzrdir to make any remote calls
557
765
        bzrdir = RemoteBzrDir(transport, _client=False)
563
771
 
564
772
    def test_revid_none(self):
565
773
        # ('ok',), body with revisions and size
566
 
        responses = [(('ok', ), 'revisions: 2\nsize: 18\n')]
567
774
        transport_path = 'quack'
568
 
        repo, client = self.setup_fake_client_and_repository(
569
 
            responses, transport_path)
 
775
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
776
        client.add_success_response_with_body(
 
777
            'revisions: 2\nsize: 18\n', 'ok')
570
778
        result = repo.gather_stats(None)
571
779
        self.assertEqual(
572
780
            [('call_expecting_body', 'Repository.gather_stats',
576
784
 
577
785
    def test_revid_no_committers(self):
578
786
        # ('ok',), body without committers
579
 
        responses = [(('ok', ),
580
 
                      'firstrev: 123456.300 3600\n'
581
 
                      'latestrev: 654231.400 0\n'
582
 
                      'revisions: 2\n'
583
 
                      'size: 18\n')]
 
787
        body = ('firstrev: 123456.300 3600\n'
 
788
                'latestrev: 654231.400 0\n'
 
789
                'revisions: 2\n'
 
790
                'size: 18\n')
584
791
        transport_path = 'quick'
585
792
        revid = u'\xc8'.encode('utf8')
586
 
        repo, client = self.setup_fake_client_and_repository(
587
 
            responses, transport_path)
 
793
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
794
        client.add_success_response_with_body(body, 'ok')
588
795
        result = repo.gather_stats(revid)
589
796
        self.assertEqual(
590
797
            [('call_expecting_body', 'Repository.gather_stats',
597
804
 
598
805
    def test_revid_with_committers(self):
599
806
        # ('ok',), body with committers
600
 
        responses = [(('ok', ),
601
 
                      'committers: 128\n'
602
 
                      'firstrev: 123456.300 3600\n'
603
 
                      'latestrev: 654231.400 0\n'
604
 
                      'revisions: 2\n'
605
 
                      'size: 18\n')]
 
807
        body = ('committers: 128\n'
 
808
                'firstrev: 123456.300 3600\n'
 
809
                'latestrev: 654231.400 0\n'
 
810
                'revisions: 2\n'
 
811
                'size: 18\n')
606
812
        transport_path = 'buick'
607
813
        revid = u'\xc8'.encode('utf8')
608
 
        repo, client = self.setup_fake_client_and_repository(
609
 
            responses, transport_path)
 
814
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
815
        client.add_success_response_with_body(body, 'ok')
610
816
        result = repo.gather_stats(revid, True)
611
817
        self.assertEqual(
612
818
            [('call_expecting_body', 'Repository.gather_stats',
624
830
    def test_get_graph(self):
625
831
        # get_graph returns a graph with the repository as the
626
832
        # parents_provider.
627
 
        responses = []
628
833
        transport_path = 'quack'
629
 
        repo, client = self.setup_fake_client_and_repository(
630
 
            responses, transport_path)
 
834
        repo, client = self.setup_fake_client_and_repository(transport_path)
631
835
        graph = repo.get_graph()
632
836
        self.assertEqual(graph._parents_provider, repo)
633
837
 
641
845
        r2 = u'\u0dab'.encode('utf8')
642
846
        lines = [' '.join([r2, r1]), r1]
643
847
        encoded_body = bz2.compress('\n'.join(lines))
644
 
        responses = [(('ok', ), encoded_body), (('ok', ), encoded_body)]
645
848
 
646
849
        transport_path = 'quack'
647
 
        repo, client = self.setup_fake_client_and_repository(
648
 
            responses, transport_path)
 
850
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
851
        client.add_success_response_with_body(encoded_body, 'ok')
 
852
        client.add_success_response_with_body(encoded_body, 'ok')
649
853
        repo.lock_read()
650
854
        graph = repo.get_graph()
651
855
        parents = graph.get_parent_map([r2])
675
879
        repo.unlock()
676
880
 
677
881
    def test_get_parent_map_reconnects_if_unknown_method(self):
678
 
        error_msg = (
679
 
            "Generic bzr smart protocol error: "
680
 
            "bad request 'Repository.get_parent_map'")
681
 
        responses = [
682
 
            (('error', error_msg), ''),
683
 
            (('ok',), '')]
684
882
        transport_path = 'quack'
685
 
        repo, client = self.setup_fake_client_and_repository(
686
 
            responses, transport_path)
 
883
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
884
        client.add_unknown_method_response('Repository,get_parent_map')
 
885
        client.add_success_response_with_body('', 'ok')
 
886
        self.assertTrue(client._medium._remote_is_at_least_1_2)
687
887
        rev_id = 'revision-id'
688
 
        parents = repo.get_parent_map([rev_id])
 
888
        expected_deprecations = [
 
889
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
 
890
            'in version 1.4.']
 
891
        parents = self.callDeprecated(
 
892
            expected_deprecations, repo.get_parent_map, [rev_id])
689
893
        self.assertEqual(
690
894
            [('call_with_body_bytes_expecting_body',
691
895
              'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
693
897
             ('call_expecting_body', 'Repository.get_revision_graph',
694
898
              ('quack/', ''))],
695
899
            client._calls)
696
 
 
 
900
        # The medium is now marked as being connected to an older server
 
901
        self.assertFalse(client._medium._remote_is_at_least_1_2)
 
902
 
 
903
    def test_get_parent_map_fallback_parentless_node(self):
 
904
        """get_parent_map falls back to get_revision_graph on old servers.  The
 
905
        results from get_revision_graph are tweaked to match the get_parent_map
 
906
        API.
 
907
 
 
908
        Specifically, a {key: ()} result from get_revision_graph means "no
 
909
        parents" for that key, which in get_parent_map results should be
 
910
        represented as {key: ('null:',)}.
 
911
 
 
912
        This is the test for https://bugs.launchpad.net/bzr/+bug/214894
 
913
        """
 
914
        rev_id = 'revision-id'
 
915
        transport_path = 'quack'
 
916
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
917
        client.add_success_response_with_body(rev_id, 'ok')
 
918
        client._medium._remote_is_at_least_1_2 = False
 
919
        expected_deprecations = [
 
920
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
 
921
            'in version 1.4.']
 
922
        parents = self.callDeprecated(
 
923
            expected_deprecations, repo.get_parent_map, [rev_id])
 
924
        self.assertEqual(
 
925
            [('call_expecting_body', 'Repository.get_revision_graph',
 
926
             ('quack/', ''))],
 
927
            client._calls)
 
928
        self.assertEqual({rev_id: ('null:',)}, parents)
 
929
 
 
930
    def test_get_parent_map_unexpected_response(self):
 
931
        repo, client = self.setup_fake_client_and_repository('path')
 
932
        client.add_success_response('something unexpected!')
 
933
        self.assertRaises(
 
934
            errors.UnexpectedSmartServerResponse,
 
935
            repo.get_parent_map, ['a-revision-id'])
697
936
 
698
937
 
699
938
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
701
940
    def test_null_revision(self):
702
941
        # a null revision has the predictable result {}, we should have no wire
703
942
        # traffic when calling it with this argument
704
 
        responses = [(('notused', ), '')]
705
943
        transport_path = 'empty'
706
 
        repo, client = self.setup_fake_client_and_repository(
707
 
            responses, transport_path)
708
 
        result = repo.get_revision_graph(NULL_REVISION)
 
944
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
945
        client.add_success_response('notused')
 
946
        result = self.applyDeprecated(one_four, repo.get_revision_graph,
 
947
            NULL_REVISION)
709
948
        self.assertEqual([], client._calls)
710
949
        self.assertEqual({}, result)
711
950
 
716
955
        lines = [' '.join([r2, r1]), r1]
717
956
        encoded_body = '\n'.join(lines)
718
957
 
719
 
        responses = [(('ok', ), encoded_body)]
720
958
        transport_path = 'sinhala'
721
 
        repo, client = self.setup_fake_client_and_repository(
722
 
            responses, transport_path)
723
 
        result = repo.get_revision_graph()
 
959
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
960
        client.add_success_response_with_body(encoded_body, 'ok')
 
961
        result = self.applyDeprecated(one_four, repo.get_revision_graph)
724
962
        self.assertEqual(
725
963
            [('call_expecting_body', 'Repository.get_revision_graph',
726
964
             ('sinhala/', ''))],
736
974
        lines = [' '.join([r2, r11, r12]), r11, r12]
737
975
        encoded_body = '\n'.join(lines)
738
976
 
739
 
        responses = [(('ok', ), encoded_body)]
740
977
        transport_path = 'sinhala'
741
 
        repo, client = self.setup_fake_client_and_repository(
742
 
            responses, transport_path)
743
 
        result = repo.get_revision_graph(r2)
 
978
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
979
        client.add_success_response_with_body(encoded_body, 'ok')
 
980
        result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
744
981
        self.assertEqual(
745
982
            [('call_expecting_body', 'Repository.get_revision_graph',
746
983
             ('sinhala/', r2))],
749
986
 
750
987
    def test_no_such_revision(self):
751
988
        revid = '123'
752
 
        responses = [(('nosuchrevision', revid), '')]
753
989
        transport_path = 'sinhala'
754
 
        repo, client = self.setup_fake_client_and_repository(
755
 
            responses, transport_path)
 
990
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
991
        client.add_error_response('nosuchrevision', revid)
756
992
        # also check that the right revision is reported in the error
757
993
        self.assertRaises(errors.NoSuchRevision,
758
 
            repo.get_revision_graph, revid)
 
994
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
759
995
        self.assertEqual(
760
996
            [('call_expecting_body', 'Repository.get_revision_graph',
761
997
             ('sinhala/', revid))],
762
998
            client._calls)
763
999
 
 
1000
    def test_unexpected_error(self):
 
1001
        revid = '123'
 
1002
        transport_path = 'sinhala'
 
1003
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1004
        client.add_error_response('AnUnexpectedError')
 
1005
        e = self.assertRaises(errors.ErrorFromSmartServer,
 
1006
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
1007
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
 
1008
 
764
1009
        
765
1010
class TestRepositoryIsShared(TestRemoteRepository):
766
1011
 
767
1012
    def test_is_shared(self):
768
1013
        # ('yes', ) for Repository.is_shared -> 'True'.
769
 
        responses = [(('yes', ), )]
770
1014
        transport_path = 'quack'
771
 
        repo, client = self.setup_fake_client_and_repository(
772
 
            responses, transport_path)
 
1015
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1016
        client.add_success_response('yes')
773
1017
        result = repo.is_shared()
774
1018
        self.assertEqual(
775
1019
            [('call', 'Repository.is_shared', ('quack/',))],
778
1022
 
779
1023
    def test_is_not_shared(self):
780
1024
        # ('no', ) for Repository.is_shared -> 'False'.
781
 
        responses = [(('no', ), )]
782
1025
        transport_path = 'qwack'
783
 
        repo, client = self.setup_fake_client_and_repository(
784
 
            responses, transport_path)
 
1026
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1027
        client.add_success_response('no')
785
1028
        result = repo.is_shared()
786
1029
        self.assertEqual(
787
1030
            [('call', 'Repository.is_shared', ('qwack/',))],
792
1035
class TestRepositoryLockWrite(TestRemoteRepository):
793
1036
 
794
1037
    def test_lock_write(self):
795
 
        responses = [(('ok', 'a token'), '')]
796
1038
        transport_path = 'quack'
797
 
        repo, client = self.setup_fake_client_and_repository(
798
 
            responses, transport_path)
 
1039
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1040
        client.add_success_response('ok', 'a token')
799
1041
        result = repo.lock_write()
800
1042
        self.assertEqual(
801
1043
            [('call', 'Repository.lock_write', ('quack/', ''))],
803
1045
        self.assertEqual('a token', result)
804
1046
 
805
1047
    def test_lock_write_already_locked(self):
806
 
        responses = [(('LockContention', ), '')]
807
1048
        transport_path = 'quack'
808
 
        repo, client = self.setup_fake_client_and_repository(
809
 
            responses, transport_path)
 
1049
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1050
        client.add_error_response('LockContention')
810
1051
        self.assertRaises(errors.LockContention, repo.lock_write)
811
1052
        self.assertEqual(
812
1053
            [('call', 'Repository.lock_write', ('quack/', ''))],
813
1054
            client._calls)
814
1055
 
815
1056
    def test_lock_write_unlockable(self):
816
 
        responses = [(('UnlockableTransport', ), '')]
817
1057
        transport_path = 'quack'
818
 
        repo, client = self.setup_fake_client_and_repository(
819
 
            responses, transport_path)
 
1058
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1059
        client.add_error_response('UnlockableTransport')
820
1060
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
821
1061
        self.assertEqual(
822
1062
            [('call', 'Repository.lock_write', ('quack/', ''))],
826
1066
class TestRepositoryUnlock(TestRemoteRepository):
827
1067
 
828
1068
    def test_unlock(self):
829
 
        responses = [(('ok', 'a token'), ''),
830
 
                     (('ok',), '')]
831
1069
        transport_path = 'quack'
832
 
        repo, client = self.setup_fake_client_and_repository(
833
 
            responses, transport_path)
 
1070
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1071
        client.add_success_response('ok', 'a token')
 
1072
        client.add_success_response('ok')
834
1073
        repo.lock_write()
835
1074
        repo.unlock()
836
1075
        self.assertEqual(
840
1079
 
841
1080
    def test_unlock_wrong_token(self):
842
1081
        # If somehow the token is wrong, unlock will raise TokenMismatch.
843
 
        responses = [(('ok', 'a token'), ''),
844
 
                     (('TokenMismatch',), '')]
845
1082
        transport_path = 'quack'
846
 
        repo, client = self.setup_fake_client_and_repository(
847
 
            responses, transport_path)
 
1083
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1084
        client.add_success_response('ok', 'a token')
 
1085
        client.add_error_response('TokenMismatch')
848
1086
        repo.lock_write()
849
1087
        self.assertRaises(errors.TokenMismatch, repo.unlock)
850
1088
 
854
1092
    def test_none(self):
855
1093
        # repo.has_revision(None) should not cause any traffic.
856
1094
        transport_path = 'quack'
857
 
        responses = None
858
 
        repo, client = self.setup_fake_client_and_repository(
859
 
            responses, transport_path)
 
1095
        repo, client = self.setup_fake_client_and_repository(transport_path)
860
1096
 
861
1097
        # The null revision is always there, so has_revision(None) == True.
862
1098
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
886
1122
    def test_repository_tarball(self):
887
1123
        # Test that Repository.tarball generates the right operations
888
1124
        transport_path = 'repo'
889
 
        expected_responses = [(('ok',), self.tarball_content),
890
 
            ]
891
1125
        expected_calls = [('call_expecting_body', 'Repository.tarball',
892
1126
                           ('repo/', 'bz2',),),
893
1127
            ]
894
 
        remote_repo, client = self.setup_fake_client_and_repository(
895
 
            expected_responses, transport_path)
 
1128
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1129
        client.add_success_response_with_body(self.tarball_content, 'ok')
896
1130
        # Now actually ask for the tarball
897
 
        tarball_file = remote_repo._get_tarball('bz2')
 
1131
        tarball_file = repo._get_tarball('bz2')
898
1132
        try:
899
1133
            self.assertEqual(expected_calls, client._calls)
900
1134
            self.assertEqual(self.tarball_content, tarball_file.read())
949
1183
        """
950
1184
        record = ('bytes', [('name1',), ('name2',)])
951
1185
        pack_stream = self.make_pack_stream([record])
952
 
        responses = [(('ok',), pack_stream), ]
953
1186
        transport_path = 'quack'
954
 
        repo, client = self.setup_fake_client_and_repository(
955
 
            responses, transport_path)
 
1187
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1188
        client.add_success_response_with_body(pack_stream, 'ok')
956
1189
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
957
1190
        stream = repo.get_data_stream_for_search(search)
958
1191
        self.assertRaises(errors.SmartProtocolError, list, stream)
959
1192
    
960
1193
    def test_backwards_compatibility(self):
961
1194
        """If the server doesn't recognise this request, fallback to VFS."""
962
 
        error_msg = (
963
 
            "Generic bzr smart protocol error: "
964
 
            "bad request 'Repository.stream_revisions_chunked'")
965
 
        responses = [
966
 
            (('error', error_msg), '')]
967
 
        repo, client = self.setup_fake_client_and_repository(
968
 
            responses, 'path')
 
1195
        repo, client = self.setup_fake_client_and_repository('path')
 
1196
        client.add_unknown_method_response(
 
1197
            'Repository.stream_revisions_chunked')
969
1198
        self.mock_called = False
970
1199
        repo._real_repository = MockRealRepository(self)
971
1200
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))