~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Andrew Bennetts
  • Date: 2009-03-10 02:44:15 UTC
  • mto: This revision was merged to the branch mainline in revision 4103.
  • Revision ID: andrew.bennetts@canonical.com-20090310024415-3fl3ie61atq39c81
Fix 'trailing' whitespace (actually just a blank line in an indented docstring).

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
    bzrdir,
 
31
    config,
 
32
    errors,
 
33
    graph,
 
34
    pack,
 
35
    remote,
 
36
    repository,
 
37
    smart,
 
38
    tests,
 
39
    urlutils,
 
40
    )
 
41
from bzrlib.branch import Branch
 
42
from bzrlib.bzrdir import BzrDir, BzrDirFormat
 
43
from bzrlib.remote import (
 
44
    RemoteBranch,
 
45
    RemoteBranchFormat,
 
46
    RemoteBzrDir,
 
47
    RemoteBzrDirFormat,
 
48
    RemoteRepository,
 
49
    )
 
50
from bzrlib.revision import NULL_REVISION
 
51
from bzrlib.smart import server, medium
 
52
from bzrlib.smart.client import _SmartClient
 
53
from bzrlib.symbol_versioning import one_four
 
54
from bzrlib.transport import get_transport, http
 
55
from bzrlib.transport.memory import MemoryTransport
 
56
from bzrlib.transport.remote import (
 
57
    RemoteTransport,
 
58
    RemoteSSHTransport,
 
59
    RemoteTCPTransport,
 
60
)
 
61
 
 
62
 
 
63
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
 
64
 
 
65
    def setUp(self):
 
66
        self.transport_server = server.SmartTCPServer_for_testing
 
67
        super(BasicRemoteObjectTests, self).setUp()
 
68
        self.transport = self.get_transport()
 
69
        # make a branch that can be opened over the smart transport
 
70
        self.local_wt = BzrDir.create_standalone_workingtree('.')
 
71
 
 
72
    def tearDown(self):
 
73
        self.transport.disconnect()
 
74
        tests.TestCaseWithTransport.tearDown(self)
 
75
 
 
76
    def test_create_remote_bzrdir(self):
 
77
        b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
 
78
        self.assertIsInstance(b, BzrDir)
 
79
 
 
80
    def test_open_remote_branch(self):
 
81
        # open a standalone branch in the working directory
 
82
        b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
 
83
        branch = b.open_branch()
 
84
        self.assertIsInstance(branch, Branch)
 
85
 
 
86
    def test_remote_repository(self):
 
87
        b = BzrDir.open_from_transport(self.transport)
 
88
        repo = b.open_repository()
 
89
        revid = u'\xc823123123'.encode('utf8')
 
90
        self.assertFalse(repo.has_revision(revid))
 
91
        self.local_wt.commit(message='test commit', rev_id=revid)
 
92
        self.assertTrue(repo.has_revision(revid))
 
93
 
 
94
    def test_remote_branch_revision_history(self):
 
95
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
96
        self.assertEqual([], b.revision_history())
 
97
        r1 = self.local_wt.commit('1st commit')
 
98
        r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
 
99
        self.assertEqual([r1, r2], b.revision_history())
 
100
 
 
101
    def test_find_correct_format(self):
 
102
        """Should open a RemoteBzrDir over a RemoteTransport"""
 
103
        fmt = BzrDirFormat.find_format(self.transport)
 
104
        self.assertTrue(RemoteBzrDirFormat
 
105
                        in BzrDirFormat._control_server_formats)
 
106
        self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
 
107
 
 
108
    def test_open_detected_smart_format(self):
 
109
        fmt = BzrDirFormat.find_format(self.transport)
 
110
        d = fmt.open(self.transport)
 
111
        self.assertIsInstance(d, BzrDir)
 
112
 
 
113
    def test_remote_branch_repr(self):
 
114
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
115
        self.assertStartsWith(str(b), 'RemoteBranch(')
 
116
 
 
117
 
 
118
class FakeProtocol(object):
 
119
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
 
120
 
 
121
    def __init__(self, body, fake_client):
 
122
        self.body = body
 
123
        self._body_buffer = None
 
124
        self._fake_client = fake_client
 
125
 
 
126
    def read_body_bytes(self, count=-1):
 
127
        if self._body_buffer is None:
 
128
            self._body_buffer = StringIO(self.body)
 
129
        bytes = self._body_buffer.read(count)
 
130
        if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
 
131
            self._fake_client.expecting_body = False
 
132
        return bytes
 
133
 
 
134
    def cancel_read_body(self):
 
135
        self._fake_client.expecting_body = False
 
136
 
 
137
    def read_streamed_body(self):
 
138
        return self.body
 
139
 
 
140
 
 
141
class FakeClient(_SmartClient):
 
142
    """Lookalike for _SmartClient allowing testing."""
 
143
 
 
144
    def __init__(self, fake_medium_base='fake base'):
 
145
        """Create a FakeClient."""
 
146
        self.responses = []
 
147
        self._calls = []
 
148
        self.expecting_body = False
 
149
        # if non-None, this is the list of expected calls, with only the
 
150
        # method name and arguments included.  the body might be hard to
 
151
        # compute so is not included. If a call is None, that call can
 
152
        # be anything.
 
153
        self._expected_calls = None
 
154
        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
 
155
 
 
156
    def add_expected_call(self, call_name, call_args, response_type,
 
157
        response_args, response_body=None):
 
158
        if self._expected_calls is None:
 
159
            self._expected_calls = []
 
160
        self._expected_calls.append((call_name, call_args))
 
161
        self.responses.append((response_type, response_args, response_body))
 
162
 
 
163
    def add_success_response(self, *args):
 
164
        self.responses.append(('success', args, None))
 
165
 
 
166
    def add_success_response_with_body(self, body, *args):
 
167
        self.responses.append(('success', args, body))
 
168
        if self._expected_calls is not None:
 
169
            self._expected_calls.append(None)
 
170
 
 
171
    def add_error_response(self, *args):
 
172
        self.responses.append(('error', args))
 
173
 
 
174
    def add_unknown_method_response(self, verb):
 
175
        self.responses.append(('unknown', verb))
 
176
 
 
177
    def finished_test(self):
 
178
        if self._expected_calls:
 
179
            raise AssertionError("%r finished but was still expecting %r"
 
180
                % (self, self._expected_calls[0]))
 
181
 
 
182
    def _get_next_response(self):
 
183
        try:
 
184
            response_tuple = self.responses.pop(0)
 
185
        except IndexError, e:
 
186
            raise AssertionError("%r didn't expect any more calls"
 
187
                % (self,))
 
188
        if response_tuple[0] == 'unknown':
 
189
            raise errors.UnknownSmartMethod(response_tuple[1])
 
190
        elif response_tuple[0] == 'error':
 
191
            raise errors.ErrorFromSmartServer(response_tuple[1])
 
192
        return response_tuple
 
193
 
 
194
    def _check_call(self, method, args):
 
195
        if self._expected_calls is None:
 
196
            # the test should be updated to say what it expects
 
197
            return
 
198
        try:
 
199
            next_call = self._expected_calls.pop(0)
 
200
        except IndexError:
 
201
            raise AssertionError("%r didn't expect any more calls "
 
202
                "but got %r%r"
 
203
                % (self, method, args,))
 
204
        if next_call is None:
 
205
            return
 
206
        if method != next_call[0] or args != next_call[1]:
 
207
            raise AssertionError("%r expected %r%r "
 
208
                "but got %r%r"
 
209
                % (self, next_call[0], next_call[1], method, args,))
 
210
 
 
211
    def call(self, method, *args):
 
212
        self._check_call(method, args)
 
213
        self._calls.append(('call', method, args))
 
214
        return self._get_next_response()[1]
 
215
 
 
216
    def call_expecting_body(self, method, *args):
 
217
        self._check_call(method, args)
 
218
        self._calls.append(('call_expecting_body', method, args))
 
219
        result = self._get_next_response()
 
220
        self.expecting_body = True
 
221
        return result[1], FakeProtocol(result[2], self)
 
222
 
 
223
    def call_with_body_bytes_expecting_body(self, method, args, body):
 
224
        self._check_call(method, args)
 
225
        self._calls.append(('call_with_body_bytes_expecting_body', method,
 
226
            args, body))
 
227
        result = self._get_next_response()
 
228
        self.expecting_body = True
 
229
        return result[1], FakeProtocol(result[2], self)
 
230
 
 
231
    def call_with_body_stream(self, args, stream):
 
232
        # Explicitly consume the stream before checking for an error, because
 
233
        # that's what happens a real medium.
 
234
        stream = list(stream)
 
235
        self._check_call(args[0], args[1:])
 
236
        self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
 
237
        return self._get_next_response()[1]
 
238
 
 
239
 
 
240
class FakeMedium(medium.SmartClientMedium):
 
241
 
 
242
    def __init__(self, client_calls, base):
 
243
        medium.SmartClientMedium.__init__(self, base)
 
244
        self._client_calls = client_calls
 
245
 
 
246
    def disconnect(self):
 
247
        self._client_calls.append(('disconnect medium',))
 
248
 
 
249
 
 
250
class TestVfsHas(tests.TestCase):
 
251
 
 
252
    def test_unicode_path(self):
 
253
        client = FakeClient('/')
 
254
        client.add_success_response('yes',)
 
255
        transport = RemoteTransport('bzr://localhost/', _client=client)
 
256
        filename = u'/hell\u00d8'.encode('utf8')
 
257
        result = transport.has(filename)
 
258
        self.assertEqual(
 
259
            [('call', 'has', (filename,))],
 
260
            client._calls)
 
261
        self.assertTrue(result)
 
262
 
 
263
 
 
264
class TestRemote(tests.TestCaseWithMemoryTransport):
 
265
 
 
266
    def get_branch_format(self):
 
267
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
268
        return reference_bzrdir_format.get_branch_format()
 
269
 
 
270
    def get_repo_format(self):
 
271
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
272
        return reference_bzrdir_format.repository_format
 
273
 
 
274
    def disable_verb(self, verb):
 
275
        """Disable a verb for one test."""
 
276
        request_handlers = smart.request.request_handlers
 
277
        orig_method = request_handlers.get(verb)
 
278
        request_handlers.remove(verb)
 
279
        def restoreVerb():
 
280
            request_handlers.register(verb, orig_method)
 
281
        self.addCleanup(restoreVerb)
 
282
 
 
283
 
 
284
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
 
285
    """Tests for the behaviour of client_medium.remote_path_from_transport."""
 
286
 
 
287
    def assertRemotePath(self, expected, client_base, transport_base):
 
288
        """Assert that the result of
 
289
        SmartClientMedium.remote_path_from_transport is the expected value for
 
290
        a given client_base and transport_base.
 
291
        """
 
292
        client_medium = medium.SmartClientMedium(client_base)
 
293
        transport = get_transport(transport_base)
 
294
        result = client_medium.remote_path_from_transport(transport)
 
295
        self.assertEqual(expected, result)
 
296
 
 
297
    def test_remote_path_from_transport(self):
 
298
        """SmartClientMedium.remote_path_from_transport calculates a URL for
 
299
        the given transport relative to the root of the client base URL.
 
300
        """
 
301
        self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
 
