~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Martin Pool
  • Date: 2009-06-19 10:00:56 UTC
  • mto: This revision was merged to the branch mainline in revision 4464.
  • Revision ID: mbp@sourcefrog.net-20090619100056-fco5ooae2ybl88ne
Fix copyrights and remove assert statement from doc_generate

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007, 2008, 2009 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
    treebuilder,
 
40
    urlutils,
 
41
    )
 
42
from bzrlib.branch import Branch
 
43
from bzrlib.bzrdir import BzrDir, BzrDirFormat
 
44
from bzrlib.remote import (
 
45
    RemoteBranch,
 
46
    RemoteBranchFormat,
 
47
    RemoteBzrDir,
 
48
    RemoteBzrDirFormat,
 
49
    RemoteRepository,
 
50
    RemoteRepositoryFormat,
 
51
    )
 
52
from bzrlib.repofmt import groupcompress_repo, pack_repo
 
53
from bzrlib.revision import NULL_REVISION
 
54
from bzrlib.smart import server, medium
 
55
from bzrlib.smart.client import _SmartClient
 
56
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
 
57
from bzrlib.tests import (
 
58
    condition_isinstance,
 
59
    split_suite_by_condition,
 
60
    multiply_tests,
 
61
    KnownFailure,
 
62
    )
 
63
from bzrlib.transport import get_transport, http
 
64
from bzrlib.transport.memory import MemoryTransport
 
65
from bzrlib.transport.remote import (
 
66
    RemoteTransport,
 
67
    RemoteSSHTransport,
 
68
    RemoteTCPTransport,
 
69
)
 
70
 
 
71
def load_tests(standard_tests, module, loader):
 
72
    to_adapt, result = split_suite_by_condition(
 
73
        standard_tests, condition_isinstance(BasicRemoteObjectTests))
 
74
    smart_server_version_scenarios = [
 
75
        ('HPSS-v2',
 
76
            {'transport_server': server.SmartTCPServer_for_testing_v2_only}),
 
77
        ('HPSS-v3',
 
78
            {'transport_server': server.SmartTCPServer_for_testing})]
 
79
    return multiply_tests(to_adapt, smart_server_version_scenarios, result)
 
80
 
 
81
 
 
82
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
 
83
 
 
84
    def setUp(self):
 
85
        super(BasicRemoteObjectTests, self).setUp()
 
86
        self.transport = self.get_transport()
 
87
        # make a branch that can be opened over the smart transport
 
88
        self.local_wt = BzrDir.create_standalone_workingtree('.')
 
89
 
 
90
    def tearDown(self):
 
91
        self.transport.disconnect()
 
92
        tests.TestCaseWithTransport.tearDown(self)
 
93
 
 
94
    def test_create_remote_bzrdir(self):
 
95
        b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
 
96
        self.assertIsInstance(b, BzrDir)
 
97
 
 
98
    def test_open_remote_branch(self):
 
99
        # open a standalone branch in the working directory
 
100
        b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
 
101
        branch = b.open_branch()
 
102
        self.assertIsInstance(branch, Branch)
 
103
 
 
104
    def test_remote_repository(self):
 
105
        b = BzrDir.open_from_transport(self.transport)
 
106
        repo = b.open_repository()
 
107
        revid = u'\xc823123123'.encode('utf8')
 
108
        self.assertFalse(repo.has_revision(revid))
 
109
        self.local_wt.commit(message='test commit', rev_id=revid)
 
110
        self.assertTrue(repo.has_revision(revid))
 
111
 
 
112
    def test_remote_branch_revision_history(self):
 
113
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
114
        self.assertEqual([], b.revision_history())
 
115
        r1 = self.local_wt.commit('1st commit')
 
116
        r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
 
117
        self.assertEqual([r1, r2], b.revision_history())
 
118
 
 
119
    def test_find_correct_format(self):
 
120
        """Should open a RemoteBzrDir over a RemoteTransport"""
 
121
        fmt = BzrDirFormat.find_format(self.transport)
 
122
        self.assertTrue(RemoteBzrDirFormat
 
123
                        in BzrDirFormat._control_server_formats)
 
124
        self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
 
125
 
 
126
    def test_open_detected_smart_format(self):
 
127
        fmt = BzrDirFormat.find_format(self.transport)
 
128
        d = fmt.open(self.transport)
 
129
        self.assertIsInstance(d, BzrDir)
 
130
 
 
131
    def test_remote_branch_repr(self):
 
132
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
133
        self.assertStartsWith(str(b), 'RemoteBranch(')
 
134
 
 
135
    def test_remote_branch_format_supports_stacking(self):
 
136
        t = self.transport
 
137
        self.make_branch('unstackable', format='pack-0.92')
 
138
        b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
 
139
        self.assertFalse(b._format.supports_stacking())
 
140
        self.make_branch('stackable', format='1.9')
 
141
        b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
 
142
        self.assertTrue(b._format.supports_stacking())
 
143
 
 
144
    def test_remote_repo_format_supports_external_references(self):
 
145
        t = self.transport
 
146
        bd = self.make_bzrdir('unstackable', format='pack-0.92')
 
147
        r = bd.create_repository()
 
148
        self.assertFalse(r._format.supports_external_lookups)
 
149
        r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
 
150
        self.assertFalse(r._format.supports_external_lookups)
 
151
        bd = self.make_bzrdir('stackable', format='1.9')
 
152
        r = bd.create_repository()
 
153
        self.assertTrue(r._format.supports_external_lookups)
 
154
        r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
 
155
        self.assertTrue(r._format.supports_external_lookups)
 
156
 
 
157
    def test_remote_branch_set_append_revisions_only(self):
 
158
        # Make a format 1.9 branch, which supports append_revisions_only
 
159
        branch = self.make_branch('branch', format='1.9')
 
160
        config = branch.get_config()
 
161
        branch.set_append_revisions_only(True)
 
162
        self.assertEqual(
 
163
            'True', config.get_user_option('append_revisions_only'))
 
164
        branch.set_append_revisions_only(False)
 
165
        self.assertEqual(
 
166
            'False', config.get_user_option('append_revisions_only'))
 
167
 
 
168
    def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
 
169
        branch = self.make_branch('branch', format='knit')
 
170
        config = branch.get_config()
 
171
        self.assertRaises(
 
172
            errors.UpgradeRequired, branch.set_append_revisions_only, True)
 
173
 
 
174
 
 
175
class FakeProtocol(object):
 
176
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
 
177
 
 
178
    def __init__(self, body, fake_client):
 
179
        self.body = body
 
180
        self._body_buffer = None
 
181
        self._fake_client = fake_client
 
182
 
 
183
    def read_body_bytes(self, count=-1):
 
184
        if self._body_buffer is None:
 
185
            self._body_buffer = StringIO(self.body)
 
186
        bytes = self._body_buffer.read(count)
 
187
        if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
 
188
            self._fake_client.expecting_body = False
 
189
        return bytes
 
190
 
 
191
    def cancel_read_body(self):
 
192
        self._fake_client.expecting_body = False
 
193
 
 
194
    def read_streamed_body(self):
 
195
        return self.body
 
196
 
 
197
 
 
198
class FakeClient(_SmartClient):
 
199
    """Lookalike for _SmartClient allowing testing."""
 
200
 
 
201
    def __init__(self, fake_medium_base='fake base'):
 
202
        """Create a FakeClient."""
 
203
        self.responses = []
 
204
        self._calls = []
 
205
        self.expecting_body = False
 
206
        # if non-None, this is the list of expected calls, with only the
 
207
        # method name and arguments included.  the body might be hard to
 
208
        # compute so is not included. If a call is None, that call can
 
209
        # be anything.
 
210
        self._expected_calls = None
 
211
        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
 
212
 
 
213
    def add_expected_call(self, call_name, call_args, response_type,
 
214
        response_args, response_body=None):
 
215
        if self._expected_calls is None:
 
216
            self._expected_calls = []
 
217
        self._expected_calls.append((call_name, call_args))
 
218
        self.responses.append((response_type, response_args, response_body))
 
219
 
 
220
    def add_success_response(self, *args):
 
221
        self.responses.append(('success', args, None))
 
222
 
 
223
    def add_success_response_with_body(self, body, *args):
 
224
        self.responses.append(('success', args, body))
 
225
        if self._expected_calls is not None:
 
226
            self._expected_calls.append(None)
 
227
 
 
228
    def add_error_response(self, *args):
 
229
        self.responses.append(('error', args))
 
230
 
 
231
    def add_unknown_method_response(self, verb):
 
232
        self.responses.append(('unknown', verb))
 
233
 
 
234
    def finished_test(self):
 
235
        if self._expected_calls:
 
236
            raise AssertionError("%r finished but was still expecting %r"
 
237
                % (self, self._expected_calls[0]))
 
238
 
 
239
    def _get_next_response(self):
 
240
        try:
 
241
            response_tuple = self.responses.pop(0)
 
242
        except IndexError, e:
 
243
            raise AssertionError("%r didn't expect any more calls"
 
244
                % (self,))
 
245
        if response_tuple[0] == 'unknown':
 
246
            raise errors.UnknownSmartMethod(response_tuple[1])
 
247
        elif response_tuple[0] == 'error':
 
248
            raise errors.ErrorFromSmartServer(response_tuple[1])
 
249
        return response_tuple
 
250
 
 
251
    def _check_call(self, method, args):
 
252
        if self._expected_calls is None:
 
253
            # the test should be updated to say what it expects
 
254
            return
 
255
        try:
 
256
            next_call = self._expected_calls.pop(0)
 
257
        except IndexError:
 
258
            raise AssertionError("%r didn't expect any more calls "
 
259
                "but got %r%r"
 
260
                % (self, method, args,))
 
261
        if next_call is None:
 
262
            return
 
263
        if method != next_call[0] or args != next_call[1]:
 
264
            raise AssertionError("%r expected %r%r "
 
265
                "but got %r%r"
 
266
                % (self, next_call[0], next_call[1], method, args,))
 
267
 
 
268
    def call(self, method, *args):
 
269
        self._check_call(method, args)
 
270
        self._calls.append(('call', method, args))
 
271
        return self._get_next_response()[1]
 
272
 
 
273
    def call_expecting_body(self, method, *args):
 
274
        self._check_call(method, args)
 
275
        self._calls.append(('call_expecting_body', method, args))
 
276
        result = self._get_next_response()
 
277
        self.expecting_body = True
 
278
        return result[1], FakeProtocol(result[2], self)
 
279
 
 
280
    def call_with_body_bytes_expecting_body(self, method, args, body):
 
281
        self._check_call(method, args)
 
282
        self._calls.append(('call_with_body_bytes_expecting_body', method,
 
283
            args, body))
 
284
        result = self._get_next_response()
 
285
        self.expecting_body = True
 
286
        return result[1], FakeProtocol(result[2], self)
 
287
 
 
288
    def call_with_body_stream(self, args, stream):
 
289
        # Explicitly consume the stream before checking for an error, because
 
290
        # that's what happens a real medium.
 
291
        stream = list(stream)
 
292
        self._check_call(args[0], args[1:])
 
293
        self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
 
294
        result = self._get_next_response()
 
295
        # The second value returned from call_with_body_stream is supposed to
 
296
        # be a response_handler object, but so far no tests depend on that.
 
297
        response_handler = None 
 
298
        return result[1], response_handler
 
299
 
 
300
 
 
301
class FakeMedium(medium.SmartClientMedium):
 
302
 
 
303
    def __init__(self, client_calls, base):
 
304
        medium.SmartClientMedium.__init__(self, base)
 
305
        self._client_calls = client_calls
 
306
 
 
307
    def disconnect(self):
 
308
        self._client_calls.append(('disconnect medium',))
 
309
 
 
310
 
 
311
class TestVfsHas(tests.TestCase):
 
312
 
 
313
    def test_unicode_path(self):
 
314
        client = FakeClient('/')
 
315
        client.add_success_response('yes',)
 
316
        transport = RemoteTransport('bzr://localhost/', _client=client)
 
317
        filename = u'/hell\u00d8'.encode('utf8')
 
318
        result = transport.has(filename)
 
319
        self.assertEqual(
 
320
            [('call', 'has', (filename,))],
 
321
            client._calls)
 
322
        self.assertTrue(result)
 
323
 
 
324
 
 
325
class TestRemote(tests.TestCaseWithMemoryTransport):
 
326
 
 
327
    def get_branch_format(self):
 
328
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
329
        return reference_bzrdir_format.get_branch_format()
 
330
 
 
331
    def get_repo_format(self):
 
332
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
333
        return reference_bzrdir_format.repository_format
 
334
 
 
335
    def disable_verb(self, verb):
 
336
        """Disable a verb for one test."""
 
337
        request_handlers = smart.request.request_handlers
 
338
        orig_method = request_handlers.get(verb)
 
339
        request_handlers.remove(verb)
 
340
        def restoreVerb():
 
341
            request_handlers.register(verb, orig_method)
 
342
        self.addCleanup(restoreVerb)
 
343
 
 
344
 
 
345
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
 
346
    """Tests for the behaviour of client_medium.remote_path_from_transport."""
 
347
 
 
348
    def assertRemotePath(self, expected, client_base, transport_base):
 
349
        """Assert that the result of
 
350
        SmartClientMedium.remote_path_from_transport is the expected value for
 
351
        a given client_base and transport_base.
 
352
        """
 
353
        client_medium = medium.SmartClientMedium(client_base)
 
354
        transport = get_transport(transport_base)
 
355
        result = client_medium.remote_path_from_transport(transport)
 
356
        self.assertEqual(expected, result)
 
357
 
 
358
    def test_remote_path_from_transport(self):
 
359
        """SmartClientMedium.remote_path_from_transport calculates a URL for
 
360
        the given transport relative to the root of the client base URL.
 
361
        """
 
362
        self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
 
