/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: Aaron Bentley
  • Date: 2008-10-28 10:31:32 UTC
  • mto: (0.14.36 prepare-shelf)
  • mto: This revision was merged to the branch mainline in revision 3820.
  • Revision ID: aaron@aaronbentley.com-20081028103132-syota6mcye41kvqh
Update to use Tree.readlines, if possible.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Tests for remote bzrdir/branch/repo/etc
 
18
 
 
19
These are proxy objects which act on remote objects by sending messages
 
20
through a smart client.  The proxies are to be created when attempting to open
 
21
the object given a transport that supports smartserver rpc operations. 
 
22
 
 
23
These tests correspond to tests.test_smart, which exercises the server side.
 
24
"""
 
25
 
 
26
import bz2
 
27
from cStringIO import StringIO
 
28
 
 
29
from bzrlib import (
 
30
    config,
 
31
    errors,
 
32
    graph,
 
33
    pack,
 
34
    remote,
 
35
    repository,
 
36
    tests,
 
37
    urlutils,
 
38
    )
 
39
from bzrlib.branch import Branch
 
40
from bzrlib.bzrdir import BzrDir, BzrDirFormat
 
41
from bzrlib.remote import (
 
42
    RemoteBranch,
 
43
    RemoteBzrDir,
 
44
    RemoteBzrDirFormat,
 
45
    RemoteRepository,
 
46
    )
 
47
from bzrlib.revision import NULL_REVISION
 
48
from bzrlib.smart import server, medium
 
49
from bzrlib.smart.client import _SmartClient
 
50
from bzrlib.symbol_versioning import one_four
 
51
from bzrlib.transport import get_transport, http
 
52
from bzrlib.transport.memory import MemoryTransport
 
53
from bzrlib.transport.remote import (
 
54
    RemoteTransport,
 
55
    RemoteSSHTransport,
 
56
    RemoteTCPTransport,
 
57
)
 
58
 
 
59
 
 
60
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
 
61
 
 
62
    def setUp(self):
 
63
        self.transport_server = server.SmartTCPServer_for_testing
 
64
        super(BasicRemoteObjectTests, self).setUp()
 
65
        self.transport = self.get_transport()
 
66
        # make a branch that can be opened over the smart transport
 
67
        self.local_wt = BzrDir.create_standalone_workingtree('.')
 
68
 
 
69
    def tearDown(self):
 
70
        self.transport.disconnect()
 
71
        tests.TestCaseWithTransport.tearDown(self)
 
72
 
 
73
    def test_create_remote_bzrdir(self):
 
74
        b = remote.RemoteBzrDir(self.transport)
 
75
        self.assertIsInstance(b, BzrDir)
 
76
 
 
77
    def test_open_remote_branch(self):
 
78
        # open a standalone branch in the working directory
 
79
        b = remote.RemoteBzrDir(self.transport)
 
80
        branch = b.open_branch()
 
81
        self.assertIsInstance(branch, Branch)
 
82
 
 
83
    def test_remote_repository(self):
 
84
        b = BzrDir.open_from_transport(self.transport)
 
85
        repo = b.open_repository()
 
86
        revid = u'\xc823123123'.encode('utf8')
 
87
        self.assertFalse(repo.has_revision(revid))
 
88
        self.local_wt.commit(message='test commit', rev_id=revid)
 
89
        self.assertTrue(repo.has_revision(revid))
 
90
 
 
91
    def test_remote_branch_revision_history(self):
 
92
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
93
        self.assertEqual([], b.revision_history())
 
94
        r1 = self.local_wt.commit('1st commit')
 
95
        r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
 
96
        self.assertEqual([r1, r2], b.revision_history())
 
97
 
 
98
    def test_find_correct_format(self):
 
99
        """Should open a RemoteBzrDir over a RemoteTransport"""
 
100
        fmt = BzrDirFormat.find_format(self.transport)
 
101
        self.assertTrue(RemoteBzrDirFormat
 
102
                        in BzrDirFormat._control_server_formats)
 
103
        self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
 
104
 
 
105
    def test_open_detected_smart_format(self):
 
106
        fmt = BzrDirFormat.find_format(self.transport)
 
107
        d = fmt.open(self.transport)
 
108
        self.assertIsInstance(d, BzrDir)
 
109
 
 
110
    def test_remote_branch_repr(self):
 
111
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
112
        self.assertStartsWith(str(b), 'RemoteBranch(')
 
113
 
 
114
 
 
115
class FakeRemoteTransport(object):
 
116
    """This class provides the minimum support for use in place of a RemoteTransport.
 
117
    
 
118
    It doesn't actually transmit requests, but rather expects them to be
 
119
    handled by a FakeClient which holds canned responses.  It does not allow
 
120
    any vfs access, therefore is not suitable for testing any operation that
 
121
    will fallback to vfs access.  Backing the test by an instance of this
 
122
    class guarantees that it's - done using non-vfs operations.
 
123
    """
 
124
 
 
125
    _default_url = 'fakeremotetransport://host/path/'
 
126
 
 
127
    def __init__(self, url=None):
 
128
        if url is None:
 
129
            url = self._default_url
 
130
        self.base = url
 
131
 
 
132
    def __repr__(self):
 
133
        return "%r(%r)" % (self.__class__.__name__,
 
134
            self.base)
 
135
 
 
136
    def clone(self, relpath):
 
137
        return FakeRemoteTransport(urlutils.join(self.base, relpath))
 
138
 
 
139
    def get(self, relpath):
 
140
        # only get is specifically stubbed out, because it's usually the first
 
141
        # thing we do.  anything else will fail with an AttributeError.
 
142
        raise AssertionError("%r doesn't support file access to %r"
 
143
            % (self, relpath))
 
144
 
 
145
 
 
146
 
 
147
class FakeProtocol(object):
 
148
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
 
149
 
 
150
    def __init__(self, body, fake_client):
 
151
        self.body = body
 
152
        self._body_buffer = None
 
153
        self._fake_client = fake_client
 
154
 
 
155
    def read_body_bytes(self, count=-1):
 
156
        if self._body_buffer is None:
 
157
            self._body_buffer = StringIO(self.body)
 
158
        bytes = self._body_buffer.read(count)
 
159
        if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
 
160
            self._fake_client.expecting_body = False
 
161
        return bytes
 
162
 
 
163
    def cancel_read_body(self):
 
164
        self._fake_client.expecting_body = False
 
165
 
 
166
    def read_streamed_body(self):
 
167
        return self.body
 
168
 
 
169
 
 
170
class FakeClient(_SmartClient):
 
171
    """Lookalike for _SmartClient allowing testing."""
 
172
    
 
173
    def __init__(self, fake_medium_base='fake base'):
 
174
        """Create a FakeClient.
 
175
 
 
176
        :param responses: A list of response-tuple, body-data pairs to be sent
 
177
            back to callers.  A special case is if the response-tuple is
 
178
            'unknown verb', then a UnknownSmartMethod will be raised for that
 
179
            call, using the second element of the tuple as the verb in the
 
180
            exception.
 