302
        self.assertRemotePath(
 
303
            'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
 
304
 
 
305
    def assertRemotePathHTTP(self, expected, transport_base, relpath):
 
306
        """Assert that the result of
 
307
        HttpTransportBase.remote_path_from_transport is the expected value for
 
308
        a given transport_base and relpath of that transport.  (Note that
 
309
        HttpTransportBase is a subclass of SmartClientMedium)
 
310
        """
 
311
        base_transport = get_transport(transport_base)
 
312
        client_medium = base_transport.get_smart_medium()
 
313
        cloned_transport = base_transport.clone(relpath)
 
314
        result = client_medium.remote_path_from_transport(cloned_transport)
 
315
        self.assertEqual(expected, result)
 
316
 
 
317
    def test_remote_path_from_transport_http(self):
 
318
        """Remote paths for HTTP transports are calculated differently to other
 
319
        transports.  They are just relative to the client base, not the root
 
320
        directory of the host.
 
321
        """
 
322
        for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
 
323
            self.assertRemotePathHTTP(
 
324
                '../xyz/', scheme + '//host/path', '../xyz/')
 
325
            self.assertRemotePathHTTP(
 
326
                'xyz/', scheme + '//host/path', 'xyz/')
 
327
 
 
328
 
 
329
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
 
330
    """Tests for the behaviour of client_medium.remote_is_at_least."""
 
331
 
 
332
    def test_initially_unlimited(self):
 
333
        """A fresh medium assumes that the remote side supports all
 
334
        versions.
 
335
        """
 
336
        client_medium = medium.SmartClientMedium('dummy base')
 
337
        self.assertFalse(client_medium._is_remote_before((99, 99)))
 
338
 
 
339
    def test__remember_remote_is_before(self):
 
340
        """Calling _remember_remote_is_before ratchets down the known remote
 
341
        version.
 
342
        """
 
343
        client_medium = medium.SmartClientMedium('dummy base')
 
344
        # Mark the remote side as being less than 1.6.  The remote side may
 
345
        # still be 1.5.
 
346
        client_medium._remember_remote_is_before((1, 6))
 
347
        self.assertTrue(client_medium._is_remote_before((1, 6)))
 
348
        self.assertFalse(client_medium._is_remote_before((1, 5)))
 
349
        # Calling _remember_remote_is_before again with a lower value works.
 
350
        client_medium._remember_remote_is_before((1, 5))
 
351
        self.assertTrue(client_medium._is_remote_before((1, 5)))
 
352
        # You cannot call _remember_remote_is_before with a larger value.
 
353
        self.assertRaises(
 
354
            AssertionError, client_medium._remember_remote_is_before, (1, 9))
 
355
 
 
356
 
 
357
class TestBzrDirCloningMetaDir(TestRemote):
 
358
 
 
359
    def test_backwards_compat(self):
 
360
        self.setup_smart_server_with_call_log()
 
361
        a_dir = self.make_bzrdir('.')
 
362
        self.reset_smart_call_log()
 
363
        verb = 'BzrDir.cloning_metadir'
 
364
        self.disable_verb(verb)
 
365
        format = a_dir.cloning_metadir()
 
366
        call_count = len([call for call in self.hpss_calls if
 
367
            call.call.method == verb])
 
368
        self.assertEqual(1, call_count)
 
369
 
 
370
    def test_current_server(self):
 
371
        transport = self.get_transport('.')
 
372
        transport = transport.clone('quack')
 
373
        self.make_bzrdir('quack')
 
374
        client = FakeClient(transport.base)
 
375
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
376
        control_name = reference_bzrdir_format.network_name()
 
377
        client.add_expected_call(
 
378
            'BzrDir.cloning_metadir', ('quack/', 'False'),
 
379
            'success', (control_name, '', ('branch', ''))),
 
380
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
381
            _client=client)
 
382
        result = a_bzrdir.cloning_metadir()
 
383
        # We should have got a reference control dir with default branch and
 
384
        # repository formats.
 
385
        # This pokes a little, just to be sure.
 
386
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
 
387
        self.assertEqual(None, result._repository_format)
 
388
        self.assertEqual(None, result._branch_format)
 
389
        client.finished_test()
 
390
 
 
391
 
 
392
class TestBzrDirOpenBranch(TestRemote):
 
393
 
 
394
    def test_backwards_compat(self):
 
395
        self.setup_smart_server_with_call_log()
 
396
        self.make_branch('.')
 
397
        a_dir = BzrDir.open(self.get_url('.'))
 
398
        self.reset_smart_call_log()
 
399
        verb = 'BzrDir.open_branchV2'
 
400
        self.disable_verb(verb)
 
401
        format = a_dir.open_branch()
 
402
        call_count = len([call for call in self.hpss_calls if
 
403
            call.call.method == verb])
 
404
        self.assertEqual(1, call_count)
 
405
 
 
406
    def test_branch_present(self):
 
407
        reference_format = self.get_repo_format()
 
408
        network_name = reference_format.network_name()
 
409
        branch_network_name = self.get_branch_format().network_name()
 
410
        transport = MemoryTransport()
 
411
        transport.mkdir('quack')
 
412
        transport = transport.clone('quack')
 
413
        client = FakeClient(transport.base)
 
414
        client.add_expected_call(
 
415
            'BzrDir.open_branchV2', ('quack/',),
 
416
            'success', ('branch', branch_network_name))
 
417
        client.add_expected_call(
 
418
            'BzrDir.find_repositoryV3', ('quack/',),
 
419
            'success', ('ok', '', 'no', 'no', 'no', network_name))
 
420
        client.add_expected_call(
 
421
            'Branch.get_stacked_on_url', ('quack/',),
 
422
            'error', ('NotStacked',))
 
423
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
424
            _client=client)
 
425
        result = bzrdir.open_branch()
 
426
        self.assertIsInstance(result, RemoteBranch)
 
427
        self.assertEqual(bzrdir, result.bzrdir)
 
428
        client.finished_test()
 
429
 
 
430
    def test_branch_missing(self):
 
431
        transport = MemoryTransport()
 
432
        transport.mkdir('quack')
 
433
        transport = transport.clone('quack')
 
434
        client = FakeClient(transport.base)
 
435
        client.add_error_response('nobranch')
 
436
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
437
            _client=client)
 
438
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
 
439
        self.assertEqual(
 
440
            [('call', 'BzrDir.open_branchV2', ('quack/',))],
 
441
            client._calls)
 
442
 
 
443
    def test__get_tree_branch(self):
 
444
        # _get_tree_branch is a form of open_branch, but it should only ask for
 
445
        # branch opening, not any other network requests.
 
446
        calls = []
 
447
        def open_branch():
 
448
            calls.append("Called")
 
449
            return "a-branch"
 
450
        transport = MemoryTransport()
 
451
        # no requests on the network - catches other api calls being made.
 
452
        client = FakeClient(transport.base)
 
453
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
454
            _client=client)
 
455
        # patch the open_branch call to record that it was called.
 
456
        bzrdir.open_branch = open_branch
 
457
        self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
 
458
        self.assertEqual(["Called"], calls)
 
459
        self.assertEqual([], client._calls)
 
460
 
 
461
    def test_url_quoting_of_path(self):
 
462
        # Relpaths on the wire should not be URL-escaped.  So "~" should be
 
463
        # transmitted as "~", not "%7E".
 
464
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
 
465
        client = FakeClient(transport.base)
 
466
        reference_format = self.get_repo_format()
 
467
        network_name = reference_format.network_name()
 
468
        branch_network_name = self.get_branch_format().network_name()
 
469
        client.add_expected_call(
 
470
            'BzrDir.open_branchV2', ('~hello/',),
 
471
            'success', ('branch', branch_network_name))
 
472
        client.add_expected_call(
 
473
            'BzrDir.find_repositoryV3', ('~hello/',),
 
474
            'success', ('ok', '', 'no', 'no', 'no', network_name))
 
475
        client.add_expected_call(
 
476
            'Branch.get_stacked_on_url', ('~hello/',),
 
477
            'error', ('NotStacked',))
 
478
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
479
            _client=client)
 
480
        result = bzrdir.open_branch()
 
481
        client.finished_test()
 
482
 
 
483
    def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
 
484
        reference_format = self.get_repo_format()
 
485
        network_name = reference_format.network_name()
 
486
        transport = MemoryTransport()
 
487
        transport.mkdir('quack')
 
488
        transport = transport.clone('quack')
 
489
        if rich_root:
 
490
            rich_response = 'yes'
 
491
        else:
 
492
            rich_response = 'no'
 
493
        if subtrees:
 
494
            subtree_response = 'yes'
 
495
        else:
 
496
            subtree_response = 'no'
 
497
        client = FakeClient(transport.base)
 
498
        client.add_success_response(
 
499
            'ok', '', rich_response, subtree_response, external_lookup,
 
500
            network_name)
 
501
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
502
            _client=client)
 
503
        result = bzrdir.open_repository()
 
504
        self.assertEqual(
 
505
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
 
506
            client._calls)
 
507
        self.assertIsInstance(result, RemoteRepository)
 
508
        self.assertEqual(bzrdir, result.bzrdir)
 
509
        self.assertEqual(rich_root, result._format.rich_root_data)
 
510
        self.assertEqual(subtrees, result._format.supports_tree_reference)
 
511
 
 
512
    def test_open_repository_sets_format_attributes(self):
 
513
        self.check_open_repository(True, True)
 
514
        self.check_open_repository(False, True)
 
515
        self.check_open_repository(True, False)
 
516
        self.check_open_repository(False, False)
 
517
        self.check_open_repository(False, False, 'yes')
 
518
 
 
519
    def test_old_server(self):
 
520
        """RemoteBzrDirFormat should fail to probe if the server version is too
 
521
        old.
 
522
        """
 
523
        self.assertRaises(errors.NotBranchError,
 
524
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
 
525
 
 
526
 
 
527
class TestBzrDirCreateBranch(TestRemote):
 
528
 
 
529
    def test_backwards_compat(self):
 
530
        self.setup_smart_server_with_call_log()
 
531
        repo = self.make_repository('.')
 
532
        self.reset_smart_call_log()
 
533
        self.disable_verb('BzrDir.create_branch')
 
534
        branch = repo.bzrdir.create_branch()
 
535
        create_branch_call_count = len([call for call in self.hpss_calls if
 
536
            call.call.method == 'BzrDir.create_branch'])
 
537
        self.assertEqual(1, create_branch_call_count)
 
538
 
 
539
    def test_current_server(self):
 
540
        transport = self.get_transport('.')
 
541
        transport = transport.clone('quack')
 
542
        self.make_repository('quack')
 
543
        client = FakeClient(transport.base)
 
544
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
545
        reference_format = reference_bzrdir_format.get_branch_format()
 
546
        network_name = reference_format.network_name()
 
547
        reference_repo_fmt = reference_bzrdir_format.repository_format
 
548
        reference_repo_name = reference_repo_fmt.network_name()
 
549
        client.add_expected_call(
 
550
            'BzrDir.create_branch', ('quack/', network_name),
 
551
            'success', ('ok', network_name, '', 'no', 'no', 'yes',
 
552
            reference_repo_name))
 
553
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
554
            _client=client)
 
555
        branch = a_bzrdir.create_branch()
 
556
        # We should have got a remote branch
 
557
        self.assertIsInstance(branch, remote.RemoteBranch)
 
558
        # its format should have the settings from the response
 
559
        format = branch._format
 
560
        self.assertEqual(network_name, format.network_name())
 
561
 
 
562
 
 
563
class TestBzrDirCreateRepository(TestRemote):
 
564
 
 
565
    def test_backwards_compat(self):
 
566
        self.setup_smart_server_with_call_log()
 
567
        bzrdir = self.make_bzrdir('.')
 
568
        self.reset_smart_call_log()
 
569
        self.disable_verb('BzrDir.create_repository')
 
570
        repo = bzrdir.create_repository()
 
571
        create_repo_call_count = len([call for call in self.hpss_calls if
 
572
            call.call.method == 'BzrDir.create_repository'])
 
573
        self.assertEqual(1, create_repo_call_count)
 
574
 
 
575
    def test_current_server(self):
 
576
        transport = self.get_transport('.')
 
577
        transport = transport.clone('quack')
 
578
        self.make_bzrdir('quack')
 
579
        client = FakeClient(transport.base)
 
580
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
581
        reference_format = reference_bzrdir_format.repository_format
 
582
        network_name = reference_format.network_name()
 
583
        client.add_expected_call(
 
584
            'BzrDir.create_repository', ('quack/',
 
585
                'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
 
586
            'success', ('ok', 'no', 'no', 'no', network_name))
 
587
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
588
            _client=client)
 
589
        repo = a_bzrdir.create_repository()
 