363
        self.assertRemotePath(
 
364
            'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
 
365
 
 
366
    def assertRemotePathHTTP(self, expected, transport_base, relpath):
 
367
        """Assert that the result of
 
368
        HttpTransportBase.remote_path_from_transport is the expected value for
 
369
        a given transport_base and relpath of that transport.  (Note that
 
370
        HttpTransportBase is a subclass of SmartClientMedium)
 
371
        """
 
372
        base_transport = get_transport(transport_base)
 
373
        client_medium = base_transport.get_smart_medium()
 
374
        cloned_transport = base_transport.clone(relpath)
 
375
        result = client_medium.remote_path_from_transport(cloned_transport)
 
376
        self.assertEqual(expected, result)
 
377
 
 
378
    def test_remote_path_from_transport_http(self):
 
379
        """Remote paths for HTTP transports are calculated differently to other
 
380
        transports.  They are just relative to the client base, not the root
 
381
        directory of the host.
 
382
        """
 
383
        for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
 
384
            self.assertRemotePathHTTP(
 
385
                '../xyz/', scheme + '//host/path', '../xyz/')
 
386
            self.assertRemotePathHTTP(
 
387
                'xyz/', scheme + '//host/path', 'xyz/')
 
388
 
 
389
 
 
390
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
 
391
    """Tests for the behaviour of client_medium.remote_is_at_least."""
 
392
 
 
393
    def test_initially_unlimited(self):
 
394
        """A fresh medium assumes that the remote side supports all
 
395
        versions.
 
396
        """
 
397
        client_medium = medium.SmartClientMedium('dummy base')
 
398
        self.assertFalse(client_medium._is_remote_before((99, 99)))
 
399
 
 
400
    def test__remember_remote_is_before(self):
 
401
        """Calling _remember_remote_is_before ratchets down the known remote
 
402
        version.
 
403
        """
 
404
        client_medium = medium.SmartClientMedium('dummy base')
 
405
        # Mark the remote side as being less than 1.6.  The remote side may
 
406
        # still be 1.5.
 
407
        client_medium._remember_remote_is_before((1, 6))
 
408
        self.assertTrue(client_medium._is_remote_before((1, 6)))
 
409
        self.assertFalse(client_medium._is_remote_before((1, 5)))
 
410
        # Calling _remember_remote_is_before again with a lower value works.
 
411
        client_medium._remember_remote_is_before((1, 5))
 
412
        self.assertTrue(client_medium._is_remote_before((1, 5)))
 
413
        # You cannot call _remember_remote_is_before with a larger value.
 
414
        self.assertRaises(
 
415
            AssertionError, client_medium._remember_remote_is_before, (1, 9))
 
416
 
 
417
 
 
418
class TestBzrDirCloningMetaDir(TestRemote):
 
419
 
 
420
    def test_backwards_compat(self):
 
421
        self.setup_smart_server_with_call_log()
 
422
        a_dir = self.make_bzrdir('.')
 
423
        self.reset_smart_call_log()
 
424
        verb = 'BzrDir.cloning_metadir'
 
425
        self.disable_verb(verb)
 
426
        format = a_dir.cloning_metadir()
 
427
        call_count = len([call for call in self.hpss_calls if
 
428
            call.call.method == verb])
 
429
        self.assertEqual(1, call_count)
 
430
 
 
431
    def test_branch_reference(self):
 
432
        transport = self.get_transport('quack')
 
433
        referenced = self.make_branch('referenced')
 
434
        expected = referenced.bzrdir.cloning_metadir()
 
435
        client = FakeClient(transport.base)
 
436
        client.add_expected_call(
 
437
            'BzrDir.cloning_metadir', ('quack/', 'False'),
 
438
            'error', ('BranchReference',)),
 
439
        client.add_expected_call(
 
440
            'BzrDir.open_branchV2', ('quack/',),
 
441
            'success', ('ref', self.get_url('referenced'))),
 
442
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
443
            _client=client)
 
444
        result = a_bzrdir.cloning_metadir()
 
445
        # We should have got a control dir matching the referenced branch.
 
446
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
 
447
        self.assertEqual(expected._repository_format, result._repository_format)
 
448
        self.assertEqual(expected._branch_format, result._branch_format)
 
449
        client.finished_test()
 
450
 
 
451
    def test_current_server(self):
 
452
        transport = self.get_transport('.')
 
453
        transport = transport.clone('quack')
 
454
        self.make_bzrdir('quack')
 
455
        client = FakeClient(transport.base)
 
456
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
457
        control_name = reference_bzrdir_format.network_name()
 
458
        client.add_expected_call(
 
459
            'BzrDir.cloning_metadir', ('quack/', 'False'),
 
460
            'success', (control_name, '', ('branch', ''))),
 
461
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
462
            _client=client)
 
463
        result = a_bzrdir.cloning_metadir()
 
464
        # We should have got a reference control dir with default branch and
 
465
        # repository formats.
 
466
        # This pokes a little, just to be sure.
 
467
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
 
468
        self.assertEqual(None, result._repository_format)
 
469
        self.assertEqual(None, result._branch_format)
 
470
        client.finished_test()
 
471
 
 
472
 
 
473
class TestBzrDirOpenBranch(TestRemote):
 
474
 
 
475
    def test_backwards_compat(self):
 
476
        self.setup_smart_server_with_call_log()
 
477
        self.make_branch('.')
 
478
        a_dir = BzrDir.open(self.get_url('.'))
 
479
        self.reset_smart_call_log()
 
480
        verb = 'BzrDir.open_branchV2'
 
481
        self.disable_verb(verb)
 
482
        format = a_dir.open_branch()
 
483
        call_count = len([call for call in self.hpss_calls if
 
484
            call.call.method == verb])
 
485
        self.assertEqual(1, call_count)
 
486
 
 
487
    def test_branch_present(self):
 
488
        reference_format = self.get_repo_format()
 
489
        network_name = reference_format.network_name()
 
490
        branch_network_name = self.get_branch_format().network_name()
 
491
        transport = MemoryTransport()
 
492
        transport.mkdir('quack')
 
493
        transport = transport.clone('quack')
 
494
        client = FakeClient(transport.base)
 
495
        client.add_expected_call(
 
496
            'BzrDir.open_branchV2', ('quack/',),
 
497
            'success', ('branch', branch_network_name))
 
498
        client.add_expected_call(
 
499
            'BzrDir.find_repositoryV3', ('quack/',),
 
500
            'success', ('ok', '', 'no', 'no', 'no', network_name))
 
501
        client.add_expected_call(
 
502
            'Branch.get_stacked_on_url', ('quack/',),
 
503
            'error', ('NotStacked',))
 
504
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
505
            _client=client)
 
506
        result = bzrdir.open_branch()
 
507
        self.assertIsInstance(result, RemoteBranch)
 
508
        self.assertEqual(bzrdir, result.bzrdir)
 
509
        client.finished_test()
 
510
 
 
511
    def test_branch_missing(self):
 
512
        transport = MemoryTransport()
 
513
        transport.mkdir('quack')
 
514
        transport = transport.clone('quack')
 
515
        client = FakeClient(transport.base)
 
516
        client.add_error_response('nobranch')
 
517
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
518
            _client=client)
 
519
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
 
520
        self.assertEqual(
 
521
            [('call', 'BzrDir.open_branchV2', ('quack/',))],
 
522
            client._calls)
 
523
 
 
524
    def test__get_tree_branch(self):
 
525
        # _get_tree_branch is a form of open_branch, but it should only ask for
 
526
        # branch opening, not any other network requests.
 
527
        calls = []
 
528
        def open_branch():
 
529
            calls.append("Called")
 
530
            return "a-branch"
 
531
        transport = MemoryTransport()
 
532
        # no requests on the network - catches other api calls being made.
 
533
        client = FakeClient(transport.base)
 
534
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
535
            _client=client)
 
536
        # patch the open_branch call to record that it was called.
 
537
        bzrdir.open_branch = open_branch
 
538
        self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
 
539
        self.assertEqual(["Called"], calls)
 
540
        self.assertEqual([], client._calls)
 
541
 
 
542
    def test_url_quoting_of_path(self):
 
543
        # Relpaths on the wire should not be URL-escaped.  So "~" should be
 
544
        # transmitted as "~", not "%7E".
 
545
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
 
546
        client = FakeClient(transport.base)
 
547
        reference_format = self.get_repo_format()
 
548
        network_name = reference_format.network_name()
 
549
        branch_network_name = self.get_branch_format().network_name()
 
550
        client.add_expected_call(
 
551
            'BzrDir.open_branchV2', ('~hello/',),
 
552
            'success', ('branch', branch_network_name))
 
553
        client.add_expected_call(
 
554
            'BzrDir.find_repositoryV3', ('~hello/',),
 
555
            'success', ('ok', '', 'no', 'no', 'no', network_name))
 
556
        client.add_expected_call(
 
557
            'Branch.get_stacked_on_url', ('~hello/',),
 
558
            'error', ('NotStacked',))
 
559
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
560
            _client=client)
 
561
        result = bzrdir.open_branch()
 
562
        client.finished_test()
 
563
 
 
564
    def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
 
565
        reference_format = self.get_repo_format()
 
566
        network_name = reference_format.network_name()
 
567
        transport = MemoryTransport()
 
568
        transport.mkdir('quack')
 
569
        transport = transport.clone('quack')
 
570
        if rich_root:
 
571
            rich_response = 'yes'
 
572
        else:
 
573
            rich_response = 'no'
 
574
        if subtrees:
 
575
            subtree_response = 'yes'
 
576
        else:
 
577
            subtree_response = 'no'
 
578
        client = FakeClient(transport.base)
 
579
        client.add_success_response(
 
580
            'ok', '', rich_response, subtree_response, external_lookup,
 
581
            network_name)
 
582
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
583
            _client=client)
 
584
        result = bzrdir.open_repository()
 
585
        self.assertEqual(
 
586
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
 
587
            client._calls)
 
588
        self.assertIsInstance(result, RemoteRepository)
 
589
        self.assertEqual(bzrdir, result.bzrdir)
 
590
        self.assertEqual(rich_root, result._format.rich_root_data)
 
591
        self.assertEqual(subtrees, result._format.supports_tree_reference)
 
592
 
 
593
    def test_open_repository_sets_format_attributes(self):
 
594
        self.check_open_repository(True, True)
 
595
        self.check_open_repository(False, True)
 
596
        self.check_open_repository(True, False)
 
597
        self.check_open_repository(False, False)
 
598
        self.check_open_repository(False, False, 'yes')
 
599
 
 
600
    def test_old_server(self):
 
601
        """RemoteBzrDirFormat should fail to probe if the server version is too
 
602
        old.
 
603
        """
 
604
        self.assertRaises(errors.NotBranchError,
 
605
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
 
606
 
 
607
 
 
608
class TestBzrDirCreateBranch(TestRemote):
 
609
 
 
610
    def test_backwards_compat(self):
 
611
        self.setup_smart_server_with_call_log()
 
612
        repo = self.make_repository('.')
 
613
        self.reset_smart_call_log()
 
614
        self.disable_verb('BzrDir.create_branch')
 
615
        branch = repo.bzrdir.create_branch()
 
616
        create_branch_call_count = len([call for call in self.hpss_calls if
 
617
            call.call.method == 'BzrDir.create_branch'])
 
618
        self.assertEqual(1, create_branch_call_count)
 
619
 
 
620
    def test_current_server(self):
 
621
        transport = self.get_transport('.')
 
622
        transport = transport.clone('quack')
 
623
        self.make_repository('quack')
 
624
        client = FakeClient(transport.base)
 
625
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
626
        reference_format = reference_bzrdir_format.get_branch_format()
 
627
        network_name = reference_format.network_name()
 
628
        reference_repo_fmt = reference_bzrdir_format.repository_format
 
629
        reference_repo_name = reference_repo_fmt.network_name()
 
630
        client.add_expected_call(
 
631
            'BzrDir.create_branch', ('quack/', network_name),
 
632
            'success', ('ok', network_name, '', 'no', 'no', 'yes',
 
633
            reference_repo_name))
 
634
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
635
            _client=client)
 
636
        branch = a_bzrdir.create_branch()
 
637
        # We should have got a remote branch
 
638
        self.assertIsInstance(branch, remote.RemoteBranch)
 
639
        # its format should have the settings from the response
 
640
        format = branch._format
 
641
        self.assertEqual(network_name, format.network_name())
 
642
 
 
643
 
 
644
class TestBzrDirCreateRepository(TestRemote):
 
645
 
 
646
    def test_backwards_compat(self):
 
647
        self.setup_smart_server_with_call_log()
 
648
        bzrdir = self.make_bzrdir('.')
 
649
        self.reset_smart_call_log()
 
650
        self.disable_verb('BzrDir.create_repository')
 
651
        repo = bzrdir.create_repository()
 
652
        create_repo_call_count = len([call for call in self.hpss_calls if
 
653
            call.call.method == 'BzrDir.create_repository'])
 
654
        self.assertEqual(1, create_repo_call_count)
 
655
 
 
656
    def test_current_server(self):
 
657
        transport = self.get_transport('.')
 
658
        transport = transport.clone('quack')
 
659
        self.make_bzrdir('quack')
 
660
        client = FakeClient(transport.base)
 
661
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
662
        reference_format = reference_bzrdir_format.repository_format
 
663
        network_name = reference_format.network_name()
 
664
        client.add_expected_call(
 
665
            'BzrDir.create_repository', ('quack/',
 
666
                'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
 
667
            'success', ('ok', 'no', 'no', 'no', network_name))
 
668
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
669
            _client=client)
 
670
        repo = a_bzrdir.create_repository()
 
671
        # We should have got a remote repository
 
672
        self.assertIsInstance(repo, remote.RemoteRepository)
 
673
        # its format should have the settings from the response
 
674
        format = repo._format
 
675
        self.assertFalse(format.rich_root_data)
 
676
        self.assertFalse(format.supports_tree_reference)
 
677
        self.assertFalse(format.supports_external_lookups)
 
678
        self.assertEqual(network_name, format.network_name())
 
679
 
 
680
 
 
681
class TestBzrDirOpenRepository(TestRemote):
 
682
 
 
683
    def test_backwards_compat_1_2_3(self):
 
684
        # fallback all the way to the first version.
 
685
        reference_format = self.get_repo_format()
 
686
        network_name = reference_format.network_name()
 
687
        client = FakeClient('bzr://example.com/')
 
688
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
 
689
        client.add_unknown_method_response('BzrDir.find_repositoryV2')
 
690
        client.add_success_response('ok', '', 'no', 'no')
 
691
        # A real repository instance will be created to determine the network
 
692
        # name.
 
693
        client.add_success_response_with_body(
 
694
            "Bazaar-NG meta directory, format 1\n", 'ok')
 
695
        client.add_success_response_with_body(
 
696
            reference_format.get_format_string(), 'ok')
 
697
        # PackRepository wants to do a stat
 
698
        client.add_success_response('stat', '0', '65535')
 
699
        remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
 
700
            _client=client)
 
701
        bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
 
702
            _client=client)
 
703
        repo = bzrdir.open_repository()
 
704
        self.assertEqual(
 
705
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
 
706
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
707
             ('call', 'BzrDir.find_repository', ('quack/',)),
 
708
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
 
709
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
 
710
             ('call', 'stat', ('/quack/.bzr/repository',)),
 
711
             ],
 
712
            client._calls)
 
713
        self.assertEqual(network_name, repo._format.network_name())
 
714
 
 
715
    def test_backwards_compat_2(self):
 
716
        # fallback to find_repositoryV2
 
717
        reference_format = self.get_repo_format()
 
718
        network_name = reference_format.network_name()
 
719
        client = FakeClient('bzr://example.com/')
 
720
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
 
721
        client.add_success_response('ok', '', 'no', 'no', 'no')
 
722
        # A real repository instance will be created to determine the network
 
723
        # name.
 
724
        client.add_success_response_with_body(
 
725
            "Bazaar-NG meta directory, format 1\n", 'ok')
 
726
        client.add_success_response_with_body(
 
727
            reference_format.get_format_string(), 'ok')
 
728
        # PackRepository wants to do a stat
 
729
        client.add_success_response('stat', '0', '65535')
 
730
        remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
 
731
            _client=client)
 
732
        bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
 
733
            _client=client)
 
734
        repo = bzrdir.open_repository()
 
735
        self.assertEqual(
 
736
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
 
737
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
738
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
 
739
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
 
740
             ('call', 'stat', ('/quack/.bzr/repository',)),
 
741
             ],
 
742
            client._calls)
 
743
        self.assertEqual(network_name, repo._format.network_name())
 
744
 
 
745
    def test_current_server(self):
 
746
        reference_format = self.get_repo_format()
 
747
        network_name = reference_format.network_name()
 
748
        transport = MemoryTransport()
 
749
        transport.mkdir('quack')
 
750
        transport = transport.clone('quack')
 
751
        client = FakeClient(transport.base)
 
752
        client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
 