181
        """
 
182
        self.responses = []
 
183
        self._calls = []
 
184
        self.expecting_body = False
 
185
        # if non-None, this is the list of expected calls, with only the
 
186
        # method name and arguments included.  the body might be hard to
 
187
        # compute so is not included
 
188
        self._expected_calls = None
 
189
        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
 
190
 
 
191
    def add_expected_call(self, call_name, call_args, response_type,
 
192
        response_args, response_body=None):
 
193
        if self._expected_calls is None:
 
194
            self._expected_calls = []
 
195
        self._expected_calls.append((call_name, call_args))
 
196
        self.responses.append((response_type, response_args, response_body))
 
197
 
 
198
    def add_success_response(self, *args):
 
199
        self.responses.append(('success', args, None))
 
200
 
 
201
    def add_success_response_with_body(self, body, *args):
 
202
        self.responses.append(('success', args, body))
 
203
 
 
204
    def add_error_response(self, *args):
 
205
        self.responses.append(('error', args))
 
206
 
 
207
    def add_unknown_method_response(self, verb):
 
208
        self.responses.append(('unknown', verb))
 
209
 
 
210
    def finished_test(self):
 
211
        if self._expected_calls:
 
212
            raise AssertionError("%r finished but was still expecting %r"
 
213
                % (self, self._expected_calls[0]))
 
214
 
 
215
    def _get_next_response(self):
 
216
        try:
 
217
            response_tuple = self.responses.pop(0)
 
218
        except IndexError, e:
 
219
            raise AssertionError("%r didn't expect any more calls"
 
220
                % (self,))
 
221
        if response_tuple[0] == 'unknown':
 
222
            raise errors.UnknownSmartMethod(response_tuple[1])
 
223
        elif response_tuple[0] == 'error':
 
224
            raise errors.ErrorFromSmartServer(response_tuple[1])
 
225
        return response_tuple
 
226
 
 
227
    def _check_call(self, method, args):
 
228
        if self._expected_calls is None:
 
229
            # the test should be updated to say what it expects
 
230
            return
 
231
        try:
 
232
            next_call = self._expected_calls.pop(0)
 
233
        except IndexError:
 
234
            raise AssertionError("%r didn't expect any more calls "
 
235
                "but got %r%r"
 
236
                % (self, method, args,))
 
237
        if method != next_call[0] or args != next_call[1]:
 
238
            raise AssertionError("%r expected %r%r "
 
239
                "but got %r%r"
 
240
                % (self, next_call[0], next_call[1], method, args,))
 
241
 
 
242
    def call(self, method, *args):
 
243
        self._check_call(method, args)
 
244
        self._calls.append(('call', method, args))
 
245
        return self._get_next_response()[1]
 
246
 
 
247
    def call_expecting_body(self, method, *args):
 
248
        self._check_call(method, args)
 
249
        self._calls.append(('call_expecting_body', method, args))
 
250
        result = self._get_next_response()
 
251
        self.expecting_body = True
 
252
        return result[1], FakeProtocol(result[2], self)
 
253
 
 
254
    def call_with_body_bytes_expecting_body(self, method, args, body):
 
255
        self._check_call(method, args)
 
256
        self._calls.append(('call_with_body_bytes_expecting_body', method,
 
257
            args, body))
 
258
        result = self._get_next_response()
 
259
        self.expecting_body = True
 
260
        return result[1], FakeProtocol(result[2], self)
 
261
 
 
262
 
 
263
class FakeMedium(medium.SmartClientMedium):
 
264
 
 
265
    def __init__(self, client_calls, base):
 
266
        medium.SmartClientMedium.__init__(self, base)
 
267
        self._client_calls = client_calls
 
268
 
 
269
    def disconnect(self):
 
270
        self._client_calls.append(('disconnect medium',))
 
271
 
 
272
 
 
273
class TestVfsHas(tests.TestCase):
 
274
 
 
275
    def test_unicode_path(self):
 
276
        client = FakeClient('/')
 
277
        client.add_success_response('yes',)
 
278
        transport = RemoteTransport('bzr://localhost/', _client=client)
 
279
        filename = u'/hell\u00d8'.encode('utf8')
 
280
        result = transport.has(filename)
 
281
        self.assertEqual(
 
282
            [('call', 'has', (filename,))],
 
283
            client._calls)
 
284
        self.assertTrue(result)
 
285
 
 
286
 
 
287
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
 
288
    """Tests for the behaviour of client_medium.remote_path_from_transport."""
 
289
 
 
290
    def assertRemotePath(self, expected, client_base, transport_base):
 
291
        """Assert that the result of
 
292
        SmartClientMedium.remote_path_from_transport is the expected value for
 
293
        a given client_base and transport_base.
 
294
        """
 
295
        client_medium = medium.SmartClientMedium(client_base)
 
296
        transport = get_transport(transport_base)
 
297
        result = client_medium.remote_path_from_transport(transport)
 
298
        self.assertEqual(expected, result)
 
299
 
 
300
    def test_remote_path_from_transport(self):
 
301
        """SmartClientMedium.remote_path_from_transport calculates a URL for
 
302
        the given transport relative to the root of the client base URL.
 
303
        """
 
304
        self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
 
305
        self.assertRemotePath(
 
306
            'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
 
307
 
 
308
    def assertRemotePathHTTP(self, expected, transport_base, relpath):
 
309
        """Assert that the result of
 
310
        HttpTransportBase.remote_path_from_transport is the expected value for
 
311
        a given transport_base and relpath of that transport.  (Note that
 
312
        HttpTransportBase is a subclass of SmartClientMedium)
 
313
        """
 
314
        base_transport = get_transport(transport_base)
 
315
        client_medium = base_transport.get_smart_medium()
 
316
        cloned_transport = base_transport.clone(relpath)
 
317
        result = client_medium.remote_path_from_transport(cloned_transport)
 
318
        self.assertEqual(expected, result)
 
319
        
 
320
    def test_remote_path_from_transport_http(self):
 
321
        """Remote paths for HTTP transports are calculated differently to other
 
322
        transports.  They are just relative to the client base, not the root
 
323
        directory of the host.
 
324
        """
 
325
        for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
 
326
            self.assertRemotePathHTTP(
 
327
                '../xyz/', scheme + '//host/path', '../xyz/')
 
328
            self.assertRemotePathHTTP(
 
329
                'xyz/', scheme + '//host/path', 'xyz/')
 
330
 
 
331
 
 
332
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
 
333
    """Tests for the behaviour of client_medium.remote_is_at_least."""
 
334
 
 
335
    def test_initially_unlimited(self):
 
336
        """A fresh medium assumes that the remote side supports all
 
337
        versions.
 
338
        """
 
339
        client_medium = medium.SmartClientMedium('dummy base')
 
340
        self.assertFalse(client_medium._is_remote_before((99, 99)))
 
341
    
 
342
    def test__remember_remote_is_before(self):
 
343
        """Calling _remember_remote_is_before ratchets down the known remote
 
344
        version.
 
345
        """
 
346
        client_medium = medium.SmartClientMedium('dummy base')
 
347
        # Mark the remote side as being less than 1.6.  The remote side may
 
348
        # still be 1.5.
 
349
        client_medium._remember_remote_is_before((1, 6))
 
350
        self.assertTrue(client_medium._is_remote_before((1, 6)))
 
351
        self.assertFalse(client_medium._is_remote_before((1, 5)))
 
352
        # Calling _remember_remote_is_before again with a lower value works.
 
353
        client_medium._remember_remote_is_before((1, 5))
 
354
        self.assertTrue(client_medium._is_remote_before((1, 5)))
 
355
        # You cannot call _remember_remote_is_before with a larger value.
 
356
        self.assertRaises(
 
357
            AssertionError, client_medium._remember_remote_is_before, (1, 9))
 
358
 
 
359
 
 
360
class TestBzrDirOpenBranch(tests.TestCase):
 
361
 
 
362
    def test_branch_present(self):
 
363
        transport = MemoryTransport()
 
364
        transport.mkdir('quack')
 
365
        transport = transport.clone('quack')
 
366
        client = FakeClient(transport.base)
 
367
        client.add_expected_call(
 
368
            'BzrDir.open_branch', ('quack/',),
 
369
            'success', ('ok', ''))
 
370
        client.add_expected_call(
 
371
            'BzrDir.find_repositoryV2', ('quack/',),
 
372
            'success', ('ok', '', 'no', 'no', 'no'))
 
373
        client.add_expected_call(
 
374
            'Branch.get_stacked_on_url', ('quack/',),
 
375
            'error', ('NotStacked',))
 
376
        bzrdir = RemoteBzrDir(transport, _client=client)
 
377
        result = bzrdir.open_branch()
 
378
        self.assertIsInstance(result, RemoteBranch)
 
379
        self.assertEqual(bzrdir, result.bzrdir)
 
380
        client.finished_test()
 
381
 
 
382
    def test_branch_missing(self):
 
383
        transport = MemoryTransport()
 
384
        transport.mkdir('quack')
 
385
        transport = transport.clone('quack')
 
386
        client = FakeClient(transport.base)
 
387
        client.add_error_response('nobranch')
 
388
        bzrdir = RemoteBzrDir(transport, _client=client)
 
389
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
 
390
        self.assertEqual(
 
391
            [('call', 'BzrDir.open_branch', ('quack/',))],
 
392
            client._calls)
 
393
 
 
394
    def test__get_tree_branch(self):
 
395
        # _get_tree_branch is a form of open_branch, but it should only ask for
 
396
        # branch opening, not any other network requests.
 
397
        calls = []
 
398
        def open_branch():
 
399
            calls.append("Called")
 
400
            return "a-branch"
 
401
        transport = MemoryTransport()
 
402
        # no requests on the network - catches other api calls being made.
 
403
        client = FakeClient(transport.base)
 
404
        bzrdir = RemoteBzrDir(transport, _client=client)
 
405
        # patch the open_branch call to record that it was called.
 
406
        bzrdir.open_branch = open_branch
 
407
        self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
 
408
        self.assertEqual(["Called"], calls)
 
409
        self.assertEqual([], client._calls)
 
410
 
 
411
    def test_url_quoting_of_path(self):
 
412
        # Relpaths on the wire should not be URL-escaped.  So "~" should be
 
413
        # transmitted as "~", not "%7E".
 
414
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
 
415
        client = FakeClient(transport.base)
 
416
        client.add_expected_call(
 
417
            'BzrDir.open_branch', ('~hello/',),
 
418
            'success', ('ok', ''))
 
419
        client.add_expected_call(
 
420
            'BzrDir.find_repositoryV2', ('~hello/',),
 
421
            'success', ('ok', '', 'no', 'no', 'no'))
 
422
        client.add_expected_call(
 
423
            'Branch.get_stacked_on_url', ('~hello/',),
 
424
            'error', ('NotStacked',))
 
425
        bzrdir = RemoteBzrDir(transport, _client=client)
 
426
        result = bzrdir.open_branch()
 
427
        client.finished_test()
 
428
 
 
429
    def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
 
430
        transport = MemoryTransport()
 
431
        transport.mkdir('quack')
 
432
        transport = transport.clone('quack')
 
433
        if rich_root:
 
434
            rich_response = 'yes'
 
435
        else:
 
436
            rich_response = 'no'
 
437
        if subtrees:
 
438
            subtree_response = 'yes'
 
439
        else:
 
440
            subtree_response = 'no'
 
441
        client = FakeClient(transport.base)
 
442
        client.add_success_response(
 
443
            'ok', '', rich_response, subtree_response, external_lookup)
 
444
        bzrdir = RemoteBzrDir(transport, _client=client)
 
445
        result = bzrdir.open_repository()
 
446
        self.assertEqual(
 
447
            [('call', 'BzrDir.find_repositoryV2', ('quack/',))],
 
448
            client._calls)
 
449
        self.assertIsInstance(result, RemoteRepository)
 
450
        self.assertEqual(bzrdir, result.bzrdir)
 
451
        self.assertEqual(rich_root, result._format.rich_root_data)
 
452
        self.assertEqual(subtrees, result._format.supports_tree_reference)
 
453
 
 
454
    def test_open_repository_sets_format_attributes(self):
 
455
        self.check_open_repository(True, True)
 
456
        self.check_open_repository(False, True)
 
457
        self.check_open_repository(True, False)
 
458
        self.check_open_repository(False, False)
 
459
        self.check_open_repository(False, False, 'yes')
 
460
 
 
461
    def test_old_server(self):
 
462
        """RemoteBzrDirFormat should fail to probe if the server version is too
 