590
        # We should have got a remote repository
 
591
        self.assertIsInstance(repo, remote.RemoteRepository)
 
592
        # its format should have the settings from the response
 
593
        format = repo._format
 
594
        self.assertFalse(format.rich_root_data)
 
595
        self.assertFalse(format.supports_tree_reference)
 
596
        self.assertFalse(format.supports_external_lookups)
 
597
        self.assertEqual(network_name, format.network_name())
 
598
 
 
599
 
 
600
class TestBzrDirOpenRepository(TestRemote):
 
601
 
 
602
    def test_backwards_compat_1_2_3(self):
 
603
        # fallback all the way to the first version.
 
604
        reference_format = self.get_repo_format()
 
605
        network_name = reference_format.network_name()
 
606
        client = FakeClient('bzr://example.com/')
 
607
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
 
608
        client.add_unknown_method_response('BzrDir.find_repositoryV2')
 
609
        client.add_success_response('ok', '', 'no', 'no')
 
610
        # A real repository instance will be created to determine the network
 
611
        # name.
 
612
        client.add_success_response_with_body(
 
613
            "Bazaar-NG meta directory, format 1\n", 'ok')
 
614
        client.add_success_response_with_body(
 
615
            reference_format.get_format_string(), 'ok')
 
616
        # PackRepository wants to do a stat
 
617
        client.add_success_response('stat', '0', '65535')
 
618
        remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
 
619
            _client=client)
 
620
        bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
 
621
            _client=client)
 
622
        repo = bzrdir.open_repository()
 
623
        self.assertEqual(
 
624
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
 
625
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
626
             ('call', 'BzrDir.find_repository', ('quack/',)),
 
627
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
 
628
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
 
629
             ('call', 'stat', ('/quack/.bzr/repository',)),
 
630
             ],
 
631
            client._calls)
 
632
        self.assertEqual(network_name, repo._format.network_name())
 
633
 
 
634
    def test_backwards_compat_2(self):
 
635
        # fallback to find_repositoryV2
 
636
        reference_format = self.get_repo_format()
 
637
        network_name = reference_format.network_name()
 
638
        client = FakeClient('bzr://example.com/')
 
639
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
 
640
        client.add_success_response('ok', '', 'no', 'no', 'no')
 
641
        # A real repository instance will be created to determine the network
 
642
        # name.
 
643
        client.add_success_response_with_body(
 
644
            "Bazaar-NG meta directory, format 1\n", 'ok')
 
645
        client.add_success_response_with_body(
 
646
            reference_format.get_format_string(), 'ok')
 
647
        # PackRepository wants to do a stat
 
648
        client.add_success_response('stat', '0', '65535')
 
649
        remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
 
650
            _client=client)
 
651
        bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
 
652
            _client=client)
 
653
        repo = bzrdir.open_repository()
 
654
        self.assertEqual(
 
655
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
 
656
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
657
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
 
658
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
 
659
             ('call', 'stat', ('/quack/.bzr/repository',)),
 
660
             ],
 
661
            client._calls)
 
662
        self.assertEqual(network_name, repo._format.network_name())
 
663
 
 
664
    def test_current_server(self):
 
665
        reference_format = self.get_repo_format()
 
666
        network_name = reference_format.network_name()
 
667
        transport = MemoryTransport()
 
668
        transport.mkdir('quack')
 
669
        transport = transport.clone('quack')
 
670
        client = FakeClient(transport.base)
 
671
        client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
 
672
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
673
            _client=client)
 
674
        repo = bzrdir.open_repository()
 
675
        self.assertEqual(
 
676
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
 
677
            client._calls)
 
678
        self.assertEqual(network_name, repo._format.network_name())
 
679
 
 
680
 
 
681
class OldSmartClient(object):
 
682
    """A fake smart client for test_old_version that just returns a version one
 
683
    response to the 'hello' (query version) command.
 
684
    """
 
685
 
 
686
    def get_request(self):
 
687
        input_file = StringIO('ok\x011\n')
 
688
        output_file = StringIO()
 
689
        client_medium = medium.SmartSimplePipesClientMedium(
 
690
            input_file, output_file)
 
691
        return medium.SmartClientStreamMediumRequest(client_medium)
 
692
 
 
693
    def protocol_version(self):
 
694
        return 1
 
695
 
 
696
 
 
697
class OldServerTransport(object):
 
698
    """A fake transport for test_old_server that reports it's smart server
 
699
    protocol version as version one.
 
700
    """
 
701
 
 
702
    def __init__(self):
 
703
        self.base = 'fake:'
 
704
 
 
705
    def get_smart_client(self):
 
706
        return OldSmartClient()
 
707
 
 
708
 
 
709
class RemoteBranchTestCase(TestRemote):
 
710
 
 
711
    def make_remote_branch(self, transport, client):
 
712
        """Make a RemoteBranch using 'client' as its _SmartClient.
 
713
 
 
714
        A RemoteBzrDir and RemoteRepository will also be created to fill out
 
715
        the RemoteBranch, albeit with stub values for some of their attributes.
 
716
        """
 
717
        # we do not want bzrdir to make any remote calls, so use False as its
 
718
        # _client.  If it tries to make a remote call, this will fail
 
719
        # immediately.
 
720
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
721
            _client=False)
 
722
        repo = RemoteRepository(bzrdir, None, _client=client)
 
723
        branch_format = self.get_branch_format()
 
724
        format = RemoteBranchFormat(network_name=branch_format.network_name())
 
725
        return RemoteBranch(bzrdir, repo, _client=client, format=format)
 
726
 
 
727
 
 
728
class TestBranchGetParent(RemoteBranchTestCase):
 
729
 
 
730
    def test_no_parent(self):
 
731
        # in an empty branch we decode the response properly
 
732
        transport = MemoryTransport()
 
733
        client = FakeClient(transport.base)
 
734
        client.add_expected_call(
 
735
            'Branch.get_stacked_on_url', ('quack/',),
 
736
            'error', ('NotStacked',))
 
737
        client.add_expected_call(
 
738
            'Branch.get_parent', ('quack/',),
 
739
            'success', ('',))
 
740
        transport.mkdir('quack')
 
741
        transport = transport.clone('quack')
 
742
        branch = self.make_remote_branch(transport, client)
 
743
        result = branch.get_parent()
 
744
        client.finished_test()
 
745
        self.assertEqual(None, result)
 
746
 
 
747
    def test_parent_relative(self):
 
748
        transport = MemoryTransport()
 
749
        client = FakeClient(transport.base)
 
750
        client.add_expected_call(
 
751
            'Branch.get_stacked_on_url', ('kwaak/',),
 
752
            'error', ('NotStacked',))
 
753
        client.add_expected_call(
 
754
            'Branch.get_parent', ('kwaak/',),
 
755
            'success', ('../foo/',))
 
756
        transport.mkdir('kwaak')
 
757
        transport = transport.clone('kwaak')
 
758
        branch = self.make_remote_branch(transport, client)
 
759
        result = branch.get_parent()
 
760
        self.assertEqual(transport.clone('../foo').base, result)
 
761
 
 
762
    def test_parent_absolute(self):
 
763
        transport = MemoryTransport()
 
764
        client = FakeClient(transport.base)
 
765
        client.add_expected_call(
 
766
            'Branch.get_stacked_on_url', ('kwaak/',),
 
767
            'error', ('NotStacked',))
 
768
        client.add_expected_call(
 
769
            'Branch.get_parent', ('kwaak/',),
 
770
            'success', ('http://foo/',))
 
771
        transport.mkdir('kwaak')
 
772
        transport = transport.clone('kwaak')
 
773
        branch = self.make_remote_branch(transport, client)
 
774
        result = branch.get_parent()
 
775
        self.assertEqual('http://foo/', result)
 
776
 
 
777
 
 
778
class TestBranchGetTagsBytes(RemoteBranchTestCase):
 
779
 
 
780
    def test_backwards_compat(self):
 
781
        self.setup_smart_server_with_call_log()
 
782
        branch = self.make_branch('.')
 
783
        self.reset_smart_call_log()
 
784
        verb = 'Branch.get_tags_bytes'
 
785
        self.disable_verb(verb)
 
786
        branch.tags.get_tag_dict()
 
787
        call_count = len([call for call in self.hpss_calls if
 
788
            call.call.method == verb])
 
789
        self.assertEqual(1, call_count)
 
790
 
 
791
    def test_trivial(self):
 
792
        transport = MemoryTransport()
 
793
        client = FakeClient(transport.base)
 
794
        client.add_expected_call(
 
795
            'Branch.get_stacked_on_url', ('quack/',),
 
796
            'error', ('NotStacked',))
 
797
        client.add_expected_call(
 
798
            'Branch.get_tags_bytes', ('quack/',),
 
799
            'success', ('',))
 
800
        transport.mkdir('quack')
 
801
        transport = transport.clone('quack')
 
802
        branch = self.make_remote_branch(transport, client)
 
803
        result = branch.tags.get_tag_dict()
 
804
        client.finished_test()
 
805
        self.assertEqual({}, result)
 
806
 
 
807
 
 
808
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
 
809
 
 
810
    def test_empty_branch(self):
 
811
        # in an empty branch we decode the response properly
 
812
        transport = MemoryTransport()
 
813
        client = FakeClient(transport.base)
 
814
        client.add_expected_call(
 
815
            'Branch.get_stacked_on_url', ('quack/',),
 
816
            'error', ('NotStacked',))
 
817
        client.add_expected_call(
 
818
            'Branch.last_revision_info', ('quack/',),
 
819
            'success', ('ok', '0', 'null:'))
 
820
        transport.mkdir('quack')
 
821
        transport = transport.clone('quack')
 
822
        branch = self.make_remote_branch(transport, client)
 
823
        result = branch.last_revision_info()
 
824
        client.finished_test()
 
825
        self.assertEqual((0, NULL_REVISION), result)
 
826
 
 
827
    def test_non_empty_branch(self):
 
828
        # in a non-empty branch we also decode the response properly
 
829
        revid = u'\xc8'.encode('utf8')
 
830
        transport = MemoryTransport()
 
831
        client = FakeClient(transport.base)
 
832
        client.add_expected_call(
 
833
            'Branch.get_stacked_on_url', ('kwaak/',),
 
834
            'error', ('NotStacked',))
 
835
        client.add_expected_call(
 
836
            'Branch.last_revision_info', ('kwaak/',),
 
837
            'success', ('ok', '2', revid))
 
838
        transport.mkdir('kwaak')
 
839
        transport = transport.clone('kwaak')
 
840
        branch = self.make_remote_branch(transport, client)
 
841
        result = branch.last_revision_info()
 
842
        self.assertEqual((2, revid), result)
 
843
 
 
844
 
 
845
class TestBranch_get_stacked_on_url(TestRemote):
 
846
    """Test Branch._get_stacked_on_url rpc"""
 
847
 
 
848
    def test_get_stacked_on_invalid_url(self):
 
849
        # test that asking for a stacked on url the server can't access works.
 
850
        # This isn't perfect, but then as we're in the same process there
 
851
        # really isn't anything we can do to be 100% sure that the server
 
852
        # doesn't just open in - this test probably needs to be rewritten using
 
853
        # a spawn()ed server.
 
854
        stacked_branch = self.make_branch('stacked', format='1.9')
 
855
        memory_branch = self.make_branch('base', format='1.9')
 
856
        vfs_url = self.get_vfs_only_url('base')
 
857
        stacked_branch.set_stacked_on_url(vfs_url)
 
858
        transport = stacked_branch.bzrdir.root_transport
 
859
        client = FakeClient(transport.base)
 
860
        client.add_expected_call(
 
861
            'Branch.get_stacked_on_url', ('stacked/',),
 
862
            'success', ('ok', vfs_url))
 
863
        # XXX: Multiple calls are bad, this second call documents what is
 
864
        # today.
 
865
        client.add_expected_call(
 
866
            'Branch.get_stacked_on_url', ('stacked/',),
 
867
            'success', ('ok', vfs_url))
 