753
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
754
            _client=client)
 
755
        repo = bzrdir.open_repository()
 
756
        self.assertEqual(
 
757
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
 
758
            client._calls)
 
759
        self.assertEqual(network_name, repo._format.network_name())
 
760
 
 
761
 
 
762
class TestBzrDirFormatInitializeEx(TestRemote):
 
763
 
 
764
    def test_success(self):
 
765
        """Simple test for typical successful call."""
 
766
        fmt = bzrdir.RemoteBzrDirFormat()
 
767
        default_format_name = BzrDirFormat.get_default_format().network_name()
 
768
        transport = self.get_transport()
 
769
        client = FakeClient(transport.base)
 
770
        client.add_expected_call(
 
771
            'BzrDirFormat.initialize_ex_1.16',
 
772
                (default_format_name, 'path', 'False', 'False', 'False', '',
 
773
                 '', '', '', 'False'),
 
774
            'success',
 
775
                ('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
 
776
                 'bzrdir fmt', 'False', '', '', 'repo lock token'))
 
777
        # XXX: It would be better to call fmt.initialize_on_transport_ex, but
 
778
        # it's currently hard to test that without supplying a real remote
 
779
        # transport connected to a real server.
 
780
        result = fmt._initialize_on_transport_ex_rpc(client, 'path',
 
781
            transport, False, False, False, None, None, None, None, False)
 
782
        client.finished_test()
 
783
 
 
784
    def test_error(self):
 
785
        """Error responses are translated, e.g. 'PermissionDenied' raises the
 
786
        corresponding error from the client.
 
787
        """
 
788
        fmt = bzrdir.RemoteBzrDirFormat()
 
789
        default_format_name = BzrDirFormat.get_default_format().network_name()
 
790
        transport = self.get_transport()
 
791
        client = FakeClient(transport.base)
 
792
        client.add_expected_call(
 
793
            'BzrDirFormat.initialize_ex_1.16',
 
794
                (default_format_name, 'path', 'False', 'False', 'False', '',
 
795
                 '', '', '', 'False'),
 
796
            'error',
 
797
                ('PermissionDenied', 'path', 'extra info'))
 
798
        # XXX: It would be better to call fmt.initialize_on_transport_ex, but
 
799
        # it's currently hard to test that without supplying a real remote
 
800
        # transport connected to a real server.
 
801
        err = self.assertRaises(errors.PermissionDenied,
 
802
            fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
 
803
            False, False, False, None, None, None, None, False)
 
804
        self.assertEqual('path', err.path)
 
805
        self.assertEqual(': extra info', err.extra)
 
806
        client.finished_test()
 
807
 
 
808
    def test_error_from_real_server(self):
 
809
        """Integration test for error translation."""
 
810
        transport = self.make_smart_server('foo')
 
811
        transport = transport.clone('no-such-path')
 
812
        fmt = bzrdir.RemoteBzrDirFormat()
 
813
        err = self.assertRaises(errors.NoSuchFile,
 
814
            fmt.initialize_on_transport_ex, transport, create_prefix=False)
 
815
 
 
816
 
 
817
class OldSmartClient(object):
 
818
    """A fake smart client for test_old_version that just returns a version one
 
819
    response to the 'hello' (query version) command.
 
820
    """
 
821
 
 
822
    def get_request(self):
 
823
        input_file = StringIO('ok\x011\n')
 
824
        output_file = StringIO()
 
825
        client_medium = medium.SmartSimplePipesClientMedium(
 
826
            input_file, output_file)
 
827
        return medium.SmartClientStreamMediumRequest(client_medium)
 
828
 
 
829
    def protocol_version(self):
 
830
        return 1
 
831
 
 
832
 
 
833
class OldServerTransport(object):
 
834
    """A fake transport for test_old_server that reports it's smart server
 
835
    protocol version as version one.
 
836
    """
 
837
 
 
838
    def __init__(self):
 
839
        self.base = 'fake:'
 
840
 
 
841
    def get_smart_client(self):
 
842
        return OldSmartClient()
 
843
 
 
844
 
 
845
class RemoteBzrDirTestCase(TestRemote):
 
846
 
 
847
    def make_remote_bzrdir(self, transport, client):
 
848
        """Make a RemotebzrDir using 'client' as the _client."""
 
849
        return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
850
            _client=client)
 
851
 
 
852
 
 
853
class RemoteBranchTestCase(RemoteBzrDirTestCase):
 
854
 
 
855
    def make_remote_branch(self, transport, client):
 
856
        """Make a RemoteBranch using 'client' as its _SmartClient.
 
857
 
 
858
        A RemoteBzrDir and RemoteRepository will also be created to fill out
 
859
        the RemoteBranch, albeit with stub values for some of their attributes.
 
860
        """
 
861
        # we do not want bzrdir to make any remote calls, so use False as its
 
862
        # _client.  If it tries to make a remote call, this will fail
 
863
        # immediately.
 
864
        bzrdir = self.make_remote_bzrdir(transport, False)
 
865
        repo = RemoteRepository(bzrdir, None, _client=client)
 
866
        branch_format = self.get_branch_format()
 
867
        format = RemoteBranchFormat(network_name=branch_format.network_name())
 
868
        return RemoteBranch(bzrdir, repo, _client=client, format=format)
 
869
 
 
870
 
 
871
class TestBranchGetParent(RemoteBranchTestCase):
 
872
 
 
873
    def test_no_parent(self):
 
874
        # in an empty branch we decode the response properly
 
875
        transport = MemoryTransport()
 
876
        client = FakeClient(transport.base)
 
877
        client.add_expected_call(
 
878
            'Branch.get_stacked_on_url', ('quack/',),
 
879
            'error', ('NotStacked',))
 
880
        client.add_expected_call(
 
881
            'Branch.get_parent', ('quack/',),
 
882
            'success', ('',))
 
883
        transport.mkdir('quack')
 
884
        transport = transport.clone('quack')
 
885
        branch = self.make_remote_branch(transport, client)
 
886
        result = branch.get_parent()
 
887
        client.finished_test()
 
888
        self.assertEqual(None, result)
 
889
 
 
890
    def test_parent_relative(self):
 
891
        transport = MemoryTransport()
 
892
        client = FakeClient(transport.base)
 
893
        client.add_expected_call(
 
894
            'Branch.get_stacked_on_url', ('kwaak/',),
 
895
            'error', ('NotStacked',))
 
896
        client.add_expected_call(
 
897
            'Branch.get_parent', ('kwaak/',),
 
898
            'success', ('../foo/',))
 
899
        transport.mkdir('kwaak')
 
900
        transport = transport.clone('kwaak')
 
901
        branch = self.make_remote_branch(transport, client)
 
902
        result = branch.get_parent()
 
903
        self.assertEqual(transport.clone('../foo').base, result)
 
904
 
 
905
    def test_parent_absolute(self):
 
906
        transport = MemoryTransport()
 
907
        client = FakeClient(transport.base)
 
908
        client.add_expected_call(
 
909
            'Branch.get_stacked_on_url', ('kwaak/',),
 
910
            'error', ('NotStacked',))
 
911
        client.add_expected_call(
 
912
            'Branch.get_parent', ('kwaak/',),
 
913
            'success', ('http://foo/',))
 
914
        transport.mkdir('kwaak')
 
915
        transport = transport.clone('kwaak')
 
916
        branch = self.make_remote_branch(transport, client)
 
917
        result = branch.get_parent()
 
918
        self.assertEqual('http://foo/', result)
 
919
        client.finished_test()
 
920
 
 
921
 
 
922
class TestBranchSetParentLocation(RemoteBranchTestCase):
 
923
 
 
924
    def test_no_parent(self):
 
925
        # We call the verb when setting parent to None
 
926
        transport = MemoryTransport()
 
927
        client = FakeClient(transport.base)
 
928
        client.add_expected_call(
 
929
            'Branch.get_stacked_on_url', ('quack/',),
 
930
            'error', ('NotStacked',))
 
931
        client.add_expected_call(
 
932
            'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
 
933
            'success', ())
 
934
        transport.mkdir('quack')
 
935
        transport = transport.clone('quack')
 
936
        branch = self.make_remote_branch(transport, client)
 
937
        branch._lock_token = 'b'
 
938
        branch._repo_lock_token = 'r'
 
939
        branch._set_parent_location(None)
 
940
        client.finished_test()
 
941
 
 
942
    def test_parent(self):
 
943
        transport = MemoryTransport()
 
944
        client = FakeClient(transport.base)
 
945
        client.add_expected_call(
 
946
            'Branch.get_stacked_on_url', ('kwaak/',),
 
947
            'error', ('NotStacked',))
 
948
        client.add_expected_call(
 
949
            'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
 
950
            'success', ())
 
951
        transport.mkdir('kwaak')
 
952
        transport = transport.clone('kwaak')
 
953
        branch = self.make_remote_branch(transport, client)
 
954
        branch._lock_token = 'b'
 
955
        branch._repo_lock_token = 'r'
 
956
        branch._set_parent_location('foo')
 
957
        client.finished_test()
 
958
 
 
959
    def test_backwards_compat(self):
 
960
        self.setup_smart_server_with_call_log()
 
961
        branch = self.make_branch('.')
 
962
        self.reset_smart_call_log()
 
963
        verb = 'Branch.set_parent_location'
 
964
        self.disable_verb(verb)
 
965
        branch.set_parent('http://foo/')
 
966
        self.assertLength(12, self.hpss_calls)
 
967
 
 
968
 
 
969
class TestBranchGetTagsBytes(RemoteBranchTestCase):
 
970
 
 
971
    def test_backwards_compat(self):
 
972
        self.setup_smart_server_with_call_log()
 
973
        branch = self.make_branch('.')
 
974
        self.reset_smart_call_log()
 
975
        verb = 'Branch.get_tags_bytes'
 
976
        self.disable_verb(verb)
 
977
        branch.tags.get_tag_dict()
 
978
        call_count = len([call for call in self.hpss_calls if
 
979
            call.call.method == verb])
 
980
        self.assertEqual(1, call_count)
 
981
 
 
982
    def test_trivial(self):
 
983
        transport = MemoryTransport()
 
984
        client = FakeClient(transport.base)
 
985
        client.add_expected_call(
 
986
            'Branch.get_stacked_on_url', ('quack/',),
 
987
            'error', ('NotStacked',))
 
988
        client.add_expected_call(
 
989
            'Branch.get_tags_bytes', ('quack/',),
 
990
            'success', ('',))
 
991
        transport.mkdir('quack')
 
992
        transport = transport.clone('quack')
 
993
        branch = self.make_remote_branch(transport, client)
 
994
        result = branch.tags.get_tag_dict()
 
995
        client.finished_test()
 
996
        self.assertEqual({}, result)
 
997
 
 
998
 
 
999
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
 
1000
 
 
1001
    def test_empty_branch(self):
 
1002
        # in an empty branch we decode the response properly
 
1003
        transport = MemoryTransport()
 
1004
        client = FakeClient(transport.base)
 
1005
        client.add_expected_call(
 
1006
            'Branch.get_stacked_on_url', ('quack/',),
 
1007
            'error', ('NotStacked',))
 
1008
        client.add_expected_call(
 
1009
            'Branch.last_revision_info', ('quack/',),
 
1010
            'success', ('ok', '0', 'null:'))
 
1011
        transport.mkdir('quack')
 
1012
        transport = transport.clone('quack')
 
1013
        branch = self.make_remote_branch(transport, client)
 
1014
        result = branch.last_revision_info()
 
1015
        client.finished_test()
 
1016
        self.assertEqual((0, NULL_REVISION), result)
 
1017
 
 
1018
    def test_non_empty_branch(self):
 
1019
        # in a non-empty branch we also decode the response properly
 
1020
        revid = u'\xc8'.encode('utf8')
 
1021
        transport = MemoryTransport()
 
1022
        client = FakeClient(transport.base)
 
1023
        client.add_expected_call(
 
1024
            'Branch.get_stacked_on_url', ('kwaak/',),
 
1025
            'error', ('NotStacked',))
 
1026
        client.add_expected_call(
 
1027
            'Branch.last_revision_info', ('kwaak/',),
 
1028
            'success', ('ok', '2', revid))
 
1029
        transport.mkdir('kwaak')
 
1030
        transport = transport.clone('kwaak')
 
1031
        branch = self.make_remote_branch(transport, client)
 
1032
        result = branch.last_revision_info()
 
1033
        self.assertEqual((2, revid), result)
 
1034
 
 
1035
 
 
1036
class TestBranch_get_stacked_on_url(TestRemote):
 
1037
    """Test Branch._get_stacked_on_url rpc"""
 
1038
 
 
1039
    def test_get_stacked_on_invalid_url(self):
 
1040
        # test that asking for a stacked on url the server can't access works.
 
1041
        # This isn't perfect, but then as we're in the same process there
 
1042
        # really isn't anything we can do to be 100% sure that the server
 
1043
        # doesn't just open in - this test probably needs to be rewritten using
 
1044
        # a spawn()ed server.
 
1045
        stacked_branch = self.make_branch('stacked', format='1.9')
 
1046
        memory_branch = self.make_branch('base', format='1.9')
 
1047
        vfs_url = self.get_vfs_only_url('base')
 
1048
        stacked_branch.set_stacked_on_url(vfs_url)
 
1049
        transport = stacked_branch.bzrdir.root_transport
 
1050
        client = FakeClient(transport.base)
 
1051
        client.add_expected_call(
 
1052
            'Branch.get_stacked_on_url', ('stacked/',),
 
1053
            'success', ('ok', vfs_url))
 
1054
        # XXX: Multiple calls are bad, this second call documents what is
 
1055
        # today.
 
1056
        client.add_expected_call(
 
1057
            'Branch.get_stacked_on_url', ('stacked/',),
 
1058
            'success', ('ok', vfs_url))
 
1059
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
1060
            _client=client)
 
1061
        repo_fmt = remote.RemoteRepositoryFormat()
 
1062
        repo_fmt._custom_format = stacked_branch.repository._format
 
1063
        branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
 
1064
            _client=client)
 
1065
        result = branch.get_stacked_on_url()
 
1066
        self.assertEqual(vfs_url, result)
 
1067
 
 
1068
    def test_backwards_compatible(self):
 
1069
        # like with bzr1.6 with no Branch.get_stacked_on_url rpc
 
1070
        base_branch = self.make_branch('base', format='1.6')
 
1071
        stacked_branch = self.make_branch('stacked', format='1.6')
 
1072
        stacked_branch.set_stacked_on_url('../base')
 
1073
        client = FakeClient(self.get_url())
 
1074
        branch_network_name = self.get_branch_format().network_name()
 
1075
        client.add_expected_call(
 
1076
            'BzrDir.open_branchV2', ('stacked/',),
 
1077
            'success', ('branch', branch_network_name))
 
1078
        client.add_expected_call(
 
1079
            'BzrDir.find_repositoryV3', ('stacked/',),
 
1080
            'success', ('ok', '', 'no', 'no', 'yes',
 
1081
                stacked_branch.repository._format.network_name()))
 
1082
        # called twice, once from constructor and then again by us
 
1083
        client.add_expected_call(
 
1084
            'Branch.get_stacked_on_url', ('stacked/',),
 
1085
            'unknown', ('Branch.get_stacked_on_url',))
 
1086
        client.add_expected_call(
 
1087
            'Branch.get_stacked_on_url', ('stacked/',),
 
1088
            'unknown', ('Branch.get_stacked_on_url',))
 