463
        old.
 
464
        """
 
465
        self.assertRaises(errors.NotBranchError,
 
466
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
 
467
 
 
468
 
 
469
class TestBzrDirOpenRepository(tests.TestCase):
 
470
 
 
471
    def test_backwards_compat_1_2(self):
 
472
        transport = MemoryTransport()
 
473
        transport.mkdir('quack')
 
474
        transport = transport.clone('quack')
 
475
        client = FakeClient(transport.base)
 
476
        client.add_unknown_method_response('RemoteRepository.find_repositoryV2')
 
477
        client.add_success_response('ok', '', 'no', 'no')
 
478
        bzrdir = RemoteBzrDir(transport, _client=client)
 
479
        repo = bzrdir.open_repository()
 
480
        self.assertEqual(
 
481
            [('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
482
             ('call', 'BzrDir.find_repository', ('quack/',))],
 
483
            client._calls)
 
484
 
 
485
 
 
486
class OldSmartClient(object):
 
487
    """A fake smart client for test_old_version that just returns a version one
 
488
    response to the 'hello' (query version) command.
 
489
    """
 
490
 
 
491
    def get_request(self):
 
492
        input_file = StringIO('ok\x011\n')
 
493
        output_file = StringIO()
 
494
        client_medium = medium.SmartSimplePipesClientMedium(
 
495
            input_file, output_file)
 
496
        return medium.SmartClientStreamMediumRequest(client_medium)
 
497
 
 
498
    def protocol_version(self):
 
499
        return 1
 
500
 
 
501
 
 
502
class OldServerTransport(object):
 
503
    """A fake transport for test_old_server that reports it's smart server
 
504
    protocol version as version one.
 
505
    """
 
506
 
 
507
    def __init__(self):
 
508
        self.base = 'fake:'
 
509
 
 
510
    def get_smart_client(self):
 
511
        return OldSmartClient()
 
512
 
 
513
 
 
514
class RemoteBranchTestCase(tests.TestCase):
 
515
 
 
516
    def make_remote_branch(self, transport, client):
 
517
        """Make a RemoteBranch using 'client' as its _SmartClient.
 
518
        
 
519
        A RemoteBzrDir and RemoteRepository will also be created to fill out
 
520
        the RemoteBranch, albeit with stub values for some of their attributes.
 