868
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
869
            _client=client)
 
870
        branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, None),
 
871
            _client=client)
 
872
        result = branch.get_stacked_on_url()
 
873
        self.assertEqual(vfs_url, result)
 
874
 
 
875
    def test_backwards_compatible(self):
 
876
        # like with bzr1.6 with no Branch.get_stacked_on_url rpc
 
877
        base_branch = self.make_branch('base', format='1.6')
 
878
        stacked_branch = self.make_branch('stacked', format='1.6')
 
879
        stacked_branch.set_stacked_on_url('../base')
 
880
        client = FakeClient(self.get_url())
 
881
        branch_network_name = self.get_branch_format().network_name()
 
882
        client.add_expected_call(
 
883
            'BzrDir.open_branchV2', ('stacked/',),
 
884
            'success', ('branch', branch_network_name))
 
885
        client.add_expected_call(
 
886
            'BzrDir.find_repositoryV3', ('stacked/',),
 
887
            'success', ('ok', '', 'no', 'no', 'no',
 
888
                stacked_branch.repository._format.network_name()))
 
889
        # called twice, once from constructor and then again by us
 
890
        client.add_expected_call(
 
891
            'Branch.get_stacked_on_url', ('stacked/',),
 
892
            'unknown', ('Branch.get_stacked_on_url',))
 
893
        client.add_expected_call(
 
894
            'Branch.get_stacked_on_url', ('stacked/',),
 
895
            'unknown', ('Branch.get_stacked_on_url',))
 
896
        # this will also do vfs access, but that goes direct to the transport
 
897
        # and isn't seen by the FakeClient.
 
898
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
899
            remote.RemoteBzrDirFormat(), _client=client)
 
900
        branch = bzrdir.open_branch()
 
901
        result = branch.get_stacked_on_url()
 
902
        self.assertEqual('../base', result)
 
903
        client.finished_test()
 
904
        # it's in the fallback list both for the RemoteRepository and its vfs
 
905
        # repository
 
906
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
907
        self.assertEqual(1,
 
908
            len(branch.repository._real_repository._fallback_repositories))
 
909
 
 
910
    def test_get_stacked_on_real_branch(self):
 
911
        base_branch = self.make_branch('base', format='1.6')
 
912
        stacked_branch = self.make_branch('stacked', format='1.6')
 
913
        stacked_branch.set_stacked_on_url('../base')
 
914
        reference_format = self.get_repo_format()
 
915
        network_name = reference_format.network_name()
 
916
        client = FakeClient(self.get_url())
 
917
        branch_network_name = self.get_branch_format().network_name()
 
918
        client.add_expected_call(
 
919
            'BzrDir.open_branchV2', ('stacked/',),
 
920
            'success', ('branch', branch_network_name))
 
921
        client.add_expected_call(
 
922
            'BzrDir.find_repositoryV3', ('stacked/',),
 
923
            'success', ('ok', '', 'no', 'no', 'no', network_name))
 
924
        # called twice, once from constructor and then again by us
 
925
        client.add_expected_call(
 
926
            'Branch.get_stacked_on_url', ('stacked/',),
 
927
            'success', ('ok', '../base'))
 
928
        client.add_expected_call(
 
929
            'Branch.get_stacked_on_url', ('stacked/',),
 
930
            'success', ('ok', '../base'))
 
931
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
932
            remote.RemoteBzrDirFormat(), _client=client)
 
933
        branch = bzrdir.open_branch()
 
934
        result = branch.get_stacked_on_url()
 
935
        self.assertEqual('../base', result)
 
936
        client.finished_test()
 
937
        # it's in the fallback list both for the RemoteRepository and its vfs
 
938
        # repository
 
939
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
940
        self.assertEqual(1,
 
941
            len(branch.repository._real_repository._fallback_repositories))
 
942
 
 
943
 
 
944
class TestBranchSetLastRevision(RemoteBranchTestCase):
 
945
 
 
946
    def test_set_empty(self):
 
947
        # set_revision_history([]) is translated to calling
 
948
        # Branch.set_last_revision(path, '') on the wire.
 
949
        transport = MemoryTransport()
 
950
        transport.mkdir('branch')
 
951
        transport = transport.clone('branch')
 
952
 
 
953
        client = FakeClient(transport.base)
 
954
        client.add_expected_call(
 
955
            'Branch.get_stacked_on_url', ('branch/',),
 
956
            'error', ('NotStacked',))
 
957
        client.add_expected_call(
 
958
            'Branch.lock_write', ('branch/', '', ''),
 
959
            'success', ('ok', 'branch token', 'repo token'))
 
960
        client.add_expected_call(
 
961
            'Branch.last_revision_info',
 
962
            ('branch/',),
 
963
            'success', ('ok', '0', 'null:'))
 
964
        client.add_expected_call(
 
965
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
 
966
            'success', ('ok',))
 
967
        client.add_expected_call(
 
968
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
969
            'success', ('ok',))
 
970
        branch = self.make_remote_branch(transport, client)
 
971
        # This is a hack to work around the problem that RemoteBranch currently
 
972
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
973
        branch._ensure_real = lambda: None
 
974
        branch.lock_write()
 
975
        result = branch.set_revision_history([])
 
976
        branch.unlock()
 
977
        self.assertEqual(None, result)
 
978
        client.finished_test()
 
979
 
 
980
    def test_set_nonempty(self):
 
981
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
 
982
        # Branch.set_last_revision(path, rev-idN) on the wire.
 
983
        transport = MemoryTransport()
 
984
        transport.mkdir('branch')
 
985
        transport = transport.clone('branch')
 
986
 
 
987
        client = FakeClient(transport.base)
 
988
        client.add_expected_call(
 
989
            'Branch.get_stacked_on_url', ('branch/',),
 
990
            'error', ('NotStacked',))
 
991
        client.add_expected_call(
 
992
            'Branch.lock_write', ('branch/', '', ''),
 
993
            'success', ('ok', 'branch token', 'repo token'))
 
994
        client.add_expected_call(
 
995
            'Branch.last_revision_info',
 
996
            ('branch/',),
 
997
            'success', ('ok', '0', 'null:'))
 
998
        lines = ['rev-id2']
 
999
        encoded_body = bz2.compress('\n'.join(lines))
 
1000
        client.add_success_response_with_body(encoded_body, 'ok')
 
1001
        client.add_expected_call(
 
1002
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
 
1003
            'success', ('ok',))
 
1004
        client.add_expected_call(
 
1005
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1006
            'success', ('ok',))
 
1007
        branch = self.make_remote_branch(transport, client)
 
1008
        # This is a hack to work around the problem that RemoteBranch currently
 
1009
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
1010
        branch._ensure_real = lambda: None
 
1011
        # Lock the branch, reset the record of remote calls.
 
1012
        branch.lock_write()
 
1013
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
 
1014
        branch.unlock()
 
1015
        self.assertEqual(None, result)
 
1016
        client.finished_test()
 
1017
 
 
1018
    def test_no_such_revision(self):
 
1019
        transport = MemoryTransport()
 
1020
        transport.mkdir('branch')
 
1021
        transport = transport.clone('branch')
 
1022
        # A response of 'NoSuchRevision' is translated into an exception.
 
1023
        client = FakeClient(transport.base)
 
1024
        client.add_expected_call(
 
1025
            'Branch.get_stacked_on_url', ('branch/',),
 
1026
            'error', ('NotStacked',))
 
1027
        client.add_expected_call(
 
1028
            'Branch.lock_write', ('branch/', '', ''),
 
1029
            'success', ('ok', 'branch token', 'repo token'))
 
1030
        client.add_expected_call(
 
1031
            'Branch.last_revision_info',
 
1032
            ('branch/',),
 
1033
            'success', ('ok', '0', 'null:'))
 
1034
        # get_graph calls to construct the revision history, for the set_rh
 
1035
        # hook
 
1036
        lines = ['rev-id']
 
1037
        encoded_body = bz2.compress('\n'.join(lines))
 
1038
        client.add_success_response_with_body(encoded_body, 'ok')
 
1039
        client.add_expected_call(
 
1040
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
1041
            'error', ('NoSuchRevision', 'rev-id'))
 
1042
        client.add_expected_call(
 
1043
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1044
            'success', ('ok',))
 
1045
 
 
1046
        branch = self.make_remote_branch(transport, client)
 
1047
        branch.lock_write()
 
