/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Jelmer Vernooij
  • Date: 2009-03-12 14:02:53 UTC
  • mfrom: (4135 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4137.
  • Revision ID: jelmer@samba.org-20090312140253-bmldbzlmsitfdrzf
Merge bzr.dev.

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