521
        """
 
522
        # we do not want bzrdir to make any remote calls, so use False as its
 
523
        # _client.  If it tries to make a remote call, this will fail
 
524
        # immediately.
 
525
        bzrdir = RemoteBzrDir(transport, _client=False)
 
526
        repo = RemoteRepository(bzrdir, None, _client=client)
 
527
        return RemoteBranch(bzrdir, repo, _client=client)
 
528
 
 
529
 
 
530
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
 
531
 
 
532
    def test_empty_branch(self):
 
533
        # in an empty branch we decode the response properly
 
534
        transport = MemoryTransport()
 
535
        client = FakeClient(transport.base)
 
536
        client.add_expected_call(
 
537
            'Branch.get_stacked_on_url', ('quack/',),
 
538
            'error', ('NotStacked',))
 
539
        client.add_expected_call(
 
540
            'Branch.last_revision_info', ('quack/',),
 
541
            'success', ('ok', '0', 'null:'))
 
542
        transport.mkdir('quack')
 
543
        transport = transport.clone('quack')
 
544
        branch = self.make_remote_branch(transport, client)
 
545
        result = branch.last_revision_info()
 
546
        client.finished_test()
 
547
        self.assertEqual((0, NULL_REVISION), result)
 
548
 
 
549
    def test_non_empty_branch(self):
 
550
        # in a non-empty branch we also decode the response properly
 
551
        revid = u'\xc8'.encode('utf8')
 
552
        transport = MemoryTransport()
 
553
        client = FakeClient(transport.base)
 
554
        client.add_expected_call(
 
555
            'Branch.get_stacked_on_url', ('kwaak/',),
 
556
            'error', ('NotStacked',))
 
557
        client.add_expected_call(
 
558
            'Branch.last_revision_info', ('kwaak/',),
 
559
            'success', ('ok', '2', revid))
 
560
        transport.mkdir('kwaak')
 
561
        transport = transport.clone('kwaak')
 
562
        branch = self.make_remote_branch(transport, client)
 
563
        result = branch.last_revision_info()
 
564
        self.assertEqual((2, revid), result)
 
565
 
 
566
 
 
567
class TestBranch_get_stacked_on_url(tests.TestCaseWithMemoryTransport):
 
568
    """Test Branch._get_stacked_on_url rpc"""
 
569
 
 
570
    def test_get_stacked_on_invalid_url(self):
 
571
        raise tests.KnownFailure('opening a branch requires the server to open the fallback repository')
 
572
        transport = FakeRemoteTransport('fakeremotetransport:///')
 
573
        client = FakeClient(transport.base)
 
574
        client.add_expected_call(
 
575
            'Branch.get_stacked_on_url', ('.',),
 
576
            'success', ('ok', 'file:///stacked/on'))
 
577
        bzrdir = RemoteBzrDir(transport, _client=client)
 
578
        branch = RemoteBranch(bzrdir, None, _client=client)
 
579
        result = branch.get_stacked_on_url()
 
580
        self.assertEqual(
 
581
            'file:///stacked/on', result)
 
582
 
 
583
    def test_backwards_compatible(self):
 
584
        # like with bzr1.6 with no Branch.get_stacked_on_url rpc
 
585
        base_branch = self.make_branch('base', format='1.6')
 
586
        stacked_branch = self.make_branch('stacked', format='1.6')
 
587
        stacked_branch.set_stacked_on_url('../base')
 
588
        client = FakeClient(self.get_url())
 
589
        client.add_expected_call(
 
590
            'BzrDir.open_branch', ('stacked/',),
 
591
            'success', ('ok', ''))
 
592
        client.add_expected_call(
 
593
            'BzrDir.find_repositoryV2', ('stacked/',),
 
594
            'success', ('ok', '', 'no', 'no', 'no'))
 
595
        # called twice, once from constructor and then again by us
 
596
        client.add_expected_call(
 
597
            'Branch.get_stacked_on_url', ('stacked/',),
 
598
            'unknown', ('Branch.get_stacked_on_url',))
 
599
        client.add_expected_call(
 
600
            'Branch.get_stacked_on_url', ('stacked/',),
 
601
            'unknown', ('Branch.get_stacked_on_url',))
 
602
        # this will also do vfs access, but that goes direct to the transport
 
603
        # and isn't seen by the FakeClient.
 
604
        bzrdir = RemoteBzrDir(self.get_transport('stacked'), _client=client)
 
605
        branch = bzrdir.open_branch()
 
606
        result = branch.get_stacked_on_url()
 
607
        self.assertEqual('../base', result)
 
608
        client.finished_test()
 
609
        # it's in the fallback list both for the RemoteRepository and its vfs
 
610
        # repository
 
611
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
612
        self.assertEqual(1,
 
613
            len(branch.repository._real_repository._fallback_repositories))
 
614
 
 
615
    def test_get_stacked_on_real_branch(self):
 
616
        base_branch = self.make_branch('base', format='1.6')
 
617
        stacked_branch = self.make_branch('stacked', format='1.6')
 
618
        stacked_branch.set_stacked_on_url('../base')
 
619
        client = FakeClient(self.get_url())
 
620
        client.add_expected_call(
 
621
            'BzrDir.open_branch', ('stacked/',),
 
622
            'success', ('ok', ''))
 
623
        client.add_expected_call(
 
624
            'BzrDir.find_repositoryV2', ('stacked/',),
 
625
            'success', ('ok', '', 'no', 'no', 'no'))
 
626
        # called twice, once from constructor and then again by us
 
627
        client.add_expected_call(
 
628
            'Branch.get_stacked_on_url', ('stacked/',),
 
629
            'success', ('ok', '../base'))
 
630
        client.add_expected_call(
 
631
            'Branch.get_stacked_on_url', ('stacked/',),
 
632
            'success', ('ok', '../base'))
 
633
        bzrdir = RemoteBzrDir(self.get_transport('stacked'), _client=client)
 
634
        branch = bzrdir.open_branch()
 
635
        result = branch.get_stacked_on_url()
 
636
        self.assertEqual('../base', result)
 
637
        client.finished_test()
 
638
        # it's in the fallback list both for the RemoteRepository and its vfs
 
639
        # repository
 
640
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
641
        self.assertEqual(1,
 
642
            len(branch.repository._real_repository._fallback_repositories))
 
643
 
 
644
 
 
645
class TestBranchSetLastRevision(RemoteBranchTestCase):
 
646
 
 
647
    def test_set_empty(self):
 
648
        # set_revision_history([]) is translated to calling
 
649
        # Branch.set_last_revision(path, '') on the wire.
 
650
        transport = MemoryTransport()
 
651
        transport.mkdir('branch')
 
652
        transport = transport.clone('branch')
 
653
 
 
654
        client = FakeClient(transport.base)
 
655
        client.add_expected_call(
 
656
            'Branch.get_stacked_on_url', ('branch/',),
 
657
            'error', ('NotStacked',))
 
658
        client.add_expected_call(
 
659
            'Branch.lock_write', ('branch/', '', ''),
 
660
            'success', ('ok', 'branch token', 'repo token'))
 
661
        client.add_expected_call(
 
662
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
 
663
            'success', ('ok',))
 
664
        client.add_expected_call(
 
665
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
666
            'success', ('ok',))
 
667
        branch = self.make_remote_branch(transport, client)
 
668
        # This is a hack to work around the problem that RemoteBranch currently
 
669
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
670
        branch._ensure_real = lambda: None
 
671
        branch.lock_write()
 
672
        result = branch.set_revision_history([])
 
673
        branch.unlock()
 
674
        self.assertEqual(None, result)
 
675
        client.finished_test()
 
676
 
 
677
    def test_set_nonempty(self):
 
678
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
 
679
        # Branch.set_last_revision(path, rev-idN) on the wire.
 
680
        transport = MemoryTransport()
 
681
        transport.mkdir('branch')
 
682
        transport = transport.clone('branch')
 
683
 
 
684
        client = FakeClient(transport.base)
 
685
        client.add_expected_call(
 
686
            'Branch.get_stacked_on_url', ('branch/',),
 
687
            'error', ('NotStacked',))
 
688
        client.add_expected_call(
 
689
            'Branch.lock_write', ('branch/', '', ''),
 
690
            'success', ('ok', 'branch token', 'repo token'))
 
691
        client.add_expected_call(
 
692
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
 
693
            'success', ('ok',))
 
694
        client.add_expected_call(
 
695
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
696
            'success', ('ok',))
 
697
        branch = self.make_remote_branch(transport, client)
 
698
        # This is a hack to work around the problem that RemoteBranch currently
 
699
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
700
        branch._ensure_real = lambda: None
 
701
        # Lock the branch, reset the record of remote calls.
 
702
        branch.lock_write()
 
703
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
 
704
        branch.unlock()
 
705
        self.assertEqual(None, result)
 
706
        client.finished_test()
 
707
 
 
708
    def test_no_such_revision(self):
 
709
        transport = MemoryTransport()
 
710
        transport.mkdir('branch')
 
711
        transport = transport.clone('branch')
 
712
        # A response of 'NoSuchRevision' is translated into an exception.
 
713
        client = FakeClient(transport.base)
 
714
        client.add_expected_call(
 
715
            'Branch.get_stacked_on_url', ('branch/',),
 
716
            'error', ('NotStacked',))
 
717
        client.add_expected_call(
 
718
            'Branch.lock_write', ('branch/', '', ''),
 
719
            'success', ('ok', 'branch token', 'repo token'))
 
720
        client.add_expected_call(
 
721
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
722
            'error', ('NoSuchRevision', 'rev-id'))
 
723
        client.add_expected_call(
 
724
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
725
            'success', ('ok',))
 
726
 
 
727
        branch = self.make_remote_branch(transport, client)
 
728
        branch.lock_write()
 
729
        self.assertRaises(
 
730
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
 
731
        branch.unlock()
 
732
        client.finished_test()
 
733
 
 
734
    def test_tip_change_rejected(self):
 
735
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
736
        be raised.
 
737
        """
 
738
        transport = MemoryTransport()
 
739
        transport.mkdir('branch')
 
740
        transport = transport.clone('branch')
 
741
        client = FakeClient(transport.base)
 
742
        rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
 
743
        rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
 
744
        client.add_expected_call(
 
745
            'Branch.get_stacked_on_url', ('branch/',),
 
746
            'error', ('NotStacked',))
 
747
        client.add_expected_call(
 
748
            'Branch.lock_write', ('branch/', '', ''),
 
749
            'success', ('ok', 'branch token', 'repo token'))
 
750
        client.add_expected_call(
 
751
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
752
            'error', ('TipChangeRejected', rejection_msg_utf8))
 
753
        client.add_expected_call(
 
754
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
755
            'success', ('ok',))
 
756
        branch = self.make_remote_branch(transport, client)
 
757
        branch._ensure_real = lambda: None
 
758
        branch.lock_write()
 
759
        self.addCleanup(branch.unlock)
 
760
        # The 'TipChangeRejected' error response triggered by calling
 
761
        # set_revision_history causes a TipChangeRejected exception.
 