1048
        self.assertRaises(
 
1049
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
 
1050
        branch.unlock()
 
1051
        client.finished_test()
 
1052
 
 
1053
    def test_tip_change_rejected(self):
 
1054
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
1055
        be raised.
 
1056
        """
 
1057
        transport = MemoryTransport()
 
1058
        transport.mkdir('branch')
 
1059
        transport = transport.clone('branch')
 
1060
        client = FakeClient(transport.base)
 
1061
        rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
 
1062
        rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
 
1063
        client.add_expected_call(
 
1064
            'Branch.get_stacked_on_url', ('branch/',),
 
1065
            'error', ('NotStacked',))
 
1066
        client.add_expected_call(
 
1067
            'Branch.lock_write', ('branch/', '', ''),
 
1068
            'success', ('ok', 'branch token', 'repo token'))
 
1069
        client.add_expected_call(
 
1070
            'Branch.last_revision_info',
 
1071
            ('branch/',),
 
1072
            'success', ('ok', '0', 'null:'))
 
1073
        lines = ['rev-id']
 
1074
        encoded_body = bz2.compress('\n'.join(lines))
 
1075
        client.add_success_response_with_body(encoded_body, 'ok')
 
1076
        client.add_expected_call(
 
1077
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
1078
            'error', ('TipChangeRejected', rejection_msg_utf8))
 
1079
        client.add_expected_call(
 
1080
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1081
            'success', ('ok',))
 
1082
        branch = self.make_remote_branch(transport, client)
 
1083
        branch._ensure_real = lambda: None
 
1084
        branch.lock_write()
 
1085
        # The 'TipChangeRejected' error response triggered by calling
 
1086
        # set_revision_history causes a TipChangeRejected exception.
 
1087
        err = self.assertRaises(
 
1088
            errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
 
1089
        # The UTF-8 message from the response has been decoded into a unicode
 
1090
        # object.
 
1091
        self.assertIsInstance(err.msg, unicode)
 
1092
        self.assertEqual(rejection_msg_unicode, err.msg)
 
1093
        branch.unlock()
 
1094
        client.finished_test()
 
1095
 
 
1096
 
 
1097
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
 
1098
 
 
1099
    def test_set_last_revision_info(self):
 
1100
        # set_last_revision_info(num, 'rev-id') is translated to calling
 
1101
        # Branch.set_last_revision_info(num, 'rev-id') on the wire.
 
1102
        transport = MemoryTransport()
 
1103
        transport.mkdir('branch')
 
1104
        transport = transport.clone('branch')
 
1105
        client = FakeClient(transport.base)
 
1106
        # get_stacked_on_url
 
1107
        client.add_error_response('NotStacked')
 
1108
        # lock_write
 
1109
        client.add_success_response('ok', 'branch token', 'repo token')
 
1110
        # query the current revision
 
1111
        client.add_success_response('ok', '0', 'null:')
 
1112
        # set_last_revision
 
1113
        client.add_success_response('ok')
 
1114
        # unlock
 
1115
        client.add_success_response('ok')
 
1116
 
 
1117
        branch = self.make_remote_branch(transport, client)
 
1118
        # Lock the branch, reset the record of remote calls.
 
1119
        branch.lock_write()
 
1120
        client._calls = []
 
1121
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
1122
        self.assertEqual(
 
1123
            [('call', 'Branch.last_revision_info', ('branch/',)),
 
1124
             ('call', 'Branch.set_last_revision_info',
 
1125
                ('branch/', 'branch token', 'repo token',
 
1126
                 '1234', 'a-revision-id'))],
 
1127
            client._calls)
 
1128
        self.assertEqual(None, result)
 
1129
 
 
1130
    def test_no_such_revision(self):
 
1131
        # A response of 'NoSuchRevision' is translated into an exception.
 
1132
        transport = MemoryTransport()
 
1133
        transport.mkdir('branch')
 
1134
        transport = transport.clone('branch')
 
1135
        client = FakeClient(transport.base)
 
1136
        # get_stacked_on_url
 
1137
        client.add_error_response('NotStacked')
 
1138
        # lock_write
 
1139
        client.add_success_response('ok', 'branch token', 'repo token')
 
1140
        # set_last_revision
 
1141
        client.add_error_response('NoSuchRevision', 'revid')
 
1142
        # unlock
 
1143
        client.add_success_response('ok')
 
1144
 
 
1145
        branch = self.make_remote_branch(transport, client)
 
1146
        # Lock the branch, reset the record of remote calls.
 
1147
        branch.lock_write()
 
1148
        client._calls = []
 
1149
 
 
1150
        self.assertRaises(
 
1151
            errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
 
1152
        branch.unlock()
 
1153
 
 
1154
    def lock_remote_branch(self, branch):
 
1155
        """Trick a RemoteBranch into thinking it is locked."""
 
1156
        branch._lock_mode = 'w'
 
1157
        branch._lock_count = 2
 
1158
        branch._lock_token = 'branch token'
 
1159
        branch._repo_lock_token = 'repo token'
 
1160
        branch.repository._lock_mode = 'w'
 
1161
        branch.repository._lock_count = 2
 
1162
        branch.repository._lock_token = 'repo token'
 
1163
 
 
1164
    def test_backwards_compatibility(self):
 
1165
        """If the server does not support the Branch.set_last_revision_info
 
1166
        verb (which is new in 1.4), then the client falls back to VFS methods.
 
1167
        """
 
1168
        # This test is a little messy.  Unlike most tests in this file, it
 
1169
        # doesn't purely test what a Remote* object sends over the wire, and
 
1170
        # how it reacts to responses from the wire.  It instead relies partly
 
1171
        # on asserting that the RemoteBranch will call
 
1172
        # self._real_branch.set_last_revision_info(...).
 
1173
 
 
1174
        # First, set up our RemoteBranch with a FakeClient that raises
 
1175
        # UnknownSmartMethod, and a StubRealBranch that logs how it is called.
 
1176
        transport = MemoryTransport()
 
1177
        transport.mkdir('branch')
 
1178
        transport = transport.clone('branch')
 
1179
        client = FakeClient(transport.base)
 
1180
        client.add_expected_call(
 
1181
            'Branch.get_stacked_on_url', ('branch/',),
 
1182
            'error', ('NotStacked',))
 
1183
        client.add_expected_call(
 
1184
            'Branch.last_revision_info',
 
1185
            ('branch/',),
 
1186
            'success', ('ok', '0', 'null:'))
 
1187
        client.add_expected_call(
 
1188
            'Branch.set_last_revision_info',
 
1189
            ('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
 
1190
            'unknown', 'Branch.set_last_revision_info')
 
1191
 
 
1192
        branch = self.make_remote_branch(transport, client)
 
1193
        class StubRealBranch(object):
 
1194
            def __init__(self):
 
1195
                self.calls = []
 
1196
            def set_last_revision_info(self, revno, revision_id):
 
1197
                self.calls.append(
 
1198
                    ('set_last_revision_info', revno, revision_id))
 
1199
            def _clear_cached_state(self):
 
1200
                pass
 
1201
        real_branch = StubRealBranch()
 
1202
        branch._real_branch = real_branch
 
1203
        self.lock_remote_branch(branch)
 
1204
 
 
1205
        # Call set_last_revision_info, and verify it behaved as expected.
 
1206
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
1207
        self.assertEqual(
 
1208
            [('set_last_revision_info', 1234, 'a-revision-id')],
 
1209
            real_branch.calls)
 
1210
        client.finished_test()
 
1211
 
 
1212
    def test_unexpected_error(self):
 
1213
        # If the server sends an error the client doesn't understand, it gets
 
1214
        # turned into an UnknownErrorFromSmartServer, which is presented as a
 
1215
        # non-internal error to the user.
 
1216
        transport = MemoryTransport()
 
1217
        transport.mkdir('branch')
 
1218
        transport = transport.clone('branch')
 
1219
        client = FakeClient(transport.base)
 
1220
        # get_stacked_on_url
 
1221
        client.add_error_response('NotStacked')
 
1222
        # lock_write
 
1223
        client.add_success_response('ok', 'branch token', 'repo token')
 
1224
        # set_last_revision
 
1225
        client.add_error_response('UnexpectedError')
 
1226
        # unlock
 
1227
        client.add_success_response('ok')
 
1228
 
 
1229
        branch = self.make_remote_branch(transport, client)
 
1230
        # Lock the branch, reset the record of remote calls.
 
1231
        branch.lock_write()
 
1232
        client._calls = []
 
1233
 
 
1234
        err = self.assertRaises(
 
1235
            errors.UnknownErrorFromSmartServer,
 
1236
            branch.set_last_revision_info, 123, 'revid')
 
1237
        self.assertEqual(('UnexpectedError',), err.error_tuple)
 
1238
        branch.unlock()
 
1239
 
 
1240
    def test_tip_change_rejected(self):
 
1241
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
1242
        be raised.
 
1243
        """
 
1244
        transport = MemoryTransport()
 
1245
        transport.mkdir('branch')
 
1246
        transport = transport.clone('branch')
 
1247
        client = FakeClient(transport.base)
 
1248
        # get_stacked_on_url
 
1249
        client.add_error_response('NotStacked')
 
1250
        # lock_write
 
1251
        client.add_success_response('ok', 'branch token', 'repo token')
 
1252
        # set_last_revision
 
1253
        client.add_error_response('TipChangeRejected', 'rejection message')
 
1254
        # unlock
 
1255
        client.add_success_response('ok')
 
1256
 
 
1257
        branch = self.make_remote_branch(transport, client)
 
1258
        # Lock the branch, reset the record of remote calls.
 
1259
        branch.lock_write()
 
1260
        self.addCleanup(branch.unlock)
 
1261
        client._calls = []
 
1262
 
 
1263
        # The 'TipChangeRejected' error response triggered by calling
 
1264
        # set_last_revision_info causes a TipChangeRejected exception.
 
1265
        err = self.assertRaises(
 
1266
            errors.TipChangeRejected,
 
1267
            branch.set_last_revision_info, 123, 'revid')
 
1268
        self.assertEqual('rejection message', err.msg)
 
1269
 
 
1270
 
 
1271
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
 
1272
    """Getting the branch configuration should use an abstract method not vfs.
 
1273
    """
 
1274
 
 
1275
    def test_get_branch_conf(self):
 
1276
        raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
 
1277
        ## # We should see that branch.get_config() does a single rpc to get the
 
1278
        ## # remote configuration file, abstracting away where that is stored on
 
1279
        ## # the server.  However at the moment it always falls back to using the
 
1280
        ## # vfs, and this would need some changes in config.py.
 
1281
 
 
1282
        ## # in an empty branch we decode the response properly
 
1283
        ## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
 
1284
        ## # we need to make a real branch because the remote_branch.control_files
 
1285
        ## # will trigger _ensure_real.
 
1286
        ## branch = self.make_branch('quack')
 
1287
        ## transport = branch.bzrdir.root_transport
 
1288
        ## # we do not want bzrdir to make any remote calls
 
1289
        ## bzrdir = RemoteBzrDir(transport, _client=False)
 
1290
        ## branch = RemoteBranch(bzrdir, None, _client=client)
 
1291
        ## config = branch.get_config()
 
1292
        ## self.assertEqual(
 
1293
        ##     [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
 
1294
        ##     client._calls)
 
1295
 
 
1296
 
 
1297
class TestBranchLockWrite(RemoteBranchTestCase):
 
1298
 
 
1299
    def test_lock_write_unlockable(self):
 
1300
        transport = MemoryTransport()
 
1301
        client = FakeClient(transport.base)
 
1302
        client.add_expected_call(
 
1303
            'Branch.get_stacked_on_url', ('quack/',),
 
1304
            'error', ('NotStacked',),)
 
1305
        client.add_expected_call(
 
1306
            'Branch.lock_write', ('quack/', '', ''),
 
1307
            'error', ('UnlockableTransport',))
 
1308
        transport.mkdir('quack')
 
1309
        transport = transport.clone('quack')
 
1310
        branch = self.make_remote_branch(transport, client)
 
1311
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
 
1312
        client.finished_test()
 
1313
 
 
1314
 
 
1315
class TestTransportIsReadonly(tests.TestCase):
 
1316
 
 
1317
    def test_true(self):
 
1318
        client = FakeClient()
 
1319
        client.add_success_response('yes')
 
1320
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1321
                                    _client=client)
 
1322
        self.assertEqual(True, transport.is_readonly())
 
1323
        self.assertEqual(
 
1324
            [('call', 'Transport.is_readonly', ())],
 
1325
            client._calls)
 
1326
 
 
1327
    def test_false(self):
 
1328
        client = FakeClient()
 
1329
        client.add_success_response('no')
 
1330
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1331
                                    _client=client)
 
1332
        self.assertEqual(False, transport.is_readonly())
 
1333
        self.assertEqual(
 
1334
            [('call', 'Transport.is_readonly', ())],
 
1335
            client._calls)
 
1336
 
 
1337
    def test_error_from_old_server(self):
 
1338
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
 
1339
 
 
1340
        Clients should treat it as a "no" response, because is_readonly is only
 
1341
        advisory anyway (a transport could be read-write, but then the
 
1342
        underlying filesystem could be readonly anyway).
 
1343
        """
 
1344
        client = FakeClient()
 
1345
        client.add_unknown_method_response('Transport.is_readonly')
 
1346
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1347
                                    _client=client)
 
1348
        self.assertEqual(False, transport.is_readonly())
 
1349
        self.assertEqual(
 
1350
            [('call', 'Transport.is_readonly', ())],
 
1351
            client._calls)
 
1352
 
 
1353
 
 
1354
class TestTransportMkdir(tests.TestCase):
 
1355
 
 
1356
    def test_permissiondenied(self):
 
1357
        client = FakeClient()
 
1358
        client.add_error_response('PermissionDenied', 'remote path', 'extra')
 
1359
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1360
                                    _client=client)
 
1361
        exc = self.assertRaises(
 
1362
            errors.PermissionDenied, transport.mkdir, 'client path')
 
1363
        expected_error = errors.PermissionDenied('/client path', 'extra')
 
1364
        self.assertEqual(expected_error, exc)
 
1365
 
 
1366
 
 
1367
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
 
1368
 
 
1369
    def test_defaults_to_none(self):
 
1370
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1371
        self.assertIs(None, t._get_credentials()[0])
 
1372
 
 
1373
    def test_uses_authentication_config(self):
 
1374
        conf = config.AuthenticationConfig()
 
1375
        conf._get_config().update(
 
1376
            {'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
 
1377
            'example.com'}})
 
1378
        conf._save()
 
1379
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1380
        self.assertEqual('bar', t._get_credentials()[0])
 
1381
 
 
1382
 
 
1383
class TestRemoteRepository(TestRemote):
 
1384
    """Base for testing RemoteRepository protocol usage.
 
1385
 
 
1386
    These tests contain frozen requests and responses.  We want any changes to
 
1387
    what is sent or expected to be require a thoughtful update to these tests
 
1388
    because they might break compatibility with different-versioned servers.
 
1389
    """
 
1390
 
 
1391
    def setup_fake_client_and_repository(self, transport_path):
 
1392
        """Create the fake client and repository for testing with.
 
1393
 
 
1394
        There's no real server here; we just have canned responses sent
 
1395
        back one by one.
 
1396
 
 
1397
        :param transport_path: Path below the root of the MemoryTransport
 
1398
            where the repository will be created.
 
