/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: Ian Clatworthy
  • Date: 2009-02-26 06:15:24 UTC
  • mto: (4157.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 4158.
  • Revision ID: ian.clatworthy@canonical.com-20090226061524-kpy3n8na3mk4ubuy
help xxx is full help; xxx -h is concise help

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