762
        err = self.assertRaises(
 
763
            errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
 
764
        # The UTF-8 message from the response has been decoded into a unicode
 
765
        # object.
 
766
        self.assertIsInstance(err.msg, unicode)
 
767
        self.assertEqual(rejection_msg_unicode, err.msg)
 
768
        branch.unlock()
 
769
        client.finished_test()
 
770
 
 
771
 
 
772
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
 
773
 
 
774
    def test_set_last_revision_info(self):
 
775
        # set_last_revision_info(num, 'rev-id') is translated to calling
 
776
        # Branch.set_last_revision_info(num, 'rev-id') on the wire.
 
777
        transport = MemoryTransport()
 
778
        transport.mkdir('branch')
 
779
        transport = transport.clone('branch')
 
780
        client = FakeClient(transport.base)
 
781
        # get_stacked_on_url
 
782
        client.add_error_response('NotStacked')
 
783
        # lock_write
 
784
        client.add_success_response('ok', 'branch token', 'repo token')
 
785
        # set_last_revision
 
786
        client.add_success_response('ok')
 
787
        # unlock
 
788
        client.add_success_response('ok')
 
789
 
 
790
        branch = self.make_remote_branch(transport, client)
 
791
        # Lock the branch, reset the record of remote calls.
 
792
        branch.lock_write()
 
793
        client._calls = []
 
794
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
795
        self.assertEqual(
 
796
            [('call', 'Branch.set_last_revision_info',
 
797
                ('branch/', 'branch token', 'repo token',
 
798
                 '1234', 'a-revision-id'))],
 
799
            client._calls)
 
800
        self.assertEqual(None, result)
 
801
 
 
802
    def test_no_such_revision(self):
 
803
        # A response of 'NoSuchRevision' is translated into an exception.
 
804
        transport = MemoryTransport()
 
805
        transport.mkdir('branch')
 
806
        transport = transport.clone('branch')
 
807
        client = FakeClient(transport.base)
 
808
        # get_stacked_on_url
 
809
        client.add_error_response('NotStacked')
 
810
        # lock_write
 
811
        client.add_success_response('ok', 'branch token', 'repo token')
 
812
        # set_last_revision
 
813
        client.add_error_response('NoSuchRevision', 'revid')
 
814
        # unlock
 
815
        client.add_success_response('ok')
 
816
 
 
817
        branch = self.make_remote_branch(transport, client)
 
818
        # Lock the branch, reset the record of remote calls.
 
819
        branch.lock_write()
 
820
        client._calls = []
 
821
 
 
822
        self.assertRaises(
 
823
            errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
 
824
        branch.unlock()
 
825
 
 
826
    def lock_remote_branch(self, branch):
 
827
        """Trick a RemoteBranch into thinking it is locked."""
 
828
        branch._lock_mode = 'w'
 
829
        branch._lock_count = 2
 
830
        branch._lock_token = 'branch token'
 
831
        branch._repo_lock_token = 'repo token'
 
832
        branch.repository._lock_mode = 'w'
 
833
        branch.repository._lock_count = 2
 
834
        branch.repository._lock_token = 'repo token'
 
835
 
 
836
    def test_backwards_compatibility(self):
 
837
        """If the server does not support the Branch.set_last_revision_info
 
838
        verb (which is new in 1.4), then the client falls back to VFS methods.
 
839
        """
 
840
        # This test is a little messy.  Unlike most tests in this file, it
 
841
        # doesn't purely test what a Remote* object sends over the wire, and
 
842
        # how it reacts to responses from the wire.  It instead relies partly
 
843
        # on asserting that the RemoteBranch will call
 
844
        # self._real_branch.set_last_revision_info(...).
 
845
 
 
846
        # First, set up our RemoteBranch with a FakeClient that raises
 
847
        # UnknownSmartMethod, and a StubRealBranch that logs how it is called.
 
848
        transport = MemoryTransport()
 
849
        transport.mkdir('branch')
 
850
        transport = transport.clone('branch')
 
851
        client = FakeClient(transport.base)
 
852
        client.add_expected_call(
 
853
            'Branch.get_stacked_on_url', ('branch/',),
 
854
            'error', ('NotStacked',))
 
855
        client.add_expected_call(
 
856
            'Branch.set_last_revision_info',
 
857
            ('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
 
858
            'unknown', 'Branch.set_last_revision_info')
 
859
 
 
860
        branch = self.make_remote_branch(transport, client)
 
861
        class StubRealBranch(object):
 
862
            def __init__(self):
 
863
                self.calls = []
 
864
            def set_last_revision_info(self, revno, revision_id):
 
865
                self.calls.append(
 
866
                    ('set_last_revision_info', revno, revision_id))
 
867
            def _clear_cached_state(self):
 
868
                pass
 
869
        real_branch = StubRealBranch()
 
870
        branch._real_branch = real_branch
 
871
        self.lock_remote_branch(branch)
 
872
 
 
873
        # Call set_last_revision_info, and verify it behaved as expected.
 
874
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
875
        self.assertEqual(
 
876
            [('set_last_revision_info', 1234, 'a-revision-id')],
 
877
            real_branch.calls)
 
878
        client.finished_test()
 
879
 
 
880
    def test_unexpected_error(self):
 
881
        # If the server sends an error the client doesn't understand, it gets
 
882
        # turned into an UnknownErrorFromSmartServer, which is presented as a
 
883
        # non-internal error to the user.
 
884
        transport = MemoryTransport()
 
885
        transport.mkdir('branch')
 
886
        transport = transport.clone('branch')
 
887
        client = FakeClient(transport.base)
 
888
        # get_stacked_on_url
 
889
        client.add_error_response('NotStacked')
 
890
        # lock_write
 
891
        client.add_success_response('ok', 'branch token', 'repo token')
 
892
        # set_last_revision
 
893
        client.add_error_response('UnexpectedError')
 
894
        # unlock
 
895
        client.add_success_response('ok')
 
896
 
 
897
        branch = self.make_remote_branch(transport, client)
 
898
        # Lock the branch, reset the record of remote calls.
 
899
        branch.lock_write()
 
900
        client._calls = []
 
901
 
 
902
        err = self.assertRaises(
 
903
            errors.UnknownErrorFromSmartServer,
 
904
            branch.set_last_revision_info, 123, 'revid')
 
905
        self.assertEqual(('UnexpectedError',), err.error_tuple)
 
906
        branch.unlock()
 
907
 
 
908
    def test_tip_change_rejected(self):
 
909
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
910
        be raised.
 
911
        """
 
912
        transport = MemoryTransport()
 
913
        transport.mkdir('branch')
 
914
        transport = transport.clone('branch')
 
915
        client = FakeClient(transport.base)
 
916
        # get_stacked_on_url
 
917
        client.add_error_response('NotStacked')
 
918
        # lock_write
 
919
        client.add_success_response('ok', 'branch token', 'repo token')
 
920
        # set_last_revision
 
921
        client.add_error_response('TipChangeRejected', 'rejection message')
 
922
        # unlock
 
923
        client.add_success_response('ok')
 
924
 
 
925
        branch = self.make_remote_branch(transport, client)
 
926
        # Lock the branch, reset the record of remote calls.
 
927
        branch.lock_write()
 
928
        self.addCleanup(branch.unlock)
 
929
        client._calls = []
 
930
 
 
931
        # The 'TipChangeRejected' error response triggered by calling
 
932
        # set_last_revision_info causes a TipChangeRejected exception.
 
933
        err = self.assertRaises(
 
934
            errors.TipChangeRejected,
 
935
            branch.set_last_revision_info, 123, 'revid')
 
936
        self.assertEqual('rejection message', err.msg)
 
937
 
 
938
 
 
939
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
 
940
    """Getting the branch configuration should use an abstract method not vfs.
 
941
    """
 
942
 
 
943
    def test_get_branch_conf(self):
 
944
        raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
 
945
        ## # We should see that branch.get_config() does a single rpc to get the
 
946
        ## # remote configuration file, abstracting away where that is stored on
 
947
        ## # the server.  However at the moment it always falls back to using the
 
948
        ## # vfs, and this would need some changes in config.py.
 
949
 
 
950
        ## # in an empty branch we decode the response properly
 
951
        ## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
 
952
        ## # we need to make a real branch because the remote_branch.control_files
 
953
        ## # will trigger _ensure_real.
 
954
        ## branch = self.make_branch('quack')
 
955
        ## transport = branch.bzrdir.root_transport
 
956
        ## # we do not want bzrdir to make any remote calls
 
957
        ## bzrdir = RemoteBzrDir(transport, _client=False)
 
958
        ## branch = RemoteBranch(bzrdir, None, _client=client)
 
959
        ## config = branch.get_config()
 
960
        ## self.assertEqual(
 
961
        ##     [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
 
962
        ##     client._calls)
 
963
 
 
964
 
 
965
class TestBranchLockWrite(RemoteBranchTestCase):
 
966
 
 
967
    def test_lock_write_unlockable(self):
 
968
        transport = MemoryTransport()
 
969
        client = FakeClient(transport.base)
 
970
        client.add_expected_call(
 
971
            'Branch.get_stacked_on_url', ('quack/',),
 
972
            'error', ('NotStacked',),)
 
973
        client.add_expected_call(
 
974
            'Branch.lock_write', ('quack/', '', ''),
 
975
            'error', ('UnlockableTransport',))
 
976
        transport.mkdir('quack')
 
977
        transport = transport.clone('quack')
 
978
        branch = self.make_remote_branch(transport, client)
 
979
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
 
980
        client.finished_test()
 
981
 
 
982
 
 
983
class TestTransportIsReadonly(tests.TestCase):
 
984
 
 
985
    def test_true(self):
 
986
        client = FakeClient()
 
987
        client.add_success_response('yes')
 
988
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
989
                                    _client=client)
 
990
        self.assertEqual(True, transport.is_readonly())
 
991
        self.assertEqual(
 
992
            [('call', 'Transport.is_readonly', ())],
 
993
            client._calls)
 
994
 
 
995
    def test_false(self):
 
996
        client = FakeClient()
 
997
        client.add_success_response('no')
 
998
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
999
                                    _client=client)
 
1000
        self.assertEqual(False, transport.is_readonly())
 
1001
        self.assertEqual(
 
1002
            [('call', 'Transport.is_readonly', ())],
 
1003
            client._calls)
 
1004
 
 
1005
    def test_error_from_old_server(self):
 
1006
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
 
1007
        
 
1008
        Clients should treat it as a "no" response, because is_readonly is only
 
1009
        advisory anyway (a transport could be read-write, but then the
 
1010
        underlying filesystem could be readonly anyway).
 
1011
        """
 
1012
        client = FakeClient()
 
1013
        client.add_unknown_method_response('Transport.is_readonly')
 
1014
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1015
                                    _client=client)
 
1016
        self.assertEqual(False, transport.is_readonly())
 
1017
        self.assertEqual(
 
1018
            [('call', 'Transport.is_readonly', ())],
 
1019
            client._calls)
 
1020
 
 
1021
 
 
1022
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
 
1023
 
 
1024
    def test_defaults_to_none(self):
 
1025
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1026
        self.assertIs(None, t._get_credentials()[0])
 
1027
 
 
1028
    def test_uses_authentication_config(self):
 
1029
        conf = config.AuthenticationConfig()
 
1030
        conf._get_config().update(
 
1031
            {'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
 
1032
            'example.com'}})
 
1033
        conf._save()
 
1034
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1035
        self.assertEqual('bar', t._get_credentials()[0])
 
1036
 
 
1037
 
 
1038
class TestRemoteRepository(tests.TestCase):
 
1039
    """Base for testing RemoteRepository protocol usage.
 
1040
    
 
1041
    These tests contain frozen requests and responses.  We want any changes to 
 
1042
    what is sent or expected to be require a thoughtful update to these tests
 
1043
    because they might break compatibility with different-versioned servers.
 
1044
    """
 
1045
 
 
1046
    def setup_fake_client_and_repository(self, transport_path):
 
1047
        """Create the fake client and repository for testing with.
 
1048
        
 
1049
        There's no real server here; we just have canned responses sent
 
1050
        back one by one.
 
1051
        
 
1052
        :param transport_path: Path below the root of the MemoryTransport
 
1053
            where the repository will be created.
 
1054
        """
 
1055
        transport = MemoryTransport()
 
1056
        transport.mkdir(transport_path)
 
1057
        client = FakeClient(transport.base)
 
1058
        transport = transport.clone(transport_path)
 
1059
        # we do not want bzrdir to make any remote calls
 
1060
        bzrdir = RemoteBzrDir(transport, _client=False)
 
1061
        repo = RemoteRepository(bzrdir, None, _client=client)
 
1062
        return repo, client
 
1063
 
 
1064
 
 
1065
class TestRepositoryGatherStats(TestRemoteRepository):
 
1066
 
 
1067
    def test_revid_none(self):
 
1068
        # ('ok',), body with revisions and size
 
1069
        transport_path = 'quack'
 
1070
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1071
        client.add_success_response_with_body(
 
1072
            'revisions: 2\nsize: 18\n', 'ok')
 
1073
        result = repo.gather_stats(None)
 
1074
        self.assertEqual(
 
1075
            [('call_expecting_body', 'Repository.gather_stats',
 
1076
             ('quack/','','no'))],
 
1077
            client._calls)
 
1078
        self.assertEqual({'revisions': 2, 'size': 18}, result)
 
1079
 
 
1080
    def test_revid_no_committers(self):
 
1081
        # ('ok',), body without committers
 
1082
        body = ('firstrev: 123456.300 3600\n'
 
1083
                'latestrev: 654231.400 0\n'
 
1084
                'revisions: 2\n'
 
1085
                'size: 18\n')
 
1086
        transport_path = 'quick'
 
1087
        revid = u'\xc8'.encode('utf8')
 
1088
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1089
        client.add_success_response_with_body(body, 'ok')
 
1090
        result = repo.gather_stats(revid)
 
1091
        self.assertEqual(
 
1092
            [('call_expecting_body', 'Repository.gather_stats',
 
1093
              ('quick/', revid, 'no'))],
 
1094
            client._calls)
 
1095
        self.assertEqual({'revisions': 2, 'size': 18,
 
1096
                          'firstrev': (123456.300, 3600),
 
1097
                          'latestrev': (654231.400, 0),},
 
1098
                         result)
 
1099
 
 
1100
    def test_revid_with_committers(self):
 
1101
        # ('ok',), body with committers
 
1102
        body = ('committers: 128\n'
 
1103
                'firstrev: 123456.300 3600\n'
 
1104
                'latestrev: 654231.400 0\n'
 
1105
                'revisions: 2\n'
 
1106
                'size: 18\n')
 
1107
        transport_path = 'buick'
 
1108
        revid = u'\xc8'.encode('utf8')
 
1109
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1110
        client.add_success_response_with_body(body, 'ok')
 
1111
        result = repo.gather_stats(revid, True)
 
1112
        self.assertEqual(
 
1113
            [('call_expecting_body', 'Repository.gather_stats',
 
1114
              ('buick/', revid, 'yes'))],
 
1115
            client._calls)
 
1116
        self.assertEqual({'revisions': 2, 'size': 18,
 
1117
                          'committers': 128,
 
1118
                          'firstrev': (123456.300, 3600),
 
1119
                          'latestrev': (654231.400, 0),},
 
1120
                         result)
 
1121
 
 
1122
 
 
1123
class TestRepositoryGetGraph(TestRemoteRepository):
 
1124
 
 
1125
    def test_get_graph(self):
 
1126
        # get_graph returns a graph with the repository as the
 
1127
        # parents_provider.
 
1128
        transport_path = 'quack'
 
1129
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1130
        graph = repo.get_graph()
 
1131
        self.assertEqual(graph._parents_provider, repo)
 
1132
 
 
1133
 
 
1134
class TestRepositoryGetParentMap(TestRemoteRepository):
 
1135
 
 
1136
    def test_get_parent_map_caching(self):
 
1137
        # get_parent_map returns from cache until unlock()
 
1138
        # setup a reponse with two revisions
 
1139
        r1 = u'\u0e33'.encode('utf8')
 
1140
        r2 = u'\u0dab'.encode('utf8')
 
1141
        lines = [' '.join([r2, r1]), r1]
 
1142
        encoded_body = bz2.compress('\n'.join(lines))
 
1143
 
 
1144
        transport_path = 'quack'
 
1145
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1146
        client.add_success_response_with_body(encoded_body, 'ok')
 
1147
        client.add_success_response_with_body(encoded_body, 'ok')
 
1148
        repo.lock_read()
 
1149
        graph = repo.get_graph()
 
1150
        parents = graph.get_parent_map([r2])
 
1151
        self.assertEqual({r2: (r1,)}, parents)
 
1152
        # locking and unlocking deeper should not reset
 
1153
        repo.lock_read()
 
1154
        repo.unlock()
 
1155
        parents = graph.get_parent_map([r1])
 
1156
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
1157
        self.assertEqual(
 
1158
            [('call_with_body_bytes_expecting_body',
 
1159
              'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
 
1160
            client._calls)
 
1161
        repo.unlock()
 
1162
        # now we call again, and it should use the second response.
 
1163
        repo.lock_read()
 
1164
        graph = repo.get_graph()
 
1165
        parents = graph.get_parent_map([r1])
 
1166
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
1167
        self.assertEqual(
 
1168
            [('call_with_body_bytes_expecting_body',
 
1169
              'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
 
1170
             ('call_with_body_bytes_expecting_body',
 
1171
              'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
 
1172
            ],
 
1173
            client._calls)
 
1174
        repo.unlock()
 
1175
 
 
1176
    def test_get_parent_map_reconnects_if_unknown_method(self):
 
1177
        transport_path = 'quack'
 
1178
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1179
        client.add_unknown_method_response('Repository,get_parent_map')
 
1180
        client.add_success_response_with_body('', 'ok')
 
1181
        self.assertFalse(client._medium._is_remote_before((1, 2)))
 
1182
        rev_id = 'revision-id'
 
1183
        expected_deprecations = [
 
1184
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
 
1185
            'in version 1.4.']
 
1186
        parents = self.callDeprecated(
 
1187
            expected_deprecations, repo.get_parent_map, [rev_id])
 
1188
        self.assertEqual(
 
1189
            [('call_with_body_bytes_expecting_body',
 
1190
              'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
 
1191
             ('disconnect medium',),
 
1192
             ('call_expecting_body', 'Repository.get_revision_graph',
 
1193
              ('quack/', ''))],
 
1194
            client._calls)
 
1195
        # The medium is now marked as being connected to an older server
 
1196
        self.assertTrue(client._medium._is_remote_before((1, 2)))
 
1197
 
 
1198
    def test_get_parent_map_fallback_parentless_node(self):
 
1199
        """get_parent_map falls back to get_revision_graph on old servers.  The
 
1200
        results from get_revision_graph are tweaked to match the get_parent_map
 
1201
        API.
 
1202
 
 
1203
        Specifically, a {key: ()} result from get_revision_graph means "no
 
1204
        parents" for that key, which in get_parent_map results should be
 
1205
        represented as {key: ('null:',)}.
 
1206
 
 
1207
        This is the test for https://bugs.launchpad.net/bzr/+bug/214894
 
1208
        """
 
1209
        rev_id = 'revision-id'
 
1210
        transport_path = 'quack'
 
1211
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1212
        client.add_success_response_with_body(rev_id, 'ok')
 
1213
        client._medium._remember_remote_is_before((1, 2))
 
1214
        expected_deprecations = [
 
1215
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
 
1216
            'in version 1.4.']
 
1217
        parents = self.callDeprecated(
 
1218
            expected_deprecations, repo.get_parent_map, [rev_id])
 
1219
        self.assertEqual(
 
1220
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1221
             ('quack/', ''))],
 
1222
            client._calls)
 
1223
        self.assertEqual({rev_id: ('null:',)}, parents)
 
1224
 
 
1225
    def test_get_parent_map_unexpected_response(self):
 
1226
        repo, client = self.setup_fake_client_and_repository('path')
 
1227
        client.add_success_response('something unexpected!')
 
1228
        self.assertRaises(
 
1229
            errors.UnexpectedSmartServerResponse,
 
1230
            repo.get_parent_map, ['a-revision-id'])
 
1231
 
 
1232
 
 
1233
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
 
1234
    
 
1235
    def test_null_revision(self):
 
1236
        # a null revision has the predictable result {}, we should have no wire
 
1237
        # traffic when calling it with this argument
 
1238
        transport_path = 'empty'
 
1239
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1240
        client.add_success_response('notused')
 
1241
        result = self.applyDeprecated(one_four, repo.get_revision_graph,
 
1242
            NULL_REVISION)
 
1243
        self.assertEqual([], client._calls)
 
1244
        self.assertEqual({}, result)
 
1245
 
 
1246
    def test_none_revision(self):
 
1247
        # with none we want the entire graph
 
1248
        r1 = u'\u0e33'.encode('utf8')
 
1249
        r2 = u'\u0dab'.encode('utf8')
 
1250
        lines = [' '.join([r2, r1]), r1]
 
1251
        encoded_body = '\n'.join(lines)
 
1252
 
 
1253
        transport_path = 'sinhala'
 
1254
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1255
        client.add_success_response_with_body(encoded_body, 'ok')
 
1256
        result = self.applyDeprecated(one_four, repo.get_revision_graph)
 
1257
        self.assertEqual(
 
1258
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1259
             ('sinhala/', ''))],
 
1260
            client._calls)
 
1261
        self.assertEqual({r1: (), r2: (r1, )}, result)
 
1262
 
 
1263
    def test_specific_revision(self):
 
1264
        # with a specific revision we want the graph for that
 
1265
        # with none we want the entire graph
 
1266
        r11 = u'\u0e33'.encode('utf8')
 
1267
        r12 = u'\xc9'.encode('utf8')
 
1268
        r2 = u'\u0dab'.encode('utf8')
 
1269
        lines = [' '.join([r2, r11, r12]), r11, r12]
 
1270
        encoded_body = '\n'.join(lines)
 
1271
 
 
1272
        transport_path = 'sinhala'
 
1273
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1274
        client.add_success_response_with_body(encoded_body, 'ok')
 
1275
        result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
 
1276
        self.assertEqual(
 
1277
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1278
             ('sinhala/', r2))],
 
1279
            client._calls)
 
