~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Robert Collins
  • Date: 2005-08-23 06:52:09 UTC
  • mto: (974.1.50) (1185.1.10) (1092.3.1)
  • mto: This revision was merged to the branch mainline in revision 1139.
  • Revision ID: robertc@robertcollins.net-20050823065209-81cd5962c401751b
move io redirection into each test case from the global runner

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