1089
        # this will also do vfs access, but that goes direct to the transport
 
1090
        # and isn't seen by the FakeClient.
 
1091
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
1092
            remote.RemoteBzrDirFormat(), _client=client)
 
1093
        branch = bzrdir.open_branch()
 
1094
        result = branch.get_stacked_on_url()
 
1095
        self.assertEqual('../base', result)
 
1096
        client.finished_test()
 
1097
        # it's in the fallback list both for the RemoteRepository and its vfs
 
1098
        # repository
 
1099
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
1100
        self.assertEqual(1,
 
1101
            len(branch.repository._real_repository._fallback_repositories))
 
1102
 
 
1103
    def test_get_stacked_on_real_branch(self):
 
1104
        base_branch = self.make_branch('base', format='1.6')
 
1105
        stacked_branch = self.make_branch('stacked', format='1.6')
 
1106
        stacked_branch.set_stacked_on_url('../base')
 
1107
        reference_format = self.get_repo_format()
 
1108
        network_name = reference_format.network_name()
 
1109
        client = FakeClient(self.get_url())
 
1110
        branch_network_name = self.get_branch_format().network_name()
 
1111
        client.add_expected_call(
 
1112
            'BzrDir.open_branchV2', ('stacked/',),
 
1113
            'success', ('branch', branch_network_name))
 
1114
        client.add_expected_call(
 
1115
            'BzrDir.find_repositoryV3', ('stacked/',),
 
1116
            'success', ('ok', '', 'no', 'no', 'yes', network_name))
 
1117
        # called twice, once from constructor and then again by us
 
1118
        client.add_expected_call(
 
1119
            'Branch.get_stacked_on_url', ('stacked/',),
 
1120
            'success', ('ok', '../base'))
 
1121
        client.add_expected_call(
 
1122
            'Branch.get_stacked_on_url', ('stacked/',),
 
1123
            'success', ('ok', '../base'))
 
1124
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
1125
            remote.RemoteBzrDirFormat(), _client=client)
 
1126
        branch = bzrdir.open_branch()
 
1127
        result = branch.get_stacked_on_url()
 
1128
        self.assertEqual('../base', result)
 
1129
        client.finished_test()
 
1130
        # it's in the fallback list both for the RemoteRepository.
 
1131
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
1132
        # And we haven't had to construct a real repository.
 
1133
        self.assertEqual(None, branch.repository._real_repository)
 
1134
 
 
1135
 
 
1136
class TestBranchSetLastRevision(RemoteBranchTestCase):
 
1137
 
 
1138
    def test_set_empty(self):
 
1139
        # set_revision_history([]) is translated to calling
 
1140
        # Branch.set_last_revision(path, '') on the wire.
 
1141
        transport = MemoryTransport()
 
1142
        transport.mkdir('branch')
 
1143
        transport = transport.clone('branch')
 
1144
 
 
1145
        client = FakeClient(transport.base)
 
1146
        client.add_expected_call(
 
1147
            'Branch.get_stacked_on_url', ('branch/',),
 
1148
            'error', ('NotStacked',))
 
1149
        client.add_expected_call(
 
1150
            'Branch.lock_write', ('branch/', '', ''),
 
1151
            'success', ('ok', 'branch token', 'repo token'))
 
1152
        client.add_expected_call(
 
1153
            'Branch.last_revision_info',
 
1154
            ('branch/',),
 
1155
            'success', ('ok', '0', 'null:'))
 
1156
        client.add_expected_call(
 
1157
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
 
1158
            'success', ('ok',))
 
1159
        client.add_expected_call(
 
1160
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1161
            'success', ('ok',))
 
1162
        branch = self.make_remote_branch(transport, client)
 
1163
        # This is a hack to work around the problem that RemoteBranch currently
 
1164
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
1165
        branch._ensure_real = lambda: None
 
1166
        branch.lock_write()
 
1167
        result = branch.set_revision_history([])
 
1168
        branch.unlock()
 
1169
        self.assertEqual(None, result)
 
1170
        client.finished_test()
 
1171
 
 
1172
    def test_set_nonempty(self):
 
1173
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
 
1174
        # Branch.set_last_revision(path, rev-idN) on the wire.
 
1175
        transport = MemoryTransport()
 
1176
        transport.mkdir('branch')
 
1177
        transport = transport.clone('branch')
 
1178
 
 
1179
        client = FakeClient(transport.base)
 
1180
        client.add_expected_call(
 
1181
            'Branch.get_stacked_on_url', ('branch/',),
 
1182
            'error', ('NotStacked',))
 
1183
        client.add_expected_call(
 
1184
            'Branch.lock_write', ('branch/', '', ''),
 
1185
            'success', ('ok', 'branch token', 'repo token'))
 
1186
        client.add_expected_call(
 
1187
            'Branch.last_revision_info',
 
1188
            ('branch/',),
 
1189
            'success', ('ok', '0', 'null:'))
 
1190
        lines = ['rev-id2']
 
1191
        encoded_body = bz2.compress('\n'.join(lines))
 
1192
        client.add_success_response_with_body(encoded_body, 'ok')
 
1193
        client.add_expected_call(
 
1194
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
 
1195
            'success', ('ok',))
 
1196
        client.add_expected_call(
 
1197
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1198
            'success', ('ok',))
 
1199
        branch = self.make_remote_branch(transport, client)
 
1200
        # This is a hack to work around the problem that RemoteBranch currently
 
1201
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
1202
        branch._ensure_real = lambda: None
 
1203
        # Lock the branch, reset the record of remote calls.
 
1204
        branch.lock_write()
 
1205
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
 
1206
        branch.unlock()
 
1207
        self.assertEqual(None, result)
 
1208
        client.finished_test()
 
1209
 
 
1210
    def test_no_such_revision(self):
 
1211
        transport = MemoryTransport()
 
1212
        transport.mkdir('branch')
 
1213
        transport = transport.clone('branch')
 
1214
        # A response of 'NoSuchRevision' is translated into an exception.
 
1215
        client = FakeClient(transport.base)
 
1216
        client.add_expected_call(
 
1217
            'Branch.get_stacked_on_url', ('branch/',),
 
1218
            'error', ('NotStacked',))
 
1219
        client.add_expected_call(
 
1220
            'Branch.lock_write', ('branch/', '', ''),
 
1221
            'success', ('ok', 'branch token', 'repo token'))
 
1222
        client.add_expected_call(
 
1223
            'Branch.last_revision_info',
 
1224
            ('branch/',),
 
1225
            'success', ('ok', '0', 'null:'))
 
1226
        # get_graph calls to construct the revision history, for the set_rh
 
1227
        # hook
 
1228
        lines = ['rev-id']
 
1229
        encoded_body = bz2.compress('\n'.join(lines))
 
1230
        client.add_success_response_with_body(encoded_body, 'ok')
 
1231
        client.add_expected_call(
 
1232
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
1233
            'error', ('NoSuchRevision', 'rev-id'))
 
1234
        client.add_expected_call(
 
1235
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1236
            'success', ('ok',))
 
1237
 
 
1238
        branch = self.make_remote_branch(transport, client)
 
1239
        branch.lock_write()
 