1399
        """
 
1400
        transport = MemoryTransport()
 
1401
        transport.mkdir(transport_path)
 
1402
        client = FakeClient(transport.base)
 
1403
        transport = transport.clone(transport_path)
 
1404
        # we do not want bzrdir to make any remote calls
 
1405
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
1406
            _client=False)
 
1407
        repo = RemoteRepository(bzrdir, None, _client=client)
 
1408
        return repo, client
 
1409
 
 
1410
 
 
1411
class TestRepositoryGatherStats(TestRemoteRepository):
 
1412
 
 
1413
    def test_revid_none(self):
 
1414
        # ('ok',), body with revisions and size
 
1415
        transport_path = 'quack'
 
1416
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1417
        client.add_success_response_with_body(
 
1418
            'revisions: 2\nsize: 18\n', 'ok')
 
1419
        result = repo.gather_stats(None)
 
1420
        self.assertEqual(
 
1421
            [('call_expecting_body', 'Repository.gather_stats',
 
1422
             ('quack/','','no'))],
 
1423
            client._calls)
 
1424
        self.assertEqual({'revisions': 2, 'size': 18}, result)
 
1425
 
 
1426
    def test_revid_no_committers(self):
 
1427
        # ('ok',), body without committers
 
1428
        body = ('firstrev: 123456.300 3600\n'
 
1429
                'latestrev: 654231.400 0\n'
 
1430
                'revisions: 2\n'
 
1431
                'size: 18\n')
 
1432
        transport_path = 'quick'
 
1433
        revid = u'\xc8'.encode('utf8')
 
1434
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1435
        client.add_success_response_with_body(body, 'ok')
 
1436
        result = repo.gather_stats(revid)
 
1437
        self.assertEqual(
 
1438
            [('call_expecting_body', 'Repository.gather_stats',
 
1439
              ('quick/', revid, 'no'))],
 
1440
            client._calls)
 
1441
        self.assertEqual({'revisions': 2, 'size': 18,
 
1442
                          'firstrev': (123456.300, 3600),
 
1443
                          'latestrev': (654231.400, 0),},
 
1444
                         result)
 
1445
 
 
1446
    def test_revid_with_committers(self):
 
1447
        # ('ok',), body with committers
 
1448
        body = ('committers: 128\n'
 
1449
                'firstrev: 123456.300 3600\n'
 
1450
                'latestrev: 654231.400 0\n'
 
1451
                'revisions: 2\n'
 
1452
                'size: 18\n')
 
1453
        transport_path = 'buick'
 
1454
        revid = u'\xc8'.encode('utf8')
 
1455
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1456
        client.add_success_response_with_body(body, 'ok')
 
1457
        result = repo.gather_stats(revid, True)
 
1458
        self.assertEqual(
 
1459
            [('call_expecting_body', 'Repository.gather_stats',
 
1460
              ('buick/', revid, 'yes'))],
 
1461
            client._calls)
 
1462
        self.assertEqual({'revisions': 2, 'size': 18,
 
1463
                          'committers': 128,
 
1464
                          'firstrev': (123456.300, 3600),
 
1465
                          'latestrev': (654231.400, 0),},
 
1466
                         result)
 
1467
 
 
1468
 
 
1469
class TestRepositoryGetGraph(TestRemoteRepository):
 
1470
 
 
1471
    def test_get_graph(self):
 
1472
        # get_graph returns a graph with a custom parents provider.
 
1473
        transport_path = 'quack'
 
1474
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1475
        graph = repo.get_graph()
 
1476
        self.assertNotEqual(graph._parents_provider, repo)
 
1477
 
 
1478
 
 
1479
class TestRepositoryGetParentMap(TestRemoteRepository):
 
1480
 
 
1481
    def test_get_parent_map_caching(self):
 
1482
        # get_parent_map returns from cache until unlock()
 
1483
        # setup a reponse with two revisions
 
1484
        r1 = u'\u0e33'.encode('utf8')
 
1485
        r2 = u'\u0dab'.encode('utf8')
 
1486
        lines = [' '.join([r2, r1]), r1]
 
1487
        encoded_body = bz2.compress('\n'.join(lines))
 
1488
 
 
1489
        transport_path = 'quack'
 
1490
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1491
        client.add_success_response_with_body(encoded_body, 'ok')
 
1492
        client.add_success_response_with_body(encoded_body, 'ok')
 
1493
        repo.lock_read()
 
1494
        graph = repo.get_graph()
 
1495
        parents = graph.get_parent_map([r2])
 
1496
        self.assertEqual({r2: (r1,)}, parents)
 
1497
        # locking and unlocking deeper should not reset
 
1498
        repo.lock_read()
 
1499
        repo.unlock()
 
1500
        parents = graph.get_parent_map([r1])
 
1501
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
1502
        self.assertEqual(
 
1503
            [('call_with_body_bytes_expecting_body',
 
1504
              'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
 
1505
            client._calls)
 
1506
        repo.unlock()
 
1507
        # now we call again, and it should use the second response.
 
1508
        repo.lock_read()
 
1509
        graph = repo.get_graph()
 
1510
        parents = graph.get_parent_map([r1])
 
1511
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
1512
        self.assertEqual(
 
1513
            [('call_with_body_bytes_expecting_body',
 
1514
              'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
 
1515
             ('call_with_body_bytes_expecting_body',
 
1516
              'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
 
1517
            ],
 
1518
            client._calls)
 
1519
        repo.unlock()
 
1520
 
 
1521
    def test_get_parent_map_reconnects_if_unknown_method(self):
 
1522
        transport_path = 'quack'
 
1523
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1524
        client.add_unknown_method_response('Repository,get_parent_map')
 
1525
        client.add_success_response_with_body('', 'ok')
 
1526
        self.assertFalse(client._medium._is_remote_before((1, 2)))
 
1527
        rev_id = 'revision-id'
 
1528
        expected_deprecations = [
 
1529
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
 
1530
            'in version 1.4.']
 
1531
        parents = self.callDeprecated(
 
1532
            expected_deprecations, repo.get_parent_map, [rev_id])
 
1533
        self.assertEqual(
 
1534
            [('call_with_body_bytes_expecting_body',
 
1535
              'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
 
1536
             ('disconnect medium',),
 
1537
             ('call_expecting_body', 'Repository.get_revision_graph',
 
1538
              ('quack/', ''))],
 
1539
            client._calls)
 
1540
        # The medium is now marked as being connected to an older server
 
1541
        self.assertTrue(client._medium._is_remote_before((1, 2)))
 
1542
 
 
1543
    def test_get_parent_map_fallback_parentless_node(self):
 
1544
        """get_parent_map falls back to get_revision_graph on old servers.  The
 
1545
        results from get_revision_graph are tweaked to match the get_parent_map
 
1546
        API.
 
1547
 
 
1548
        Specifically, a {key: ()} result from get_revision_graph means "no
 
1549
        parents" for that key, which in get_parent_map results should be
 
1550
        represented as {key: ('null:',)}.
 
1551
 
 
1552
        This is the test for https://bugs.launchpad.net/bzr/+bug/214894
 
1553
        """
 
1554
        rev_id = 'revision-id'
 
1555
        transport_path = 'quack'
 
1556
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1557
        client.add_success_response_with_body(rev_id, 'ok')
 
1558
        client._medium._remember_remote_is_before((1, 2))
 
1559
        expected_deprecations = [
 
1560
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
 
1561
            'in version 1.4.']
 
1562
        parents = self.callDeprecated(
 
1563
            expected_deprecations, repo.get_parent_map, [rev_id])
 
1564
        self.assertEqual(
 
1565
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1566
             ('quack/', ''))],
 
1567
            client._calls)
 
1568
        self.assertEqual({rev_id: ('null:',)}, parents)
 
1569
 
 
1570
    def test_get_parent_map_unexpected_response(self):
 
1571
        repo, client = self.setup_fake_client_and_repository('path')
 
1572
        client.add_success_response('something unexpected!')
 
1573
        self.assertRaises(
 
1574
            errors.UnexpectedSmartServerResponse,
 
1575
            repo.get_parent_map, ['a-revision-id'])
 
1576
 
 
1577
 
 
1578
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
 
1579
 
 
1580
    def test_allows_new_revisions(self):
 
1581
        """get_parent_map's results can be updated by commit."""
 
1582
        smart_server = server.SmartTCPServer_for_testing()
 
1583
        smart_server.setUp()
 
1584
        self.addCleanup(smart_server.tearDown)
 
1585
        self.make_branch('branch')
 
1586
        branch = Branch.open(smart_server.get_url() + '/branch')
 
1587
        tree = branch.create_checkout('tree', lightweight=True)
 
1588
        tree.lock_write()
 
1589
        self.addCleanup(tree.unlock)
 
1590
        graph = tree.branch.repository.get_graph()
 
1591
        # This provides an opportunity for the missing rev-id to be cached.
 
1592
        self.assertEqual({}, graph.get_parent_map(['rev1']))
 
1593
        tree.commit('message', rev_id='rev1')
 
1594
        graph = tree.branch.repository.get_graph()
 
1595
        self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
 
1596
 
 
1597
 
 
1598
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
 
1599
 
 
1600
    def test_null_revision(self):
 
1601
        # a null revision has the predictable result {}, we should have no wire
 
1602
        # traffic when calling it with this argument
 
1603
        transport_path = 'empty'
 
1604
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1605
        client.add_success_response('notused')
 
1606
        result = self.applyDeprecated(one_four, repo.get_revision_graph,
 
1607
            NULL_REVISION)
 
1608
        self.assertEqual([], client._calls)
 
1609
        self.assertEqual({}, result)
 
1610
 
 
1611
    def test_none_revision(self):
 
1612
        # with none we want the entire graph
 
1613
        r1 = u'\u0e33'.encode('utf8')
 
1614
        r2 = u'\u0dab'.encode('utf8')
 
1615
        lines = [' '.join([r2, r1]), r1]
 
1616
        encoded_body = '\n'.join(lines)
 
1617
 
 
1618
        transport_path = 'sinhala'
 
1619
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1620
        client.add_success_response_with_body(encoded_body, 'ok')
 
1621
        result = self.applyDeprecated(one_four, repo.get_revision_graph)
 
1622
        self.assertEqual(
 
1623
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1624
             ('sinhala/', ''))],
 
1625
            client._calls)
 
1626
        self.assertEqual({r1: (), r2: (r1, )}, result)
 
1627
 
 
1628
    def test_specific_revision(self):
 
1629
        # with a specific revision we want the graph for that
 
1630
        # with none we want the entire graph
 
1631
        r11 = u'\u0e33'.encode('utf8')
 
1632
        r12 = u'\xc9'.encode('utf8')
 
1633
        r2 = u'\u0dab'.encode('utf8')
 
1634
        lines = [' '.join([r2, r11, r12]), r11, r12]
 
1635
        encoded_body = '\n'.join(lines)
 
1636
 
 
1637
        transport_path = 'sinhala'
 
1638
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1639
        client.add_success_response_with_body(encoded_body, 'ok')
 
1640
        result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
 
1641
        self.assertEqual(
 
1642
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1643
             ('sinhala/', r2))],
 
1644
            client._calls)
 
1645
        self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
 
1646
 
 
1647
    def test_no_such_revision(self):
 
1648
        revid = '123'
 
1649
        transport_path = 'sinhala'
 
1650
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1651
        client.add_error_response('nosuchrevision', revid)
 
1652
        # also check that the right revision is reported in the error
 
1653
        self.assertRaises(errors.NoSuchRevision,
 
1654
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
1655
        self.assertEqual(
 
1656
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1657
             ('sinhala/', revid))],
 
1658
            client._calls)
 
1659
 
 
1660
    def test_unexpected_error(self):
 
1661
        revid = '123'
 
1662
        transport_path = 'sinhala'
 
1663
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1664
        client.add_error_response('AnUnexpectedError')
 
