/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: Canonical.com Patch Queue Manager
  • Date: 2008-10-01 07:56:03 UTC
  • mfrom: (3224.5.40 faster-startup)
  • Revision ID: pqm@pqm.ubuntu.com-20081001075603-s9nynw8y85fmrprj
Reduce startup time by a small amount. (Andrew Bennetts)

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