1240
        self.assertRaises(
 
1241
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
 
1242
        branch.unlock()
 
1243
        client.finished_test()
 
1244
 
 
1245
    def test_tip_change_rejected(self):
 
1246
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
1247
        be raised.
 
1248
        """
 
1249
        transport = MemoryTransport()
 
1250
        transport.mkdir('branch')
 
1251
        transport = transport.clone('branch')
 
1252
        client = FakeClient(transport.base)
 
1253
        rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
 
1254
        rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
 
1255
        client.add_expected_call(
 
1256
            'Branch.get_stacked_on_url', ('branch/',),
 
1257
            'error', ('NotStacked',))
 
1258
        client.add_expected_call(
 
1259
            'Branch.lock_write', ('branch/', '', ''),
 
1260
            'success', ('ok', 'branch token', 'repo token'))
 
1261
        client.add_expected_call(
 
1262
            'Branch.last_revision_info',
 
1263
            ('branch/',),
 
1264
            'success', ('ok', '0', 'null:'))
 
1265
        lines = ['rev-id']
 
1266
        encoded_body = bz2.compress('\n'.join(lines))
 
1267
        client.add_success_response_with_body(encoded_body, 'ok')
 
1268
        client.add_expected_call(
 
1269
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
1270
            'error', ('TipChangeRejected', rejection_msg_utf8))
 
1271
        client.add_expected_call(
 
1272
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1273
            'success', ('ok',))
 
1274
        branch = self.make_remote_branch(transport, client)
 
1275
        branch._ensure_real = lambda: None
 
1276
        branch.lock_write()
 
1277
        # The 'TipChangeRejected' error response triggered by calling
 
1278
        # set_revision_history causes a TipChangeRejected exception.
 
1279
        err = self.assertRaises(
 
1280
            errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
 
1281
        # The UTF-8 message from the response has been decoded into a unicode
 
1282
        # object.
 
1283
        self.assertIsInstance(err.msg, unicode)
 
1284
        self.assertEqual(rejection_msg_unicode, err.msg)
 
1285
        branch.unlock()
 
1286
        client.finished_test()
 
1287
 
 
1288
 
 
1289
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
 
1290
 
 
1291
    def test_set_last_revision_info(self):
 
1292
        # set_last_revision_info(num, 'rev-id') is translated to calling
 
1293
        # Branch.set_last_revision_info(num, 'rev-id') on the wire.
 
1294
        transport = MemoryTransport()
 
1295
        transport.mkdir('branch')
 
1296
        transport = transport.clone('branch')
 
1297
        client = FakeClient(transport.base)
 
1298
        # get_stacked_on_url
 
1299
        client.add_error_response('NotStacked')
 
1300
        # lock_write
 
1301
        client.add_success_response('ok', 'branch token', 'repo token')
 
1302
        # query the current revision
 
1303
        client.add_success_response('ok', '0', 'null:')
 
1304
        # set_last_revision
 
1305
        client.add_success_response('ok')
 
1306
        # unlock
 
1307
        client.add_success_response('ok')
 
1308
 
 
1309
        branch = self.make_remote_branch(transport, client)
 
1310
        # Lock the branch, reset the record of remote calls.
 
1311
        branch.lock_write()
 
1312
        client._calls = []
 
1313
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
1314
        self.assertEqual(
 
1315
            [('call', 'Branch.last_revision_info', ('branch/',)),
 
1316
             ('call', 'Branch.set_last_revision_info',
 
1317
                ('branch/', 'branch token', 'repo token',
 
1318
                 '1234', 'a-revision-id'))],
 
1319
            client._calls)
 
1320
        self.assertEqual(None, result)
 
1321
 
 
1322
    def test_no_such_revision(self):
 
1323
        # A response of 'NoSuchRevision' is translated into an exception.
 
1324
        transport = MemoryTransport()
 
1325
        transport.mkdir('branch')
 
1326
        transport = transport.clone('branch')
 
1327
        client = FakeClient(transport.base)
 
1328
        # get_stacked_on_url
 
1329
        client.add_error_response('NotStacked')
 
1330
        # lock_write
 
1331
        client.add_success_response('ok', 'branch token', 'repo token')
 
1332
        # set_last_revision
 
1333
        client.add_error_response('NoSuchRevision', 'revid')
 
1334
        # unlock
 
1335
        client.add_success_response('ok')
 
1336
 
 
1337
        branch = self.make_remote_branch(transport, client)
 
1338
        # Lock the branch, reset the record of remote calls.
 
1339
        branch.lock_write()
 
1340
        client._calls = []
 
1341
 
 
1342
        self.assertRaises(
 
1343
            errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
 
1344
        branch.unlock()
 
1345
 
 
1346
    def lock_remote_branch(self, branch):
 
1347
        """Trick a RemoteBranch into thinking it is locked."""
 
1348
        branch._lock_mode = 'w'
 
1349
        branch._lock_count = 2
 
1350
        branch._lock_token = 'branch token'
 
1351
        branch._repo_lock_token = 'repo token'
 
1352
        branch.repository._lock_mode = 'w'
 
1353
        branch.repository._lock_count = 2
 
1354
        branch.repository._lock_token = 'repo token'
 
1355
 
 
1356
    def test_backwards_compatibility(self):
 
1357
        """If the server does not support the Branch.set_last_revision_info
 
1358
        verb (which is new in 1.4), then the client falls back to VFS methods.
 
1359
        """
 
1360
        # This test is a little messy.  Unlike most tests in this file, it
 
1361
        # doesn't purely test what a Remote* object sends over the wire, and
 
1362
        # how it reacts to responses from the wire.  It instead relies partly
 
1363
        # on asserting that the RemoteBranch will call
 
1364
        # self._real_branch.set_last_revision_info(...).
 
1365
 
 
1366
        # First, set up our RemoteBranch with a FakeClient that raises
 
1367
        # UnknownSmartMethod, and a StubRealBranch that logs how it is called.
 
1368
        transport = MemoryTransport()
 
1369
        transport.mkdir('branch')
 
1370
        transport = transport.clone('branch')
 
1371
        client = FakeClient(transport.base)
 
1372
        client.add_expected_call(
 
1373
            'Branch.get_stacked_on_url', ('branch/',),
 
1374
            'error', ('NotStacked',))
 
1375
        client.add_expected_call(
 
1376
            'Branch.last_revision_info',
 
1377
            ('branch/',),
 
1378
            'success', ('ok', '0', 'null:'))
 
1379
        client.add_expected_call(
 
1380
            'Branch.set_last_revision_info',
 
1381
            ('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
 
1382
            'unknown', 'Branch.set_last_revision_info')
 
1383
 
 
1384
        branch = self.make_remote_branch(transport, client)
 
1385
        class StubRealBranch(object):
 
1386
            def __init__(self):
 
1387
                self.calls = []
 
1388
            def set_last_revision_info(self, revno, revision_id):
 
1389
                self.calls.append(
 
1390
                    ('set_last_revision_info', revno, revision_id))
 
1391
            def _clear_cached_state(self):
 
1392
                pass
 
1393
        real_branch = StubRealBranch()
 
1394
        branch._real_branch = real_branch
 
1395
        self.lock_remote_branch(branch)
 
1396
 
 
1397
        # Call set_last_revision_info, and verify it behaved as expected.
 
1398
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
1399
        self.assertEqual(
 
1400
            [('set_last_revision_info', 1234, 'a-revision-id')],
 
1401
            real_branch.calls)
 
1402
        client.finished_test()
 
1403
 
 
1404
    def test_unexpected_error(self):
 
1405
        # If the server sends an error the client doesn't understand, it gets
 
1406
        # turned into an UnknownErrorFromSmartServer, which is presented as a
 
1407
        # non-internal error to the user.
 
1408
        transport = MemoryTransport()
 
1409
        transport.mkdir('branch')
 
1410
        transport = transport.clone('branch')
 
1411
        client = FakeClient(transport.base)
 
1412
        # get_stacked_on_url
 
1413
        client.add_error_response('NotStacked')
 
1414
        # lock_write
 
1415
        client.add_success_response('ok', 'branch token', 'repo token')
 
1416
        # set_last_revision
 
1417
        client.add_error_response('UnexpectedError')
 
1418
        # unlock
 
1419
        client.add_success_response('ok')
 
1420
 
 
1421
        branch = self.make_remote_branch(transport, client)
 
1422
        # Lock the branch, reset the record of remote calls.
 
1423
        branch.lock_write()
 
1424
        client._calls = []
 
1425
 
 
1426
        err = self.assertRaises(
 
1427
            errors.UnknownErrorFromSmartServer,
 
1428
            branch.set_last_revision_info, 123, 'revid')
 
1429
        self.assertEqual(('UnexpectedError',), err.error_tuple)
 
1430
        branch.unlock()
 
1431
 
 
1432
    def test_tip_change_rejected(self):
 
1433
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
1434
        be raised.
 
1435
        """
 
1436
        transport = MemoryTransport()
 
1437
        transport.mkdir('branch')
 
1438
        transport = transport.clone('branch')
 
1439
        client = FakeClient(transport.base)
 
1440
        # get_stacked_on_url
 
1441
        client.add_error_response('NotStacked')
 
1442
        # lock_write
 
1443
        client.add_success_response('ok', 'branch token', 'repo token')
 
1444
        # set_last_revision
 
1445
        client.add_error_response('TipChangeRejected', 'rejection message')
 
1446
        # unlock
 
1447
        client.add_success_response('ok')
 
1448
 
 
1449
        branch = self.make_remote_branch(transport, client)
 
1450
        # Lock the branch, reset the record of remote calls.
 
1451
        branch.lock_write()
 
1452
        self.addCleanup(branch.unlock)
 
1453
        client._calls = []
 
1454
 
 
1455
        # The 'TipChangeRejected' error response triggered by calling
 
1456
        # set_last_revision_info causes a TipChangeRejected exception.
 
1457
        err = self.assertRaises(
 
1458
            errors.TipChangeRejected,
 
1459
            branch.set_last_revision_info, 123, 'revid')
 
1460
        self.assertEqual('rejection message', err.msg)
 
1461
 
 
1462
 
 
1463
class TestBranchGetSetConfig(RemoteBranchTestCase):
 
1464
 
 
1465
    def test_get_branch_conf(self):
 
1466
        # in an empty branch we decode the response properly
 
1467
        client = FakeClient()
 
1468
        client.add_expected_call(
 
1469
            'Branch.get_stacked_on_url', ('memory:///',),
 
1470
            'error', ('NotStacked',),)
 
1471
        client.add_success_response_with_body('# config file body', 'ok')
 
1472
        transport = MemoryTransport()
 
1473
        branch = self.make_remote_branch(transport, client)
 
1474
        config = branch.get_config()
 
1475
        config.has_explicit_nickname()
 
1476
        self.assertEqual(
 
1477
            [('call', 'Branch.get_stacked_on_url', ('memory:///',)),
 
1478
             ('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
 
1479
            client._calls)
 
1480
 
 
1481
    def test_get_multi_line_branch_conf(self):
 
1482
        # Make sure that multiple-line branch.conf files are supported
 
1483
        #
 
1484
        # https://bugs.edge.launchpad.net/bzr/+bug/354075
 
1485
        client = FakeClient()
 
1486
        client.add_expected_call(
 
1487
            'Branch.get_stacked_on_url', ('memory:///',),
 
1488
            'error', ('NotStacked',),)
 
1489
        client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
 
1490
        transport = MemoryTransport()
 
1491
        branch = self.make_remote_branch(transport, client)
 
1492
        config = branch.get_config()
 
1493
        self.assertEqual(u'2', config.get_user_option('b'))
 
1494
 
 
1495
    def test_set_option(self):
 
1496
        client = FakeClient()
 
1497
        client.add_expected_call(
 
1498
            'Branch.get_stacked_on_url', ('memory:///',),
 
1499
            'error', ('NotStacked',),)
 
1500
        client.add_expected_call(
 
1501
            'Branch.lock_write', ('memory:///', '', ''),
 
1502
            'success', ('ok', 'branch token', 'repo token'))
 
1503
        client.add_expected_call(
 
1504
            'Branch.set_config_option', ('memory:///', 'branch token',
 
1505
            'repo token', 'foo', 'bar', ''),
 
1506
            'success', ())
 
1507
        client.add_expected_call(
 
1508
            'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
 
1509
            'success', ('ok',))
 
1510
        transport = MemoryTransport()
 
1511
        branch = self.make_remote_branch(transport, client)
 
1512
        branch.lock_write()
 
1513
        config = branch._get_config()
 
1514
        config.set_option('foo', 'bar')
 
1515
        branch.unlock()
 
1516
        client.finished_test()
 
1517
 
 
1518
    def test_backwards_compat_set_option(self):
 
1519
        self.setup_smart_server_with_call_log()
 
1520
        branch = self.make_branch('.')
 
1521
        verb = 'Branch.set_config_option'
 
1522
        self.disable_verb(verb)
 
1523
        branch.lock_write()
 
1524
        self.addCleanup(branch.unlock)
 
1525
        self.reset_smart_call_log()
 
1526
        branch._get_config().set_option('value', 'name')
 
1527
        self.assertLength(10, self.hpss_calls)
 
1528
        self.assertEqual('value', branch._get_config().get_option('name'))
 
1529
 
 
1530
 
 
1531
class TestBranchLockWrite(RemoteBranchTestCase):
 
1532
 
 
1533
    def test_lock_write_unlockable(self):
 
1534
        transport = MemoryTransport()
 
1535
        client = FakeClient(transport.base)
 
1536
        client.add_expected_call(
 
1537
            'Branch.get_stacked_on_url', ('quack/',),
 
1538
            'error', ('NotStacked',),)
 
1539
        client.add_expected_call(
 
1540
            'Branch.lock_write', ('quack/', '', ''),
 
1541
            'error', ('UnlockableTransport',))
 
1542
        transport.mkdir('quack')
 
1543
        transport = transport.clone('quack')
 
1544
        branch = self.make_remote_branch(transport, client)
 
1545
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
 
1546
        client.finished_test()
 
1547
 
 
1548
 
 
1549
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
 
1550
 
 
1551
    def test__get_config(self):
 
1552
        client = FakeClient()
 
1553
        client.add_success_response_with_body('default_stack_on = /\n', 'ok')
 
1554
        transport = MemoryTransport()
 
1555
        bzrdir = self.make_remote_bzrdir(transport, client)
 
1556
        config = bzrdir.get_config()
 
1557
        self.assertEqual('/', config.get_default_stack_on())
 
1558
        self.assertEqual(
 
1559
            [('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
 
1560
            client._calls)
 
1561
 
 
1562
    def test_set_option_uses_vfs(self):
 
1563
        self.setup_smart_server_with_call_log()
 
1564
        bzrdir = self.make_bzrdir('.')
 
1565
        self.reset_smart_call_log()
 
1566
        config = bzrdir.get_config()
 
1567
        config.set_default_stack_on('/')
 
1568
        self.assertLength(3, self.hpss_calls)
 
1569
 
 
1570
    def test_backwards_compat_get_option(self):
 
1571
        self.setup_smart_server_with_call_log()
 
1572
        bzrdir = self.make_bzrdir('.')
 
1573
        verb = 'BzrDir.get_config_file'
 
1574
        self.disable_verb(verb)
 
1575
        self.reset_smart_call_log()
 
1576
        self.assertEqual(None,
 
1577
            bzrdir._get_config().get_option('default_stack_on'))
 
1578
        self.assertLength(3, self.hpss_calls)
 
1579
 
 
1580
 
 
1581
class TestTransportIsReadonly(tests.TestCase):
 
1582
 
 
1583
    def test_true(self):
 
1584
        client = FakeClient()
 
1585
        client.add_success_response('yes')
 
1586
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1587
                                    _client=client)
 
1588
        self.assertEqual(True, transport.is_readonly())
 
1589
        self.assertEqual(
 
1590
            [('call', 'Transport.is_readonly', ())],
 
1591
            client._calls)
 
1592
 
 
1593
    def test_false(self):
 
1594
        client = FakeClient()
 
1595
        client.add_success_response('no')
 
1596
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1597
                                    _client=client)
 
1598
        self.assertEqual(False, transport.is_readonly())
 
1599
        self.assertEqual(
 
1600
            [('call', 'Transport.is_readonly', ())],
 
1601
            client._calls)
 
1602
 
 
1603
    def test_error_from_old_server(self):
 
1604
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
 
1605
 
 
1606
        Clients should treat it as a "no" response, because is_readonly is only
 
1607
        advisory anyway (a transport could be read-write, but then the
 
1608
        underlying filesystem could be readonly anyway).
 
1609
        """
 
1610
        client = FakeClient()
 
1611
        client.add_unknown_method_response('Transport.is_readonly')
 
1612
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1613
                                    _client=client)
 
1614
        self.assertEqual(False, transport.is_readonly())
 
1615
        self.assertEqual(
 
1616
            [('call', 'Transport.is_readonly', ())],
 
1617
            client._calls)
 
1618
 
 
1619
 
 
1620
class TestTransportMkdir(tests.TestCase):
 
1621
 
 
1622
    def test_permissiondenied(self):
 
1623
        client = FakeClient()
 
1624
        client.add_error_response('PermissionDenied', 'remote path', 'extra')
 
1625
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1626
                                    _client=client)
 
1627
        exc = self.assertRaises(
 
1628
            errors.PermissionDenied, transport.mkdir, 'client path')
 
1629
        expected_error = errors.PermissionDenied('/client path', 'extra')
 
1630
        self.assertEqual(expected_error, exc)
 
1631
 
 
1632
 
 
1633
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
 
1634
 
 
1635
    def test_defaults_to_none(self):
 
1636
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1637
        self.assertIs(None, t._get_credentials()[0])
 
1638
 
 
1639
    def test_uses_authentication_config(self):
 
1640
        conf = config.AuthenticationConfig()
 
1641
        conf._get_config().update(
 
1642
            {'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
 
1643
            'example.com'}})
 
1644
        conf._save()
 
1645
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1646
        self.assertEqual('bar', t._get_credentials()[0])
 
1647
 
 
1648
 
 
1649
class TestRemoteRepository(TestRemote):
 
1650
    """Base for testing RemoteRepository protocol usage.
 
1651
 
 
1652
    These tests contain frozen requests and responses.  We want any changes to
 
1653
    what is sent or expected to be require a thoughtful update to these tests
 
1654
    because they might break compatibility with different-versioned servers.
 
1655
    """
 
1656
 
 
1657
    def setup_fake_client_and_repository(self, transport_path):
 
1658
        """Create the fake client and repository for testing with.
 
1659
 
 
1660
        There's no real server here; we just have canned responses sent
 
1661
        back one by one.
 
1662
 
 
1663
        :param transport_path: Path below the root of the MemoryTransport
 
1664
            where the repository will be created.
 
1665
        """
 
1666
        transport = MemoryTransport()
 
1667
        transport.mkdir(transport_path)
 
1668
        client = FakeClient(transport.base)
 
1669
        transport = transport.clone(transport_path)
 
1670
        # we do not want bzrdir to make any remote calls
 
1671
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
1672
            _client=False)
 
1673
        repo = RemoteRepository(bzrdir, None, _client=client)
 
1674
        return repo, client
 
1675
 
 
1676
 
 
1677
class TestRepositoryFormat(TestRemoteRepository):
 
1678
 
 
1679
    def test_fast_delta(self):
 
1680
        true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
 
1681
        true_format = RemoteRepositoryFormat()
 
1682
        true_format._network_name = true_name
 
1683
        self.assertEqual(True, true_format.fast_deltas)
 
1684
        false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
 
1685
        false_format = RemoteRepositoryFormat()
 
1686
        false_format._network_name = false_name
 
1687
        self.assertEqual(False, false_format.fast_deltas)
 
1688
 
 
1689
 
 
1690
class TestRepositoryGatherStats(TestRemoteRepository):
 
1691
 
 
1692
    def test_revid_none(self):
 
1693
        # ('ok',), body with revisions and size
 
1694
        transport_path = 'quack'
 
1695
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1696
        client.add_success_response_with_body(
 
1697
            'revisions: 2\nsize: 18\n', 'ok')
 
1698
        result = repo.gather_stats(None)
 
1699
        self.assertEqual(
 
1700
            [('call_expecting_body', 'Repository.gather_stats',
 
1701
             ('quack/','','no'))],
 
1702
            client._calls)
 
1703
        self.assertEqual({'revisions': 2, 'size': 18}, result)
 
1704
 
 
1705
    def test_revid_no_committers(self):
 
1706
        # ('ok',), body without committers
 
1707
        body = ('firstrev: 123456.300 3600\n'
 
1708
                'latestrev: 654231.400 0\n'
 
1709
                'revisions: 2\n'
 
1710
                'size: 18\n')
 
1711
        transport_path = 'quick'
 
1712
        revid = u'\xc8'.encode('utf8')
 
1713
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1714
        client.add_success_response_with_body(body, 'ok')
 
1715
        result = repo.gather_stats(revid)
 
1716
        self.assertEqual(
 
1717
            [('call_expecting_body', 'Repository.gather_stats',
 
1718
              ('quick/', revid, 'no'))],
 
1719
            client._calls)
 
1720
        self.assertEqual({'revisions': 2, 'size': 18,
 
1721
                          'firstrev': (123456.300, 3600),
 
1722
                          'latestrev': (654231.400, 0),},
 
1723
                         result)
 
1724
 
 
1725
    def test_revid_with_committers(self):
 
1726
        # ('ok',), body with committers
 
1727
        body = ('committers: 128\n'
 
1728
                'firstrev: 123456.300 3600\n'
 
1729
                'latestrev: 654231.400 0\n'
 
1730
                'revisions: 2\n'
 
1731
                'size: 18\n')
 
1732
        transport_path = 'buick'
 
1733
        revid = u'\xc8'.encode('utf8')
 
1734
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1735
        client.add_success_response_with_body(body, 'ok')
 
1736
        result = repo.gather_stats(revid, True)
 
1737
        self.assertEqual(
 
1738
            [('call_expecting_body', 'Repository.gather_stats',
 
1739
              ('buick/', revid, 'yes'))],
 
1740
            client._calls)
 
1741
        self.assertEqual({'revisions': 2, 'size': 18,
 
1742
                          'committers': 128,
 
1743
                          'firstrev': (123456.300, 3600),
 
1744
                          'latestrev': (654231.400, 0),},
 
1745
                         result)
 
1746
 
 
1747
 
 
1748
class TestRepositoryGetGraph(TestRemoteRepository):
 
1749
 
 
1750
    def test_get_graph(self):
 
1751
        # get_graph returns a graph with a custom parents provider.
 
1752
        transport_path = 'quack'
 
1753
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1754
        graph = repo.get_graph()
 
1755
        self.assertNotEqual(graph._parents_provider, repo)
 
1756
 
 
1757
 
 
1758
class TestRepositoryGetParentMap(TestRemoteRepository):
 
1759
 
 
1760
    def test_get_parent_map_caching(self):
 
1761
        # get_parent_map returns from cache until unlock()
 
1762
        # setup a reponse with two revisions
 
1763
        r1 = u'\u0e33'.encode('utf8')
 
1764
        r2 = u'\u0dab'.encode('utf8')
 
1765
        lines = [' '.join([r2, r1]), r1]
 
1766
        encoded_body = bz2.compress('\n'.join(lines))
 
1767
 
 
1768
        transport_path = 'quack'
 
1769
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1770
        client.add_success_response_with_body(encoded_body, 'ok')
 
1771
        client.add_success_response_with_body(encoded_body, 'ok')
 
1772
        repo.lock_read()
 
1773
        graph = repo.get_graph()
 
1774
        parents = graph.get_parent_map([r2])
 
1775
        self.assertEqual({r2: (r1,)}, parents)
 
1776
        # locking and unlocking deeper should not reset
 
1777
        repo.lock_read()
 
1778
        repo.unlock()
 
1779
        parents = graph.get_parent_map([r1])
 
1780
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
1781
        self.assertEqual(
 
1782
            [('call_with_body_bytes_expecting_body',
 
1783
              'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
 
1784
              '\n\n0')],
 
1785
            client._calls)
 
1786
        repo.unlock()
 
1787
        # now we call again, and it should use the second response.
 
1788
        repo.lock_read()
 
1789
        graph = repo.get_graph()
 
1790
        parents = graph.get_parent_map([r1])
 
1791
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
1792
        self.assertEqual(
 
1793
            [('call_with_body_bytes_expecting_body',
 
1794
              'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
 
1795
              '\n\n0'),
 
1796
             ('call_with_body_bytes_expecting_body',
 
1797
              'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
 
1798
              '\n\n0'),
 
1799
            ],
 
1800
            client._calls)
 
1801
        repo.unlock()
 
1802
 
 
1803
    def test_get_parent_map_reconnects_if_unknown_method(self):
 
1804
        transport_path = 'quack'
 
1805
        rev_id = 'revision-id'
 
1806
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1807
        client.add_unknown_method_response('Repository.get_parent_map')
 
1808
        client.add_success_response_with_body(rev_id, 'ok')
 
1809
        self.assertFalse(client._medium._is_remote_before((1, 2)))
 
1810
        parents = repo.get_parent_map([rev_id])
 
1811
        self.assertEqual(
 
1812
            [('call_with_body_bytes_expecting_body',
 
1813
              'Repository.get_parent_map', ('quack/', 'include-missing:',
 
1814
              rev_id), '\n\n0'),
 
1815
             ('disconnect medium',),
 
1816
             ('call_expecting_body', 'Repository.get_revision_graph',
 
1817
              ('quack/', ''))],
 
1818
            client._calls)
 
1819
        # The medium is now marked as being connected to an older server
 
1820
        self.assertTrue(client._medium._is_remote_before((1, 2)))
 
1821
        self.assertEqual({rev_id: ('null:',)}, parents)
 
1822
 
 
1823
    def test_get_parent_map_fallback_parentless_node(self):
 
1824
        """get_parent_map falls back to get_revision_graph on old servers.  The
 
1825
        results from get_revision_graph are tweaked to match the get_parent_map
 
1826
        API.
 
1827
 
 
1828
        Specifically, a {key: ()} result from get_revision_graph means "no
 
1829
        parents" for that key, which in get_parent_map results should be
 
1830
        represented as {key: ('null:',)}.
 
1831
 
 
1832
        This is the test for https://bugs.launchpad.net/bzr/+bug/214894
 
1833
        """
 
1834
        rev_id = 'revision-id'
 
1835
        transport_path = 'quack'
 
1836
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1837
        client.add_success_response_with_body(rev_id, 'ok')
 
1838
        client._medium._remember_remote_is_before((1, 2))
 
1839
        parents = repo.get_parent_map([rev_id])
 
1840
        self.assertEqual(
 
1841
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1842
             ('quack/', ''))],
 
1843
            client._calls)
 
1844
        self.assertEqual({rev_id: ('null:',)}, parents)
 
1845
 
 
1846
    def test_get_parent_map_unexpected_response(self):
 
1847
        repo, client = self.setup_fake_client_and_repository('path')
 
1848
        client.add_success_response('something unexpected!')
 
1849
        self.assertRaises(
 
1850
            errors.UnexpectedSmartServerResponse,
 
1851
            repo.get_parent_map, ['a-revision-id'])
 
1852
 
 
1853
    def test_get_parent_map_negative_caches_missing_keys(self):
 
1854
        self.setup_smart_server_with_call_log()
 
1855
        repo = self.make_repository('foo')
 
1856
        self.assertIsInstance(repo, RemoteRepository)
 
1857
        repo.lock_read()
 
1858
        self.addCleanup(repo.unlock)
 
1859
        self.reset_smart_call_log()
 
1860
        graph = repo.get_graph()
 
1861
        self.assertEqual({},
 
1862
            graph.get_parent_map(['some-missing', 'other-missing']))
 
1863
        self.assertLength(1, self.hpss_calls)
 
1864
        # No call if we repeat this
 
1865
        self.reset_smart_call_log()
 
1866
        graph = repo.get_graph()
 
1867
        self.assertEqual({},
 
1868
            graph.get_parent_map(['some-missing', 'other-missing']))
 
1869
        self.assertLength(0, self.hpss_calls)
 
1870
        # Asking for more unknown keys makes a request.
 
1871
        self.reset_smart_call_log()
 
1872
        graph = repo.get_graph()
 
1873
        self.assertEqual({},
 
1874
            graph.get_parent_map(['some-missing', 'other-missing',
 
1875
                'more-missing']))
 
1876
        self.assertLength(1, self.hpss_calls)
 
1877
 
 
1878
    def disableExtraResults(self):
 
1879
        old_flag = SmartServerRepositoryGetParentMap.no_extra_results
 
1880
        SmartServerRepositoryGetParentMap.no_extra_results = True
 
1881
        def reset_values():
 
1882
            SmartServerRepositoryGetParentMap.no_extra_results = old_flag
 
1883
        self.addCleanup(reset_values)
 
1884
 
 
1885
    def test_null_cached_missing_and_stop_key(self):
 
1886
        self.setup_smart_server_with_call_log()
 
1887
        # Make a branch with a single revision.
 
1888
        builder = self.make_branch_builder('foo')
 
1889
        builder.start_series()
 
1890
        builder.build_snapshot('first', None, [
 
1891
            ('add', ('', 'root-id', 'directory', ''))])
 
1892
        builder.finish_series()
 
1893
        branch = builder.get_branch()
 
1894
        repo = branch.repository
 
1895
        self.assertIsInstance(repo, RemoteRepository)
 
1896
        # Stop the server from sending extra results.
 
1897
        self.disableExtraResults()
 
1898
        repo.lock_read()
 
1899
        self.addCleanup(repo.unlock)
 
1900
        self.reset_smart_call_log()
 
1901
        graph = repo.get_graph()
 
1902
        # Query for 'first' and 'null:'.  Because 'null:' is a parent of
 
1903
        # 'first' it will be a candidate for the stop_keys of subsequent
 
1904
        # requests, and because 'null:' was queried but not returned it will be
 
1905
        # cached as missing.
 
1906
        self.assertEqual({'first': ('null:',)},
 
1907
            graph.get_parent_map(['first', 'null:']))
 
1908
        # Now query for another key.  This request will pass along a recipe of
 
1909
        # start and stop keys describing the already cached results, and this
 
1910
        # recipe's revision count must be correct (or else it will trigger an
 
1911
        # error from the server).
 
1912
        self.assertEqual({}, graph.get_parent_map(['another-key']))
 
1913
        # This assertion guards against disableExtraResults silently failing to
 
1914
        # work, thus invalidating the test.
 
1915
        self.assertLength(2, self.hpss_calls)
 
1916
 
 
1917
    def test_get_parent_map_gets_ghosts_from_result(self):
 
1918
        # asking for a revision should negatively cache close ghosts in its
 
1919
        # ancestry.
 
1920
        self.setup_smart_server_with_call_log()
 
1921
        tree = self.make_branch_and_memory_tree('foo')
 
1922
        tree.lock_write()
 
1923
        try:
 
1924
            builder = treebuilder.TreeBuilder()
 
1925
            builder.start_tree(tree)
 
1926
            builder.build([])
 
1927
            builder.finish_tree()
 
1928
            tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
 
1929
            rev_id = tree.commit('')
 
1930
        finally:
 
1931
            tree.unlock()
 
1932
        tree.lock_read()
 
1933
        self.addCleanup(tree.unlock)
 
1934
        repo = tree.branch.repository
 
1935
        self.assertIsInstance(repo, RemoteRepository)
 
1936
        # ask for rev_id
 
1937
        repo.get_parent_map([rev_id])
 
1938
        self.reset_smart_call_log()
 
1939
        # Now asking for rev_id's ghost parent should not make calls
 
1940
        self.assertEqual({}, repo.get_parent_map(['non-existant']))
 
1941
        self.assertLength(0, self.hpss_calls)
 
1942
 
 
1943
 
 
1944
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
 
1945
 
 
1946
    def test_allows_new_revisions(self):
 
1947
        """get_parent_map's results can be updated by commit."""
 
1948
        smart_server = server.SmartTCPServer_for_testing()
 
1949
        smart_server.setUp()
 
1950
        self.addCleanup(smart_server.tearDown)
 
1951
        self.make_branch('branch')
 
1952
        branch = Branch.open(smart_server.get_url() + '/branch')
 
1953
        tree = branch.create_checkout('tree', lightweight=True)
 
1954
        tree.lock_write()
 
1955
        self.addCleanup(tree.unlock)
 
1956
        graph = tree.branch.repository.get_graph()
 
1957
        # This provides an opportunity for the missing rev-id to be cached.
 
1958
        self.assertEqual({}, graph.get_parent_map(['rev1']))
 
1959
        tree.commit('message', rev_id='rev1')
 
1960
        graph = tree.branch.repository.get_graph()
 
1961
        self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
 
1962
 
 
1963
 
 
1964
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
 
1965
 
 
1966
    def test_null_revision(self):
 
1967
        # a null revision has the predictable result {}, we should have no wire
 
1968
        # traffic when calling it with this argument
 
1969
        transport_path = 'empty'
 
1970
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1971
        client.add_success_response('notused')
 
1972
        # actual RemoteRepository.get_revision_graph is gone, but there's an
 
1973
        # equivalent private method for testing
 
1974
        result = repo._get_revision_graph(NULL_REVISION)
 
1975
        self.assertEqual([], client._calls)
 
1976
        self.assertEqual({}, result)
 
1977
 
 
1978
    def test_none_revision(self):
 
1979
        # with none we want the entire graph
 
1980
        r1 = u'\u0e33'.encode('utf8')
 
1981
        r2 = u'\u0dab'.encode('utf8')
 
1982
        lines = [' '.join([r2, r1]), r1]
 
1983
        encoded_body = '\n'.join(lines)
 
1984
 
 
1985
        transport_path = 'sinhala'
 
1986
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1987
        client.add_success_response_with_body(encoded_body, 'ok')
 
1988
        # actual RemoteRepository.get_revision_graph is gone, but there's an
 
1989
        # equivalent private method for testing
 
1990
        result = repo._get_revision_graph(None)
 
1991
        self.assertEqual(
 
1992
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1993
             ('sinhala/', ''))],
 
1994
            client._calls)
 
1995
        self.assertEqual({r1: (), r2: (r1, )}, result)
 
1996
 
 
1997
    def test_specific_revision(self):
 
1998
        # with a specific revision we want the graph for that
 
1999
        # with none we want the entire graph
 
2000
        r11 = u'\u0e33'.encode('utf8')
 
2001
        r12 = u'\xc9'.encode('utf8')
 
2002
        r2 = u'\u0dab'.encode('utf8')
 
2003
        lines = [' '.join([r2, r11, r12]), r11, r12]
 
2004
        encoded_body = '\n'.join(lines)
 
2005
 
 
2006
        transport_path = 'sinhala'
 
2007
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2008
        client.add_success_response_with_body(encoded_body, 'ok')
 
2009
        result = repo._get_revision_graph(r2)
 
2010
        self.assertEqual(
 
2011
            [('call_expecting_body', 'Repository.get_revision_graph',
 
2012
             ('sinhala/', r2))],
 
2013
            client._calls)
 
2014
        self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
 
2015
 
 
2016
    def test_no_such_revision(self):
 
2017
        revid = '123'
 
2018
        transport_path = 'sinhala'
 
2019
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2020
        client.add_error_response('nosuchrevision', revid)
 
2021
        # also check that the right revision is reported in the error
 
2022
        self.assertRaises(errors.NoSuchRevision,
 
2023
            repo._get_revision_graph, revid)
 
2024
        self.assertEqual(
 
2025
            [('call_expecting_body', 'Repository.get_revision_graph',
 
2026
             ('sinhala/', revid))],
 
2027
            client._calls)
 
2028
 
 
2029
    def test_unexpected_error(self):
 
2030
        revid = '123'
 
2031
        transport_path = 'sinhala'
 
2032
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2033
        client.add_error_response('AnUnexpectedError')
 
2034
        e = self.assertRaises(errors.UnknownErrorFromSmartServer,
 
2035
            repo._get_revision_graph, revid)
 
2036
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
 
2037
 
 
2038
 
 
2039
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
 
2040
 
 
2041
    def test_ok(self):
 
2042
        repo, client = self.setup_fake_client_and_repository('quack')
 
2043
        client.add_expected_call(
 
2044
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2045
            'success', ('ok', 'rev-five'))
 
2046
        result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
 
2047
        self.assertEqual((True, 'rev-five'), result)
 
2048
        client.finished_test()
 
2049
 
 
2050
    def test_history_incomplete(self):
 
2051
        repo, client = self.setup_fake_client_and_repository('quack')
 
2052
        client.add_expected_call(
 
2053
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2054
            'success', ('history-incomplete', 10, 'rev-ten'))
 
2055
        result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
 
2056
        self.assertEqual((False, (10, 'rev-ten')), result)
 
2057
        client.finished_test()
 
2058
 
 
2059
    def test_history_incomplete_with_fallback(self):
 
2060
        """A 'history-incomplete' response causes the fallback repository to be
 
2061
        queried too, if one is set.
 
2062
        """
 
2063
        # Make a repo with a fallback repo, both using a FakeClient.
 
2064
        format = remote.response_tuple_to_repo_format(
 
2065
            ('yes', 'no', 'yes', 'fake-network-name'))
 
2066
        repo, client = self.setup_fake_client_and_repository('quack')
 
2067
        repo._format = format
 
2068
        fallback_repo, ignored = self.setup_fake_client_and_repository(
 
2069
            'fallback')
 
2070
        fallback_repo._client = client
 
2071
        repo.add_fallback_repository(fallback_repo)
 
2072
        # First the client should ask the primary repo
 
2073
        client.add_expected_call(
 
2074
            'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
 
2075
            'success', ('history-incomplete', 2, 'rev-two'))
 
2076
        # Then it should ask the fallback, using revno/revid from the
 
2077
        # history-incomplete response as the known revno/revid.
 
2078
        client.add_expected_call(
 
2079
            'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
 
2080
            'success', ('ok', 'rev-one'))
 
2081
        result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
 
2082
        self.assertEqual((True, 'rev-one'), result)
 
2083
        client.finished_test()
 
2084
 
 
2085
    def test_nosuchrevision(self):
 
2086
        # 'nosuchrevision' is returned when the known-revid is not found in the
 
2087
        # remote repo.  The client translates that response to NoSuchRevision.
 
2088
        repo, client = self.setup_fake_client_and_repository('quack')
 
2089
        client.add_expected_call(
 
2090
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2091
            'error', ('nosuchrevision', 'rev-foo'))
 
2092
        self.assertRaises(
 
2093
            errors.NoSuchRevision,
 
2094
            repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
 
2095
        client.finished_test()
 
2096
 
 
2097
 
 
2098
class TestRepositoryIsShared(TestRemoteRepository):
 
2099
 
 
2100
    def test_is_shared(self):
 
2101
        # ('yes', ) for Repository.is_shared -> 'True'.
 
2102
        transport_path = 'quack'
 
2103
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2104
        client.add_success_response('yes')
 
2105
        result = repo.is_shared()
 
2106
        self.assertEqual(
 
2107
            [('call', 'Repository.is_shared', ('quack/',))],
 
2108
            client._calls)
 
2109
        self.assertEqual(True, result)
 
2110
 
 
2111
    def test_is_not_shared(self):
 
2112
        # ('no', ) for Repository.is_shared -> 'False'.
 
2113
        transport_path = 'qwack'
 
2114
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2115
        client.add_success_response('no')
 
2116
        result = repo.is_shared()
 
2117
        self.assertEqual(
 
2118
            [('call', 'Repository.is_shared', ('qwack/',))],
 
2119
            client._calls)
 
2120
        self.assertEqual(False, result)
 
2121
 
 
2122
 
 
2123
class TestRepositoryLockWrite(TestRemoteRepository):
 
2124
 
 
2125
    def test_lock_write(self):
 
2126
        transport_path = 'quack'
 
2127
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2128
        client.add_success_response('ok', 'a token')
 
2129
        result = repo.lock_write()
 
2130
        self.assertEqual(
 
2131
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
2132
            client._calls)
 
2133
        self.assertEqual('a token', result)
 
2134
 
 
2135
    def test_lock_write_already_locked(self):
 
2136
        transport_path = 'quack'
 
2137
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2138
        client.add_error_response('LockContention')
 
2139
        self.assertRaises(errors.LockContention, repo.lock_write)
 
2140
        self.assertEqual(
 
2141
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
2142
            client._calls)
 
2143
 
 
2144
    def test_lock_write_unlockable(self):
 
2145
        transport_path = 'quack'
 
2146
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2147
        client.add_error_response('UnlockableTransport')
 
2148
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
 
2149
        self.assertEqual(
 
2150
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
2151
            client._calls)
 
2152
 
 
2153
 
 
2154
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
 
2155
 
 
2156
    def test_backwards_compat(self):
 
2157
        self.setup_smart_server_with_call_log()
 
2158
        repo = self.make_repository('.')
 
2159
        self.reset_smart_call_log()
 
2160
        verb = 'Repository.set_make_working_trees'
 
2161
        self.disable_verb(verb)
 
2162
        repo.set_make_working_trees(True)
 
2163
        call_count = len([call for call in self.hpss_calls if
 
2164
            call.call.method == verb])
 
2165
        self.assertEqual(1, call_count)
 
2166
 
 
2167
    def test_current(self):
 
2168
        transport_path = 'quack'
 
2169
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2170
        client.add_expected_call(
 
2171
            'Repository.set_make_working_trees', ('quack/', 'True'),
 
2172
            'success', ('ok',))
 
2173
        client.add_expected_call(
 
2174
            'Repository.set_make_working_trees', ('quack/', 'False'),
 
2175
            'success', ('ok',))
 
2176
        repo.set_make_working_trees(True)
 
2177
        repo.set_make_working_trees(False)
 
2178
 
 
2179
 
 
2180
class TestRepositoryUnlock(TestRemoteRepository):
 
2181
 
 
2182
    def test_unlock(self):
 
2183
        transport_path = 'quack'
 
2184
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2185
        client.add_success_response('ok', 'a token')
 
2186
        client.add_success_response('ok')
 
2187
        repo.lock_write()
 
2188
        repo.unlock()
 
2189
        self.assertEqual(
 
2190
            [('call', 'Repository.lock_write', ('quack/', '')),
 
2191
             ('call', 'Repository.unlock', ('quack/', 'a token'))],
 
2192
            client._calls)
 
2193
 
 
2194
    def test_unlock_wrong_token(self):
 
2195
        # If somehow the token is wrong, unlock will raise TokenMismatch.
 
2196
        transport_path = 'quack'
 
2197
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2198
        client.add_success_response('ok', 'a token')
 
2199
        client.add_error_response('TokenMismatch')
 
2200
        repo.lock_write()
 
2201
        self.assertRaises(errors.TokenMismatch, repo.unlock)
 
2202
 
 
2203
 
 
2204
class TestRepositoryHasRevision(TestRemoteRepository):
 
2205
 
 
2206
    def test_none(self):
 
2207
        # repo.has_revision(None) should not cause any traffic.
 
2208
        transport_path = 'quack'
 
2209
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2210
 
 
2211
        # The null revision is always there, so has_revision(None) == True.
 
2212
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
 
2213
 
 
2214
        # The remote repo shouldn't be accessed.
 
2215
        self.assertEqual([], client._calls)
 
2216
 
 
2217
 
 
2218
class TestRepositoryInsertStream(TestRemoteRepository):
 
2219
 
 
2220
    def test_unlocked_repo(self):
 
2221
        transport_path = 'quack'
 
2222
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2223
        client.add_expected_call(
 
2224
            'Repository.insert_stream', ('quack/', ''),
 
2225
            'success', ('ok',))
 
2226
        client.add_expected_call(
 
2227
            'Repository.insert_stream', ('quack/', ''),
 
2228
            'success', ('ok',))
 
2229
        sink = repo._get_sink()
 
2230
        fmt = repository.RepositoryFormat.get_default_format()
 
2231
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
 
2232
        self.assertEqual([], resume_tokens)
 
2233
        self.assertEqual(set(), missing_keys)
 
2234
        client.finished_test()
 
2235
 
 
2236
    def test_locked_repo_with_no_lock_token(self):
 
2237
        transport_path = 'quack'
 
2238
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2239
        client.add_expected_call(
 
2240
            'Repository.lock_write', ('quack/', ''),
 
2241
            'success', ('ok', ''))
 
2242
        client.add_expected_call(
 
2243
            'Repository.insert_stream', ('quack/', ''),
 
2244
            'success', ('ok',))
 
2245
        client.add_expected_call(
 
2246
            'Repository.insert_stream', ('quack/', ''),
 
2247
            'success', ('ok',))
 
2248
        repo.lock_write()
 
2249
        sink = repo._get_sink()
 
2250
        fmt = repository.RepositoryFormat.get_default_format()
 
2251
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
 
2252
        self.assertEqual([], resume_tokens)
 
2253
        self.assertEqual(set(), missing_keys)
 
2254
        client.finished_test()
 
2255
 
 
2256
    def test_locked_repo_with_lock_token(self):
 
2257
        transport_path = 'quack'
 
2258
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2259
        client.add_expected_call(
 
2260
            'Repository.lock_write', ('quack/', ''),
 
2261
            'success', ('ok', 'a token'))
 
2262
        client.add_expected_call(
 
2263
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
 
2264
            'success', ('ok',))
 
2265
        client.add_expected_call(
 
2266
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
 
2267
            'success', ('ok',))
 
2268
        repo.lock_write()
 
2269
        sink = repo._get_sink()
 
2270
        fmt = repository.RepositoryFormat.get_default_format()
 
2271
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
 
2272
        self.assertEqual([], resume_tokens)
 
2273
        self.assertEqual(set(), missing_keys)
 
2274
        client.finished_test()
 
2275
 
 
2276
 
 
2277
class TestRepositoryTarball(TestRemoteRepository):
 
2278
 
 
2279
    # This is a canned tarball reponse we can validate against
 
2280
    tarball_content = (
 
2281
        'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
 
2282
        'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
 
2283
        'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
 
2284
        'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
 
2285
        'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
 
2286
        'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
 
2287
        'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
 
2288
        'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
 
2289
        '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
 
2290
        'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
 
2291
        'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
 
2292
        'nWQ7QH/F3JFOFCQ0aSPfA='
 
2293
        ).decode('base64')
 
2294
 
 
2295
    def test_repository_tarball(self):
 
2296
        # Test that Repository.tarball generates the right operations
 
2297
        transport_path = 'repo'
 
2298
        expected_calls = [('call_expecting_body', 'Repository.tarball',
 
2299
                           ('repo/', 'bz2',),),
 
2300
            ]
 
2301
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2302
        client.add_success_response_with_body(self.tarball_content, 'ok')
 
2303
        # Now actually ask for the tarball
 
2304
        tarball_file = repo._get_tarball('bz2')
 
2305
        try:
 
2306
            self.assertEqual(expected_calls, client._calls)
 
2307
            self.assertEqual(self.tarball_content, tarball_file.read())
 
2308
        finally:
 
2309
            tarball_file.close()
 
2310
 
 
2311
 
 
2312
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
 
2313
    """RemoteRepository.copy_content_into optimizations"""
 
2314
 
 
2315
    def test_copy_content_remote_to_local(self):
 
2316
        self.transport_server = server.SmartTCPServer_for_testing
 
2317
        src_repo = self.make_repository('repo1')
 
2318
        src_repo = repository.Repository.open(self.get_url('repo1'))
 
2319
        # At the moment the tarball-based copy_content_into can't write back
 
2320
        # into a smart server.  It would be good if it could upload the
 
2321
        # tarball; once that works we'd have to create repositories of
 
2322
        # different formats. -- mbp 20070410
 
2323
        dest_url = self.get_vfs_only_url('repo2')
 
2324
        dest_bzrdir = BzrDir.create(dest_url)
 
2325
        dest_repo = dest_bzrdir.create_repository()
 
2326
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
 
2327
        self.assertTrue(isinstance(src_repo, RemoteRepository))
 
2328
        src_repo.copy_content_into(dest_repo)
 
2329
 
 
2330
 
 
2331
class _StubRealPackRepository(object):
 
2332
 
 
2333
    def __init__(self, calls):
 
2334
        self.calls = calls
 
2335
        self._pack_collection = _StubPackCollection(calls)
 
2336
 
 
2337
    def is_in_write_group(self):
 
2338
        return False
 
2339
 
 
2340
    def refresh_data(self):
 
2341
        self.calls.append(('pack collection reload_pack_names',))
 
2342
 
 
2343
 
 
2344
class _StubPackCollection(object):
 
2345
 
 
2346
    def __init__(self, calls):
 
2347
        self.calls = calls
 
2348
 
 
2349
    def autopack(self):
 
2350
        self.calls.append(('pack collection autopack',))
 
2351
 
 
2352
 
 
2353
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
 
2354
    """Tests for RemoteRepository.autopack implementation."""
 
2355
 
 
2356
    def test_ok(self):
 
2357
        """When the server returns 'ok' and there's no _real_repository, then
 
2358
        nothing else happens: the autopack method is done.
 
2359
        """
 
2360
        transport_path = 'quack'
 
2361
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2362
        client.add_expected_call(
 
2363
            'PackRepository.autopack', ('quack/',), 'success', ('ok',))
 
2364
        repo.autopack()
 
2365
        client.finished_test()
 
2366
 
 
2367
    def test_ok_with_real_repo(self):
 
2368
        """When the server returns 'ok' and there is a _real_repository, then
 
2369
        the _real_repository's reload_pack_name's method will be called.
 
2370
        """
 
2371
        transport_path = 'quack'
 
2372
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2373
        client.add_expected_call(
 
2374
            'PackRepository.autopack', ('quack/',),
 
2375
            'success', ('ok',))
 
2376
        repo._real_repository = _StubRealPackRepository(client._calls)
 
2377
        repo.autopack()
 
2378
        self.assertEqual(
 
2379
            [('call', 'PackRepository.autopack', ('quack/',)),
 
2380
             ('pack collection reload_pack_names',)],
 
2381
            client._calls)
 
2382
 
 
2383
    def test_backwards_compatibility(self):
 
2384
        """If the server does not recognise the PackRepository.autopack verb,
 
2385
        fallback to the real_repository's implementation.
 
2386
        """
 
2387
        transport_path = 'quack'
 
2388
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2389
        client.add_unknown_method_response('PackRepository.autopack')
 
2390
        def stub_ensure_real():
 
2391
            client._calls.append(('_ensure_real',))
 
2392
            repo._real_repository = _StubRealPackRepository(client._calls)
 
2393
        repo._ensure_real = stub_ensure_real
 
2394
        repo.autopack()
 
2395
        self.assertEqual(
 
2396
            [('call', 'PackRepository.autopack', ('quack/',)),
 
2397
             ('_ensure_real',),
 
2398
             ('pack collection autopack',)],
 
2399
            client._calls)
 
2400
 
 
2401
 
 
2402
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
 
2403
    """Base class for unit tests for bzrlib.remote._translate_error."""
 
2404
 
 
2405
    def translateTuple(self, error_tuple, **context):
 
2406
        """Call _translate_error with an ErrorFromSmartServer built from the
 
2407
        given error_tuple.
 
2408
 
 
2409
        :param error_tuple: A tuple of a smart server response, as would be
 
2410
            passed to an ErrorFromSmartServer.
 
2411
        :kwargs context: context items to call _translate_error with.
 
2412
 
 
2413
        :returns: The error raised by _translate_error.
 
2414
        """
 
2415
        # Raise the ErrorFromSmartServer before passing it as an argument,
 
2416
        # because _translate_error may need to re-raise it with a bare 'raise'
 
2417
        # statement.
 
2418
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2419
        translated_error = self.translateErrorFromSmartServer(
 
2420
            server_error, **context)
 
2421
        return translated_error
 
2422
 
 
2423
    def translateErrorFromSmartServer(self, error_object, **context):
 
2424
        """Like translateTuple, but takes an already constructed
 
2425
        ErrorFromSmartServer rather than a tuple.
 
2426
        """
 
2427
        try:
 
2428
            raise error_object
 
2429
        except errors.ErrorFromSmartServer, server_error:
 
2430
            translated_error = self.assertRaises(
 
2431
                errors.BzrError, remote._translate_error, server_error,
 
2432
                **context)
 
2433
        return translated_error
 
2434
 
 
2435
 
 
2436
class TestErrorTranslationSuccess(TestErrorTranslationBase):
 
2437
    """Unit tests for bzrlib.remote._translate_error.
 
2438
 
 
2439
    Given an ErrorFromSmartServer (which has an error tuple from a smart
 
2440
    server) and some context, _translate_error raises more specific errors from
 
2441
    bzrlib.errors.
 
2442
 
 
2443
    This test case covers the cases where _translate_error succeeds in
 
2444
    translating an ErrorFromSmartServer to something better.  See
 
2445
    TestErrorTranslationRobustness for other cases.
 
2446
    """
 
2447
 
 
2448
    def test_NoSuchRevision(self):
 
2449
        branch = self.make_branch('')
 
2450
        revid = 'revid'
 
2451
        translated_error = self.translateTuple(
 
2452
            ('NoSuchRevision', revid), branch=branch)
 
2453
        expected_error = errors.NoSuchRevision(branch, revid)
 
2454
        self.assertEqual(expected_error, translated_error)
 
2455
 
 
2456
    def test_nosuchrevision(self):
 
2457
        repository = self.make_repository('')
 
2458
        revid = 'revid'
 
2459
        translated_error = self.translateTuple(
 
2460
            ('nosuchrevision', revid), repository=repository)
 
2461
        expected_error = errors.NoSuchRevision(repository, revid)
 
2462
        self.assertEqual(expected_error, translated_error)
 
2463
 
 
2464
    def test_nobranch(self):
 
2465
        bzrdir = self.make_bzrdir('')
 
2466
        translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
 
2467
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
 
2468
        self.assertEqual(expected_error, translated_error)
 
2469
 
 
2470
    def test_LockContention(self):
 
2471
        translated_error = self.translateTuple(('LockContention',))
 
2472
        expected_error = errors.LockContention('(remote lock)')
 
2473
        self.assertEqual(expected_error, translated_error)
 
2474
 
 
2475
    def test_UnlockableTransport(self):
 
2476
        bzrdir = self.make_bzrdir('')
 
2477
        translated_error = self.translateTuple(
 
2478
            ('UnlockableTransport',), bzrdir=bzrdir)
 
2479
        expected_error = errors.UnlockableTransport(bzrdir.root_transport)
 
2480
        self.assertEqual(expected_error, translated_error)
 
2481
 
 
2482
    def test_LockFailed(self):
 
2483
        lock = 'str() of a server lock'
 
2484
        why = 'str() of why'
 
2485
        translated_error = self.translateTuple(('LockFailed', lock, why))
 
2486
        expected_error = errors.LockFailed(lock, why)
 
2487
        self.assertEqual(expected_error, translated_error)
 
2488
 
 
2489
    def test_TokenMismatch(self):
 
2490
        token = 'a lock token'
 
2491
        translated_error = self.translateTuple(('TokenMismatch',), token=token)
 
2492
        expected_error = errors.TokenMismatch(token, '(remote token)')
 
2493
        self.assertEqual(expected_error, translated_error)
 
2494
 
 
2495
    def test_Diverged(self):
 
2496
        branch = self.make_branch('a')
 
2497
        other_branch = self.make_branch('b')
 
2498
        translated_error = self.translateTuple(
 
2499
            ('Diverged',), branch=branch, other_branch=other_branch)
 
2500
        expected_error = errors.DivergedBranches(branch, other_branch)
 
2501
        self.assertEqual(expected_error, translated_error)
 
2502
 
 
2503
    def test_ReadError_no_args(self):
 
2504
        path = 'a path'
 
2505
        translated_error = self.translateTuple(('ReadError',), path=path)
 
2506
        expected_error = errors.ReadError(path)
 
2507
        self.assertEqual(expected_error, translated_error)
 
2508
 
 
2509
    def test_ReadError(self):
 
2510
        path = 'a path'
 
2511
        translated_error = self.translateTuple(('ReadError', path))
 
2512
        expected_error = errors.ReadError(path)
 
2513
        self.assertEqual(expected_error, translated_error)
 
2514
 
 
2515
    def test_PermissionDenied_no_args(self):
 
2516
        path = 'a path'
 
2517
        translated_error = self.translateTuple(('PermissionDenied',), path=path)
 
2518
        expected_error = errors.PermissionDenied(path)
 
2519
        self.assertEqual(expected_error, translated_error)
 
2520
 
 
2521
    def test_PermissionDenied_one_arg(self):
 
2522
        path = 'a path'
 
2523
        translated_error = self.translateTuple(('PermissionDenied', path))
 
2524
        expected_error = errors.PermissionDenied(path)
 
2525
        self.assertEqual(expected_error, translated_error)
 
2526
 
 
2527
    def test_PermissionDenied_one_arg_and_context(self):
 
2528
        """Given a choice between a path from the local context and a path on
 
2529
        the wire, _translate_error prefers the path from the local context.
 
2530
        """
 
2531
        local_path = 'local path'
 
2532
        remote_path = 'remote path'
 
2533
        translated_error = self.translateTuple(
 
2534
            ('PermissionDenied', remote_path), path=local_path)
 
2535
        expected_error = errors.PermissionDenied(local_path)
 
2536
        self.assertEqual(expected_error, translated_error)
 
2537
 
 
2538
    def test_PermissionDenied_two_args(self):
 
2539
        path = 'a path'
 
2540
        extra = 'a string with extra info'
 
2541
        translated_error = self.translateTuple(
 
2542
            ('PermissionDenied', path, extra))
 
2543
        expected_error = errors.PermissionDenied(path, extra)
 
2544
        self.assertEqual(expected_error, translated_error)
 
2545
 
 
2546
 
 
2547
class TestErrorTranslationRobustness(TestErrorTranslationBase):
 
2548
    """Unit tests for bzrlib.remote._translate_error's robustness.
 
2549
 
 
2550
    TestErrorTranslationSuccess is for cases where _translate_error can
 
2551
    translate successfully.  This class about how _translate_err behaves when
 
2552
    it fails to translate: it re-raises the original error.
 
2553
    """
 
2554
 
 
2555
    def test_unrecognised_server_error(self):
 
2556
        """If the error code from the server is not recognised, the original
 
2557
        ErrorFromSmartServer is propagated unmodified.
 
2558
        """
 
2559
        error_tuple = ('An unknown error tuple',)
 
2560
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2561
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2562
        expected_error = errors.UnknownErrorFromSmartServer(server_error)
 
2563
        self.assertEqual(expected_error, translated_error)
 
2564
 
 
2565
    def test_context_missing_a_key(self):
 
2566
        """In case of a bug in the client, or perhaps an unexpected response
 
2567
        from a server, _translate_error returns the original error tuple from
 
2568
        the server and mutters a warning.
 
2569
        """
 
2570
        # To translate a NoSuchRevision error _translate_error needs a 'branch'
 
2571
        # in the context dict.  So let's give it an empty context dict instead
 
2572
        # to exercise its error recovery.
 
2573
        empty_context = {}
 
2574
        error_tuple = ('NoSuchRevision', 'revid')
 
2575
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2576
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2577
        self.assertEqual(server_error, translated_error)
 
2578
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
2579
        # been muttered to the log file for developer to look at.
 
2580
        self.assertContainsRe(
 
2581
            self._get_log(keep_log_file=True),
 
2582
            "Missing key 'branch' in context")
 
2583
 
 
2584
    def test_path_missing(self):
 
2585
        """Some translations (PermissionDenied, ReadError) can determine the
 
2586
        'path' variable from either the wire or the local context.  If neither
 
2587
        has it, then an error is raised.
 
2588
        """
 
2589
        error_tuple = ('ReadError',)
 
2590
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2591
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2592
        self.assertEqual(server_error, translated_error)
 
2593
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
2594
        # been muttered to the log file for developer to look at.
 
2595
        self.assertContainsRe(
 
2596
            self._get_log(keep_log_file=True), "Missing key 'path' in context")
 
2597
 
 
2598
 
 
2599
class TestStacking(tests.TestCaseWithTransport):
 
2600
    """Tests for operations on stacked remote repositories.
 
2601
 
 
2602
    The underlying format type must support stacking.
 
2603
    """
 
2604
 
 
2605
    def test_access_stacked_remote(self):
 
2606
        # based on <http://launchpad.net/bugs/261315>
 
2607
        # make a branch stacked on another repository containing an empty
 
2608
        # revision, then open it over hpss - we should be able to see that
 
2609
        # revision.
 
2610
        base_transport = self.get_transport()
 
2611
        base_builder = self.make_branch_builder('base', format='1.9')
 
2612
        base_builder.start_series()
 
2613
        base_revid = base_builder.build_snapshot('rev-id', None,
 
2614
            [('add', ('', None, 'directory', None))],
 
2615
            'message')
 
2616
        base_builder.finish_series()
 
2617
        stacked_branch = self.make_branch('stacked', format='1.9')
 
2618
        stacked_branch.set_stacked_on_url('../base')
 
2619
        # start a server looking at this
 
2620
        smart_server = server.SmartTCPServer_for_testing()
 
2621
        smart_server.setUp()
 
2622
        self.addCleanup(smart_server.tearDown)
 
2623
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
 
2624
        # can get its branch and repository
 
2625
        remote_branch = remote_bzrdir.open_branch()
 
2626
        remote_repo = remote_branch.repository
 
2627
        remote_repo.lock_read()
 
2628
        try:
 
2629
            # it should have an appropriate fallback repository, which should also
 
2630
            # be a RemoteRepository
 
2631
            self.assertLength(1, remote_repo._fallback_repositories)
 
2632
            self.assertIsInstance(remote_repo._fallback_repositories[0],
 
2633
                RemoteRepository)
 
2634
            # and it has the revision committed to the underlying repository;
 
2635
            # these have varying implementations so we try several of them
 
2636
            self.assertTrue(remote_repo.has_revisions([base_revid]))
 
2637
            self.assertTrue(remote_repo.has_revision(base_revid))
 
2638
            self.assertEqual(remote_repo.get_revision(base_revid).message,
 
2639
                'message')
 
2640
        finally:
 
2641
            remote_repo.unlock()
 
2642
 
 
2643
    def prepare_stacked_remote_branch(self):
 
2644
        """Get stacked_upon and stacked branches with content in each."""
 
2645
        self.setup_smart_server_with_call_log()
 
2646
        tree1 = self.make_branch_and_tree('tree1', format='1.9')
 
2647
        tree1.commit('rev1', rev_id='rev1')
 
2648
        tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
 
2649
            ).open_workingtree()
 
2650
        tree2.commit('local changes make me feel good.')
 
2651
        branch2 = Branch.open(self.get_url('tree2'))
 
2652
        branch2.lock_read()
 
2653
        self.addCleanup(branch2.unlock)
 
2654
        return tree1.branch, branch2
 
2655
 
 
2656
    def test_stacked_get_parent_map(self):
 
2657
        # the public implementation of get_parent_map obeys stacking
 
2658
        _, branch = self.prepare_stacked_remote_branch()
 
2659
        repo = branch.repository
 
2660
        self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
 
2661
 
 
2662
    def test_unstacked_get_parent_map(self):
 
2663
        # _unstacked_provider.get_parent_map ignores stacking
 
2664
        _, branch = self.prepare_stacked_remote_branch()
 
2665
        provider = branch.repository._unstacked_provider
 
2666
        self.assertEqual([], provider.get_parent_map(['rev1']).keys())
 
2667
 
 
2668
    def fetch_stream_to_rev_order(self, stream):
 
2669
        result = []
 
2670
        for kind, substream in stream:
 
2671
            if not kind == 'revisions':
 
2672
                list(substream)
 
2673
            else:
 
2674
                for content in substream:
 
2675
                    result.append(content.key[-1])
 
2676
        return result
 
2677
 
 
2678
    def get_ordered_revs(self, format, order):
 
2679
        """Get a list of the revisions in a stream to format format.
 
2680
 
 
2681
        :param format: The format of the target.
 
2682
        :param order: the order that target should have requested.
 
2683
        :result: The revision ids in the stream, in the order seen,
 
2684
            the topological order of revisions in the source.
 
2685
        """
 
2686
        unordered_format = bzrdir.format_registry.get(format)()
 
2687
        target_repository_format = unordered_format.repository_format
 
2688
        # Cross check
 
2689
        self.assertEqual(order, target_repository_format._fetch_order)
 
2690
        trunk, stacked = self.prepare_stacked_remote_branch()
 
2691
        source = stacked.repository._get_source(target_repository_format)
 
2692
        tip = stacked.last_revision()
 
2693
        revs = stacked.repository.get_ancestry(tip)
 
2694
        search = graph.PendingAncestryResult([tip], stacked.repository)
 
2695
        self.reset_smart_call_log()
 
2696
        stream = source.get_stream(search)
 
2697
        if None in revs:
 
2698
            revs.remove(None)
 
2699
        # We trust that if a revision is in the stream the rest of the new
 
2700
        # content for it is too, as per our main fetch tests; here we are
 
2701
        # checking that the revisions are actually included at all, and their
 
2702
        # order.
 
2703
        return self.fetch_stream_to_rev_order(stream), revs
 
2704
 
 
2705
    def test_stacked_get_stream_unordered(self):
 
2706
        # Repository._get_source.get_stream() from a stacked repository with
 
2707
        # unordered yields the full data from both stacked and stacked upon
 
2708
        # sources.
 
2709
        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
 
2710
        self.assertEqual(set(expected_revs), set(rev_ord))
 
2711
        # Getting unordered results should have made a streaming data request
 
2712
        # from the server, then one from the backing branch.
 
2713
        self.assertLength(2, self.hpss_calls)
 
2714
 
 
2715
    def test_stacked_get_stream_topological(self):
 
2716
        # Repository._get_source.get_stream() from a stacked repository with
 
2717
        # topological sorting yields the full data from both stacked and
 
2718
        # stacked upon sources in topological order.
 
2719
        rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
 
2720
        self.assertEqual(expected_revs, rev_ord)
 
2721
        # Getting topological sort requires VFS calls still
 
2722
        self.assertLength(12, self.hpss_calls)
 
2723
 
 
2724
    def test_stacked_get_stream_groupcompress(self):
 
2725
        # Repository._get_source.get_stream() from a stacked repository with
 
2726
        # groupcompress sorting yields the full data from both stacked and
 
2727
        # stacked upon sources in groupcompress order.
 
2728
        raise tests.TestSkipped('No groupcompress ordered format available')
 
2729
        rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
 
2730
        self.assertEqual(expected_revs, reversed(rev_ord))
 
2731
        # Getting unordered results should have made a streaming data request
 
2732
        # from the backing branch, and one from the stacked on branch.
 
2733
        self.assertLength(2, self.hpss_calls)
 
2734
 
 
2735
    def test_stacked_pull_more_than_stacking_has_bug_360791(self):
 
2736
        # When pulling some fixed amount of content that is more than the
 
2737
        # source has (because some is coming from a fallback branch, no error
 
2738
        # should be received. This was reported as bug 360791.
 
2739
        # Need three branches: a trunk, a stacked branch, and a preexisting
 
2740
        # branch pulling content from stacked and trunk.
 
2741
        self.setup_smart_server_with_call_log()
 
2742
        trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
 
2743
        r1 = trunk.commit('start')
 
2744
        stacked_branch = trunk.branch.create_clone_on_transport(
 
2745
            self.get_transport('stacked'), stacked_on=trunk.branch.base)
 
2746
        local = self.make_branch('local', format='1.9-rich-root')
 
2747
        local.repository.fetch(stacked_branch.repository,
 
2748
            stacked_branch.last_revision())
 
2749
 
 
2750
 
 
2751
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
 
2752
 
 
2753
    def setUp(self):
 
2754
        super(TestRemoteBranchEffort, self).setUp()
 
2755
        # Create a smart server that publishes whatever the backing VFS server
 
2756
        # does.
 
2757
        self.smart_server = server.SmartTCPServer_for_testing()
 
2758
        self.smart_server.setUp(self.get_server())
 
2759
        self.addCleanup(self.smart_server.tearDown)
 
2760
        # Log all HPSS calls into self.hpss_calls.
 
2761
        _SmartClient.hooks.install_named_hook(
 
2762
            'call', self.capture_hpss_call, None)
 
2763
        self.hpss_calls = []
 
2764
 
 
2765
    def capture_hpss_call(self, params):
 
2766
        self.hpss_calls.append(params.method)
 
2767
 
 
2768
    def test_copy_content_into_avoids_revision_history(self):
 
2769
        local = self.make_branch('local')
 
2770
        remote_backing_tree = self.make_branch_and_tree('remote')
 
2771
        remote_backing_tree.commit("Commit.")
 
2772
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
2773
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
2774
        local.repository.fetch(remote_branch.repository)
 
2775
        self.hpss_calls = []
 
2776
        remote_branch.copy_content_into(local)
 
2777
        self.assertFalse('Branch.revision_history' in self.hpss_calls)