1665
        e = self.assertRaises(errors.UnknownErrorFromSmartServer,
 
1666
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
1667
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
 
1668
 
 
1669
 
 
1670
class TestRepositoryIsShared(TestRemoteRepository):
 
1671
 
 
1672
    def test_is_shared(self):
 
1673
        # ('yes', ) for Repository.is_shared -> 'True'.
 
1674
        transport_path = 'quack'
 
1675
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1676
        client.add_success_response('yes')
 
1677
        result = repo.is_shared()
 
1678
        self.assertEqual(
 
1679
            [('call', 'Repository.is_shared', ('quack/',))],
 
1680
            client._calls)
 
1681
        self.assertEqual(True, result)
 
1682
 
 
1683
    def test_is_not_shared(self):
 
1684
        # ('no', ) for Repository.is_shared -> 'False'.
 
1685
        transport_path = 'qwack'
 
1686
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1687
        client.add_success_response('no')
 
1688
        result = repo.is_shared()
 
1689
        self.assertEqual(
 
1690
            [('call', 'Repository.is_shared', ('qwack/',))],
 
1691
            client._calls)
 
1692
        self.assertEqual(False, result)
 
1693
 
 
1694
 
 
1695
class TestRepositoryLockWrite(TestRemoteRepository):
 
1696
 
 
1697
    def test_lock_write(self):
 
1698
        transport_path = 'quack'
 
1699
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1700
        client.add_success_response('ok', 'a token')
 
1701
        result = repo.lock_write()
 
1702
        self.assertEqual(
 
1703
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
1704
            client._calls)
 
1705
        self.assertEqual('a token', result)
 
1706
 
 
1707
    def test_lock_write_already_locked(self):
 
1708
        transport_path = 'quack'
 
1709
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1710
        client.add_error_response('LockContention')
 
1711
        self.assertRaises(errors.LockContention, repo.lock_write)
 
1712
        self.assertEqual(
 
1713
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
1714
            client._calls)
 
1715
 
 
1716
    def test_lock_write_unlockable(self):
 
1717
        transport_path = 'quack'
 
1718
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1719
        client.add_error_response('UnlockableTransport')
 
1720
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
 
1721
        self.assertEqual(
 
1722
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
1723
            client._calls)
 
1724
 
 
1725
 
 
1726
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
 
1727
 
 
1728
    def test_backwards_compat(self):
 
1729
        self.setup_smart_server_with_call_log()
 
1730
        repo = self.make_repository('.')
 
1731
        self.reset_smart_call_log()
 
1732
        verb = 'Repository.set_make_working_trees'
 
1733
        self.disable_verb(verb)
 
1734
        repo.set_make_working_trees(True)
 
1735
        call_count = len([call for call in self.hpss_calls if
 
1736
            call.call.method == verb])
 
1737
        self.assertEqual(1, call_count)
 
1738
 
 
1739
    def test_current(self):
 
1740
        transport_path = 'quack'
 
1741
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1742
        client.add_expected_call(
 
1743
            'Repository.set_make_working_trees', ('quack/', 'True'),
 
1744
            'success', ('ok',))
 
1745
        client.add_expected_call(
 
1746
            'Repository.set_make_working_trees', ('quack/', 'False'),
 
1747
            'success', ('ok',))
 
1748
        repo.set_make_working_trees(True)
 
1749
        repo.set_make_working_trees(False)
 
1750
 
 
1751
 
 
1752
class TestRepositoryUnlock(TestRemoteRepository):
 
1753
 
 
1754
    def test_unlock(self):
 
1755
        transport_path = 'quack'
 
1756
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1757
        client.add_success_response('ok', 'a token')
 
1758
        client.add_success_response('ok')
 
1759
        repo.lock_write()
 
1760
        repo.unlock()
 
1761
        self.assertEqual(
 
1762
            [('call', 'Repository.lock_write', ('quack/', '')),
 
1763
             ('call', 'Repository.unlock', ('quack/', 'a token'))],
 
1764
            client._calls)
 
1765
 
 
1766
    def test_unlock_wrong_token(self):
 
1767
        # If somehow the token is wrong, unlock will raise TokenMismatch.
 
1768
        transport_path = 'quack'
 
1769
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1770
        client.add_success_response('ok', 'a token')
 
1771
        client.add_error_response('TokenMismatch')
 
1772
        repo.lock_write()
 
1773
        self.assertRaises(errors.TokenMismatch, repo.unlock)
 
1774
 
 
1775
 
 
1776
class TestRepositoryHasRevision(TestRemoteRepository):
 
1777
 
 
1778
    def test_none(self):
 
1779
        # repo.has_revision(None) should not cause any traffic.
 
1780
        transport_path = 'quack'
 
1781
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1782
 
 
1783
        # The null revision is always there, so has_revision(None) == True.
 
1784
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
 
1785
 
 
1786
        # The remote repo shouldn't be accessed.
 
1787
        self.assertEqual([], client._calls)
 
1788
 
 
1789
 
 
1790
class TestRepositoryTarball(TestRemoteRepository):
 
1791
 
 
1792
    # This is a canned tarball reponse we can validate against
 
1793
    tarball_content = (
 
1794
        'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
 
1795
        'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
 
1796
        'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
 
1797
        'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
 
1798
        'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
 
1799
        'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
 
1800
        'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
 
1801
        'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
 
1802
        '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
 
1803
        'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
 
1804
        'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
 
1805
        'nWQ7QH/F3JFOFCQ0aSPfA='
 
1806
        ).decode('base64')
 
1807
 
 
1808
    def test_repository_tarball(self):
 
1809
        # Test that Repository.tarball generates the right operations
 
1810
        transport_path = 'repo'
 
1811
        expected_calls = [('call_expecting_body', 'Repository.tarball',
 
1812
                           ('repo/', 'bz2',),),
 
1813
            ]
 
1814
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1815
        client.add_success_response_with_body(self.tarball_content, 'ok')
 
1816
        # Now actually ask for the tarball
 
1817
        tarball_file = repo._get_tarball('bz2')
 
1818
        try:
 
1819
            self.assertEqual(expected_calls, client._calls)
 
1820
            self.assertEqual(self.tarball_content, tarball_file.read())
 
1821
        finally:
 
1822
            tarball_file.close()
 
1823
 
 
1824
 
 
1825
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
 
1826
    """RemoteRepository.copy_content_into optimizations"""
 
1827
 
 
1828
    def test_copy_content_remote_to_local(self):
 
1829
        self.transport_server = server.SmartTCPServer_for_testing
 
1830
        src_repo = self.make_repository('repo1')
 
1831
        src_repo = repository.Repository.open(self.get_url('repo1'))
 
1832
        # At the moment the tarball-based copy_content_into can't write back
 
1833
        # into a smart server.  It would be good if it could upload the
 
1834
        # tarball; once that works we'd have to create repositories of
 
1835
        # different formats. -- mbp 20070410
 
1836
        dest_url = self.get_vfs_only_url('repo2')
 
1837
        dest_bzrdir = BzrDir.create(dest_url)
 
1838
        dest_repo = dest_bzrdir.create_repository()
 
1839
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
 
1840
        self.assertTrue(isinstance(src_repo, RemoteRepository))
 
1841
        src_repo.copy_content_into(dest_repo)
 
1842
 
 
1843
 
 
1844
class _StubRealPackRepository(object):
 
1845
 
 
1846
    def __init__(self, calls):
 
1847
        self._pack_collection = _StubPackCollection(calls)
 
1848
 
 
1849
 
 
1850
class _StubPackCollection(object):
 
1851
 
 
1852
    def __init__(self, calls):
 
1853
        self.calls = calls
 
1854
 
 
1855
    def autopack(self):
 
1856
        self.calls.append(('pack collection autopack',))
 
1857
 
 
1858
    def reload_pack_names(self):
 
1859
        self.calls.append(('pack collection reload_pack_names',))
 
1860
 
 
1861
 
 
1862
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
 
1863
    """Tests for RemoteRepository.autopack implementation."""
 
1864
 
 
1865
    def test_ok(self):
 
1866
        """When the server returns 'ok' and there's no _real_repository, then
 
1867
        nothing else happens: the autopack method is done.
 
1868
        """
 
1869
        transport_path = 'quack'
 
1870
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1871
        client.add_expected_call(
 
1872
            'PackRepository.autopack', ('quack/',), 'success', ('ok',))
 
1873
        repo.autopack()
 
1874
        client.finished_test()
 
1875
 
 
1876
    def test_ok_with_real_repo(self):
 
1877
        """When the server returns 'ok' and there is a _real_repository, then
 
1878
        the _real_repository's reload_pack_name's method will be called.
 
1879
        """
 
1880
        transport_path = 'quack'
 
1881
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1882
        client.add_expected_call(
 
1883
            'PackRepository.autopack', ('quack/',),
 
1884
            'success', ('ok',))
 
1885
        repo._real_repository = _StubRealPackRepository(client._calls)
 
1886
        repo.autopack()
 
1887
        self.assertEqual(
 
1888
            [('call', 'PackRepository.autopack', ('quack/',)),
 
1889
             ('pack collection reload_pack_names',)],
 
1890
            client._calls)
 
1891
 
 
1892
    def test_backwards_compatibility(self):
 
1893
        """If the server does not recognise the PackRepository.autopack verb,
 
1894
        fallback to the real_repository's implementation.
 
1895
        """
 
1896
        transport_path = 'quack'
 
1897
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1898
        client.add_unknown_method_response('PackRepository.autopack')
 
1899
        def stub_ensure_real():
 
1900
            client._calls.append(('_ensure_real',))
 
1901
            repo._real_repository = _StubRealPackRepository(client._calls)
 
1902
        repo._ensure_real = stub_ensure_real
 
1903
        repo.autopack()
 
1904
        self.assertEqual(
 
1905
            [('call', 'PackRepository.autopack', ('quack/',)),
 
1906
             ('_ensure_real',),
 
1907
             ('pack collection autopack',)],
 
1908
            client._calls)
 
1909
 
 
1910
 
 
1911
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
 
1912
    """Base class for unit tests for bzrlib.remote._translate_error."""
 
1913
 
 
1914
    def translateTuple(self, error_tuple, **context):
 
1915
        """Call _translate_error with an ErrorFromSmartServer built from the
 
1916
        given error_tuple.
 
1917
 
 
1918
        :param error_tuple: A tuple of a smart server response, as would be
 
1919
            passed to an ErrorFromSmartServer.
 
1920
        :kwargs context: context items to call _translate_error with.
 
1921
 
 
1922
        :returns: The error raised by _translate_error.
 
1923
        """
 
1924
        # Raise the ErrorFromSmartServer before passing it as an argument,
 
1925
        # because _translate_error may need to re-raise it with a bare 'raise'
 
1926
        # statement.
 
1927
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
1928
        translated_error = self.translateErrorFromSmartServer(
 
1929
            server_error, **context)
 
1930
        return translated_error
 
1931
 
 
1932
    def translateErrorFromSmartServer(self, error_object, **context):
 
1933
        """Like translateTuple, but takes an already constructed
 
1934
        ErrorFromSmartServer rather than a tuple.
 
1935
        """
 
1936
        try:
 
1937
            raise error_object
 
1938
        except errors.ErrorFromSmartServer, server_error:
 
1939
            translated_error = self.assertRaises(
 
1940
                errors.BzrError, remote._translate_error, server_error,
 
1941
                **context)
 
1942
        return translated_error
 
1943
 
 
1944
 
 
1945
class TestErrorTranslationSuccess(TestErrorTranslationBase):
 
1946
    """Unit tests for bzrlib.remote._translate_error.
 
1947
 
 
1948
    Given an ErrorFromSmartServer (which has an error tuple from a smart
 
1949
    server) and some context, _translate_error raises more specific errors from
 
1950
    bzrlib.errors.
 
1951
 
 
1952
    This test case covers the cases where _translate_error succeeds in
 
1953
    translating an ErrorFromSmartServer to something better.  See
 
1954
    TestErrorTranslationRobustness for other cases.
 
1955
    """
 
1956
 
 
1957
    def test_NoSuchRevision(self):
 
1958
        branch = self.make_branch('')
 
1959
        revid = 'revid'
 
1960
        translated_error = self.translateTuple(
 
1961
            ('NoSuchRevision', revid), branch=branch)
 
1962
        expected_error = errors.NoSuchRevision(branch, revid)
 
1963
        self.assertEqual(expected_error, translated_error)
 
1964
 
 
1965
    def test_nosuchrevision(self):
 
1966
        repository = self.make_repository('')
 
1967
        revid = 'revid'
 
1968
        translated_error = self.translateTuple(
 
1969
            ('nosuchrevision', revid), repository=repository)
 
1970
        expected_error = errors.NoSuchRevision(repository, revid)
 
1971
        self.assertEqual(expected_error, translated_error)
 
1972
 
 
1973
    def test_nobranch(self):
 
1974
        bzrdir = self.make_bzrdir('')
 
1975
        translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
 
1976
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
 
1977
        self.assertEqual(expected_error, translated_error)
 
1978
 
 
1979
    def test_LockContention(self):
 
1980
        translated_error = self.translateTuple(('LockContention',))
 
1981
        expected_error = errors.LockContention('(remote lock)')
 
1982
        self.assertEqual(expected_error, translated_error)
 
1983
 
 
1984
    def test_UnlockableTransport(self):
 
1985
        bzrdir = self.make_bzrdir('')
 
1986
        translated_error = self.translateTuple(
 
1987
            ('UnlockableTransport',), bzrdir=bzrdir)
 
1988
        expected_error = errors.UnlockableTransport(bzrdir.root_transport)
 
1989
        self.assertEqual(expected_error, translated_error)
 
1990
 
 
1991
    def test_LockFailed(self):
 
1992
        lock = 'str() of a server lock'
 
1993
        why = 'str() of why'
 
1994
        translated_error = self.translateTuple(('LockFailed', lock, why))
 
1995
        expected_error = errors.LockFailed(lock, why)
 
1996
        self.assertEqual(expected_error, translated_error)
 
1997
 
 
1998
    def test_TokenMismatch(self):
 
1999
        token = 'a lock token'
 
2000
        translated_error = self.translateTuple(('TokenMismatch',), token=token)
 
2001
        expected_error = errors.TokenMismatch(token, '(remote token)')
 
2002
        self.assertEqual(expected_error, translated_error)
 
2003
 
 
2004
    def test_Diverged(self):
 
2005
        branch = self.make_branch('a')
 
2006
        other_branch = self.make_branch('b')
 
2007
        translated_error = self.translateTuple(
 
2008
            ('Diverged',), branch=branch, other_branch=other_branch)
 
2009
        expected_error = errors.DivergedBranches(branch, other_branch)
 
2010
        self.assertEqual(expected_error, translated_error)
 
2011
 
 
2012
    def test_ReadError_no_args(self):
 
2013
        path = 'a path'
 
2014
        translated_error = self.translateTuple(('ReadError',), path=path)
 
2015
        expected_error = errors.ReadError(path)
 
2016
        self.assertEqual(expected_error, translated_error)
 
2017
 
 
2018
    def test_ReadError(self):
 
2019
        path = 'a path'
 
2020
        translated_error = self.translateTuple(('ReadError', path))
 
2021
        expected_error = errors.ReadError(path)
 
2022
        self.assertEqual(expected_error, translated_error)
 
2023
 
 
2024
    def test_PermissionDenied_no_args(self):
 
2025
        path = 'a path'
 
2026
        translated_error = self.translateTuple(('PermissionDenied',), path=path)
 
2027
        expected_error = errors.PermissionDenied(path)
 
2028
        self.assertEqual(expected_error, translated_error)
 
2029
 
 
2030
    def test_PermissionDenied_one_arg(self):
 
2031
        path = 'a path'
 
2032
        translated_error = self.translateTuple(('PermissionDenied', path))
 
2033
        expected_error = errors.PermissionDenied(path)
 
2034
        self.assertEqual(expected_error, translated_error)
 
2035
 
 
2036
    def test_PermissionDenied_one_arg_and_context(self):
 
2037
        """Given a choice between a path from the local context and a path on
 
2038
        the wire, _translate_error prefers the path from the local context.
 
2039
        """
 
2040
        local_path = 'local path'
 
2041
        remote_path = 'remote path'
 
2042
        translated_error = self.translateTuple(
 
2043
            ('PermissionDenied', remote_path), path=local_path)
 
2044
        expected_error = errors.PermissionDenied(local_path)
 
2045
        self.assertEqual(expected_error, translated_error)
 
2046
 
 
2047
    def test_PermissionDenied_two_args(self):
 
2048
        path = 'a path'
 
2049
        extra = 'a string with extra info'
 
2050
        translated_error = self.translateTuple(
 
2051
            ('PermissionDenied', path, extra))
 
2052
        expected_error = errors.PermissionDenied(path, extra)
 
2053
        self.assertEqual(expected_error, translated_error)
 
2054
 
 
2055
 
 
2056
class TestErrorTranslationRobustness(TestErrorTranslationBase):
 
2057
    """Unit tests for bzrlib.remote._translate_error's robustness.
 
2058
 
 
2059
    TestErrorTranslationSuccess is for cases where _translate_error can
 
2060
    translate successfully.  This class about how _translate_err behaves when
 
2061
    it fails to translate: it re-raises the original error.
 
2062
    """
 
2063
 
 
2064
    def test_unrecognised_server_error(self):
 
2065
        """If the error code from the server is not recognised, the original
 
2066
        ErrorFromSmartServer is propagated unmodified.
 
2067
        """
 
2068
        error_tuple = ('An unknown error tuple',)
 
2069
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2070
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2071
        expected_error = errors.UnknownErrorFromSmartServer(server_error)
 
2072
        self.assertEqual(expected_error, translated_error)
 
2073
 
 
2074
    def test_context_missing_a_key(self):
 
2075
        """In case of a bug in the client, or perhaps an unexpected response
 
2076
        from a server, _translate_error returns the original error tuple from
 
2077
        the server and mutters a warning.
 
2078
        """
 
2079
        # To translate a NoSuchRevision error _translate_error needs a 'branch'
 
2080
        # in the context dict.  So let's give it an empty context dict instead
 
2081
        # to exercise its error recovery.
 
2082
        empty_context = {}
 
2083
        error_tuple = ('NoSuchRevision', 'revid')
 
2084
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2085
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2086
        self.assertEqual(server_error, translated_error)
 
2087
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
2088
        # been muttered to the log file for developer to look at.
 
2089
        self.assertContainsRe(
 
2090
            self._get_log(keep_log_file=True),
 
2091
            "Missing key 'branch' in context")
 
2092
 
 
2093
    def test_path_missing(self):
 
2094
        """Some translations (PermissionDenied, ReadError) can determine the
 
2095
        'path' variable from either the wire or the local context.  If neither
 
2096
        has it, then an error is raised.
 
2097
        """
 
2098
        error_tuple = ('ReadError',)
 
2099
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2100
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2101
        self.assertEqual(server_error, translated_error)
 
2102
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
2103
        # been muttered to the log file for developer to look at.
 
2104
        self.assertContainsRe(
 
2105
            self._get_log(keep_log_file=True), "Missing key 'path' in context")
 
2106
 
 
2107
 
 
2108
class TestStacking(tests.TestCaseWithTransport):
 
2109
    """Tests for operations on stacked remote repositories.
 
2110
 
 
2111
    The underlying format type must support stacking.
 
2112
    """
 
2113
 
 
2114
    def test_access_stacked_remote(self):
 
2115
        # based on <http://launchpad.net/bugs/261315>
 
2116
        # make a branch stacked on another repository containing an empty
 
2117
        # revision, then open it over hpss - we should be able to see that
 
2118
        # revision.
 
2119
        base_transport = self.get_transport()
 
2120
        base_builder = self.make_branch_builder('base', format='1.6')
 
2121
        base_builder.start_series()
 
2122
        base_revid = base_builder.build_snapshot('rev-id', None,
 
2123
            [('add', ('', None, 'directory', None))],
 
2124
            'message')
 
2125
        base_builder.finish_series()
 
2126
        stacked_branch = self.make_branch('stacked', format='1.6')
 
2127
        stacked_branch.set_stacked_on_url('../base')
 
2128
        # start a server looking at this
 
2129
        smart_server = server.SmartTCPServer_for_testing()
 
2130
        smart_server.setUp()
 
2131
        self.addCleanup(smart_server.tearDown)
 
2132
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
 
2133
        # can get its branch and repository
 
2134
        remote_branch = remote_bzrdir.open_branch()
 
2135
        remote_repo = remote_branch.repository
 
2136
        remote_repo.lock_read()
 
2137
        try:
 
2138
            # it should have an appropriate fallback repository, which should also
 
2139
            # be a RemoteRepository
 
2140
            self.assertEquals(len(remote_repo._fallback_repositories), 1)
 
2141
            self.assertIsInstance(remote_repo._fallback_repositories[0],
 
2142
                RemoteRepository)
 
2143
            # and it has the revision committed to the underlying repository;
 
2144
            # these have varying implementations so we try several of them
 
2145
            self.assertTrue(remote_repo.has_revisions([base_revid]))
 
2146
            self.assertTrue(remote_repo.has_revision(base_revid))
 
2147
            self.assertEqual(remote_repo.get_revision(base_revid).message,
 
2148
                'message')
 
2149
        finally:
 
2150
            remote_repo.unlock()
 
2151
 
 
2152
    def prepare_stacked_remote_branch(self):
 
2153
        smart_server = server.SmartTCPServer_for_testing()
 
2154
        smart_server.setUp()
 
2155
        self.addCleanup(smart_server.tearDown)
 
2156
        tree1 = self.make_branch_and_tree('tree1')
 
2157
        tree1.commit('rev1', rev_id='rev1')
 
2158
        tree2 = self.make_branch_and_tree('tree2', format='1.6')
 
2159
        tree2.branch.set_stacked_on_url(tree1.branch.base)
 
2160
        branch2 = Branch.open(smart_server.get_url() + '/tree2')
 
2161
        branch2.lock_read()
 
2162
        self.addCleanup(branch2.unlock)
 
2163
        return branch2
 
2164
 
 
2165
    def test_stacked_get_parent_map(self):
 
2166
        # the public implementation of get_parent_map obeys stacking
 
2167
        branch = self.prepare_stacked_remote_branch()
 
2168
        repo = branch.repository
 
2169
        self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
 
2170
 
 
2171
    def test_unstacked_get_parent_map(self):
 
2172
        # _unstacked_provider.get_parent_map ignores stacking
 
2173
        branch = self.prepare_stacked_remote_branch()
 
2174
        provider = branch.repository._unstacked_provider
 
2175
        self.assertEqual([], provider.get_parent_map(['rev1']).keys())
 
2176
 
 
2177
 
 
2178
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
 
2179
 
 
2180
    def setUp(self):
 
2181
        super(TestRemoteBranchEffort, self).setUp()
 
2182
        # Create a smart server that publishes whatever the backing VFS server
 
2183
        # does.
 
2184
        self.smart_server = server.SmartTCPServer_for_testing()
 
2185
        self.smart_server.setUp(self.get_server())
 
2186
        self.addCleanup(self.smart_server.tearDown)
 
2187
        # Log all HPSS calls into self.hpss_calls.
 
2188
        _SmartClient.hooks.install_named_hook(
 
2189
            'call', self.capture_hpss_call, None)
 
2190
        self.hpss_calls = []
 
2191
 
 
2192
    def capture_hpss_call(self, params):
 
2193
        self.hpss_calls.append(params.method)
 
2194
 
 
2195
    def test_copy_content_into_avoids_revision_history(self):
 
2196
        local = self.make_branch('local')
 
2197
        remote_backing_tree = self.make_branch_and_tree('remote')
 
2198
        remote_backing_tree.commit("Commit.")
 
2199
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
2200
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
2201
        local.repository.fetch(remote_branch.repository)
 
2202
        self.hpss_calls = []
 
2203
        remote_branch.copy_content_into(local)
 
2204
        self.assertFalse('Branch.revision_history' in self.hpss_calls)