1280
        self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
 
1281
 
 
1282
    def test_no_such_revision(self):
 
1283
        revid = '123'
 
1284
        transport_path = 'sinhala'
 
1285
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1286
        client.add_error_response('nosuchrevision', revid)
 
1287
        # also check that the right revision is reported in the error
 
1288
        self.assertRaises(errors.NoSuchRevision,
 
1289
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
1290
        self.assertEqual(
 
1291
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1292
             ('sinhala/', revid))],
 
1293
            client._calls)
 
1294
 
 
1295
    def test_unexpected_error(self):
 
1296
        revid = '123'
 
1297
        transport_path = 'sinhala'
 
1298
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1299
        client.add_error_response('AnUnexpectedError')
 
1300
        e = self.assertRaises(errors.UnknownErrorFromSmartServer,
 
1301
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
1302
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
 
1303
 
 
1304
        
 
1305
class TestRepositoryIsShared(TestRemoteRepository):
 
1306
 
 
1307
    def test_is_shared(self):
 
1308
        # ('yes', ) for Repository.is_shared -> 'True'.
 
1309
        transport_path = 'quack'
 
1310
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1311
        client.add_success_response('yes')
 
1312
        result = repo.is_shared()
 
1313
        self.assertEqual(
 
1314
            [('call', 'Repository.is_shared', ('quack/',))],
 
1315
            client._calls)
 
1316
        self.assertEqual(True, result)
 
1317
 
 
1318
    def test_is_not_shared(self):
 
1319
        # ('no', ) for Repository.is_shared -> 'False'.
 
1320
        transport_path = 'qwack'
 
1321
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1322
        client.add_success_response('no')
 
1323
        result = repo.is_shared()
 
1324
        self.assertEqual(
 
1325
            [('call', 'Repository.is_shared', ('qwack/',))],
 
1326
            client._calls)
 
1327
        self.assertEqual(False, result)
 
1328
 
 
1329
 
 
1330
class TestRepositoryLockWrite(TestRemoteRepository):
 
1331
 
 
1332
    def test_lock_write(self):
 
1333
        transport_path = 'quack'
 
1334
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1335
        client.add_success_response('ok', 'a token')
 
1336
        result = repo.lock_write()
 
1337
        self.assertEqual(
 
1338
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
1339
            client._calls)
 
1340
        self.assertEqual('a token', result)
 
1341
 
 
1342
    def test_lock_write_already_locked(self):
 
1343
        transport_path = 'quack'
 
1344
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1345
        client.add_error_response('LockContention')
 
1346
        self.assertRaises(errors.LockContention, repo.lock_write)
 
1347
        self.assertEqual(
 
1348
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
1349
            client._calls)
 
1350
 
 
1351
    def test_lock_write_unlockable(self):
 
1352
        transport_path = 'quack'
 
1353
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1354
        client.add_error_response('UnlockableTransport')
 
1355
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
 
1356
        self.assertEqual(
 
1357
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
1358
            client._calls)
 
1359
 
 
1360
 
 
1361
class TestRepositoryUnlock(TestRemoteRepository):
 
1362
 
 
1363
    def test_unlock(self):
 
1364
        transport_path = 'quack'
 
1365
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1366
        client.add_success_response('ok', 'a token')
 
1367
        client.add_success_response('ok')
 
1368
        repo.lock_write()
 
1369
        repo.unlock()
 
1370
        self.assertEqual(
 
1371
            [('call', 'Repository.lock_write', ('quack/', '')),
 
1372
             ('call', 'Repository.unlock', ('quack/', 'a token'))],
 
1373
            client._calls)
 
1374
 
 
1375
    def test_unlock_wrong_token(self):
 
1376
        # If somehow the token is wrong, unlock will raise TokenMismatch.
 
1377
        transport_path = 'quack'
 
1378
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1379
        client.add_success_response('ok', 'a token')
 
1380
        client.add_error_response('TokenMismatch')
 
1381
        repo.lock_write()
 
1382
        self.assertRaises(errors.TokenMismatch, repo.unlock)
 
1383
 
 
1384
 
 
1385
class TestRepositoryHasRevision(TestRemoteRepository):
 
1386
 
 
1387
    def test_none(self):
 
1388
        # repo.has_revision(None) should not cause any traffic.
 
1389
        transport_path = 'quack'
 
1390
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1391
 
 
1392
        # The null revision is always there, so has_revision(None) == True.
 
1393
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
 
1394
 
 
1395
        # The remote repo shouldn't be accessed.
 
1396
        self.assertEqual([], client._calls)
 
1397
 
 
1398
 
 
1399
class TestRepositoryTarball(TestRemoteRepository):
 
1400
 
 
1401
    # This is a canned tarball reponse we can validate against
 
1402
    tarball_content = (
 
1403
        'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
 
1404
        'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
 
1405
        'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
 
1406
        'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
 
1407
        'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
 
1408
        'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
 
1409
        'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
 
1410
        'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
 
1411
        '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
 
1412
        'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
 
1413
        'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
 
1414
        'nWQ7QH/F3JFOFCQ0aSPfA='
 
1415
        ).decode('base64')
 
1416
 
 
1417
    def test_repository_tarball(self):
 
1418
        # Test that Repository.tarball generates the right operations
 
1419
        transport_path = 'repo'
 
1420
        expected_calls = [('call_expecting_body', 'Repository.tarball',
 
1421
                           ('repo/', 'bz2',),),
 
1422
            ]
 
1423
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1424
        client.add_success_response_with_body(self.tarball_content, 'ok')
 
1425
        # Now actually ask for the tarball
 
1426
        tarball_file = repo._get_tarball('bz2')
 
1427
        try:
 
1428
            self.assertEqual(expected_calls, client._calls)
 
1429
            self.assertEqual(self.tarball_content, tarball_file.read())
 
1430
        finally:
 
1431
            tarball_file.close()
 
1432
 
 
1433
 
 
1434
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
 
1435
    """RemoteRepository.copy_content_into optimizations"""
 
1436
 
 
1437
    def test_copy_content_remote_to_local(self):
 
1438
        self.transport_server = server.SmartTCPServer_for_testing
 
1439
        src_repo = self.make_repository('repo1')
 
1440
        src_repo = repository.Repository.open(self.get_url('repo1'))
 
1441
        # At the moment the tarball-based copy_content_into can't write back
 
1442
        # into a smart server.  It would be good if it could upload the
 
1443
        # tarball; once that works we'd have to create repositories of
 
1444
        # different formats. -- mbp 20070410
 
1445
        dest_url = self.get_vfs_only_url('repo2')
 
1446
        dest_bzrdir = BzrDir.create(dest_url)
 
1447
        dest_repo = dest_bzrdir.create_repository()
 
1448
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
 
1449
        self.assertTrue(isinstance(src_repo, RemoteRepository))
 
1450
        src_repo.copy_content_into(dest_repo)
 
1451
 
 
1452
 
 
1453
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
 
1454
    """Base class for unit tests for bzrlib.remote._translate_error."""
 
1455
 
 
1456
    def translateTuple(self, error_tuple, **context):
 
1457
        """Call _translate_error with an ErrorFromSmartServer built from the
 
1458
        given error_tuple.
 
1459
 
 
1460
        :param error_tuple: A tuple of a smart server response, as would be
 
1461
            passed to an ErrorFromSmartServer.
 
1462
        :kwargs context: context items to call _translate_error with.
 
1463
 
 
1464
        :returns: The error raised by _translate_error.
 
1465
        """
 
1466
        # Raise the ErrorFromSmartServer before passing it as an argument,
 
1467
        # because _translate_error may need to re-raise it with a bare 'raise'
 
1468
        # statement.
 
1469
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
1470
        translated_error = self.translateErrorFromSmartServer(
 
1471
            server_error, **context)
 
1472
        return translated_error
 
1473
 
 
1474
    def translateErrorFromSmartServer(self, error_object, **context):
 
1475
        """Like translateTuple, but takes an already constructed
 
1476
        ErrorFromSmartServer rather than a tuple.
 
1477
        """
 
1478
        try:
 
1479
            raise error_object
 
1480
        except errors.ErrorFromSmartServer, server_error:
 
1481
            translated_error = self.assertRaises(
 
1482
                errors.BzrError, remote._translate_error, server_error,
 
1483
                **context)
 
1484
        return translated_error
 
1485
 
 
1486
    
 
1487
class TestErrorTranslationSuccess(TestErrorTranslationBase):
 
1488
    """Unit tests for bzrlib.remote._translate_error.
 
1489
    
 
1490
    Given an ErrorFromSmartServer (which has an error tuple from a smart
 
1491
    server) and some context, _translate_error raises more specific errors from
 
1492
    bzrlib.errors.
 
1493
 
 
1494
    This test case covers the cases where _translate_error succeeds in
 
1495
    translating an ErrorFromSmartServer to something better.  See
 
1496
    TestErrorTranslationRobustness for other cases.
 
1497
    """
 
1498
 
 
1499
    def test_NoSuchRevision(self):
 
1500
        branch = self.make_branch('')
 
1501
        revid = 'revid'
 
1502
        translated_error = self.translateTuple(
 
1503
            ('NoSuchRevision', revid), branch=branch)
 
1504
        expected_error = errors.NoSuchRevision(branch, revid)
 
1505
        self.assertEqual(expected_error, translated_error)
 
1506
 
 
1507
    def test_nosuchrevision(self):
 
1508
        repository = self.make_repository('')
 
1509
        revid = 'revid'
 
1510
        translated_error = self.translateTuple(
 
1511
            ('nosuchrevision', revid), repository=repository)
 
1512
        expected_error = errors.NoSuchRevision(repository, revid)
 
1513
        self.assertEqual(expected_error, translated_error)
 
1514
 
 
1515
    def test_nobranch(self):
 
1516
        bzrdir = self.make_bzrdir('')
 
1517
        translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
 
1518
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
 
1519
        self.assertEqual(expected_error, translated_error)
 
1520
 
 
1521
    def test_LockContention(self):
 
1522
        translated_error = self.translateTuple(('LockContention',))
 
1523
        expected_error = errors.LockContention('(remote lock)')
 
1524
        self.assertEqual(expected_error, translated_error)
 
1525
 
 
1526
    def test_UnlockableTransport(self):
 
1527
        bzrdir = self.make_bzrdir('')
 
1528
        translated_error = self.translateTuple(
 
1529
            ('UnlockableTransport',), bzrdir=bzrdir)
 
1530
        expected_error = errors.UnlockableTransport(bzrdir.root_transport)
 
1531
        self.assertEqual(expected_error, translated_error)
 
1532
 
 
1533
    def test_LockFailed(self):
 
1534
        lock = 'str() of a server lock'
 
1535
        why = 'str() of why'
 
1536
        translated_error = self.translateTuple(('LockFailed', lock, why))
 
1537
        expected_error = errors.LockFailed(lock, why)
 
1538
        self.assertEqual(expected_error, translated_error)
 
1539
 
 
1540
    def test_TokenMismatch(self):
 
1541
        token = 'a lock token'
 
1542
        translated_error = self.translateTuple(('TokenMismatch',), token=token)
 
1543
        expected_error = errors.TokenMismatch(token, '(remote token)')
 
1544
        self.assertEqual(expected_error, translated_error)
 
1545
 
 
1546
    def test_Diverged(self):
 
1547
        branch = self.make_branch('a')
 
1548
        other_branch = self.make_branch('b')
 
1549
        translated_error = self.translateTuple(
 
1550
            ('Diverged',), branch=branch, other_branch=other_branch)
 
1551
        expected_error = errors.DivergedBranches(branch, other_branch)
 
1552
        self.assertEqual(expected_error, translated_error)
 
1553
 
 
1554
 
 
1555
class TestErrorTranslationRobustness(TestErrorTranslationBase):
 
1556
    """Unit tests for bzrlib.remote._translate_error's robustness.
 
1557
    
 
1558
    TestErrorTranslationSuccess is for cases where _translate_error can
 
1559
    translate successfully.  This class about how _translate_err behaves when
 
1560
    it fails to translate: it re-raises the original error.
 
1561
    """
 
1562
 
 
1563
    def test_unrecognised_server_error(self):
 
1564
        """If the error code from the server is not recognised, the original
 
1565
        ErrorFromSmartServer is propagated unmodified.
 
1566
        """
 
1567
        error_tuple = ('An unknown error tuple',)
 
1568
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
1569
        translated_error = self.translateErrorFromSmartServer(server_error)
 
1570
        expected_error = errors.UnknownErrorFromSmartServer(server_error)
 
1571
        self.assertEqual(expected_error, translated_error)
 
1572
 
 
1573
    def test_context_missing_a_key(self):
 
1574
        """In case of a bug in the client, or perhaps an unexpected response
 
1575
        from a server, _translate_error returns the original error tuple from
 
1576
        the server and mutters a warning.
 
1577
        """
 
1578
        # To translate a NoSuchRevision error _translate_error needs a 'branch'
 
1579
        # in the context dict.  So let's give it an empty context dict instead
 
1580
        # to exercise its error recovery.
 
1581
        empty_context = {}
 
1582
        error_tuple = ('NoSuchRevision', 'revid')
 
1583
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
1584
        translated_error = self.translateErrorFromSmartServer(server_error)
 
1585
        self.assertEqual(server_error, translated_error)
 
1586
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
1587
        # been muttered to the log file for developer to look at.
 
1588
        self.assertContainsRe(
 
1589
            self._get_log(keep_log_file=True),
 
1590
            "Missing key 'branch' in context")
 
1591
        
 
1592
 
 
1593
class TestStacking(tests.TestCaseWithTransport):
 
1594
    """Tests for operations on stacked remote repositories.
 
1595
    
 
1596
    The underlying format type must support stacking.
 
1597
    """
 
1598
 
 
1599
    def test_access_stacked_remote(self):
 
1600
        # based on <http://launchpad.net/bugs/261315>
 
1601
        # make a branch stacked on another repository containing an empty
 
1602
        # revision, then open it over hpss - we should be able to see that
 
1603
        # revision.
 
1604
        base_transport = self.get_transport()
 
1605
        base_builder = self.make_branch_builder('base', format='1.6')
 
1606
        base_builder.start_series()
 
1607
        base_revid = base_builder.build_snapshot('rev-id', None,
 
1608
            [('add', ('', None, 'directory', None))],
 
1609
            'message')
 
1610
        base_builder.finish_series()
 
1611
        stacked_branch = self.make_branch('stacked', format='1.6')
 
1612
        stacked_branch.set_stacked_on_url('../base')
 
1613
        # start a server looking at this
 
1614
        smart_server = server.SmartTCPServer_for_testing()
 
1615
        smart_server.setUp()
 
1616
        self.addCleanup(smart_server.tearDown)
 
1617
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
 
1618
        # can get its branch and repository
 
1619
        remote_branch = remote_bzrdir.open_branch()
 
1620
        remote_repo = remote_branch.repository
 
1621
        remote_repo.lock_read()
 
1622
        try:
 
1623
            # it should have an appropriate fallback repository, which should also
 
1624
            # be a RemoteRepository
 
1625
            self.assertEquals(len(remote_repo._fallback_repositories), 1)
 
1626
            self.assertIsInstance(remote_repo._fallback_repositories[0],
 
1627
                RemoteRepository)
 
1628
            # and it has the revision committed to the underlying repository;
 
1629
            # these have varying implementations so we try several of them
 
1630
            self.assertTrue(remote_repo.has_revisions([base_revid]))
 
1631
            self.assertTrue(remote_repo.has_revision(base_revid))
 
1632
            self.assertEqual(remote_repo.get_revision(base_revid).message,
 
1633
                'message')
 
1634
        finally:
 
1635
            remote_repo.unlock()