/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/tests/test_remote.py

  • Committer: Jelmer Vernooij
  • Date: 2018-05-19 13:16:11 UTC
  • mto: (6968.4.3 git-archive)
  • mto: This revision was merged to the branch mainline in revision 6972.
  • Revision ID: jelmer@jelmer.uk-20180519131611-l9h9ud41j7qg1m03
Move tar/zip to breezy.archive.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2013, 2016 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 base64
 
27
import bz2
 
28
import zlib
 
29
 
 
30
from .. import (
 
31
    bencode,
 
32
    branch,
 
33
    config,
 
34
    controldir,
 
35
    errors,
 
36
    repository,
 
37
    tests,
 
38
    transport,
 
39
    treebuilder,
 
40
    )
 
41
from ..branch import Branch
 
42
from ..bzr import (
 
43
    bzrdir,
 
44
    inventory,
 
45
    inventory_delta,
 
46
    remote,
 
47
    versionedfile,
 
48
    vf_search,
 
49
    )
 
50
from ..bzr.bzrdir import (
 
51
    BzrDir,
 
52
    BzrDirFormat,
 
53
    )
 
54
from ..bzr import (
 
55
    RemoteBzrProber,
 
56
    )
 
57
from ..bzr.chk_serializer import chk_bencode_serializer
 
58
from ..bzr.remote import (
 
59
    RemoteBranch,
 
60
    RemoteBranchFormat,
 
61
    RemoteBzrDir,
 
62
    RemoteBzrDirFormat,
 
63
    RemoteRepository,
 
64
    RemoteRepositoryFormat,
 
65
    )
 
66
from ..bzr import groupcompress_repo, knitpack_repo
 
67
from ..revision import (
 
68
    NULL_REVISION,
 
69
    Revision,
 
70
    )
 
71
from ..sixish import (
 
72
    BytesIO,
 
73
    text_type,
 
74
    )
 
75
from ..bzr.smart import medium, request
 
76
from ..bzr.smart.client import _SmartClient
 
77
from ..bzr.smart.repository import (
 
78
    SmartServerRepositoryGetParentMap,
 
79
    SmartServerRepositoryGetStream_1_19,
 
80
    _stream_to_byte_stream,
 
81
    )
 
82
from . import (
 
83
    test_server,
 
84
    )
 
85
from .scenarios import load_tests_apply_scenarios
 
86
from ..transport.memory import MemoryTransport
 
87
from ..transport.remote import (
 
88
    RemoteTransport,
 
89
    RemoteSSHTransport,
 
90
    RemoteTCPTransport,
 
91
    )
 
92
 
 
93
 
 
94
load_tests = load_tests_apply_scenarios
 
95
 
 
96
 
 
97
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
 
98
 
 
99
    scenarios = [
 
100
        ('HPSS-v2',
 
101
            {'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
 
102
        ('HPSS-v3',
 
103
            {'transport_server': test_server.SmartTCPServer_for_testing})]
 
104
 
 
105
 
 
106
    def setUp(self):
 
107
        super(BasicRemoteObjectTests, self).setUp()
 
108
        self.transport = self.get_transport()
 
109
        # make a branch that can be opened over the smart transport
 
110
        self.local_wt = BzrDir.create_standalone_workingtree('.')
 
111
        self.addCleanup(self.transport.disconnect)
 
112
 
 
113
    def test_create_remote_bzrdir(self):
 
114
        b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
 
115
        self.assertIsInstance(b, BzrDir)
 
116
 
 
117
    def test_open_remote_branch(self):
 
118
        # open a standalone branch in the working directory
 
119
        b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
 
120
        branch = b.open_branch()
 
121
        self.assertIsInstance(branch, Branch)
 
122
 
 
123
    def test_remote_repository(self):
 
124
        b = BzrDir.open_from_transport(self.transport)
 
125
        repo = b.open_repository()
 
126
        revid = u'\xc823123123'.encode('utf8')
 
127
        self.assertFalse(repo.has_revision(revid))
 
128
        self.local_wt.commit(message='test commit', rev_id=revid)
 
129
        self.assertTrue(repo.has_revision(revid))
 
130
 
 
131
    def test_find_correct_format(self):
 
132
        """Should open a RemoteBzrDir over a RemoteTransport"""
 
133
        fmt = BzrDirFormat.find_format(self.transport)
 
134
        self.assertTrue(RemoteBzrProber
 
135
                        in controldir.ControlDirFormat._server_probers)
 
136
        self.assertIsInstance(fmt, RemoteBzrDirFormat)
 
137
 
 
138
    def test_open_detected_smart_format(self):
 
139
        fmt = BzrDirFormat.find_format(self.transport)
 
140
        d = fmt.open(self.transport)
 
141
        self.assertIsInstance(d, BzrDir)
 
142
 
 
143
    def test_remote_branch_repr(self):
 
144
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
145
        self.assertStartsWith(str(b), 'RemoteBranch(')
 
146
 
 
147
    def test_remote_bzrdir_repr(self):
 
148
        b = BzrDir.open_from_transport(self.transport)
 
149
        self.assertStartsWith(str(b), 'RemoteBzrDir(')
 
150
 
 
151
    def test_remote_branch_format_supports_stacking(self):
 
152
        t = self.transport
 
153
        self.make_branch('unstackable', format='pack-0.92')
 
154
        b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
 
155
        self.assertFalse(b._format.supports_stacking())
 
156
        self.make_branch('stackable', format='1.9')
 
157
        b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
 
158
        self.assertTrue(b._format.supports_stacking())
 
159
 
 
160
    def test_remote_repo_format_supports_external_references(self):
 
161
        t = self.transport
 
162
        bd = self.make_controldir('unstackable', format='pack-0.92')
 
163
        r = bd.create_repository()
 
164
        self.assertFalse(r._format.supports_external_lookups)
 
165
        r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
 
166
        self.assertFalse(r._format.supports_external_lookups)
 
167
        bd = self.make_controldir('stackable', format='1.9')
 
168
        r = bd.create_repository()
 
169
        self.assertTrue(r._format.supports_external_lookups)
 
170
        r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
 
171
        self.assertTrue(r._format.supports_external_lookups)
 
172
 
 
173
    def test_remote_branch_set_append_revisions_only(self):
 
174
        # Make a format 1.9 branch, which supports append_revisions_only
 
175
        branch = self.make_branch('branch', format='1.9')
 
176
        branch.set_append_revisions_only(True)
 
177
        config = branch.get_config_stack()
 
178
        self.assertEqual(
 
179
            True, config.get('append_revisions_only'))
 
180
        branch.set_append_revisions_only(False)
 
181
        config = branch.get_config_stack()
 
182
        self.assertEqual(
 
183
            False, config.get('append_revisions_only'))
 
184
 
 
185
    def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
 
186
        branch = self.make_branch('branch', format='knit')
 
187
        self.assertRaises(
 
188
            errors.UpgradeRequired, branch.set_append_revisions_only, True)
 
189
 
 
190
 
 
191
class FakeProtocol(object):
 
192
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
 
193
 
 
194
    def __init__(self, body, fake_client):
 
195
        self.body = body
 
196
        self._body_buffer = None
 
197
        self._fake_client = fake_client
 
198
 
 
199
    def read_body_bytes(self, count=-1):
 
200
        if self._body_buffer is None:
 
201
            self._body_buffer = BytesIO(self.body)
 
202
        bytes = self._body_buffer.read(count)
 
203
        if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
 
204
            self._fake_client.expecting_body = False
 
205
        return bytes
 
206
 
 
207
    def cancel_read_body(self):
 
208
        self._fake_client.expecting_body = False
 
209
 
 
210
    def read_streamed_body(self):
 
211
        return self.body
 
212
 
 
213
 
 
214
class FakeClient(_SmartClient):
 
215
    """Lookalike for _SmartClient allowing testing."""
 
216
 
 
217
    def __init__(self, fake_medium_base='fake base'):
 
218
        """Create a FakeClient."""
 
219
        self.responses = []
 
220
        self._calls = []
 
221
        self.expecting_body = False
 
222
        # if non-None, this is the list of expected calls, with only the
 
223
        # method name and arguments included.  the body might be hard to
 
224
        # compute so is not included. If a call is None, that call can
 
225
        # be anything.
 
226
        self._expected_calls = None
 
227
        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
 
228
 
 
229
    def add_expected_call(self, call_name, call_args, response_type,
 
230
        response_args, response_body=None):
 
231
        if self._expected_calls is None:
 
232
            self._expected_calls = []
 
233
        self._expected_calls.append((call_name, call_args))
 
234
        self.responses.append((response_type, response_args, response_body))
 
235
 
 
236
    def add_success_response(self, *args):
 
237
        self.responses.append((b'success', args, None))
 
238
 
 
239
    def add_success_response_with_body(self, body, *args):
 
240
        self.responses.append((b'success', args, body))
 
241
        if self._expected_calls is not None:
 
242
            self._expected_calls.append(None)
 
243
 
 
244
    def add_error_response(self, *args):
 
245
        self.responses.append((b'error', args))
 
246
 
 
247
    def add_unknown_method_response(self, verb):
 
248
        self.responses.append((b'unknown', verb))
 
249
 
 
250
    def finished_test(self):
 
251
        if self._expected_calls:
 
252
            raise AssertionError("%r finished but was still expecting %r"
 
253
                % (self, self._expected_calls[0]))
 
254
 
 
255
    def _get_next_response(self):
 
256
        try:
 
257
            response_tuple = self.responses.pop(0)
 
258
        except IndexError as e:
 
259
            raise AssertionError("%r didn't expect any more calls"
 
260
                % (self,))
 
261
        if response_tuple[0] == b'unknown':
 
262
            raise errors.UnknownSmartMethod(response_tuple[1])
 
263
        elif response_tuple[0] == b'error':
 
264
            raise errors.ErrorFromSmartServer(response_tuple[1])
 
265
        return response_tuple
 
266
 
 
267
    def _check_call(self, method, args):
 
268
        if self._expected_calls is None:
 
269
            # the test should be updated to say what it expects
 
270
            return
 
271
        try:
 
272
            next_call = self._expected_calls.pop(0)
 
273
        except IndexError:
 
274
            raise AssertionError("%r didn't expect any more calls "
 
275
                "but got %r%r"
 
276
                % (self, method, args,))
 
277
        if next_call is None:
 
278
            return
 
279
        if method != next_call[0] or args != next_call[1]:
 
280
            raise AssertionError("%r expected %r%r "
 
281
                "but got %r%r"
 
282
                % (self, next_call[0], next_call[1], method, args,))
 
283
 
 
284
    def call(self, method, *args):
 
285
        self._check_call(method, args)
 
286
        self._calls.append(('call', method, args))
 
287
        return self._get_next_response()[1]
 
288
 
 
289
    def call_expecting_body(self, method, *args):
 
290
        self._check_call(method, args)
 
291
        self._calls.append(('call_expecting_body', method, args))
 
292
        result = self._get_next_response()
 
293
        self.expecting_body = True
 
294
        return result[1], FakeProtocol(result[2], self)
 
295
 
 
296
    def call_with_body_bytes(self, method, args, body):
 
297
        self._check_call(method, args)
 
298
        self._calls.append(('call_with_body_bytes', method, args, body))
 
299
        result = self._get_next_response()
 
300
        return result[1], FakeProtocol(result[2], self)
 
301
 
 
302
    def call_with_body_bytes_expecting_body(self, method, args, body):
 
303
        self._check_call(method, args)
 
304
        self._calls.append(('call_with_body_bytes_expecting_body', method,
 
305
            args, body))
 
306
        result = self._get_next_response()
 
307
        self.expecting_body = True
 
308
        return result[1], FakeProtocol(result[2], self)
 
309
 
 
310
    def call_with_body_stream(self, args, stream):
 
311
        # Explicitly consume the stream before checking for an error, because
 
312
        # that's what happens a real medium.
 
313
        stream = list(stream)
 
314
        self._check_call(args[0], args[1:])
 
315
        self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
 
316
        result = self._get_next_response()
 
317
        # The second value returned from call_with_body_stream is supposed to
 
318
        # be a response_handler object, but so far no tests depend on that.
 
319
        response_handler = None 
 
320
        return result[1], response_handler
 
321
 
 
322
 
 
323
class FakeMedium(medium.SmartClientMedium):
 
324
 
 
325
    def __init__(self, client_calls, base):
 
326
        medium.SmartClientMedium.__init__(self, base)
 
327
        self._client_calls = client_calls
 
328
 
 
329
    def disconnect(self):
 
330
        self._client_calls.append(('disconnect medium',))
 
331
 
 
332
 
 
333
class TestVfsHas(tests.TestCase):
 
334
 
 
335
    def test_unicode_path(self):
 
336
        client = FakeClient('/')
 
337
        client.add_success_response(b'yes',)
 
338
        transport = RemoteTransport('bzr://localhost/', _client=client)
 
339
        filename = u'/hell\u00d8'.encode('utf-8')
 
340
        result = transport.has(filename)
 
341
        self.assertEqual(
 
342
            [('call', b'has', (filename,))],
 
343
            client._calls)
 
344
        self.assertTrue(result)
 
345
 
 
346
 
 
347
class TestRemote(tests.TestCaseWithMemoryTransport):
 
348
 
 
349
    def get_branch_format(self):
 
350
        reference_bzrdir_format = controldir.format_registry.get('default')()
 
351
        return reference_bzrdir_format.get_branch_format()
 
352
 
 
353
    def get_repo_format(self):
 
354
        reference_bzrdir_format = controldir.format_registry.get('default')()
 
355
        return reference_bzrdir_format.repository_format
 
356
 
 
357
    def assertFinished(self, fake_client):
 
358
        """Assert that all of a FakeClient's expected calls have occurred."""
 
359
        fake_client.finished_test()
 
360
 
 
361
 
 
362
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
 
363
    """Tests for the behaviour of client_medium.remote_path_from_transport."""
 
364
 
 
365
    def assertRemotePath(self, expected, client_base, transport_base):
 
366
        """Assert that the result of
 
367
        SmartClientMedium.remote_path_from_transport is the expected value for
 
368
        a given client_base and transport_base.
 
369
        """
 
370
        client_medium = medium.SmartClientMedium(client_base)
 
371
        t = transport.get_transport(transport_base)
 
372
        result = client_medium.remote_path_from_transport(t)
 
373
        self.assertEqual(expected, result)
 
374
 
 
375
    def test_remote_path_from_transport(self):
 
376
        """SmartClientMedium.remote_path_from_transport calculates a URL for
 
377
        the given transport relative to the root of the client base URL.
 
378
        """
 
379
        self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
 
380
        self.assertRemotePath(
 
381
            'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
 
382
 
 
383
    def assertRemotePathHTTP(self, expected, transport_base, relpath):
 
384
        """Assert that the result of
 
385
        HttpTransportBase.remote_path_from_transport is the expected value for
 
386
        a given transport_base and relpath of that transport.  (Note that
 
387
        HttpTransportBase is a subclass of SmartClientMedium)
 
388
        """
 
389
        base_transport = transport.get_transport(transport_base)
 
390
        client_medium = base_transport.get_smart_medium()
 
391
        cloned_transport = base_transport.clone(relpath)
 
392
        result = client_medium.remote_path_from_transport(cloned_transport)
 
393
        self.assertEqual(expected, result)
 
394
 
 
395
    def test_remote_path_from_transport_http(self):
 
396
        """Remote paths for HTTP transports are calculated differently to other
 
397
        transports.  They are just relative to the client base, not the root
 
398
        directory of the host.
 
399
        """
 
400
        for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
 
401
            self.assertRemotePathHTTP(
 
402
                '../xyz/', scheme + '//host/path', '../xyz/')
 
403
            self.assertRemotePathHTTP(
 
404
                'xyz/', scheme + '//host/path', 'xyz/')
 
405
 
 
406
 
 
407
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
 
408
    """Tests for the behaviour of client_medium.remote_is_at_least."""
 
409
 
 
410
    def test_initially_unlimited(self):
 
411
        """A fresh medium assumes that the remote side supports all
 
412
        versions.
 
413
        """
 
414
        client_medium = medium.SmartClientMedium('dummy base')
 
415
        self.assertFalse(client_medium._is_remote_before((99, 99)))
 
416
 
 
417
    def test__remember_remote_is_before(self):
 
418
        """Calling _remember_remote_is_before ratchets down the known remote
 
419
        version.
 
420
        """
 
421
        client_medium = medium.SmartClientMedium('dummy base')
 
422
        # Mark the remote side as being less than 1.6.  The remote side may
 
423
        # still be 1.5.
 
424
        client_medium._remember_remote_is_before((1, 6))
 
425
        self.assertTrue(client_medium._is_remote_before((1, 6)))
 
426
        self.assertFalse(client_medium._is_remote_before((1, 5)))
 
427
        # Calling _remember_remote_is_before again with a lower value works.
 
428
        client_medium._remember_remote_is_before((1, 5))
 
429
        self.assertTrue(client_medium._is_remote_before((1, 5)))
 
430
        # If you call _remember_remote_is_before with a higher value it logs a
 
431
        # warning, and continues to remember the lower value.
 
432
        self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
 
433
        client_medium._remember_remote_is_before((1, 9))
 
434
        self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
 
435
        self.assertTrue(client_medium._is_remote_before((1, 5)))
 
436
 
 
437
 
 
438
class TestBzrDirCloningMetaDir(TestRemote):
 
439
 
 
440
    def test_backwards_compat(self):
 
441
        self.setup_smart_server_with_call_log()
 
442
        a_dir = self.make_controldir('.')
 
443
        self.reset_smart_call_log()
 
444
        verb = b'BzrDir.cloning_metadir'
 
445
        self.disable_verb(verb)
 
446
        format = a_dir.cloning_metadir()
 
447
        call_count = len([call for call in self.hpss_calls if
 
448
            call.call.method == verb])
 
449
        self.assertEqual(1, call_count)
 
450
 
 
451
    def test_branch_reference(self):
 
452
        transport = self.get_transport('quack')
 
453
        referenced = self.make_branch('referenced')
 
454
        expected = referenced.controldir.cloning_metadir()
 
455
        client = FakeClient(transport.base)
 
456
        client.add_expected_call(
 
457
            b'BzrDir.cloning_metadir', (b'quack/', b'False'),
 
458
            b'error', (b'BranchReference',)),
 
459
        client.add_expected_call(
 
460
            b'BzrDir.open_branchV3', (b'quack/',),
 
461
            b'success', (b'ref', self.get_url('referenced'))),
 
462
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
463
            _client=client)
 
464
        result = a_controldir.cloning_metadir()
 
465
        # We should have got a control dir matching the referenced branch.
 
466
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
 
467
        self.assertEqual(expected._repository_format, result._repository_format)
 
468
        self.assertEqual(expected._branch_format, result._branch_format)
 
469
        self.assertFinished(client)
 
470
 
 
471
    def test_current_server(self):
 
472
        transport = self.get_transport('.')
 
473
        transport = transport.clone('quack')
 
474
        self.make_controldir('quack')
 
475
        client = FakeClient(transport.base)
 
476
        reference_bzrdir_format = controldir.format_registry.get('default')()
 
477
        control_name = reference_bzrdir_format.network_name()
 
478
        client.add_expected_call(
 
479
            b'BzrDir.cloning_metadir', (b'quack/', b'False'),
 
480
            b'success', (control_name, b'', (b'branch', b''))),
 
481
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
482
            _client=client)
 
483
        result = a_controldir.cloning_metadir()
 
484
        # We should have got a reference control dir with default branch and
 
485
        # repository formats.
 
486
        # This pokes a little, just to be sure.
 
487
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
 
488
        self.assertEqual(None, result._repository_format)
 
489
        self.assertEqual(None, result._branch_format)
 
490
        self.assertFinished(client)
 
491
 
 
492
    def test_unknown(self):
 
493
        transport = self.get_transport('quack')
 
494
        referenced = self.make_branch('referenced')
 
495
        expected = referenced.controldir.cloning_metadir()
 
496
        client = FakeClient(transport.base)
 
497
        client.add_expected_call(
 
498
            b'BzrDir.cloning_metadir', (b'quack/', b'False'),
 
499
            b'success', (b'unknown', b'unknown', (b'branch', b''))),
 
500
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
501
            _client=client)
 
502
        self.assertRaises(errors.UnknownFormatError, a_controldir.cloning_metadir)
 
503
 
 
504
 
 
505
class TestBzrDirCheckoutMetaDir(TestRemote):
 
506
 
 
507
    def test__get_checkout_format(self):
 
508
        transport = MemoryTransport()
 
509
        client = FakeClient(transport.base)
 
510
        reference_bzrdir_format = controldir.format_registry.get('default')()
 
511
        control_name = reference_bzrdir_format.network_name()
 
512
        client.add_expected_call(
 
513
            b'BzrDir.checkout_metadir', (b'quack/', ),
 
514
            b'success', (control_name, b'', b''))
 
515
        transport.mkdir('quack')
 
516
        transport = transport.clone('quack')
 
517
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
518
            _client=client)
 
519
        result = a_controldir.checkout_metadir()
 
520
        # We should have got a reference control dir with default branch and
 
521
        # repository formats.
 
522
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
 
523
        self.assertEqual(None, result._repository_format)
 
524
        self.assertEqual(None, result._branch_format)
 
525
        self.assertFinished(client)
 
526
 
 
527
    def test_unknown_format(self):
 
528
        transport = MemoryTransport()
 
529
        client = FakeClient(transport.base)
 
530
        client.add_expected_call(
 
531
            b'BzrDir.checkout_metadir', (b'quack/',),
 
532
            b'success', (b'dontknow', b'', b''))
 
533
        transport.mkdir('quack')
 
534
        transport = transport.clone('quack')
 
535
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
536
            _client=client)
 
537
        self.assertRaises(errors.UnknownFormatError,
 
538
            a_controldir.checkout_metadir)
 
539
        self.assertFinished(client)
 
540
 
 
541
 
 
542
class TestBzrDirGetBranches(TestRemote):
 
543
 
 
544
    def test_get_branches(self):
 
545
        transport = MemoryTransport()
 
546
        client = FakeClient(transport.base)
 
547
        reference_bzrdir_format = controldir.format_registry.get('default')()
 
548
        branch_name = reference_bzrdir_format.get_branch_format().network_name()
 
549
        client.add_success_response_with_body(
 
550
            bencode.bencode({
 
551
                b"foo": (b"branch", branch_name),
 
552
                b"": (b"branch", branch_name)}), b"success")
 
553
        client.add_success_response(
 
554
            b'ok', b'', b'no', b'no', b'no',
 
555
                reference_bzrdir_format.repository_format.network_name())
 
556
        client.add_error_response(b'NotStacked')
 
557
        client.add_success_response(
 
558
            b'ok', b'', b'no', b'no', b'no',
 
559
                reference_bzrdir_format.repository_format.network_name())
 
560
        client.add_error_response(b'NotStacked')
 
561
        transport.mkdir('quack')
 
562
        transport = transport.clone('quack')
 
563
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
564
            _client=client)
 
565
        result = a_controldir.get_branches()
 
566
        self.assertEqual({b"", b"foo"}, set(result.keys()))
 
567
        self.assertEqual(
 
568
            [('call_expecting_body', b'BzrDir.get_branches', (b'quack/',)),
 
569
             ('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
 
570
             ('call', b'Branch.get_stacked_on_url', (b'quack/', )),
 
571
             ('call', b'BzrDir.find_repositoryV3', (b'quack/', )),
 
572
             ('call', b'Branch.get_stacked_on_url', (b'quack/', ))],
 
573
            client._calls)
 
574
 
 
575
 
 
576
class TestBzrDirDestroyBranch(TestRemote):
 
577
 
 
578
    def test_destroy_default(self):
 
579
        transport = self.get_transport('quack')
 
580
        referenced = self.make_branch('referenced')
 
581
        client = FakeClient(transport.base)
 
582
        client.add_expected_call(
 
583
            b'BzrDir.destroy_branch', (b'quack/', ),
 
584
            b'success', (b'ok',)),
 
585
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
586
            _client=client)
 
587
        a_controldir.destroy_branch()
 
588
        self.assertFinished(client)
 
589
 
 
590
 
 
591
class TestBzrDirHasWorkingTree(TestRemote):
 
592
 
 
593
    def test_has_workingtree(self):
 
594
        transport = self.get_transport('quack')
 
595
        client = FakeClient(transport.base)
 
596
        client.add_expected_call(
 
597
            b'BzrDir.has_workingtree', (b'quack/',),
 
598
            b'success', (b'yes',)),
 
599
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
600
            _client=client)
 
601
        self.assertTrue(a_controldir.has_workingtree())
 
602
        self.assertFinished(client)
 
603
 
 
604
    def test_no_workingtree(self):
 
605
        transport = self.get_transport('quack')
 
606
        client = FakeClient(transport.base)
 
607
        client.add_expected_call(
 
608
            b'BzrDir.has_workingtree', (b'quack/',),
 
609
            b'success', (b'no',)),
 
610
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
611
            _client=client)
 
612
        self.assertFalse(a_controldir.has_workingtree())
 
613
        self.assertFinished(client)
 
614
 
 
615
 
 
616
class TestBzrDirDestroyRepository(TestRemote):
 
617
 
 
618
    def test_destroy_repository(self):
 
619
        transport = self.get_transport('quack')
 
620
        client = FakeClient(transport.base)
 
621
        client.add_expected_call(
 
622
            b'BzrDir.destroy_repository', (b'quack/',),
 
623
            b'success', (b'ok',)),
 
624
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
625
            _client=client)
 
626
        a_controldir.destroy_repository()
 
627
        self.assertFinished(client)
 
628
 
 
629
 
 
630
class TestBzrDirOpen(TestRemote):
 
631
 
 
632
    def make_fake_client_and_transport(self, path='quack'):
 
633
        transport = MemoryTransport()
 
634
        transport.mkdir(path)
 
635
        transport = transport.clone(path)
 
636
        client = FakeClient(transport.base)
 
637
        return client, transport
 
638
 
 
639
    def test_absent(self):
 
640
        client, transport = self.make_fake_client_and_transport()
 
641
        client.add_expected_call(
 
642
            b'BzrDir.open_2.1', (b'quack/',), b'success', (b'no',))
 
643
        self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
 
644
                RemoteBzrDirFormat(), _client=client, _force_probe=True)
 
645
        self.assertFinished(client)
 
646
 
 
647
    def test_present_without_workingtree(self):
 
648
        client, transport = self.make_fake_client_and_transport()
 
649
        client.add_expected_call(
 
650
            b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'no'))
 
651
        bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
652
            _client=client, _force_probe=True)
 
653
        self.assertIsInstance(bd, RemoteBzrDir)
 
654
        self.assertFalse(bd.has_workingtree())
 
655
        self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
 
656
        self.assertFinished(client)
 
657
 
 
658
    def test_present_with_workingtree(self):
 
659
        client, transport = self.make_fake_client_and_transport()
 
660
        client.add_expected_call(
 
661
            b'BzrDir.open_2.1', (b'quack/',), b'success', (b'yes', b'yes'))
 
662
        bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
663
            _client=client, _force_probe=True)
 
664
        self.assertIsInstance(bd, RemoteBzrDir)
 
665
        self.assertTrue(bd.has_workingtree())
 
666
        self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
 
667
        self.assertFinished(client)
 
668
 
 
669
    def test_backwards_compat(self):
 
670
        client, transport = self.make_fake_client_and_transport()
 
671
        client.add_expected_call(
 
672
            b'BzrDir.open_2.1', (b'quack/',), b'unknown', (b'BzrDir.open_2.1',))
 
673
        client.add_expected_call(
 
674
            b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
 
675
        bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
676
            _client=client, _force_probe=True)
 
677
        self.assertIsInstance(bd, RemoteBzrDir)
 
678
        self.assertFinished(client)
 
679
 
 
680
    def test_backwards_compat_hpss_v2(self):
 
681
        client, transport = self.make_fake_client_and_transport()
 
682
        # Monkey-patch fake client to simulate real-world behaviour with v2
 
683
        # server: upon first RPC call detect the protocol version, and because
 
684
        # the version is 2 also do _remember_remote_is_before((1, 6)) before
 
685
        # continuing with the RPC.
 
686
        orig_check_call = client._check_call
 
687
        def check_call(method, args):
 
688
            client._medium._protocol_version = 2
 
689
            client._medium._remember_remote_is_before((1, 6))
 
690
            client._check_call = orig_check_call
 
691
            client._check_call(method, args)
 
692
        client._check_call = check_call
 
693
        client.add_expected_call(
 
694
            b'BzrDir.open_2.1', (b'quack/',), b'unknown', (b'BzrDir.open_2.1',))
 
695
        client.add_expected_call(
 
696
            b'BzrDir.open', (b'quack/',), b'success', (b'yes',))
 
697
        bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
698
            _client=client, _force_probe=True)
 
699
        self.assertIsInstance(bd, RemoteBzrDir)
 
700
        self.assertFinished(client)
 
701
 
 
702
 
 
703
class TestBzrDirOpenBranch(TestRemote):
 
704
 
 
705
    def test_backwards_compat(self):
 
706
        self.setup_smart_server_with_call_log()
 
707
        self.make_branch('.')
 
708
        a_dir = BzrDir.open(self.get_url('.'))
 
709
        self.reset_smart_call_log()
 
710
        verb = b'BzrDir.open_branchV3'
 
711
        self.disable_verb(verb)
 
712
        format = a_dir.open_branch()
 
713
        call_count = len([call for call in self.hpss_calls if
 
714
            call.call.method == verb])
 
715
        self.assertEqual(1, call_count)
 
716
 
 
717
    def test_branch_present(self):
 
718
        reference_format = self.get_repo_format()
 
719
        network_name = reference_format.network_name()
 
720
        branch_network_name = self.get_branch_format().network_name()
 
721
        transport = MemoryTransport()
 
722
        transport.mkdir('quack')
 
723
        transport = transport.clone('quack')
 
724
        client = FakeClient(transport.base)
 
725
        client.add_expected_call(
 
726
            b'BzrDir.open_branchV3', (b'quack/',),
 
727
            b'success', (b'branch', branch_network_name))
 
728
        client.add_expected_call(
 
729
            b'BzrDir.find_repositoryV3', (b'quack/',),
 
730
            b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
 
731
        client.add_expected_call(
 
732
            b'Branch.get_stacked_on_url', (b'quack/',),
 
733
            b'error', (b'NotStacked',))
 
734
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
735
            _client=client)
 
736
        result = bzrdir.open_branch()
 
737
        self.assertIsInstance(result, RemoteBranch)
 
738
        self.assertEqual(bzrdir, result.controldir)
 
739
        self.assertFinished(client)
 
740
 
 
741
    def test_branch_missing(self):
 
742
        transport = MemoryTransport()
 
743
        transport.mkdir('quack')
 
744
        transport = transport.clone('quack')
 
745
        client = FakeClient(transport.base)
 
746
        client.add_error_response(b'nobranch')
 
747
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
748
            _client=client)
 
749
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
 
750
        self.assertEqual(
 
751
            [('call', b'BzrDir.open_branchV3', (b'quack/',))],
 
752
            client._calls)
 
753
 
 
754
    def test__get_tree_branch(self):
 
755
        # _get_tree_branch is a form of open_branch, but it should only ask for
 
756
        # branch opening, not any other network requests.
 
757
        calls = []
 
758
        def open_branch(name=None, possible_transports=None):
 
759
            calls.append("Called")
 
760
            return "a-branch"
 
761
        transport = MemoryTransport()
 
762
        # no requests on the network - catches other api calls being made.
 
763
        client = FakeClient(transport.base)
 
764
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
765
            _client=client)
 
766
        # patch the open_branch call to record that it was called.
 
767
        bzrdir.open_branch = open_branch
 
768
        self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
 
769
        self.assertEqual(["Called"], calls)
 
770
        self.assertEqual([], client._calls)
 
771
 
 
772
    def test_url_quoting_of_path(self):
 
773
        # Relpaths on the wire should not be URL-escaped.  So "~" should be
 
774
        # transmitted as "~", not "%7E".
 
775
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
 
776
        client = FakeClient(transport.base)
 
777
        reference_format = self.get_repo_format()
 
778
        network_name = reference_format.network_name()
 
779
        branch_network_name = self.get_branch_format().network_name()
 
780
        client.add_expected_call(
 
781
            b'BzrDir.open_branchV3', (b'~hello/',),
 
782
            b'success', (b'branch', branch_network_name))
 
783
        client.add_expected_call(
 
784
            b'BzrDir.find_repositoryV3', (b'~hello/',),
 
785
            b'success', (b'ok', b'', b'no', b'no', b'no', network_name))
 
786
        client.add_expected_call(
 
787
            b'Branch.get_stacked_on_url', (b'~hello/',),
 
788
            b'error', (b'NotStacked',))
 
789
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
790
            _client=client)
 
791
        result = bzrdir.open_branch()
 
792
        self.assertFinished(client)
 
793
 
 
794
    def check_open_repository(self, rich_root, subtrees, external_lookup=b'no'):
 
795
        reference_format = self.get_repo_format()
 
796
        network_name = reference_format.network_name()
 
797
        transport = MemoryTransport()
 
798
        transport.mkdir('quack')
 
799
        transport = transport.clone('quack')
 
800
        if rich_root:
 
801
            rich_response = b'yes'
 
802
        else:
 
803
            rich_response = b'no'
 
804
        if subtrees:
 
805
            subtree_response = b'yes'
 
806
        else:
 
807
            subtree_response = b'no'
 
808
        client = FakeClient(transport.base)
 
809
        client.add_success_response(
 
810
            b'ok', b'', rich_response, subtree_response, external_lookup,
 
811
            network_name)
 
812
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
813
            _client=client)
 
814
        result = bzrdir.open_repository()
 
815
        self.assertEqual(
 
816
            [('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
 
817
            client._calls)
 
818
        self.assertIsInstance(result, RemoteRepository)
 
819
        self.assertEqual(bzrdir, result.controldir)
 
820
        self.assertEqual(rich_root, result._format.rich_root_data)
 
821
        self.assertEqual(subtrees, result._format.supports_tree_reference)
 
822
 
 
823
    def test_open_repository_sets_format_attributes(self):
 
824
        self.check_open_repository(True, True)
 
825
        self.check_open_repository(False, True)
 
826
        self.check_open_repository(True, False)
 
827
        self.check_open_repository(False, False)
 
828
        self.check_open_repository(False, False, b'yes')
 
829
 
 
830
    def test_old_server(self):
 
831
        """RemoteBzrDirFormat should fail to probe if the server version is too
 
832
        old.
 
833
        """
 
834
        self.assertRaises(errors.NotBranchError,
 
835
            RemoteBzrProber.probe_transport, OldServerTransport())
 
836
 
 
837
 
 
838
class TestBzrDirCreateBranch(TestRemote):
 
839
 
 
840
    def test_backwards_compat(self):
 
841
        self.setup_smart_server_with_call_log()
 
842
        repo = self.make_repository('.')
 
843
        self.reset_smart_call_log()
 
844
        self.disable_verb(b'BzrDir.create_branch')
 
845
        branch = repo.controldir.create_branch()
 
846
        create_branch_call_count = len([call for call in self.hpss_calls if
 
847
            call.call.method == b'BzrDir.create_branch'])
 
848
        self.assertEqual(1, create_branch_call_count)
 
849
 
 
850
    def test_current_server(self):
 
851
        transport = self.get_transport('.')
 
852
        transport = transport.clone('quack')
 
853
        self.make_repository('quack')
 
854
        client = FakeClient(transport.base)
 
855
        reference_bzrdir_format = controldir.format_registry.get('default')()
 
856
        reference_format = reference_bzrdir_format.get_branch_format()
 
857
        network_name = reference_format.network_name()
 
858
        reference_repo_fmt = reference_bzrdir_format.repository_format
 
859
        reference_repo_name = reference_repo_fmt.network_name()
 
860
        client.add_expected_call(
 
861
            b'BzrDir.create_branch', (b'quack/', network_name),
 
862
            b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
 
863
            reference_repo_name))
 
864
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
865
            _client=client)
 
866
        branch = a_controldir.create_branch()
 
867
        # We should have got a remote branch
 
868
        self.assertIsInstance(branch, remote.RemoteBranch)
 
869
        # its format should have the settings from the response
 
870
        format = branch._format
 
871
        self.assertEqual(network_name, format.network_name())
 
872
 
 
873
    def test_already_open_repo_and_reused_medium(self):
 
874
        """Bug 726584: create_branch(..., repository=repo) should work
 
875
        regardless of what the smart medium's base URL is.
 
876
        """
 
877
        self.transport_server = test_server.SmartTCPServer_for_testing
 
878
        transport = self.get_transport('.')
 
879
        repo = self.make_repository('quack')
 
880
        # Client's medium rooted a transport root (not at the bzrdir)
 
881
        client = FakeClient(transport.base)
 
882
        transport = transport.clone('quack')
 
883
        reference_bzrdir_format = controldir.format_registry.get('default')()
 
884
        reference_format = reference_bzrdir_format.get_branch_format()
 
885
        network_name = reference_format.network_name()
 
886
        reference_repo_fmt = reference_bzrdir_format.repository_format
 
887
        reference_repo_name = reference_repo_fmt.network_name()
 
888
        client.add_expected_call(
 
889
            b'BzrDir.create_branch', (b'extra/quack/', network_name),
 
890
            b'success', (b'ok', network_name, b'', b'no', b'no', b'yes',
 
891
            reference_repo_name))
 
892
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
893
            _client=client)
 
894
        branch = a_controldir.create_branch(repository=repo)
 
895
        # We should have got a remote branch
 
896
        self.assertIsInstance(branch, remote.RemoteBranch)
 
897
        # its format should have the settings from the response
 
898
        format = branch._format
 
899
        self.assertEqual(network_name, format.network_name())
 
900
 
 
901
 
 
902
class TestBzrDirCreateRepository(TestRemote):
 
903
 
 
904
    def test_backwards_compat(self):
 
905
        self.setup_smart_server_with_call_log()
 
906
        bzrdir = self.make_controldir('.')
 
907
        self.reset_smart_call_log()
 
908
        self.disable_verb(b'BzrDir.create_repository')
 
909
        repo = bzrdir.create_repository()
 
910
        create_repo_call_count = len([call for call in self.hpss_calls if
 
911
            call.call.method == b'BzrDir.create_repository'])
 
912
        self.assertEqual(1, create_repo_call_count)
 
913
 
 
914
    def test_current_server(self):
 
915
        transport = self.get_transport('.')
 
916
        transport = transport.clone('quack')
 
917
        self.make_controldir('quack')
 
918
        client = FakeClient(transport.base)
 
919
        reference_bzrdir_format = controldir.format_registry.get('default')()
 
920
        reference_format = reference_bzrdir_format.repository_format
 
921
        network_name = reference_format.network_name()
 
922
        client.add_expected_call(
 
923
            b'BzrDir.create_repository', (b'quack/',
 
924
                b'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
925
                b'False'),
 
926
            b'success', (b'ok', b'yes', b'yes', b'yes', network_name))
 
927
        a_controldir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
928
            _client=client)
 
929
        repo = a_controldir.create_repository()
 
930
        # We should have got a remote repository
 
931
        self.assertIsInstance(repo, remote.RemoteRepository)
 
932
        # its format should have the settings from the response
 
933
        format = repo._format
 
934
        self.assertTrue(format.rich_root_data)
 
935
        self.assertTrue(format.supports_tree_reference)
 
936
        self.assertTrue(format.supports_external_lookups)
 
937
        self.assertEqual(network_name, format.network_name())
 
938
 
 
939
 
 
940
class TestBzrDirOpenRepository(TestRemote):
 
941
 
 
942
    def test_backwards_compat_1_2_3(self):
 
943
        # fallback all the way to the first version.
 
944
        reference_format = self.get_repo_format()
 
945
        network_name = reference_format.network_name()
 
946
        server_url = 'bzr://example.com/'
 
947
        self.permit_url(server_url)
 
948
        client = FakeClient(server_url)
 
949
        client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
 
950
        client.add_unknown_method_response(b'BzrDir.find_repositoryV2')
 
951
        client.add_success_response(b'ok', b'', b'no', b'no')
 
952
        # A real repository instance will be created to determine the network
 
953
        # name.
 
954
        client.add_success_response_with_body(
 
955
            b"Bazaar-NG meta directory, format 1\n", b'ok')
 
956
        client.add_success_response(b'stat', b'0', b'65535')
 
957
        client.add_success_response_with_body(
 
958
            reference_format.get_format_string(), b'ok')
 
959
        # PackRepository wants to do a stat
 
960
        client.add_success_response(b'stat', b'0', b'65535')
 
961
        remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
 
962
            _client=client)
 
963
        bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
 
964
            _client=client)
 
965
        repo = bzrdir.open_repository()
 
966
        self.assertEqual(
 
967
            [('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
 
968
             ('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
 
969
             ('call', b'BzrDir.find_repository', (b'quack/',)),
 
970
             ('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
 
971
             ('call', b'stat', (b'/quack/.bzr',)),
 
972
             ('call_expecting_body', b'get', (b'/quack/.bzr/repository/format',)),
 
973
             ('call', b'stat', (b'/quack/.bzr/repository',)),
 
974
             ],
 
975
            client._calls)
 
976
        self.assertEqual(network_name, repo._format.network_name())
 
977
 
 
978
    def test_backwards_compat_2(self):
 
979
        # fallback to find_repositoryV2
 
980
        reference_format = self.get_repo_format()
 
981
        network_name = reference_format.network_name()
 
982
        server_url = 'bzr://example.com/'
 
983
        self.permit_url(server_url)
 
984
        client = FakeClient(server_url)
 
985
        client.add_unknown_method_response(b'BzrDir.find_repositoryV3')
 
986
        client.add_success_response(b'ok', b'', b'no', b'no', b'no')
 
987
        # A real repository instance will be created to determine the network
 
988
        # name.
 
989
        client.add_success_response_with_body(
 
990
            b"Bazaar-NG meta directory, format 1\n", b'ok')
 
991
        client.add_success_response(b'stat', b'0', b'65535')
 
992
        client.add_success_response_with_body(
 
993
            reference_format.get_format_string(), b'ok')
 
994
        # PackRepository wants to do a stat
 
995
        client.add_success_response(b'stat', b'0', b'65535')
 
996
        remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
 
997
            _client=client)
 
998
        bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
 
999
            _client=client)
 
1000
        repo = bzrdir.open_repository()
 
1001
        self.assertEqual(
 
1002
            [('call', b'BzrDir.find_repositoryV3', (b'quack/',)),
 
1003
             ('call', b'BzrDir.find_repositoryV2', (b'quack/',)),
 
1004
             ('call_expecting_body', b'get', (b'/quack/.bzr/branch-format',)),
 
1005
             ('call', b'stat', (b'/quack/.bzr',)),
 
1006
             ('call_expecting_body', b'get', (b'/quack/.bzr/repository/format',)),
 
1007
             ('call', b'stat', (b'/quack/.bzr/repository',)),
 
1008
             ],
 
1009
            client._calls)
 
1010
        self.assertEqual(network_name, repo._format.network_name())
 
1011
 
 
1012
    def test_current_server(self):
 
1013
        reference_format = self.get_repo_format()
 
1014
        network_name = reference_format.network_name()
 
1015
        transport = MemoryTransport()
 
1016
        transport.mkdir('quack')
 
1017
        transport = transport.clone('quack')
 
1018
        client = FakeClient(transport.base)
 
1019
        client.add_success_response(b'ok', b'', b'no', b'no', b'no', network_name)
 
1020
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
1021
            _client=client)
 
1022
        repo = bzrdir.open_repository()
 
1023
        self.assertEqual(
 
1024
            [('call', b'BzrDir.find_repositoryV3', (b'quack/',))],
 
1025
            client._calls)
 
1026
        self.assertEqual(network_name, repo._format.network_name())
 
1027
 
 
1028
 
 
1029
class TestBzrDirFormatInitializeEx(TestRemote):
 
1030
 
 
1031
    def test_success(self):
 
1032
        """Simple test for typical successful call."""
 
1033
        fmt = RemoteBzrDirFormat()
 
1034
        default_format_name = BzrDirFormat.get_default_format().network_name()
 
1035
        transport = self.get_transport()
 
1036
        client = FakeClient(transport.base)
 
1037
        client.add_expected_call(
 
1038
            b'BzrDirFormat.initialize_ex_1.16',
 
1039
                (default_format_name, b'path', b'False', b'False', b'False', b'',
 
1040
                 b'', b'', b'', b'False'),
 
1041
            b'success',
 
1042
                (b'.', b'no', b'no', b'yes', b'repo fmt', b'repo bzrdir fmt',
 
1043
                 b'bzrdir fmt', b'False', b'', b'', b'repo lock token'))
 
1044
        # XXX: It would be better to call fmt.initialize_on_transport_ex, but
 
1045
        # it's currently hard to test that without supplying a real remote
 
1046
        # transport connected to a real server.
 
1047
        result = fmt._initialize_on_transport_ex_rpc(client, 'path',
 
1048
            transport, False, False, False, None, None, None, None, False)
 
1049
        self.assertFinished(client)
 
1050
 
 
1051
    def test_error(self):
 
1052
        """Error responses are translated, e.g. 'PermissionDenied' raises the
 
1053
        corresponding error from the client.
 
1054
        """
 
1055
        fmt = RemoteBzrDirFormat()
 
1056
        default_format_name = BzrDirFormat.get_default_format().network_name()
 
1057
        transport = self.get_transport()
 
1058
        client = FakeClient(transport.base)
 
1059
        client.add_expected_call(
 
1060
            b'BzrDirFormat.initialize_ex_1.16',
 
1061
                (default_format_name, b'path', b'False', b'False', b'False', b'',
 
1062
                 b'', b'', b'', b'False'),
 
1063
            b'error',
 
1064
                (b'PermissionDenied', b'path', b'extra info'))
 
1065
        # XXX: It would be better to call fmt.initialize_on_transport_ex, but
 
1066
        # it's currently hard to test that without supplying a real remote
 
1067
        # transport connected to a real server.
 
1068
        err = self.assertRaises(errors.PermissionDenied,
 
1069
            fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
 
1070
            False, False, False, None, None, None, None, False)
 
1071
        self.assertEqual('path', err.path)
 
1072
        self.assertEqual(': extra info', err.extra)
 
1073
        self.assertFinished(client)
 
1074
 
 
1075
    def test_error_from_real_server(self):
 
1076
        """Integration test for error translation."""
 
1077
        transport = self.make_smart_server('foo')
 
1078
        transport = transport.clone('no-such-path')
 
1079
        fmt = RemoteBzrDirFormat()
 
1080
        err = self.assertRaises(errors.NoSuchFile,
 
1081
            fmt.initialize_on_transport_ex, transport, create_prefix=False)
 
1082
 
 
1083
 
 
1084
class OldSmartClient(object):
 
1085
    """A fake smart client for test_old_version that just returns a version one
 
1086
    response to the 'hello' (query version) command.
 
1087
    """
 
1088
 
 
1089
    def get_request(self):
 
1090
        input_file = BytesIO(b'ok\x011\n')
 
1091
        output_file = BytesIO()
 
1092
        client_medium = medium.SmartSimplePipesClientMedium(
 
1093
            input_file, output_file)
 
1094
        return medium.SmartClientStreamMediumRequest(client_medium)
 
1095
 
 
1096
    def protocol_version(self):
 
1097
        return 1
 
1098
 
 
1099
 
 
1100
class OldServerTransport(object):
 
1101
    """A fake transport for test_old_server that reports it's smart server
 
1102
    protocol version as version one.
 
1103
    """
 
1104
 
 
1105
    def __init__(self):
 
1106
        self.base = 'fake:'
 
1107
 
 
1108
    def get_smart_client(self):
 
1109
        return OldSmartClient()
 
1110
 
 
1111
 
 
1112
class RemoteBzrDirTestCase(TestRemote):
 
1113
 
 
1114
    def make_remote_bzrdir(self, transport, client):
 
1115
        """Make a RemotebzrDir using 'client' as the _client."""
 
1116
        return RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
1117
            _client=client)
 
1118
 
 
1119
 
 
1120
class RemoteBranchTestCase(RemoteBzrDirTestCase):
 
1121
 
 
1122
    def lock_remote_branch(self, branch):
 
1123
        """Trick a RemoteBranch into thinking it is locked."""
 
1124
        branch._lock_mode = 'w'
 
1125
        branch._lock_count = 2
 
1126
        branch._lock_token = b'branch token'
 
1127
        branch._repo_lock_token = b'repo token'
 
1128
        branch.repository._lock_mode = 'w'
 
1129
        branch.repository._lock_count = 2
 
1130
        branch.repository._lock_token = b'repo token'
 
1131
 
 
1132
    def make_remote_branch(self, transport, client):
 
1133
        """Make a RemoteBranch using 'client' as its _SmartClient.
 
1134
 
 
1135
        A RemoteBzrDir and RemoteRepository will also be created to fill out
 
1136
        the RemoteBranch, albeit with stub values for some of their attributes.
 
1137
        """
 
1138
        # we do not want bzrdir to make any remote calls, so use False as its
 
1139
        # _client.  If it tries to make a remote call, this will fail
 
1140
        # immediately.
 
1141
        bzrdir = self.make_remote_bzrdir(transport, False)
 
1142
        repo = RemoteRepository(bzrdir, None, _client=client)
 
1143
        branch_format = self.get_branch_format()
 
1144
        format = RemoteBranchFormat(network_name=branch_format.network_name())
 
1145
        return RemoteBranch(bzrdir, repo, _client=client, format=format)
 
1146
 
 
1147
 
 
1148
class TestBranchBreakLock(RemoteBranchTestCase):
 
1149
 
 
1150
    def test_break_lock(self):
 
1151
        transport_path = 'quack'
 
1152
        transport = MemoryTransport()
 
1153
        client = FakeClient(transport.base)
 
1154
        client.add_expected_call(
 
1155
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1156
            b'error', (b'NotStacked',))
 
1157
        client.add_expected_call(
 
1158
            b'Branch.break_lock', (b'quack/',),
 
1159
            b'success', (b'ok',))
 
1160
        transport.mkdir('quack')
 
1161
        transport = transport.clone('quack')
 
1162
        branch = self.make_remote_branch(transport, client)
 
1163
        branch.break_lock()
 
1164
        self.assertFinished(client)
 
1165
 
 
1166
 
 
1167
class TestBranchGetPhysicalLockStatus(RemoteBranchTestCase):
 
1168
 
 
1169
    def test_get_physical_lock_status_yes(self):
 
1170
        transport = MemoryTransport()
 
1171
        client = FakeClient(transport.base)
 
1172
        client.add_expected_call(
 
1173
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1174
            b'error', (b'NotStacked',))
 
1175
        client.add_expected_call(
 
1176
            b'Branch.get_physical_lock_status', (b'quack/',),
 
1177
            b'success', (b'yes',))
 
1178
        transport.mkdir('quack')
 
1179
        transport = transport.clone('quack')
 
1180
        branch = self.make_remote_branch(transport, client)
 
1181
        result = branch.get_physical_lock_status()
 
1182
        self.assertFinished(client)
 
1183
        self.assertEqual(True, result)
 
1184
 
 
1185
    def test_get_physical_lock_status_no(self):
 
1186
        transport = MemoryTransport()
 
1187
        client = FakeClient(transport.base)
 
1188
        client.add_expected_call(
 
1189
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1190
            b'error', (b'NotStacked',))
 
1191
        client.add_expected_call(
 
1192
            b'Branch.get_physical_lock_status', (b'quack/',),
 
1193
            b'success', (b'no',))
 
1194
        transport.mkdir('quack')
 
1195
        transport = transport.clone('quack')
 
1196
        branch = self.make_remote_branch(transport, client)
 
1197
        result = branch.get_physical_lock_status()
 
1198
        self.assertFinished(client)
 
1199
        self.assertEqual(False, result)
 
1200
 
 
1201
 
 
1202
class TestBranchGetParent(RemoteBranchTestCase):
 
1203
 
 
1204
    def test_no_parent(self):
 
1205
        # in an empty branch we decode the response properly
 
1206
        transport = MemoryTransport()
 
1207
        client = FakeClient(transport.base)
 
1208
        client.add_expected_call(
 
1209
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1210
            b'error', (b'NotStacked',))
 
1211
        client.add_expected_call(
 
1212
            b'Branch.get_parent', (b'quack/',),
 
1213
            b'success', (b'',))
 
1214
        transport.mkdir('quack')
 
1215
        transport = transport.clone('quack')
 
1216
        branch = self.make_remote_branch(transport, client)
 
1217
        result = branch.get_parent()
 
1218
        self.assertFinished(client)
 
1219
        self.assertEqual(None, result)
 
1220
 
 
1221
    def test_parent_relative(self):
 
1222
        transport = MemoryTransport()
 
1223
        client = FakeClient(transport.base)
 
1224
        client.add_expected_call(
 
1225
            b'Branch.get_stacked_on_url', (b'kwaak/',),
 
1226
            b'error', (b'NotStacked',))
 
1227
        client.add_expected_call(
 
1228
            b'Branch.get_parent', (b'kwaak/',),
 
1229
            b'success', (b'../foo/',))
 
1230
        transport.mkdir('kwaak')
 
1231
        transport = transport.clone('kwaak')
 
1232
        branch = self.make_remote_branch(transport, client)
 
1233
        result = branch.get_parent()
 
1234
        self.assertEqual(transport.clone('../foo').base, result)
 
1235
 
 
1236
    def test_parent_absolute(self):
 
1237
        transport = MemoryTransport()
 
1238
        client = FakeClient(transport.base)
 
1239
        client.add_expected_call(
 
1240
            b'Branch.get_stacked_on_url', (b'kwaak/',),
 
1241
            b'error', (b'NotStacked',))
 
1242
        client.add_expected_call(
 
1243
            b'Branch.get_parent', (b'kwaak/',),
 
1244
            b'success', (b'http://foo/',))
 
1245
        transport.mkdir('kwaak')
 
1246
        transport = transport.clone('kwaak')
 
1247
        branch = self.make_remote_branch(transport, client)
 
1248
        result = branch.get_parent()
 
1249
        self.assertEqual('http://foo/', result)
 
1250
        self.assertFinished(client)
 
1251
 
 
1252
 
 
1253
class TestBranchSetParentLocation(RemoteBranchTestCase):
 
1254
 
 
1255
    def test_no_parent(self):
 
1256
        # We call the verb when setting parent to None
 
1257
        transport = MemoryTransport()
 
1258
        client = FakeClient(transport.base)
 
1259
        client.add_expected_call(
 
1260
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1261
            b'error', (b'NotStacked',))
 
1262
        client.add_expected_call(
 
1263
            b'Branch.set_parent_location', (b'quack/', b'b', b'r', b''),
 
1264
            b'success', ())
 
1265
        transport.mkdir('quack')
 
1266
        transport = transport.clone('quack')
 
1267
        branch = self.make_remote_branch(transport, client)
 
1268
        branch._lock_token = b'b'
 
1269
        branch._repo_lock_token = b'r'
 
1270
        branch._set_parent_location(None)
 
1271
        self.assertFinished(client)
 
1272
 
 
1273
    def test_parent(self):
 
1274
        transport = MemoryTransport()
 
1275
        client = FakeClient(transport.base)
 
1276
        client.add_expected_call(
 
1277
            b'Branch.get_stacked_on_url', (b'kwaak/',),
 
1278
            b'error', (b'NotStacked',))
 
1279
        client.add_expected_call(
 
1280
            b'Branch.set_parent_location', (b'kwaak/', b'b', b'r', b'foo'),
 
1281
            b'success', ())
 
1282
        transport.mkdir('kwaak')
 
1283
        transport = transport.clone('kwaak')
 
1284
        branch = self.make_remote_branch(transport, client)
 
1285
        branch._lock_token = b'b'
 
1286
        branch._repo_lock_token = b'r'
 
1287
        branch._set_parent_location('foo')
 
1288
        self.assertFinished(client)
 
1289
 
 
1290
    def test_backwards_compat(self):
 
1291
        self.setup_smart_server_with_call_log()
 
1292
        branch = self.make_branch('.')
 
1293
        self.reset_smart_call_log()
 
1294
        verb = b'Branch.set_parent_location'
 
1295
        self.disable_verb(verb)
 
1296
        branch.set_parent('http://foo/')
 
1297
        self.assertLength(14, self.hpss_calls)
 
1298
 
 
1299
 
 
1300
class TestBranchGetTagsBytes(RemoteBranchTestCase):
 
1301
 
 
1302
    def test_backwards_compat(self):
 
1303
        self.setup_smart_server_with_call_log()
 
1304
        branch = self.make_branch('.')
 
1305
        self.reset_smart_call_log()
 
1306
        verb = b'Branch.get_tags_bytes'
 
1307
        self.disable_verb(verb)
 
1308
        branch.tags.get_tag_dict()
 
1309
        call_count = len([call for call in self.hpss_calls if
 
1310
            call.call.method == verb])
 
1311
        self.assertEqual(1, call_count)
 
1312
 
 
1313
    def test_trivial(self):
 
1314
        transport = MemoryTransport()
 
1315
        client = FakeClient(transport.base)
 
1316
        client.add_expected_call(
 
1317
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1318
            b'error', (b'NotStacked',))
 
1319
        client.add_expected_call(
 
1320
            b'Branch.get_tags_bytes', (b'quack/',),
 
1321
            b'success', (b'',))
 
1322
        transport.mkdir('quack')
 
1323
        transport = transport.clone('quack')
 
1324
        branch = self.make_remote_branch(transport, client)
 
1325
        result = branch.tags.get_tag_dict()
 
1326
        self.assertFinished(client)
 
1327
        self.assertEqual({}, result)
 
1328
 
 
1329
 
 
1330
class TestBranchSetTagsBytes(RemoteBranchTestCase):
 
1331
 
 
1332
    def test_trivial(self):
 
1333
        transport = MemoryTransport()
 
1334
        client = FakeClient(transport.base)
 
1335
        client.add_expected_call(
 
1336
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1337
            b'error', (b'NotStacked',))
 
1338
        client.add_expected_call(
 
1339
            b'Branch.set_tags_bytes', (b'quack/', b'branch token', b'repo token'),
 
1340
            b'success', ('',))
 
1341
        transport.mkdir('quack')
 
1342
        transport = transport.clone('quack')
 
1343
        branch = self.make_remote_branch(transport, client)
 
1344
        self.lock_remote_branch(branch)
 
1345
        branch._set_tags_bytes(b'tags bytes')
 
1346
        self.assertFinished(client)
 
1347
        self.assertEqual(b'tags bytes', client._calls[-1][-1])
 
1348
 
 
1349
    def test_backwards_compatible(self):
 
1350
        transport = MemoryTransport()
 
1351
        client = FakeClient(transport.base)
 
1352
        client.add_expected_call(
 
1353
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1354
            b'error', (b'NotStacked',))
 
1355
        client.add_expected_call(
 
1356
            b'Branch.set_tags_bytes', (b'quack/', b'branch token', b'repo token'),
 
1357
            b'unknown', (b'Branch.set_tags_bytes',))
 
1358
        transport.mkdir('quack')
 
1359
        transport = transport.clone('quack')
 
1360
        branch = self.make_remote_branch(transport, client)
 
1361
        self.lock_remote_branch(branch)
 
1362
        class StubRealBranch(object):
 
1363
            def __init__(self):
 
1364
                self.calls = []
 
1365
            def _set_tags_bytes(self, bytes):
 
1366
                self.calls.append(('set_tags_bytes', bytes))
 
1367
        real_branch = StubRealBranch()
 
1368
        branch._real_branch = real_branch
 
1369
        branch._set_tags_bytes(b'tags bytes')
 
1370
        # Call a second time, to exercise the 'remote version already inferred'
 
1371
        # code path.
 
1372
        branch._set_tags_bytes(b'tags bytes')
 
1373
        self.assertFinished(client)
 
1374
        self.assertEqual(
 
1375
            [('set_tags_bytes', b'tags bytes')] * 2, real_branch.calls)
 
1376
 
 
1377
 
 
1378
class TestBranchHeadsToFetch(RemoteBranchTestCase):
 
1379
 
 
1380
    def test_uses_last_revision_info_and_tags_by_default(self):
 
1381
        transport = MemoryTransport()
 
1382
        client = FakeClient(transport.base)
 
1383
        client.add_expected_call(
 
1384
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1385
            b'error', (b'NotStacked',))
 
1386
        client.add_expected_call(
 
1387
            b'Branch.last_revision_info', (b'quack/',),
 
1388
            b'success', (b'ok', b'1', b'rev-tip'))
 
1389
        client.add_expected_call(
 
1390
            b'Branch.get_config_file', (b'quack/',),
 
1391
            b'success', (b'ok',), '')
 
1392
        transport.mkdir('quack')
 
1393
        transport = transport.clone('quack')
 
1394
        branch = self.make_remote_branch(transport, client)
 
1395
        result = branch.heads_to_fetch()
 
1396
        self.assertFinished(client)
 
1397
        self.assertEqual(({b'rev-tip'}, set()), result)
 
1398
 
 
1399
    def test_uses_last_revision_info_and_tags_when_set(self):
 
1400
        transport = MemoryTransport()
 
1401
        client = FakeClient(transport.base)
 
1402
        client.add_expected_call(
 
1403
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1404
            b'error', (b'NotStacked',))
 
1405
        client.add_expected_call(
 
1406
            b'Branch.last_revision_info', (b'quack/',),
 
1407
            b'success', (b'ok', b'1', b'rev-tip'))
 
1408
        client.add_expected_call(
 
1409
            b'Branch.get_config_file', (b'quack/',),
 
1410
            b'success', (b'ok',), b'branch.fetch_tags = True')
 
1411
        # XXX: this will break if the default format's serialization of tags
 
1412
        # changes, or if the RPC for fetching tags changes from get_tags_bytes.
 
1413
        client.add_expected_call(
 
1414
            b'Branch.get_tags_bytes', (b'quack/',),
 
1415
            b'success', (b'd5:tag-17:rev-foo5:tag-27:rev-bare',))
 
1416
        transport.mkdir('quack')
 
1417
        transport = transport.clone('quack')
 
1418
        branch = self.make_remote_branch(transport, client)
 
1419
        result = branch.heads_to_fetch()
 
1420
        self.assertFinished(client)
 
1421
        self.assertEqual(
 
1422
            ({b'rev-tip'}, {b'rev-foo', b'rev-bar'}), result)
 
1423
 
 
1424
    def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
 
1425
        transport = MemoryTransport()
 
1426
        client = FakeClient(transport.base)
 
1427
        client.add_expected_call(
 
1428
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1429
            b'error', (b'NotStacked',))
 
1430
        client.add_expected_call(
 
1431
            b'Branch.heads_to_fetch', (b'quack/',),
 
1432
            b'success', ([b'tip'], [b'tagged-1', b'tagged-2']))
 
1433
        transport.mkdir('quack')
 
1434
        transport = transport.clone('quack')
 
1435
        branch = self.make_remote_branch(transport, client)
 
1436
        branch._format._use_default_local_heads_to_fetch = lambda: False
 
1437
        result = branch.heads_to_fetch()
 
1438
        self.assertFinished(client)
 
1439
        self.assertEqual(({b'tip'}, {b'tagged-1', b'tagged-2'}), result)
 
1440
 
 
1441
    def make_branch_with_tags(self):
 
1442
        self.setup_smart_server_with_call_log()
 
1443
        # Make a branch with a single revision.
 
1444
        builder = self.make_branch_builder('foo')
 
1445
        builder.start_series()
 
1446
        builder.build_snapshot(None, [
 
1447
            ('add', ('', b'root-id', 'directory', ''))],
 
1448
            revision_id=b'tip')
 
1449
        builder.finish_series()
 
1450
        branch = builder.get_branch()
 
1451
        # Add two tags to that branch
 
1452
        branch.tags.set_tag('tag-1', b'rev-1')
 
1453
        branch.tags.set_tag('tag-2', b'rev-2')
 
1454
        return branch
 
1455
 
 
1456
    def test_backwards_compatible(self):
 
1457
        br = self.make_branch_with_tags()
 
1458
        br.get_config_stack().set('branch.fetch_tags', True)
 
1459
        self.addCleanup(br.lock_read().unlock)
 
1460
        # Disable the heads_to_fetch verb
 
1461
        verb = b'Branch.heads_to_fetch'
 
1462
        self.disable_verb(verb)
 
1463
        self.reset_smart_call_log()
 
1464
        result = br.heads_to_fetch()
 
1465
        self.assertEqual(({b'tip'}, {b'rev-1', b'rev-2'}), result)
 
1466
        self.assertEqual(
 
1467
            [b'Branch.last_revision_info', b'Branch.get_tags_bytes'],
 
1468
            [call.call.method for call in self.hpss_calls])
 
1469
 
 
1470
    def test_backwards_compatible_no_tags(self):
 
1471
        br = self.make_branch_with_tags()
 
1472
        br.get_config_stack().set('branch.fetch_tags', False)
 
1473
        self.addCleanup(br.lock_read().unlock)
 
1474
        # Disable the heads_to_fetch verb
 
1475
        verb = b'Branch.heads_to_fetch'
 
1476
        self.disable_verb(verb)
 
1477
        self.reset_smart_call_log()
 
1478
        result = br.heads_to_fetch()
 
1479
        self.assertEqual(({b'tip'}, set()), result)
 
1480
        self.assertEqual(
 
1481
            [b'Branch.last_revision_info'],
 
1482
            [call.call.method for call in self.hpss_calls])
 
1483
 
 
1484
 
 
1485
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
 
1486
 
 
1487
    def test_empty_branch(self):
 
1488
        # in an empty branch we decode the response properly
 
1489
        transport = MemoryTransport()
 
1490
        client = FakeClient(transport.base)
 
1491
        client.add_expected_call(
 
1492
            b'Branch.get_stacked_on_url', (b'quack/',),
 
1493
            b'error', (b'NotStacked',))
 
1494
        client.add_expected_call(
 
1495
            b'Branch.last_revision_info', (b'quack/',),
 
1496
            b'success', (b'ok', b'0', b'null:'))
 
1497
        transport.mkdir('quack')
 
1498
        transport = transport.clone('quack')
 
1499
        branch = self.make_remote_branch(transport, client)
 
1500
        result = branch.last_revision_info()
 
1501
        self.assertFinished(client)
 
1502
        self.assertEqual((0, NULL_REVISION), result)
 
1503
 
 
1504
    def test_non_empty_branch(self):
 
1505
        # in a non-empty branch we also decode the response properly
 
1506
        revid = u'\xc8'.encode('utf8')
 
1507
        transport = MemoryTransport()
 
1508
        client = FakeClient(transport.base)
 
1509
        client.add_expected_call(
 
1510
            b'Branch.get_stacked_on_url', (b'kwaak/',),
 
1511
            b'error', (b'NotStacked',))
 
1512
        client.add_expected_call(
 
1513
            b'Branch.last_revision_info', (b'kwaak/',),
 
1514
            b'success', (b'ok', b'2', revid))
 
1515
        transport.mkdir('kwaak')
 
1516
        transport = transport.clone('kwaak')
 
1517
        branch = self.make_remote_branch(transport, client)
 
1518
        result = branch.last_revision_info()
 
1519
        self.assertEqual((2, revid), result)
 
1520
 
 
1521
 
 
1522
class TestBranch_get_stacked_on_url(TestRemote):
 
1523
    """Test Branch._get_stacked_on_url rpc"""
 
1524
 
 
1525
    def test_get_stacked_on_invalid_url(self):
 
1526
        # test that asking for a stacked on url the server can't access works.
 
1527
        # This isn't perfect, but then as we're in the same process there
 
1528
        # really isn't anything we can do to be 100% sure that the server
 
1529
        # doesn't just open in - this test probably needs to be rewritten using
 
1530
        # a spawn()ed server.
 
1531
        stacked_branch = self.make_branch('stacked', format='1.9')
 
1532
        memory_branch = self.make_branch('base', format='1.9')
 
1533
        vfs_url = self.get_vfs_only_url('base')
 
1534
        stacked_branch.set_stacked_on_url(vfs_url)
 
1535
        transport = stacked_branch.controldir.root_transport
 
1536
        client = FakeClient(transport.base)
 
1537
        client.add_expected_call(
 
1538
            b'Branch.get_stacked_on_url', (b'stacked/',),
 
1539
            b'success', (b'ok', vfs_url))
 
1540
        # XXX: Multiple calls are bad, this second call documents what is
 
1541
        # today.
 
1542
        client.add_expected_call(
 
1543
            b'Branch.get_stacked_on_url', (b'stacked/',),
 
1544
            b'success', (b'ok', vfs_url))
 
1545
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
1546
            _client=client)
 
1547
        repo_fmt = remote.RemoteRepositoryFormat()
 
1548
        repo_fmt._custom_format = stacked_branch.repository._format
 
1549
        branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
 
1550
            _client=client)
 
1551
        result = branch.get_stacked_on_url()
 
1552
        self.assertEqual(vfs_url, result)
 
1553
 
 
1554
    def test_backwards_compatible(self):
 
1555
        # like with bzr1.6 with no Branch.get_stacked_on_url rpc
 
1556
        base_branch = self.make_branch('base', format='1.6')
 
1557
        stacked_branch = self.make_branch('stacked', format='1.6')
 
1558
        stacked_branch.set_stacked_on_url('../base')
 
1559
        client = FakeClient(self.get_url())
 
1560
        branch_network_name = self.get_branch_format().network_name()
 
1561
        client.add_expected_call(
 
1562
            b'BzrDir.open_branchV3', (b'stacked/',),
 
1563
            b'success', (b'branch', branch_network_name))
 
1564
        client.add_expected_call(
 
1565
            b'BzrDir.find_repositoryV3', (b'stacked/',),
 
1566
            b'success', (b'ok', b'', b'no', b'no', b'yes',
 
1567
                stacked_branch.repository._format.network_name()))
 
1568
        # called twice, once from constructor and then again by us
 
1569
        client.add_expected_call(
 
1570
            b'Branch.get_stacked_on_url', (b'stacked/',),
 
1571
            b'unknown', (b'Branch.get_stacked_on_url',))
 
1572
        client.add_expected_call(
 
1573
            b'Branch.get_stacked_on_url', (b'stacked/',),
 
1574
            b'unknown', (b'Branch.get_stacked_on_url',))
 
1575
        # this will also do vfs access, but that goes direct to the transport
 
1576
        # and isn't seen by the FakeClient.
 
1577
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
1578
            RemoteBzrDirFormat(), _client=client)
 
1579
        branch = bzrdir.open_branch()
 
1580
        result = branch.get_stacked_on_url()
 
1581
        self.assertEqual('../base', result)
 
1582
        self.assertFinished(client)
 
1583
        # it's in the fallback list both for the RemoteRepository and its vfs
 
1584
        # repository
 
1585
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
1586
        self.assertEqual(1,
 
1587
            len(branch.repository._real_repository._fallback_repositories))
 
1588
 
 
1589
    def test_get_stacked_on_real_branch(self):
 
1590
        base_branch = self.make_branch('base')
 
1591
        stacked_branch = self.make_branch('stacked')
 
1592
        stacked_branch.set_stacked_on_url('../base')
 
1593
        reference_format = self.get_repo_format()
 
1594
        network_name = reference_format.network_name()
 
1595
        client = FakeClient(self.get_url())
 
1596
        branch_network_name = self.get_branch_format().network_name()
 
1597
        client.add_expected_call(
 
1598
            b'BzrDir.open_branchV3', ('stacked/',),
 
1599
            b'success', ('branch', branch_network_name))
 
1600
        client.add_expected_call(
 
1601
            b'BzrDir.find_repositoryV3', (b'stacked/',),
 
1602
            b'success', (b'ok', b'', b'yes', b'no', b'yes', network_name))
 
1603
        # called twice, once from constructor and then again by us
 
1604
        client.add_expected_call(
 
1605
            b'Branch.get_stacked_on_url', (b'stacked/',),
 
1606
            b'success', (b'ok', b'../base'))
 
1607
        client.add_expected_call(
 
1608
            b'Branch.get_stacked_on_url', (b'stacked/',),
 
1609
            b'success', (b'ok', b'../base'))
 
1610
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
1611
            RemoteBzrDirFormat(), _client=client)
 
1612
        branch = bzrdir.open_branch()
 
1613
        result = branch.get_stacked_on_url()
 
1614
        self.assertEqual('../base', result)
 
1615
        self.assertFinished(client)
 
1616
        # it's in the fallback list both for the RemoteRepository.
 
1617
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
1618
        # And we haven't had to construct a real repository.
 
1619
        self.assertEqual(None, branch.repository._real_repository)
 
1620
 
 
1621
 
 
1622
class TestBranchSetLastRevision(RemoteBranchTestCase):
 
1623
 
 
1624
    def test_set_empty(self):
 
1625
        # _set_last_revision_info('null:') is translated to calling
 
1626
        # Branch.set_last_revision(path, '') on the wire.
 
1627
        transport = MemoryTransport()
 
1628
        transport.mkdir('branch')
 
1629
        transport = transport.clone('branch')
 
1630
 
 
1631
        client = FakeClient(transport.base)
 
1632
        client.add_expected_call(
 
1633
            b'Branch.get_stacked_on_url', (b'branch/',),
 
1634
            b'error', (b'NotStacked',))
 
1635
        client.add_expected_call(
 
1636
            b'Branch.lock_write', (b'branch/', b'', b''),
 
1637
            b'success', (b'ok', b'branch token', b'repo token'))
 
1638
        client.add_expected_call(
 
1639
            b'Branch.last_revision_info',
 
1640
            (b'branch/',),
 
1641
            b'success', (b'ok', b'0', b'null:'))
 
1642
        client.add_expected_call(
 
1643
            b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'null:',),
 
1644
            b'success', (b'ok',))
 
1645
        client.add_expected_call(
 
1646
            b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
 
1647
            b'success', (b'ok',))
 
1648
        branch = self.make_remote_branch(transport, client)
 
1649
        branch.lock_write()
 
1650
        result = branch._set_last_revision(NULL_REVISION)
 
1651
        branch.unlock()
 
1652
        self.assertEqual(None, result)
 
1653
        self.assertFinished(client)
 
1654
 
 
1655
    def test_set_nonempty(self):
 
1656
        # set_last_revision_info(N, rev-idN) is translated to calling
 
1657
        # Branch.set_last_revision(path, rev-idN) on the wire.
 
1658
        transport = MemoryTransport()
 
1659
        transport.mkdir('branch')
 
1660
        transport = transport.clone('branch')
 
1661
 
 
1662
        client = FakeClient(transport.base)
 
1663
        client.add_expected_call(
 
1664
            b'Branch.get_stacked_on_url', (b'branch/',),
 
1665
            b'error', (b'NotStacked',))
 
1666
        client.add_expected_call(
 
1667
            b'Branch.lock_write', (b'branch/', b'', b''),
 
1668
            b'success', (b'ok', b'branch token', b'repo token'))
 
1669
        client.add_expected_call(
 
1670
            b'Branch.last_revision_info',
 
1671
            (b'branch/',),
 
1672
            b'success', (b'ok', b'0', b'null:'))
 
1673
        lines = [b'rev-id2']
 
1674
        encoded_body = bz2.compress(b'\n'.join(lines))
 
1675
        client.add_success_response_with_body(encoded_body, b'ok')
 
1676
        client.add_expected_call(
 
1677
            b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'rev-id2',),
 
1678
            b'success', (b'ok',))
 
1679
        client.add_expected_call(
 
1680
            b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
 
1681
            b'success', (b'ok',))
 
1682
        branch = self.make_remote_branch(transport, client)
 
1683
        # Lock the branch, reset the record of remote calls.
 
1684
        branch.lock_write()
 
1685
        result = branch._set_last_revision(b'rev-id2')
 
1686
        branch.unlock()
 
1687
        self.assertEqual(None, result)
 
1688
        self.assertFinished(client)
 
1689
 
 
1690
    def test_no_such_revision(self):
 
1691
        transport = MemoryTransport()
 
1692
        transport.mkdir('branch')
 
1693
        transport = transport.clone('branch')
 
1694
        # A response of 'NoSuchRevision' is translated into an exception.
 
1695
        client = FakeClient(transport.base)
 
1696
        client.add_expected_call(
 
1697
            b'Branch.get_stacked_on_url', (b'branch/',),
 
1698
            b'error', (b'NotStacked',))
 
1699
        client.add_expected_call(
 
1700
            b'Branch.lock_write', (b'branch/', b'', b''),
 
1701
            b'success', (b'ok', b'branch token', b'repo token'))
 
1702
        client.add_expected_call(
 
1703
            b'Branch.last_revision_info',
 
1704
            (b'branch/',),
 
1705
            b'success', (b'ok', b'0', b'null:'))
 
1706
        # get_graph calls to construct the revision history, for the set_rh
 
1707
        # hook
 
1708
        lines = [b'rev-id']
 
1709
        encoded_body = bz2.compress(b'\n'.join(lines))
 
1710
        client.add_success_response_with_body(encoded_body, b'ok')
 
1711
        client.add_expected_call(
 
1712
            b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'rev-id',),
 
1713
            b'error', (b'NoSuchRevision', b'rev-id'))
 
1714
        client.add_expected_call(
 
1715
            b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
 
1716
            b'success', (b'ok',))
 
1717
 
 
1718
        branch = self.make_remote_branch(transport, client)
 
1719
        branch.lock_write()
 
1720
        self.assertRaises(
 
1721
            errors.NoSuchRevision, branch._set_last_revision, b'rev-id')
 
1722
        branch.unlock()
 
1723
        self.assertFinished(client)
 
1724
 
 
1725
    def test_tip_change_rejected(self):
 
1726
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
1727
        be raised.
 
1728
        """
 
1729
        transport = MemoryTransport()
 
1730
        transport.mkdir('branch')
 
1731
        transport = transport.clone('branch')
 
1732
        client = FakeClient(transport.base)
 
1733
        rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
 
1734
        rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
 
1735
        client.add_expected_call(
 
1736
            b'Branch.get_stacked_on_url', (b'branch/',),
 
1737
            b'error', (b'NotStacked',))
 
1738
        client.add_expected_call(
 
1739
            b'Branch.lock_write', (b'branch/', b'', b''),
 
1740
            b'success', (b'ok', b'branch token', b'repo token'))
 
1741
        client.add_expected_call(
 
1742
            b'Branch.last_revision_info',
 
1743
            (b'branch/',),
 
1744
            b'success', (b'ok', b'0', b'null:'))
 
1745
        lines = [b'rev-id']
 
1746
        encoded_body = bz2.compress(b'\n'.join(lines))
 
1747
        client.add_success_response_with_body(encoded_body, b'ok')
 
1748
        client.add_expected_call(
 
1749
            b'Branch.set_last_revision', (b'branch/', b'branch token', b'repo token', b'rev-id',),
 
1750
            b'error', (b'TipChangeRejected', rejection_msg_utf8))
 
1751
        client.add_expected_call(
 
1752
            b'Branch.unlock', (b'branch/', b'branch token', b'repo token'),
 
1753
            b'success', (b'ok',))
 
1754
        branch = self.make_remote_branch(transport, client)
 
1755
        branch.lock_write()
 
1756
        # The 'TipChangeRejected' error response triggered by calling
 
1757
        # set_last_revision_info causes a TipChangeRejected exception.
 
1758
        err = self.assertRaises(
 
1759
            errors.TipChangeRejected,
 
1760
            branch._set_last_revision, b'rev-id')
 
1761
        # The UTF-8 message from the response has been decoded into a unicode
 
1762
        # object.
 
1763
        self.assertIsInstance(err.msg, text_type)
 
1764
        self.assertEqual(rejection_msg_unicode, err.msg)
 
1765
        branch.unlock()
 
1766
        self.assertFinished(client)
 
1767
 
 
1768
 
 
1769
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
 
1770
 
 
1771
    def test_set_last_revision_info(self):
 
1772
        # set_last_revision_info(num, 'rev-id') is translated to calling
 
1773
        # Branch.set_last_revision_info(num, 'rev-id') on the wire.
 
1774
        transport = MemoryTransport()
 
1775
        transport.mkdir('branch')
 
1776
        transport = transport.clone('branch')
 
1777
        client = FakeClient(transport.base)
 
1778
        # get_stacked_on_url
 
1779
        client.add_error_response(b'NotStacked')
 
1780
        # lock_write
 
1781
        client.add_success_response(b'ok', b'branch token', b'repo token')
 
1782
        # query the current revision
 
1783
        client.add_success_response(b'ok', b'0', b'null:')
 
1784
        # set_last_revision
 
1785
        client.add_success_response(b'ok')
 
1786
        # unlock
 
1787
        client.add_success_response(b'ok')
 
1788
 
 
1789
        branch = self.make_remote_branch(transport, client)
 
1790
        # Lock the branch, reset the record of remote calls.
 
1791
        branch.lock_write()
 
1792
        client._calls = []
 
1793
        result = branch.set_last_revision_info(1234, b'a-revision-id')
 
1794
        self.assertEqual(
 
1795
            [('call', b'Branch.last_revision_info', (b'branch/',)),
 
1796
             ('call', b'Branch.set_last_revision_info',
 
1797
                (b'branch/', b'branch token', b'repo token',
 
1798
                 b'1234', b'a-revision-id'))],
 
1799
            client._calls)
 
1800
        self.assertEqual(None, result)
 
1801
 
 
1802
    def test_no_such_revision(self):
 
1803
        # A response of 'NoSuchRevision' is translated into an exception.
 
1804
        transport = MemoryTransport()
 
1805
        transport.mkdir('branch')
 
1806
        transport = transport.clone('branch')
 
1807
        client = FakeClient(transport.base)
 
1808
        # get_stacked_on_url
 
1809
        client.add_error_response(b'NotStacked')
 
1810
        # lock_write
 
1811
        client.add_success_response(b'ok', b'branch token', b'repo token')
 
1812
        # set_last_revision
 
1813
        client.add_error_response(b'NoSuchRevision', b'revid')
 
1814
        # unlock
 
1815
        client.add_success_response(b'ok')
 
1816
 
 
1817
        branch = self.make_remote_branch(transport, client)
 
1818
        # Lock the branch, reset the record of remote calls.
 
1819
        branch.lock_write()
 
1820
        client._calls = []
 
1821
 
 
1822
        self.assertRaises(
 
1823
            errors.NoSuchRevision, branch.set_last_revision_info, 123, b'revid')
 
1824
        branch.unlock()
 
1825
 
 
1826
    def test_backwards_compatibility(self):
 
1827
        """If the server does not support the Branch.set_last_revision_info
 
1828
        verb (which is new in 1.4), then the client falls back to VFS methods.
 
1829
        """
 
1830
        # This test is a little messy.  Unlike most tests in this file, it
 
1831
        # doesn't purely test what a Remote* object sends over the wire, and
 
1832
        # how it reacts to responses from the wire.  It instead relies partly
 
1833
        # on asserting that the RemoteBranch will call
 
1834
        # self._real_branch.set_last_revision_info(...).
 
1835
 
 
1836
        # First, set up our RemoteBranch with a FakeClient that raises
 
1837
        # UnknownSmartMethod, and a StubRealBranch that logs how it is called.
 
1838
        transport = MemoryTransport()
 
1839
        transport.mkdir('branch')
 
1840
        transport = transport.clone('branch')
 
1841
        client = FakeClient(transport.base)
 
1842
        client.add_expected_call(
 
1843
            b'Branch.get_stacked_on_url', (b'branch/',),
 
1844
            b'error', (b'NotStacked',))
 
1845
        client.add_expected_call(
 
1846
            b'Branch.last_revision_info',
 
1847
            (b'branch/',),
 
1848
            b'success', (b'ok', b'0', b'null:'))
 
1849
        client.add_expected_call(
 
1850
            b'Branch.set_last_revision_info',
 
1851
            (b'branch/', b'branch token', b'repo token', b'1234', b'a-revision-id',),
 
1852
            b'unknown', b'Branch.set_last_revision_info')
 
1853
 
 
1854
        branch = self.make_remote_branch(transport, client)
 
1855
        class StubRealBranch(object):
 
1856
            def __init__(self):
 
1857
                self.calls = []
 
1858
            def set_last_revision_info(self, revno, revision_id):
 
1859
                self.calls.append(
 
1860
                    ('set_last_revision_info', revno, revision_id))
 
1861
            def _clear_cached_state(self):
 
1862
                pass
 
1863
        real_branch = StubRealBranch()
 
1864
        branch._real_branch = real_branch
 
1865
        self.lock_remote_branch(branch)
 
1866
 
 
1867
        # Call set_last_revision_info, and verify it behaved as expected.
 
1868
        result = branch.set_last_revision_info(1234, b'a-revision-id')
 
1869
        self.assertEqual(
 
1870
            [('set_last_revision_info', 1234, b'a-revision-id')],
 
1871
            real_branch.calls)
 
1872
        self.assertFinished(client)
 
1873
 
 
1874
    def test_unexpected_error(self):
 
1875
        # If the server sends an error the client doesn't understand, it gets
 
1876
        # turned into an UnknownErrorFromSmartServer, which is presented as a
 
1877
        # non-internal error to the user.
 
1878
        transport = MemoryTransport()
 
1879
        transport.mkdir('branch')
 
1880
        transport = transport.clone('branch')
 
1881
        client = FakeClient(transport.base)
 
1882
        # get_stacked_on_url
 
1883
        client.add_error_response(b'NotStacked')
 
1884
        # lock_write
 
1885
        client.add_success_response(b'ok', b'branch token', b'repo token')
 
1886
        # set_last_revision
 
1887
        client.add_error_response(b'UnexpectedError')
 
1888
        # unlock
 
1889
        client.add_success_response(b'ok')
 
1890
 
 
1891
        branch = self.make_remote_branch(transport, client)
 
1892
        # Lock the branch, reset the record of remote calls.
 
1893
        branch.lock_write()
 
1894
        client._calls = []
 
1895
 
 
1896
        err = self.assertRaises(
 
1897
            errors.UnknownErrorFromSmartServer,
 
1898
            branch.set_last_revision_info, 123, b'revid')
 
1899
        self.assertEqual((b'UnexpectedError',), err.error_tuple)
 
1900
        branch.unlock()
 
1901
 
 
1902
    def test_tip_change_rejected(self):
 
1903
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
1904
        be raised.
 
1905
        """
 
1906
        transport = MemoryTransport()
 
1907
        transport.mkdir('branch')
 
1908
        transport = transport.clone('branch')
 
1909
        client = FakeClient(transport.base)
 
1910
        # get_stacked_on_url
 
1911
        client.add_error_response(b'NotStacked')
 
1912
        # lock_write
 
1913
        client.add_success_response(b'ok', b'branch token', b'repo token')
 
1914
        # set_last_revision
 
1915
        client.add_error_response(b'TipChangeRejected', b'rejection message')
 
1916
        # unlock
 
1917
        client.add_success_response(b'ok')
 
1918
 
 
1919
        branch = self.make_remote_branch(transport, client)
 
1920
        # Lock the branch, reset the record of remote calls.
 
1921
        branch.lock_write()
 
1922
        self.addCleanup(branch.unlock)
 
1923
        client._calls = []
 
1924
 
 
1925
        # The 'TipChangeRejected' error response triggered by calling
 
1926
        # set_last_revision_info causes a TipChangeRejected exception.
 
1927
        err = self.assertRaises(
 
1928
            errors.TipChangeRejected,
 
1929
            branch.set_last_revision_info, 123, b'revid')
 
1930
        self.assertEqual('rejection message', err.msg)
 
1931
 
 
1932
 
 
1933
class TestBranchGetSetConfig(RemoteBranchTestCase):
 
1934
 
 
1935
    def test_get_branch_conf(self):
 
1936
        # in an empty branch we decode the response properly
 
1937
        client = FakeClient()
 
1938
        client.add_expected_call(
 
1939
            b'Branch.get_stacked_on_url', (b'memory:///',),
 
1940
            b'error', (b'NotStacked',),)
 
1941
        client.add_success_response_with_body(b'# config file body', b'ok')
 
1942
        transport = MemoryTransport()
 
1943
        branch = self.make_remote_branch(transport, client)
 
1944
        config = branch.get_config()
 
1945
        config.has_explicit_nickname()
 
1946
        self.assertEqual(
 
1947
            [('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
 
1948
             ('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
 
1949
            client._calls)
 
1950
 
 
1951
    def test_get_multi_line_branch_conf(self):
 
1952
        # Make sure that multiple-line branch.conf files are supported
 
1953
        #
 
1954
        # https://bugs.launchpad.net/bzr/+bug/354075
 
1955
        client = FakeClient()
 
1956
        client.add_expected_call(
 
1957
            b'Branch.get_stacked_on_url', (b'memory:///',),
 
1958
            b'error', (b'NotStacked',),)
 
1959
        client.add_success_response_with_body(b'a = 1\nb = 2\nc = 3\n', b'ok')
 
1960
        transport = MemoryTransport()
 
1961
        branch = self.make_remote_branch(transport, client)
 
1962
        config = branch.get_config()
 
1963
        self.assertEqual(u'2', config.get_user_option('b'))
 
1964
 
 
1965
    def test_set_option(self):
 
1966
        client = FakeClient()
 
1967
        client.add_expected_call(
 
1968
            b'Branch.get_stacked_on_url', (b'memory:///',),
 
1969
            b'error', (b'NotStacked',),)
 
1970
        client.add_expected_call(
 
1971
            b'Branch.lock_write', (b'memory:///', b'', b''),
 
1972
            b'success', (b'ok', b'branch token', b'repo token'))
 
1973
        client.add_expected_call(
 
1974
            b'Branch.set_config_option', (b'memory:///', b'branch token',
 
1975
            b'repo token', b'foo', b'bar', b''),
 
1976
            b'success', ())
 
1977
        client.add_expected_call(
 
1978
            b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
 
1979
            b'success', (b'ok',))
 
1980
        transport = MemoryTransport()
 
1981
        branch = self.make_remote_branch(transport, client)
 
1982
        branch.lock_write()
 
1983
        config = branch._get_config()
 
1984
        config.set_option('foo', 'bar')
 
1985
        branch.unlock()
 
1986
        self.assertFinished(client)
 
1987
 
 
1988
    def test_set_option_with_dict(self):
 
1989
        client = FakeClient()
 
1990
        client.add_expected_call(
 
1991
            b'Branch.get_stacked_on_url', (b'memory:///',),
 
1992
            b'error', (b'NotStacked',),)
 
1993
        client.add_expected_call(
 
1994
            b'Branch.lock_write', (b'memory:///', b'', b''),
 
1995
            b'success', (b'ok', b'branch token', b'repo token'))
 
1996
        encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
 
1997
        client.add_expected_call(
 
1998
            b'Branch.set_config_option_dict', (b'memory:///', b'branch token',
 
1999
            b'repo token', encoded_dict_value, b'foo', b''),
 
2000
            b'success', ())
 
2001
        client.add_expected_call(
 
2002
            b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
 
2003
            b'success', (b'ok',))
 
2004
        transport = MemoryTransport()
 
2005
        branch = self.make_remote_branch(transport, client)
 
2006
        branch.lock_write()
 
2007
        config = branch._get_config()
 
2008
        config.set_option(
 
2009
            {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
 
2010
            'foo')
 
2011
        branch.unlock()
 
2012
        self.assertFinished(client)
 
2013
 
 
2014
    def test_backwards_compat_set_option(self):
 
2015
        self.setup_smart_server_with_call_log()
 
2016
        branch = self.make_branch('.')
 
2017
        verb = b'Branch.set_config_option'
 
2018
        self.disable_verb(verb)
 
2019
        branch.lock_write()
 
2020
        self.addCleanup(branch.unlock)
 
2021
        self.reset_smart_call_log()
 
2022
        branch._get_config().set_option('value', 'name')
 
2023
        self.assertLength(11, self.hpss_calls)
 
2024
        self.assertEqual('value', branch._get_config().get_option('name'))
 
2025
 
 
2026
    def test_backwards_compat_set_option_with_dict(self):
 
2027
        self.setup_smart_server_with_call_log()
 
2028
        branch = self.make_branch('.')
 
2029
        verb = b'Branch.set_config_option_dict'
 
2030
        self.disable_verb(verb)
 
2031
        branch.lock_write()
 
2032
        self.addCleanup(branch.unlock)
 
2033
        self.reset_smart_call_log()
 
2034
        config = branch._get_config()
 
2035
        value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
 
2036
        config.set_option(value_dict, 'name')
 
2037
        self.assertLength(11, self.hpss_calls)
 
2038
        self.assertEqual(value_dict, branch._get_config().get_option('name'))
 
2039
 
 
2040
 
 
2041
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
 
2042
 
 
2043
    def test_get_branch_conf(self):
 
2044
        # in an empty branch we decode the response properly
 
2045
        client = FakeClient()
 
2046
        client.add_expected_call(
 
2047
            b'Branch.get_stacked_on_url', (b'memory:///',),
 
2048
            b'error', (b'NotStacked',),)
 
2049
        client.add_success_response_with_body(b'# config file body', b'ok')
 
2050
        transport = MemoryTransport()
 
2051
        branch = self.make_remote_branch(transport, client)
 
2052
        config = branch.get_config_stack()
 
2053
        config.get("email")
 
2054
        config.get("log_format")
 
2055
        self.assertEqual(
 
2056
            [('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
 
2057
             ('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
 
2058
            client._calls)
 
2059
 
 
2060
    def test_set_branch_conf(self):
 
2061
        client = FakeClient()
 
2062
        client.add_expected_call(
 
2063
            b'Branch.get_stacked_on_url', (b'memory:///',),
 
2064
            b'error', (b'NotStacked',),)
 
2065
        client.add_expected_call(
 
2066
            b'Branch.lock_write', (b'memory:///', b'', b''),
 
2067
            b'success', (b'ok', b'branch token', b'repo token'))
 
2068
        client.add_expected_call(
 
2069
            b'Branch.get_config_file', (b'memory:///', ),
 
2070
            b'success', (b'ok', ), b"# line 1\n")
 
2071
        client.add_expected_call(
 
2072
            b'Branch.get_config_file', (b'memory:///', ),
 
2073
            b'success', (b'ok', ), b"# line 1\n")
 
2074
        client.add_expected_call(
 
2075
            b'Branch.put_config_file', (b'memory:///', b'branch token',
 
2076
            b'repo token'),
 
2077
            b'success', (b'ok',))
 
2078
        client.add_expected_call(
 
2079
            b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
 
2080
            b'success', (b'ok',))
 
2081
        transport = MemoryTransport()
 
2082
        branch = self.make_remote_branch(transport, client)
 
2083
        branch.lock_write()
 
2084
        config = branch.get_config_stack()
 
2085
        config.set('email', 'The Dude <lebowski@example.com>')
 
2086
        branch.unlock()
 
2087
        self.assertFinished(client)
 
2088
        self.assertEqual(
 
2089
            [('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
 
2090
             ('call', b'Branch.lock_write', (b'memory:///', b'', b'')),
 
2091
             ('call_expecting_body', b'Branch.get_config_file', (b'memory:///',)),
 
2092
             ('call_expecting_body', b'Branch.get_config_file', (b'memory:///',)),
 
2093
             ('call_with_body_bytes_expecting_body', b'Branch.put_config_file',
 
2094
                 (b'memory:///', b'branch token', b'repo token'),
 
2095
                 b'# line 1\nemail = The Dude <lebowski@example.com>\n'),
 
2096
             ('call', b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'))],
 
2097
            client._calls)
 
2098
 
 
2099
 
 
2100
class TestBranchLockWrite(RemoteBranchTestCase):
 
2101
 
 
2102
    def test_lock_write_unlockable(self):
 
2103
        transport = MemoryTransport()
 
2104
        client = FakeClient(transport.base)
 
2105
        client.add_expected_call(
 
2106
            b'Branch.get_stacked_on_url', (b'quack/',),
 
2107
            b'error', (b'NotStacked',),)
 
2108
        client.add_expected_call(
 
2109
            b'Branch.lock_write', (b'quack/', b'', b''),
 
2110
            b'error', (b'UnlockableTransport',))
 
2111
        transport.mkdir('quack')
 
2112
        transport = transport.clone('quack')
 
2113
        branch = self.make_remote_branch(transport, client)
 
2114
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
 
2115
        self.assertFinished(client)
 
2116
 
 
2117
 
 
2118
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
 
2119
 
 
2120
    def test_simple(self):
 
2121
        transport = MemoryTransport()
 
2122
        client = FakeClient(transport.base)
 
2123
        client.add_expected_call(
 
2124
            b'Branch.get_stacked_on_url', (b'quack/',),
 
2125
            b'error', (b'NotStacked',),)
 
2126
        client.add_expected_call(
 
2127
            b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
 
2128
            b'success', (b'ok', b'0',),)
 
2129
        client.add_expected_call(
 
2130
            b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
 
2131
            b'error', (b'NoSuchRevision', b'unknown',),)
 
2132
        transport.mkdir('quack')
 
2133
        transport = transport.clone('quack')
 
2134
        branch = self.make_remote_branch(transport, client)
 
2135
        self.assertEqual(0, branch.revision_id_to_revno(b'null:'))
 
2136
        self.assertRaises(errors.NoSuchRevision,
 
2137
            branch.revision_id_to_revno, b'unknown')
 
2138
        self.assertFinished(client)
 
2139
 
 
2140
    def test_dotted(self):
 
2141
        transport = MemoryTransport()
 
2142
        client = FakeClient(transport.base)
 
2143
        client.add_expected_call(
 
2144
            b'Branch.get_stacked_on_url', (b'quack/',),
 
2145
            b'error', (b'NotStacked',),)
 
2146
        client.add_expected_call(
 
2147
            b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
 
2148
            b'success', (b'ok', b'0',),)
 
2149
        client.add_expected_call(
 
2150
            b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
 
2151
            b'error', (b'NoSuchRevision', b'unknown',),)
 
2152
        transport.mkdir('quack')
 
2153
        transport = transport.clone('quack')
 
2154
        branch = self.make_remote_branch(transport, client)
 
2155
        self.assertEqual((0, ), branch.revision_id_to_dotted_revno(b'null:'))
 
2156
        self.assertRaises(errors.NoSuchRevision,
 
2157
            branch.revision_id_to_dotted_revno, b'unknown')
 
2158
        self.assertFinished(client)
 
2159
 
 
2160
    def test_dotted_no_smart_verb(self):
 
2161
        self.setup_smart_server_with_call_log()
 
2162
        branch = self.make_branch('.')
 
2163
        self.disable_verb(b'Branch.revision_id_to_revno')
 
2164
        self.reset_smart_call_log()
 
2165
        self.assertEqual((0, ),
 
2166
            branch.revision_id_to_dotted_revno(b'null:'))
 
2167
        self.assertLength(8, self.hpss_calls)
 
2168
 
 
2169
 
 
2170
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
 
2171
 
 
2172
    def test__get_config(self):
 
2173
        client = FakeClient()
 
2174
        client.add_success_response_with_body(b'default_stack_on = /\n', b'ok')
 
2175
        transport = MemoryTransport()
 
2176
        bzrdir = self.make_remote_bzrdir(transport, client)
 
2177
        config = bzrdir.get_config()
 
2178
        self.assertEqual('/', config.get_default_stack_on())
 
2179
        self.assertEqual(
 
2180
            [('call_expecting_body', b'BzrDir.get_config_file', (b'memory:///',))],
 
2181
            client._calls)
 
2182
 
 
2183
    def test_set_option_uses_vfs(self):
 
2184
        self.setup_smart_server_with_call_log()
 
2185
        bzrdir = self.make_controldir('.')
 
2186
        self.reset_smart_call_log()
 
2187
        config = bzrdir.get_config()
 
2188
        config.set_default_stack_on('/')
 
2189
        self.assertLength(4, self.hpss_calls)
 
2190
 
 
2191
    def test_backwards_compat_get_option(self):
 
2192
        self.setup_smart_server_with_call_log()
 
2193
        bzrdir = self.make_controldir('.')
 
2194
        verb = b'BzrDir.get_config_file'
 
2195
        self.disable_verb(verb)
 
2196
        self.reset_smart_call_log()
 
2197
        self.assertEqual(None,
 
2198
            bzrdir._get_config().get_option('default_stack_on'))
 
2199
        self.assertLength(4, self.hpss_calls)
 
2200
 
 
2201
 
 
2202
class TestTransportIsReadonly(tests.TestCase):
 
2203
 
 
2204
    def test_true(self):
 
2205
        client = FakeClient()
 
2206
        client.add_success_response(b'yes')
 
2207
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
2208
                                    _client=client)
 
2209
        self.assertEqual(True, transport.is_readonly())
 
2210
        self.assertEqual(
 
2211
            [('call', b'Transport.is_readonly', ())],
 
2212
            client._calls)
 
2213
 
 
2214
    def test_false(self):
 
2215
        client = FakeClient()
 
2216
        client.add_success_response(b'no')
 
2217
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
2218
                                    _client=client)
 
2219
        self.assertEqual(False, transport.is_readonly())
 
2220
        self.assertEqual(
 
2221
            [('call', b'Transport.is_readonly', ())],
 
2222
            client._calls)
 
2223
 
 
2224
    def test_error_from_old_server(self):
 
2225
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
 
2226
 
 
2227
        Clients should treat it as a "no" response, because is_readonly is only
 
2228
        advisory anyway (a transport could be read-write, but then the
 
2229
        underlying filesystem could be readonly anyway).
 
2230
        """
 
2231
        client = FakeClient()
 
2232
        client.add_unknown_method_response(b'Transport.is_readonly')
 
2233
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
2234
                                    _client=client)
 
2235
        self.assertEqual(False, transport.is_readonly())
 
2236
        self.assertEqual(
 
2237
            [('call', b'Transport.is_readonly', ())],
 
2238
            client._calls)
 
2239
 
 
2240
 
 
2241
class TestTransportMkdir(tests.TestCase):
 
2242
 
 
2243
    def test_permissiondenied(self):
 
2244
        client = FakeClient()
 
2245
        client.add_error_response(b'PermissionDenied', b'remote path', b'extra')
 
2246
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
2247
                                    _client=client)
 
2248
        exc = self.assertRaises(
 
2249
            errors.PermissionDenied, transport.mkdir, 'client path')
 
2250
        expected_error = errors.PermissionDenied('/client path', 'extra')
 
2251
        self.assertEqual(expected_error, exc)
 
2252
 
 
2253
 
 
2254
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
 
2255
 
 
2256
    def test_defaults_to_none(self):
 
2257
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
2258
        self.assertIs(None, t._get_credentials()[0])
 
2259
 
 
2260
    def test_uses_authentication_config(self):
 
2261
        conf = config.AuthenticationConfig()
 
2262
        conf._get_config().update(
 
2263
            {'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
 
2264
            'example.com'}})
 
2265
        conf._save()
 
2266
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
2267
        self.assertEqual('bar', t._get_credentials()[0])
 
2268
 
 
2269
 
 
2270
class TestRemoteRepository(TestRemote):
 
2271
    """Base for testing RemoteRepository protocol usage.
 
2272
 
 
2273
    These tests contain frozen requests and responses.  We want any changes to
 
2274
    what is sent or expected to be require a thoughtful update to these tests
 
2275
    because they might break compatibility with different-versioned servers.
 
2276
    """
 
2277
 
 
2278
    def setup_fake_client_and_repository(self, transport_path):
 
2279
        """Create the fake client and repository for testing with.
 
2280
 
 
2281
        There's no real server here; we just have canned responses sent
 
2282
        back one by one.
 
2283
 
 
2284
        :param transport_path: Path below the root of the MemoryTransport
 
2285
            where the repository will be created.
 
2286
        """
 
2287
        transport = MemoryTransport()
 
2288
        transport.mkdir(transport_path)
 
2289
        client = FakeClient(transport.base)
 
2290
        transport = transport.clone(transport_path)
 
2291
        # we do not want bzrdir to make any remote calls
 
2292
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
2293
            _client=False)
 
2294
        repo = RemoteRepository(bzrdir, None, _client=client)
 
2295
        return repo, client
 
2296
 
 
2297
 
 
2298
def remoted_description(format):
 
2299
    return 'Remote: ' + format.get_format_description()
 
2300
 
 
2301
 
 
2302
class TestBranchFormat(tests.TestCase):
 
2303
 
 
2304
    def test_get_format_description(self):
 
2305
        remote_format = RemoteBranchFormat()
 
2306
        real_format = branch.format_registry.get_default()
 
2307
        remote_format._network_name = real_format.network_name()
 
2308
        self.assertEqual(remoted_description(real_format),
 
2309
            remote_format.get_format_description())
 
2310
 
 
2311
 
 
2312
class TestRepositoryFormat(TestRemoteRepository):
 
2313
 
 
2314
    def test_fast_delta(self):
 
2315
        true_name = groupcompress_repo.RepositoryFormat2a().network_name()
 
2316
        true_format = RemoteRepositoryFormat()
 
2317
        true_format._network_name = true_name
 
2318
        self.assertEqual(True, true_format.fast_deltas)
 
2319
        false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
 
2320
        false_format = RemoteRepositoryFormat()
 
2321
        false_format._network_name = false_name
 
2322
        self.assertEqual(False, false_format.fast_deltas)
 
2323
 
 
2324
    def test_get_format_description(self):
 
2325
        remote_repo_format = RemoteRepositoryFormat()
 
2326
        real_format = repository.format_registry.get_default()
 
2327
        remote_repo_format._network_name = real_format.network_name()
 
2328
        self.assertEqual(remoted_description(real_format),
 
2329
            remote_repo_format.get_format_description())
 
2330
 
 
2331
 
 
2332
class TestRepositoryAllRevisionIds(TestRemoteRepository):
 
2333
 
 
2334
    def test_empty(self):
 
2335
        transport_path = 'quack'
 
2336
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2337
        client.add_success_response_with_body(b'', b'ok')
 
2338
        self.assertEqual([], repo.all_revision_ids())
 
2339
        self.assertEqual(
 
2340
            [('call_expecting_body', b'Repository.all_revision_ids',
 
2341
             (b'quack/',))],
 
2342
            client._calls)
 
2343
 
 
2344
    def test_with_some_content(self):
 
2345
        transport_path = 'quack'
 
2346
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2347
        client.add_success_response_with_body(
 
2348
            b'rev1\nrev2\nanotherrev\n', b'ok')
 
2349
        self.assertEqual([b"rev1", b"rev2", b"anotherrev"],
 
2350
            repo.all_revision_ids())
 
2351
        self.assertEqual(
 
2352
            [('call_expecting_body', b'Repository.all_revision_ids',
 
2353
             (b'quack/',))],
 
2354
            client._calls)
 
2355
 
 
2356
 
 
2357
class TestRepositoryGatherStats(TestRemoteRepository):
 
2358
 
 
2359
    def test_revid_none(self):
 
2360
        # ('ok',), body with revisions and size
 
2361
        transport_path = 'quack'
 
2362
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2363
        client.add_success_response_with_body(
 
2364
            b'revisions: 2\nsize: 18\n', b'ok')
 
2365
        result = repo.gather_stats(None)
 
2366
        self.assertEqual(
 
2367
            [('call_expecting_body', b'Repository.gather_stats',
 
2368
             (b'quack/', b'', b'no'))],
 
2369
            client._calls)
 
2370
        self.assertEqual({'revisions': 2, 'size': 18}, result)
 
2371
 
 
2372
    def test_revid_no_committers(self):
 
2373
        # ('ok',), body without committers
 
2374
        body = (b'firstrev: 123456.300 3600\n'
 
2375
                b'latestrev: 654231.400 0\n'
 
2376
                b'revisions: 2\n'
 
2377
                b'size: 18\n')
 
2378
        transport_path = 'quick'
 
2379
        revid = u'\xc8'.encode('utf8')
 
2380
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2381
        client.add_success_response_with_body(body, b'ok')
 
2382
        result = repo.gather_stats(revid)
 
2383
        self.assertEqual(
 
2384
            [('call_expecting_body', b'Repository.gather_stats',
 
2385
              (b'quick/', revid, b'no'))],
 
2386
            client._calls)
 
2387
        self.assertEqual({'revisions': 2, 'size': 18,
 
2388
                          'firstrev': (123456.300, 3600),
 
2389
                          'latestrev': (654231.400, 0),},
 
2390
                         result)
 
2391
 
 
2392
    def test_revid_with_committers(self):
 
2393
        # ('ok',), body with committers
 
2394
        body = (b'committers: 128\n'
 
2395
                b'firstrev: 123456.300 3600\n'
 
2396
                b'latestrev: 654231.400 0\n'
 
2397
                b'revisions: 2\n'
 
2398
                b'size: 18\n')
 
2399
        transport_path = 'buick'
 
2400
        revid = u'\xc8'.encode('utf8')
 
2401
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2402
        client.add_success_response_with_body(body, b'ok')
 
2403
        result = repo.gather_stats(revid, True)
 
2404
        self.assertEqual(
 
2405
            [('call_expecting_body', b'Repository.gather_stats',
 
2406
              (b'buick/', revid, b'yes'))],
 
2407
            client._calls)
 
2408
        self.assertEqual({'revisions': 2, 'size': 18,
 
2409
                          'committers': 128,
 
2410
                          'firstrev': (123456.300, 3600),
 
2411
                          'latestrev': (654231.400, 0),},
 
2412
                         result)
 
2413
 
 
2414
 
 
2415
class TestRepositoryBreakLock(TestRemoteRepository):
 
2416
 
 
2417
    def test_break_lock(self):
 
2418
        transport_path = 'quack'
 
2419
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2420
        client.add_success_response(b'ok')
 
2421
        repo.break_lock()
 
2422
        self.assertEqual(
 
2423
            [('call', b'Repository.break_lock', (b'quack/',))],
 
2424
            client._calls)
 
2425
 
 
2426
 
 
2427
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
 
2428
 
 
2429
    def test_get_serializer_format(self):
 
2430
        transport_path = 'hill'
 
2431
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2432
        client.add_success_response(b'ok', b'7')
 
2433
        self.assertEqual(b'7', repo.get_serializer_format())
 
2434
        self.assertEqual(
 
2435
            [('call', b'VersionedFileRepository.get_serializer_format',
 
2436
              (b'hill/', ))],
 
2437
            client._calls)
 
2438
 
 
2439
 
 
2440
class TestRepositoryReconcile(TestRemoteRepository):
 
2441
 
 
2442
    def test_reconcile(self):
 
2443
        transport_path = 'hill'
 
2444
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2445
        body = (b"garbage_inventories: 2\n"
 
2446
                b"inconsistent_parents: 3\n")
 
2447
        client.add_expected_call(
 
2448
            b'Repository.lock_write', (b'hill/', b''),
 
2449
            b'success', (b'ok', b'a token'))
 
2450
        client.add_success_response_with_body(body, b'ok')
 
2451
        reconciler = repo.reconcile()
 
2452
        self.assertEqual(
 
2453
            [('call', b'Repository.lock_write', (b'hill/', b'')),
 
2454
             ('call_expecting_body', b'Repository.reconcile',
 
2455
                (b'hill/', b'a token'))],
 
2456
            client._calls)
 
2457
        self.assertEqual(2, reconciler.garbage_inventories)
 
2458
        self.assertEqual(3, reconciler.inconsistent_parents)
 
2459
 
 
2460
 
 
2461
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
 
2462
 
 
2463
    def test_text(self):
 
2464
        # ('ok',), body with signature text
 
2465
        transport_path = 'quack'
 
2466
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2467
        client.add_success_response_with_body(
 
2468
            b'THETEXT', b'ok')
 
2469
        self.assertEqual(b"THETEXT", repo.get_signature_text(b"revid"))
 
2470
        self.assertEqual(
 
2471
            [('call_expecting_body', b'Repository.get_revision_signature_text',
 
2472
             (b'quack/', b'revid'))],
 
2473
            client._calls)
 
2474
 
 
2475
    def test_no_signature(self):
 
2476
        transport_path = 'quick'
 
2477
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2478
        client.add_error_response(b'nosuchrevision', b'unknown')
 
2479
        self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
 
2480
                b"unknown")
 
2481
        self.assertEqual(
 
2482
            [('call_expecting_body', b'Repository.get_revision_signature_text',
 
2483
              (b'quick/', b'unknown'))],
 
2484
            client._calls)
 
2485
 
 
2486
 
 
2487
class TestRepositoryGetGraph(TestRemoteRepository):
 
2488
 
 
2489
    def test_get_graph(self):
 
2490
        # get_graph returns a graph with a custom parents provider.
 
2491
        transport_path = 'quack'
 
2492
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2493
        graph = repo.get_graph()
 
2494
        self.assertNotEqual(graph._parents_provider, repo)
 
2495
 
 
2496
 
 
2497
class TestRepositoryAddSignatureText(TestRemoteRepository):
 
2498
 
 
2499
    def test_add_signature_text(self):
 
2500
        transport_path = 'quack'
 
2501
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2502
        client.add_expected_call(
 
2503
            b'Repository.lock_write', (b'quack/', b''),
 
2504
            b'success', (b'ok', b'a token'))
 
2505
        client.add_expected_call(
 
2506
            b'Repository.start_write_group', (b'quack/', b'a token'),
 
2507
            b'success', (b'ok', (b'token1', )))
 
2508
        client.add_expected_call(
 
2509
            b'Repository.add_signature_text', (b'quack/', b'a token', b'rev1',
 
2510
                b'token1'),
 
2511
            b'success', (b'ok', ), None)
 
2512
        repo.lock_write()
 
2513
        repo.start_write_group()
 
2514
        self.assertIs(None,
 
2515
            repo.add_signature_text(b"rev1", b"every bloody emperor"))
 
2516
        self.assertEqual(
 
2517
            ('call_with_body_bytes_expecting_body',
 
2518
              b'Repository.add_signature_text',
 
2519
                (b'quack/', b'a token', b'rev1', b'token1'),
 
2520
              b'every bloody emperor'),
 
2521
            client._calls[-1])
 
2522
 
 
2523
 
 
2524
class TestRepositoryGetParentMap(TestRemoteRepository):
 
2525
 
 
2526
    def test_get_parent_map_caching(self):
 
2527
        # get_parent_map returns from cache until unlock()
 
2528
        # setup a reponse with two revisions
 
2529
        r1 = u'\u0e33'.encode('utf8')
 
2530
        r2 = u'\u0dab'.encode('utf8')
 
2531
        lines = [b' '.join([r2, r1]), r1]
 
2532
        encoded_body = bz2.compress(b'\n'.join(lines))
 
2533
 
 
2534
        transport_path = 'quack'
 
2535
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2536
        client.add_success_response_with_body(encoded_body, b'ok')
 
2537
        client.add_success_response_with_body(encoded_body, b'ok')
 
2538
        repo.lock_read()
 
2539
        graph = repo.get_graph()
 
2540
        parents = graph.get_parent_map([r2])
 
2541
        self.assertEqual({r2: (r1,)}, parents)
 
2542
        # locking and unlocking deeper should not reset
 
2543
        repo.lock_read()
 
2544
        repo.unlock()
 
2545
        parents = graph.get_parent_map([r1])
 
2546
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
2547
        self.assertEqual(
 
2548
            [('call_with_body_bytes_expecting_body',
 
2549
              b'Repository.get_parent_map', (b'quack/', b'include-missing:', r2),
 
2550
              b'\n\n0')],
 
2551
            client._calls)
 
2552
        repo.unlock()
 
2553
        # now we call again, and it should use the second response.
 
2554
        repo.lock_read()
 
2555
        graph = repo.get_graph()
 
2556
        parents = graph.get_parent_map([r1])
 
2557
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
2558
        self.assertEqual(
 
2559
            [('call_with_body_bytes_expecting_body',
 
2560
              b'Repository.get_parent_map', (b'quack/', b'include-missing:', r2),
 
2561
              b'\n\n0'),
 
2562
             ('call_with_body_bytes_expecting_body',
 
2563
              b'Repository.get_parent_map', (b'quack/', b'include-missing:', r1),
 
2564
              b'\n\n0'),
 
2565
            ],
 
2566
            client._calls)
 
2567
        repo.unlock()
 
2568
 
 
2569
    def test_get_parent_map_reconnects_if_unknown_method(self):
 
2570
        transport_path = 'quack'
 
2571
        rev_id = b'revision-id'
 
2572
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2573
        client.add_unknown_method_response(b'Repository.get_parent_map')
 
2574
        client.add_success_response_with_body(rev_id, b'ok')
 
2575
        self.assertFalse(client._medium._is_remote_before((1, 2)))
 
2576
        parents = repo.get_parent_map([rev_id])
 
2577
        self.assertEqual(
 
2578
            [('call_with_body_bytes_expecting_body',
 
2579
              b'Repository.get_parent_map',
 
2580
              (b'quack/', b'include-missing:', rev_id), b'\n\n0'),
 
2581
             ('disconnect medium',),
 
2582
             ('call_expecting_body', b'Repository.get_revision_graph',
 
2583
              (b'quack/', b''))],
 
2584
            client._calls)
 
2585
        # The medium is now marked as being connected to an older server
 
2586
        self.assertTrue(client._medium._is_remote_before((1, 2)))
 
2587
        self.assertEqual({rev_id: (b'null:',)}, parents)
 
2588
 
 
2589
    def test_get_parent_map_fallback_parentless_node(self):
 
2590
        """get_parent_map falls back to get_revision_graph on old servers.  The
 
2591
        results from get_revision_graph are tweaked to match the get_parent_map
 
2592
        API.
 
2593
 
 
2594
        Specifically, a {key: ()} result from get_revision_graph means "no
 
2595
        parents" for that key, which in get_parent_map results should be
 
2596
        represented as {key: ('null:',)}.
 
2597
 
 
2598
        This is the test for https://bugs.launchpad.net/bzr/+bug/214894
 
2599
        """
 
2600
        rev_id = b'revision-id'
 
2601
        transport_path = 'quack'
 
2602
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2603
        client.add_success_response_with_body(rev_id, b'ok')
 
2604
        client._medium._remember_remote_is_before((1, 2))
 
2605
        parents = repo.get_parent_map([rev_id])
 
2606
        self.assertEqual(
 
2607
            [('call_expecting_body', b'Repository.get_revision_graph',
 
2608
             (b'quack/', b''))],
 
2609
            client._calls)
 
2610
        self.assertEqual({rev_id: (b'null:',)}, parents)
 
2611
 
 
2612
    def test_get_parent_map_unexpected_response(self):
 
2613
        repo, client = self.setup_fake_client_and_repository('path')
 
2614
        client.add_success_response(b'something unexpected!')
 
2615
        self.assertRaises(
 
2616
            errors.UnexpectedSmartServerResponse,
 
2617
            repo.get_parent_map, [b'a-revision-id'])
 
2618
 
 
2619
    def test_get_parent_map_negative_caches_missing_keys(self):
 
2620
        self.setup_smart_server_with_call_log()
 
2621
        repo = self.make_repository('foo')
 
2622
        self.assertIsInstance(repo, RemoteRepository)
 
2623
        repo.lock_read()
 
2624
        self.addCleanup(repo.unlock)
 
2625
        self.reset_smart_call_log()
 
2626
        graph = repo.get_graph()
 
2627
        self.assertEqual({},
 
2628
            graph.get_parent_map([b'some-missing', b'other-missing']))
 
2629
        self.assertLength(1, self.hpss_calls)
 
2630
        # No call if we repeat this
 
2631
        self.reset_smart_call_log()
 
2632
        graph = repo.get_graph()
 
2633
        self.assertEqual({},
 
2634
            graph.get_parent_map([b'some-missing', b'other-missing']))
 
2635
        self.assertLength(0, self.hpss_calls)
 
2636
        # Asking for more unknown keys makes a request.
 
2637
        self.reset_smart_call_log()
 
2638
        graph = repo.get_graph()
 
2639
        self.assertEqual({},
 
2640
            graph.get_parent_map([b'some-missing', b'other-missing',
 
2641
                b'more-missing']))
 
2642
        self.assertLength(1, self.hpss_calls)
 
2643
 
 
2644
    def disableExtraResults(self):
 
2645
        self.overrideAttr(SmartServerRepositoryGetParentMap,
 
2646
                          'no_extra_results', True)
 
2647
 
 
2648
    def test_null_cached_missing_and_stop_key(self):
 
2649
        self.setup_smart_server_with_call_log()
 
2650
        # Make a branch with a single revision.
 
2651
        builder = self.make_branch_builder('foo')
 
2652
        builder.start_series()
 
2653
        builder.build_snapshot(None, [
 
2654
            ('add', ('', b'root-id', 'directory', ''))],
 
2655
            revision_id=b'first')
 
2656
        builder.finish_series()
 
2657
        branch = builder.get_branch()
 
2658
        repo = branch.repository
 
2659
        self.assertIsInstance(repo, RemoteRepository)
 
2660
        # Stop the server from sending extra results.
 
2661
        self.disableExtraResults()
 
2662
        repo.lock_read()
 
2663
        self.addCleanup(repo.unlock)
 
2664
        self.reset_smart_call_log()
 
2665
        graph = repo.get_graph()
 
2666
        # Query for 'first' and 'null:'.  Because 'null:' is a parent of
 
2667
        # 'first' it will be a candidate for the stop_keys of subsequent
 
2668
        # requests, and because 'null:' was queried but not returned it will be
 
2669
        # cached as missing.
 
2670
        self.assertEqual({b'first': (b'null:',)},
 
2671
            graph.get_parent_map([b'first', b'null:']))
 
2672
        # Now query for another key.  This request will pass along a recipe of
 
2673
        # start and stop keys describing the already cached results, and this
 
2674
        # recipe's revision count must be correct (or else it will trigger an
 
2675
        # error from the server).
 
2676
        self.assertEqual({}, graph.get_parent_map([b'another-key']))
 
2677
        # This assertion guards against disableExtraResults silently failing to
 
2678
        # work, thus invalidating the test.
 
2679
        self.assertLength(2, self.hpss_calls)
 
2680
 
 
2681
    def test_get_parent_map_gets_ghosts_from_result(self):
 
2682
        # asking for a revision should negatively cache close ghosts in its
 
2683
        # ancestry.
 
2684
        self.setup_smart_server_with_call_log()
 
2685
        tree = self.make_branch_and_memory_tree('foo')
 
2686
        with tree.lock_write():
 
2687
            builder = treebuilder.TreeBuilder()
 
2688
            builder.start_tree(tree)
 
2689
            builder.build([])
 
2690
            builder.finish_tree()
 
2691
            tree.set_parent_ids([b'non-existant'], allow_leftmost_as_ghost=True)
 
2692
            rev_id = tree.commit('')
 
2693
        tree.lock_read()
 
2694
        self.addCleanup(tree.unlock)
 
2695
        repo = tree.branch.repository
 
2696
        self.assertIsInstance(repo, RemoteRepository)
 
2697
        # ask for rev_id
 
2698
        repo.get_parent_map([rev_id])
 
2699
        self.reset_smart_call_log()
 
2700
        # Now asking for rev_id's ghost parent should not make calls
 
2701
        self.assertEqual({}, repo.get_parent_map([b'non-existant']))
 
2702
        self.assertLength(0, self.hpss_calls)
 
2703
 
 
2704
    def test_exposes_get_cached_parent_map(self):
 
2705
        """RemoteRepository exposes get_cached_parent_map from
 
2706
        _unstacked_provider
 
2707
        """
 
2708
        r1 = u'\u0e33'.encode('utf8')
 
2709
        r2 = u'\u0dab'.encode('utf8')
 
2710
        lines = [b' '.join([r2, r1]), r1]
 
2711
        encoded_body = bz2.compress(b'\n'.join(lines))
 
2712
 
 
2713
        transport_path = 'quack'
 
2714
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2715
        client.add_success_response_with_body(encoded_body, b'ok')
 
2716
        repo.lock_read()
 
2717
        # get_cached_parent_map should *not* trigger an RPC
 
2718
        self.assertEqual({}, repo.get_cached_parent_map([r1]))
 
2719
        self.assertEqual([], client._calls)
 
2720
        self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
 
2721
        self.assertEqual({r1: (NULL_REVISION,)},
 
2722
            repo.get_cached_parent_map([r1]))
 
2723
        self.assertEqual(
 
2724
            [('call_with_body_bytes_expecting_body',
 
2725
              b'Repository.get_parent_map', (b'quack/', b'include-missing:', r2),
 
2726
              b'\n\n0')],
 
2727
            client._calls)
 
2728
        repo.unlock()
 
2729
 
 
2730
 
 
2731
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
 
2732
 
 
2733
    def test_allows_new_revisions(self):
 
2734
        """get_parent_map's results can be updated by commit."""
 
2735
        smart_server = test_server.SmartTCPServer_for_testing()
 
2736
        self.start_server(smart_server)
 
2737
        self.make_branch('branch')
 
2738
        branch = Branch.open(smart_server.get_url() + '/branch')
 
2739
        tree = branch.create_checkout('tree', lightweight=True)
 
2740
        tree.lock_write()
 
2741
        self.addCleanup(tree.unlock)
 
2742
        graph = tree.branch.repository.get_graph()
 
2743
        # This provides an opportunity for the missing rev-id to be cached.
 
2744
        self.assertEqual({}, graph.get_parent_map([b'rev1']))
 
2745
        tree.commit('message', rev_id=b'rev1')
 
2746
        graph = tree.branch.repository.get_graph()
 
2747
        self.assertEqual({b'rev1': (b'null:',)}, graph.get_parent_map([b'rev1']))
 
2748
 
 
2749
 
 
2750
class TestRepositoryGetRevisions(TestRemoteRepository):
 
2751
 
 
2752
    def test_hpss_missing_revision(self):
 
2753
        transport_path = 'quack'
 
2754
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2755
        client.add_success_response_with_body(
 
2756
            b'', b'ok', b'10')
 
2757
        self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
 
2758
            [b'somerev1', b'anotherrev2'])
 
2759
        self.assertEqual(
 
2760
            [('call_with_body_bytes_expecting_body', b'Repository.iter_revisions',
 
2761
             (b'quack/', ), b"somerev1\nanotherrev2")],
 
2762
            client._calls)
 
2763
 
 
2764
    def test_hpss_get_single_revision(self):
 
2765
        transport_path = 'quack'
 
2766
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2767
        somerev1 = Revision(b"somerev1")
 
2768
        somerev1.committer = "Joe Committer <joe@example.com>"
 
2769
        somerev1.timestamp = 1321828927
 
2770
        somerev1.timezone = -60
 
2771
        somerev1.inventory_sha1 = b"691b39be74c67b1212a75fcb19c433aaed903c2b"
 
2772
        somerev1.message = "Message"
 
2773
        body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
 
2774
            somerev1))
 
2775
        # Split up body into two bits to make sure the zlib compression object
 
2776
        # gets data fed twice.
 
2777
        client.add_success_response_with_body(
 
2778
                [body[:10], body[10:]], b'ok', b'10')
 
2779
        revs = repo.get_revisions([b'somerev1'])
 
2780
        self.assertEqual(revs, [somerev1])
 
2781
        self.assertEqual(
 
2782
            [('call_with_body_bytes_expecting_body', b'Repository.iter_revisions',
 
2783
             (b'quack/', ), b"somerev1")],
 
2784
            client._calls)
 
2785
 
 
2786
 
 
2787
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
 
2788
 
 
2789
    def test_null_revision(self):
 
2790
        # a null revision has the predictable result {}, we should have no wire
 
2791
        # traffic when calling it with this argument
 
2792
        transport_path = 'empty'
 
2793
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2794
        client.add_success_response(b'notused')
 
2795
        # actual RemoteRepository.get_revision_graph is gone, but there's an
 
2796
        # equivalent private method for testing
 
2797
        result = repo._get_revision_graph(NULL_REVISION)
 
2798
        self.assertEqual([], client._calls)
 
2799
        self.assertEqual({}, result)
 
2800
 
 
2801
    def test_none_revision(self):
 
2802
        # with none we want the entire graph
 
2803
        r1 = u'\u0e33'.encode('utf8')
 
2804
        r2 = u'\u0dab'.encode('utf8')
 
2805
        lines = [b' '.join([r2, r1]), r1]
 
2806
        encoded_body = b'\n'.join(lines)
 
2807
 
 
2808
        transport_path = 'sinhala'
 
2809
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2810
        client.add_success_response_with_body(encoded_body, b'ok')
 
2811
        # actual RemoteRepository.get_revision_graph is gone, but there's an
 
2812
        # equivalent private method for testing
 
2813
        result = repo._get_revision_graph(None)
 
2814
        self.assertEqual(
 
2815
            [('call_expecting_body', b'Repository.get_revision_graph',
 
2816
             (b'sinhala/', b''))],
 
2817
            client._calls)
 
2818
        self.assertEqual({r1: (), r2: (r1, )}, result)
 
2819
 
 
2820
    def test_specific_revision(self):
 
2821
        # with a specific revision we want the graph for that
 
2822
        # with none we want the entire graph
 
2823
        r11 = u'\u0e33'.encode('utf8')
 
2824
        r12 = u'\xc9'.encode('utf8')
 
2825
        r2 = u'\u0dab'.encode('utf8')
 
2826
        lines = [b' '.join([r2, r11, r12]), r11, r12]
 
2827
        encoded_body = b'\n'.join(lines)
 
2828
 
 
2829
        transport_path = 'sinhala'
 
2830
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2831
        client.add_success_response_with_body(encoded_body, b'ok')
 
2832
        result = repo._get_revision_graph(r2)
 
2833
        self.assertEqual(
 
2834
            [('call_expecting_body', b'Repository.get_revision_graph',
 
2835
             (b'sinhala/', r2))],
 
2836
            client._calls)
 
2837
        self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
 
2838
 
 
2839
    def test_no_such_revision(self):
 
2840
        revid = b'123'
 
2841
        transport_path = 'sinhala'
 
2842
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2843
        client.add_error_response(b'nosuchrevision', revid)
 
2844
        # also check that the right revision is reported in the error
 
2845
        self.assertRaises(errors.NoSuchRevision,
 
2846
            repo._get_revision_graph, revid)
 
2847
        self.assertEqual(
 
2848
            [('call_expecting_body', b'Repository.get_revision_graph',
 
2849
             (b'sinhala/', revid))],
 
2850
            client._calls)
 
2851
 
 
2852
    def test_unexpected_error(self):
 
2853
        revid = '123'
 
2854
        transport_path = 'sinhala'
 
2855
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2856
        client.add_error_response(b'AnUnexpectedError')
 
2857
        e = self.assertRaises(errors.UnknownErrorFromSmartServer,
 
2858
            repo._get_revision_graph, revid)
 
2859
        self.assertEqual((b'AnUnexpectedError',), e.error_tuple)
 
2860
 
 
2861
 
 
2862
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
 
2863
 
 
2864
    def test_ok(self):
 
2865
        repo, client = self.setup_fake_client_and_repository('quack')
 
2866
        client.add_expected_call(
 
2867
            b'Repository.get_rev_id_for_revno', (b'quack/', 5, (42, b'rev-foo')),
 
2868
            b'success', (b'ok', b'rev-five'))
 
2869
        result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
 
2870
        self.assertEqual((True, b'rev-five'), result)
 
2871
        self.assertFinished(client)
 
2872
 
 
2873
    def test_history_incomplete(self):
 
2874
        repo, client = self.setup_fake_client_and_repository('quack')
 
2875
        client.add_expected_call(
 
2876
            b'Repository.get_rev_id_for_revno', (b'quack/', 5, (42, b'rev-foo')),
 
2877
            b'success', (b'history-incomplete', 10, b'rev-ten'))
 
2878
        result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
 
2879
        self.assertEqual((False, (10, b'rev-ten')), result)
 
2880
        self.assertFinished(client)
 
2881
 
 
2882
    def test_history_incomplete_with_fallback(self):
 
2883
        """A 'history-incomplete' response causes the fallback repository to be
 
2884
        queried too, if one is set.
 
2885
        """
 
2886
        # Make a repo with a fallback repo, both using a FakeClient.
 
2887
        format = remote.response_tuple_to_repo_format(
 
2888
            (b'yes', b'no', b'yes', self.get_repo_format().network_name()))
 
2889
        repo, client = self.setup_fake_client_and_repository('quack')
 
2890
        repo._format = format
 
2891
        fallback_repo, ignored = self.setup_fake_client_and_repository(
 
2892
            'fallback')
 
2893
        fallback_repo._client = client
 
2894
        fallback_repo._format = format
 
2895
        repo.add_fallback_repository(fallback_repo)
 
2896
        # First the client should ask the primary repo
 
2897
        client.add_expected_call(
 
2898
            b'Repository.get_rev_id_for_revno', (b'quack/', 1, (42, b'rev-foo')),
 
2899
            b'success', (b'history-incomplete', 2, b'rev-two'))
 
2900
        # Then it should ask the fallback, using revno/revid from the
 
2901
        # history-incomplete response as the known revno/revid.
 
2902
        client.add_expected_call(
 
2903
            b'Repository.get_rev_id_for_revno', (b'fallback/', 1, (2, b'rev-two')),
 
2904
            b'success', (b'ok', b'rev-one'))
 
2905
        result = repo.get_rev_id_for_revno(1, (42, b'rev-foo'))
 
2906
        self.assertEqual((True, b'rev-one'), result)
 
2907
        self.assertFinished(client)
 
2908
 
 
2909
    def test_nosuchrevision(self):
 
2910
        # 'nosuchrevision' is returned when the known-revid is not found in the
 
2911
        # remote repo.  The client translates that response to NoSuchRevision.
 
2912
        repo, client = self.setup_fake_client_and_repository('quack')
 
2913
        client.add_expected_call(
 
2914
            b'Repository.get_rev_id_for_revno', (b'quack/', 5, (42, b'rev-foo')),
 
2915
            b'error', (b'nosuchrevision', b'rev-foo'))
 
2916
        self.assertRaises(
 
2917
            errors.NoSuchRevision,
 
2918
            repo.get_rev_id_for_revno, 5, (42, b'rev-foo'))
 
2919
        self.assertFinished(client)
 
2920
 
 
2921
    def test_branch_fallback_locking(self):
 
2922
        """RemoteBranch.get_rev_id takes a read lock, and tries to call the
 
2923
        get_rev_id_for_revno verb.  If the verb is unknown the VFS fallback
 
2924
        will be invoked, which will fail if the repo is unlocked.
 
2925
        """
 
2926
        self.setup_smart_server_with_call_log()
 
2927
        tree = self.make_branch_and_memory_tree('.')
 
2928
        tree.lock_write()
 
2929
        tree.add('')
 
2930
        rev1 = tree.commit('First')
 
2931
        rev2 = tree.commit('Second')
 
2932
        tree.unlock()
 
2933
        branch = tree.branch
 
2934
        self.assertFalse(branch.is_locked())
 
2935
        self.reset_smart_call_log()
 
2936
        verb = b'Repository.get_rev_id_for_revno'
 
2937
        self.disable_verb(verb)
 
2938
        self.assertEqual(rev1, branch.get_rev_id(1))
 
2939
        self.assertLength(1, [call for call in self.hpss_calls if
 
2940
                              call.call.method == verb])
 
2941
 
 
2942
 
 
2943
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
 
2944
 
 
2945
    def test_has_signature_for_revision_id(self):
 
2946
        # ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
 
2947
        transport_path = 'quack'
 
2948
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2949
        client.add_success_response(b'yes')
 
2950
        result = repo.has_signature_for_revision_id(b'A')
 
2951
        self.assertEqual(
 
2952
            [('call', b'Repository.has_signature_for_revision_id',
 
2953
              (b'quack/', b'A'))],
 
2954
            client._calls)
 
2955
        self.assertEqual(True, result)
 
2956
 
 
2957
    def test_is_not_shared(self):
 
2958
        # ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
 
2959
        transport_path = 'qwack'
 
2960
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2961
        client.add_success_response(b'no')
 
2962
        result = repo.has_signature_for_revision_id(b'A')
 
2963
        self.assertEqual(
 
2964
            [('call', b'Repository.has_signature_for_revision_id',
 
2965
              (b'qwack/', b'A'))],
 
2966
            client._calls)
 
2967
        self.assertEqual(False, result)
 
2968
 
 
2969
 
 
2970
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
 
2971
 
 
2972
    def test_get_physical_lock_status_yes(self):
 
2973
        transport_path = 'qwack'
 
2974
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2975
        client.add_success_response(b'yes')
 
2976
        result = repo.get_physical_lock_status()
 
2977
        self.assertEqual(
 
2978
            [('call', b'Repository.get_physical_lock_status',
 
2979
              (b'qwack/', ))],
 
2980
            client._calls)
 
2981
        self.assertEqual(True, result)
 
2982
 
 
2983
    def test_get_physical_lock_status_no(self):
 
2984
        transport_path = 'qwack'
 
2985
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2986
        client.add_success_response(b'no')
 
2987
        result = repo.get_physical_lock_status()
 
2988
        self.assertEqual(
 
2989
            [('call', b'Repository.get_physical_lock_status',
 
2990
              (b'qwack/', ))],
 
2991
            client._calls)
 
2992
        self.assertEqual(False, result)
 
2993
 
 
2994
 
 
2995
class TestRepositoryIsShared(TestRemoteRepository):
 
2996
 
 
2997
    def test_is_shared(self):
 
2998
        # ('yes', ) for Repository.is_shared -> 'True'.
 
2999
        transport_path = 'quack'
 
3000
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3001
        client.add_success_response(b'yes')
 
3002
        result = repo.is_shared()
 
3003
        self.assertEqual(
 
3004
            [('call', b'Repository.is_shared', (b'quack/',))],
 
3005
            client._calls)
 
3006
        self.assertEqual(True, result)
 
3007
 
 
3008
    def test_is_not_shared(self):
 
3009
        # ('no', ) for Repository.is_shared -> 'False'.
 
3010
        transport_path = 'qwack'
 
3011
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3012
        client.add_success_response(b'no')
 
3013
        result = repo.is_shared()
 
3014
        self.assertEqual(
 
3015
            [('call', b'Repository.is_shared', (b'qwack/',))],
 
3016
            client._calls)
 
3017
        self.assertEqual(False, result)
 
3018
 
 
3019
 
 
3020
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
 
3021
 
 
3022
    def test_make_working_trees(self):
 
3023
        # ('yes', ) for Repository.make_working_trees -> 'True'.
 
3024
        transport_path = 'quack'
 
3025
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3026
        client.add_success_response(b'yes')
 
3027
        result = repo.make_working_trees()
 
3028
        self.assertEqual(
 
3029
            [('call', b'Repository.make_working_trees', (b'quack/',))],
 
3030
            client._calls)
 
3031
        self.assertEqual(True, result)
 
3032
 
 
3033
    def test_no_working_trees(self):
 
3034
        # ('no', ) for Repository.make_working_trees -> 'False'.
 
3035
        transport_path = 'qwack'
 
3036
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3037
        client.add_success_response(b'no')
 
3038
        result = repo.make_working_trees()
 
3039
        self.assertEqual(
 
3040
            [('call', b'Repository.make_working_trees', (b'qwack/',))],
 
3041
            client._calls)
 
3042
        self.assertEqual(False, result)
 
3043
 
 
3044
 
 
3045
class TestRepositoryLockWrite(TestRemoteRepository):
 
3046
 
 
3047
    def test_lock_write(self):
 
3048
        transport_path = 'quack'
 
3049
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3050
        client.add_success_response(b'ok', b'a token')
 
3051
        token = repo.lock_write().repository_token
 
3052
        self.assertEqual(
 
3053
            [('call', b'Repository.lock_write', (b'quack/', b''))],
 
3054
            client._calls)
 
3055
        self.assertEqual(b'a token', token)
 
3056
 
 
3057
    def test_lock_write_already_locked(self):
 
3058
        transport_path = 'quack'
 
3059
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3060
        client.add_error_response(b'LockContention')
 
3061
        self.assertRaises(errors.LockContention, repo.lock_write)
 
3062
        self.assertEqual(
 
3063
            [('call', b'Repository.lock_write', (b'quack/', b''))],
 
3064
            client._calls)
 
3065
 
 
3066
    def test_lock_write_unlockable(self):
 
3067
        transport_path = 'quack'
 
3068
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3069
        client.add_error_response(b'UnlockableTransport')
 
3070
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
 
3071
        self.assertEqual(
 
3072
            [('call', b'Repository.lock_write', (b'quack/', b''))],
 
3073
            client._calls)
 
3074
 
 
3075
 
 
3076
class TestRepositoryWriteGroups(TestRemoteRepository):
 
3077
 
 
3078
    def test_start_write_group(self):
 
3079
        transport_path = 'quack'
 
3080
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3081
        client.add_expected_call(
 
3082
            b'Repository.lock_write', (b'quack/', b''),
 
3083
            b'success', (b'ok', b'a token'))
 
3084
        client.add_expected_call(
 
3085
            b'Repository.start_write_group', (b'quack/', b'a token'),
 
3086
            b'success', (b'ok', (b'token1', )))
 
3087
        repo.lock_write()
 
3088
        repo.start_write_group()
 
3089
 
 
3090
    def test_start_write_group_unsuspendable(self):
 
3091
        # Some repositories do not support suspending write
 
3092
        # groups. For those, fall back to the "real" repository.
 
3093
        transport_path = 'quack'
 
3094
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3095
        def stub_ensure_real():
 
3096
            client._calls.append(('_ensure_real',))
 
3097
            repo._real_repository = _StubRealPackRepository(client._calls)
 
3098
        repo._ensure_real = stub_ensure_real
 
3099
        client.add_expected_call(
 
3100
            b'Repository.lock_write', (b'quack/', b''),
 
3101
            b'success', (b'ok', b'a token'))
 
3102
        client.add_expected_call(
 
3103
            b'Repository.start_write_group', (b'quack/', b'a token'),
 
3104
            b'error', (b'UnsuspendableWriteGroup',))
 
3105
        repo.lock_write()
 
3106
        repo.start_write_group()
 
3107
        self.assertEqual(client._calls[-2:], [
 
3108
            ('_ensure_real',),
 
3109
            ('start_write_group',)])
 
3110
 
 
3111
    def test_commit_write_group(self):
 
3112
        transport_path = 'quack'
 
3113
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3114
        client.add_expected_call(
 
3115
            b'Repository.lock_write', (b'quack/', b''),
 
3116
            b'success', (b'ok', b'a token'))
 
3117
        client.add_expected_call(
 
3118
            b'Repository.start_write_group', (b'quack/', b'a token'),
 
3119
            b'success', (b'ok', [b'token1']))
 
3120
        client.add_expected_call(
 
3121
            b'Repository.commit_write_group', (b'quack/', b'a token', [b'token1']),
 
3122
            b'success', (b'ok',))
 
3123
        repo.lock_write()
 
3124
        repo.start_write_group()
 
3125
        repo.commit_write_group()
 
3126
 
 
3127
    def test_abort_write_group(self):
 
3128
        transport_path = 'quack'
 
3129
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3130
        client.add_expected_call(
 
3131
            b'Repository.lock_write', (b'quack/', b''),
 
3132
            b'success', (b'ok', b'a token'))
 
3133
        client.add_expected_call(
 
3134
            b'Repository.start_write_group', (b'quack/', b'a token'),
 
3135
            b'success', (b'ok', [b'token1']))
 
3136
        client.add_expected_call(
 
3137
            b'Repository.abort_write_group', (b'quack/', b'a token', [b'token1']),
 
3138
            b'success', (b'ok',))
 
3139
        repo.lock_write()
 
3140
        repo.start_write_group()
 
3141
        repo.abort_write_group(False)
 
3142
 
 
3143
    def test_suspend_write_group(self):
 
3144
        transport_path = 'quack'
 
3145
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3146
        self.assertEqual([], repo.suspend_write_group())
 
3147
 
 
3148
    def test_resume_write_group(self):
 
3149
        transport_path = 'quack'
 
3150
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3151
        client.add_expected_call(
 
3152
            b'Repository.lock_write', (b'quack/', b''),
 
3153
            b'success', (b'ok', b'a token'))
 
3154
        client.add_expected_call(
 
3155
            b'Repository.check_write_group', (b'quack/', b'a token', [b'token1']),
 
3156
            b'success', (b'ok',))
 
3157
        repo.lock_write()
 
3158
        repo.resume_write_group([b'token1'])
 
3159
 
 
3160
 
 
3161
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
 
3162
 
 
3163
    def test_backwards_compat(self):
 
3164
        self.setup_smart_server_with_call_log()
 
3165
        repo = self.make_repository('.')
 
3166
        self.reset_smart_call_log()
 
3167
        verb = b'Repository.set_make_working_trees'
 
3168
        self.disable_verb(verb)
 
3169
        repo.set_make_working_trees(True)
 
3170
        call_count = len([call for call in self.hpss_calls if
 
3171
            call.call.method == verb])
 
3172
        self.assertEqual(1, call_count)
 
3173
 
 
3174
    def test_current(self):
 
3175
        transport_path = 'quack'
 
3176
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3177
        client.add_expected_call(
 
3178
            b'Repository.set_make_working_trees', (b'quack/', b'True'),
 
3179
            b'success', (b'ok',))
 
3180
        client.add_expected_call(
 
3181
            b'Repository.set_make_working_trees', (b'quack/', b'False'),
 
3182
            b'success', (b'ok',))
 
3183
        repo.set_make_working_trees(True)
 
3184
        repo.set_make_working_trees(False)
 
3185
 
 
3186
 
 
3187
class TestRepositoryUnlock(TestRemoteRepository):
 
3188
 
 
3189
    def test_unlock(self):
 
3190
        transport_path = 'quack'
 
3191
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3192
        client.add_success_response(b'ok', b'a token')
 
3193
        client.add_success_response(b'ok')
 
3194
        repo.lock_write()
 
3195
        repo.unlock()
 
3196
        self.assertEqual(
 
3197
            [('call', b'Repository.lock_write', (b'quack/', b'')),
 
3198
             ('call', b'Repository.unlock', (b'quack/', b'a token'))],
 
3199
            client._calls)
 
3200
 
 
3201
    def test_unlock_wrong_token(self):
 
3202
        # If somehow the token is wrong, unlock will raise TokenMismatch.
 
3203
        transport_path = 'quack'
 
3204
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3205
        client.add_success_response(b'ok', b'a token')
 
3206
        client.add_error_response(b'TokenMismatch')
 
3207
        repo.lock_write()
 
3208
        self.assertRaises(errors.TokenMismatch, repo.unlock)
 
3209
 
 
3210
 
 
3211
class TestRepositoryHasRevision(TestRemoteRepository):
 
3212
 
 
3213
    def test_none(self):
 
3214
        # repo.has_revision(None) should not cause any traffic.
 
3215
        transport_path = 'quack'
 
3216
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3217
 
 
3218
        # The null revision is always there, so has_revision(None) == True.
 
3219
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
 
3220
 
 
3221
        # The remote repo shouldn't be accessed.
 
3222
        self.assertEqual([], client._calls)
 
3223
 
 
3224
 
 
3225
class TestRepositoryIterFilesBytes(TestRemoteRepository):
 
3226
    """Test Repository.iter_file_bytes."""
 
3227
 
 
3228
    def test_single(self):
 
3229
        transport_path = 'quack'
 
3230
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3231
        client.add_expected_call(
 
3232
            b'Repository.iter_files_bytes', (b'quack/', ),
 
3233
            b'success', (b'ok',), iter([b"ok\x000", b"\n", zlib.compress(b"mydata" * 10)]))
 
3234
        for (identifier, byte_stream) in repo.iter_files_bytes([(b"somefile",
 
3235
                b"somerev", b"myid")]):
 
3236
            self.assertEqual(b"myid", identifier)
 
3237
            self.assertEqual(b"".join(byte_stream), b"mydata" * 10)
 
3238
 
 
3239
    def test_missing(self):
 
3240
        transport_path = 'quack'
 
3241
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3242
        client.add_expected_call(
 
3243
            b'Repository.iter_files_bytes',
 
3244
                (b'quack/', ),
 
3245
            b'error', (b'RevisionNotPresent', b'somefile', b'somerev'),
 
3246
            iter([b"absent\0somefile\0somerev\n"]))
 
3247
        self.assertRaises(errors.RevisionNotPresent, list,
 
3248
                repo.iter_files_bytes(
 
3249
                [(b"somefile", b"somerev", b"myid")]))
 
3250
 
 
3251
 
 
3252
class TestRepositoryInsertStreamBase(TestRemoteRepository):
 
3253
    """Base class for Repository.insert_stream and .insert_stream_1.19
 
3254
    tests.
 
3255
    """
 
3256
    
 
3257
    def checkInsertEmptyStream(self, repo, client):
 
3258
        """Insert an empty stream, checking the result.
 
3259
 
 
3260
        This checks that there are no resume_tokens or missing_keys, and that
 
3261
        the client is finished.
 
3262
        """
 
3263
        sink = repo._get_sink()
 
3264
        fmt = repository.format_registry.get_default()
 
3265
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
 
3266
        self.assertEqual([], resume_tokens)
 
3267
        self.assertEqual(set(), missing_keys)
 
3268
        self.assertFinished(client)
 
3269
 
 
3270
 
 
3271
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
 
3272
    """Tests for using Repository.insert_stream verb when the _1.19 variant is
 
3273
    not available.
 
3274
 
 
3275
    This test case is very similar to TestRepositoryInsertStream_1_19.
 
3276
    """
 
3277
 
 
3278
    def setUp(self):
 
3279
        super(TestRepositoryInsertStream, self).setUp()
 
3280
        self.disable_verb(b'Repository.insert_stream_1.19')
 
3281
 
 
3282
    def test_unlocked_repo(self):
 
3283
        transport_path = 'quack'
 
3284
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3285
        client.add_expected_call(
 
3286
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3287
            b'unknown', (b'Repository.insert_stream_1.19',))
 
3288
        client.add_expected_call(
 
3289
            b'Repository.insert_stream', (b'quack/', b''),
 
3290
            b'success', (b'ok',))
 
3291
        client.add_expected_call(
 
3292
            b'Repository.insert_stream', (b'quack/', b''),
 
3293
            b'success', (b'ok',))
 
3294
        self.checkInsertEmptyStream(repo, client)
 
3295
 
 
3296
    def test_locked_repo_with_no_lock_token(self):
 
3297
        transport_path = 'quack'
 
3298
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3299
        client.add_expected_call(
 
3300
            b'Repository.lock_write', (b'quack/', b''),
 
3301
            b'success', (b'ok', b''))
 
3302
        client.add_expected_call(
 
3303
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3304
            b'unknown', (b'Repository.insert_stream_1.19',))
 
3305
        client.add_expected_call(
 
3306
            b'Repository.insert_stream', (b'quack/', b''),
 
3307
            b'success', (b'ok',))
 
3308
        client.add_expected_call(
 
3309
            b'Repository.insert_stream', (b'quack/', b''),
 
3310
            b'success', (b'ok',))
 
3311
        repo.lock_write()
 
3312
        self.checkInsertEmptyStream(repo, client)
 
3313
 
 
3314
    def test_locked_repo_with_lock_token(self):
 
3315
        transport_path = 'quack'
 
3316
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3317
        client.add_expected_call(
 
3318
            b'Repository.lock_write', (b'quack/', b''),
 
3319
            b'success', (b'ok', b'a token'))
 
3320
        client.add_expected_call(
 
3321
            b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
 
3322
            b'unknown', (b'Repository.insert_stream_1.19',))
 
3323
        client.add_expected_call(
 
3324
            b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
 
3325
            b'success', (b'ok',))
 
3326
        client.add_expected_call(
 
3327
            b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
 
3328
            b'success', (b'ok',))
 
3329
        repo.lock_write()
 
3330
        self.checkInsertEmptyStream(repo, client)
 
3331
 
 
3332
    def test_stream_with_inventory_deltas(self):
 
3333
        """'inventory-deltas' substreams cannot be sent to the
 
3334
        Repository.insert_stream verb, because not all servers that implement
 
3335
        that verb will accept them.  So when one is encountered the RemoteSink
 
3336
        immediately stops using that verb and falls back to VFS insert_stream.
 
3337
        """
 
3338
        transport_path = 'quack'
 
3339
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3340
        client.add_expected_call(
 
3341
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3342
            b'unknown', (b'Repository.insert_stream_1.19',))
 
3343
        client.add_expected_call(
 
3344
            b'Repository.insert_stream', (b'quack/', b''),
 
3345
            b'success', (b'ok',))
 
3346
        client.add_expected_call(
 
3347
            b'Repository.insert_stream', (b'quack/', b''),
 
3348
            b'success', (b'ok',))
 
3349
        # Create a fake real repository for insert_stream to fall back on, so
 
3350
        # that we can directly see the records the RemoteSink passes to the
 
3351
        # real sink.
 
3352
        class FakeRealSink:
 
3353
            def __init__(self):
 
3354
                self.records = []
 
3355
            def insert_stream(self, stream, src_format, resume_tokens):
 
3356
                for substream_kind, substream in stream:
 
3357
                    self.records.append(
 
3358
                        (substream_kind, [record.key for record in substream]))
 
3359
                return [b'fake tokens'], [b'fake missing keys']
 
3360
        fake_real_sink = FakeRealSink()
 
3361
        class FakeRealRepository:
 
3362
            def _get_sink(self):
 
3363
                return fake_real_sink
 
3364
            def is_in_write_group(self):
 
3365
                return False
 
3366
            def refresh_data(self):
 
3367
                return True
 
3368
        repo._real_repository = FakeRealRepository()
 
3369
        sink = repo._get_sink()
 
3370
        fmt = repository.format_registry.get_default()
 
3371
        stream = self.make_stream_with_inv_deltas(fmt)
 
3372
        resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
 
3373
        # Every record from the first inventory delta should have been sent to
 
3374
        # the VFS sink.
 
3375
        expected_records = [
 
3376
            ('inventory-deltas', [(b'rev2',), (b'rev3',)]),
 
3377
            ('texts', [(b'some-rev', b'some-file')])]
 
3378
        self.assertEqual(expected_records, fake_real_sink.records)
 
3379
        # The return values from the real sink's insert_stream are propagated
 
3380
        # back to the original caller.
 
3381
        self.assertEqual([b'fake tokens'], resume_tokens)
 
3382
        self.assertEqual([b'fake missing keys'], missing_keys)
 
3383
        self.assertFinished(client)
 
3384
 
 
3385
    def make_stream_with_inv_deltas(self, fmt):
 
3386
        """Make a simple stream with an inventory delta followed by more
 
3387
        records and more substreams to test that all records and substreams
 
3388
        from that point on are used.
 
3389
 
 
3390
        This sends, in order:
 
3391
           * inventories substream: rev1, rev2, rev3.  rev2 and rev3 are
 
3392
             inventory-deltas.
 
3393
           * texts substream: (some-rev, some-file)
 
3394
        """
 
3395
        # Define a stream using generators so that it isn't rewindable.
 
3396
        inv = inventory.Inventory(revision_id=b'rev1')
 
3397
        inv.root.revision = b'rev1'
 
3398
        def stream_with_inv_delta():
 
3399
            yield ('inventories', inventories_substream())
 
3400
            yield ('inventory-deltas', inventory_delta_substream())
 
3401
            yield ('texts', [
 
3402
                versionedfile.FulltextContentFactory(
 
3403
                    (b'some-rev', 'some-file'), (), None, 'content')])
 
3404
        def inventories_substream():
 
3405
            # An empty inventory fulltext.  This will be streamed normally.
 
3406
            text = fmt._serializer.write_inventory_to_string(inv)
 
3407
            yield versionedfile.FulltextContentFactory(
 
3408
                (b'rev1',), (), None, text)
 
3409
        def inventory_delta_substream():
 
3410
            # An inventory delta.  This can't be streamed via this verb, so it
 
3411
            # will trigger a fallback to VFS insert_stream.
 
3412
            entry = inv.make_entry(
 
3413
                'directory', 'newdir', inv.root.file_id, b'newdir-id')
 
3414
            entry.revision = 'ghost'
 
3415
            delta = [(None, 'newdir', b'newdir-id', entry)]
 
3416
            serializer = inventory_delta.InventoryDeltaSerializer(
 
3417
                versioned_root=True, tree_references=False)
 
3418
            lines = serializer.delta_to_lines(b'rev1', b'rev2', delta)
 
3419
            yield versionedfile.ChunkedContentFactory(
 
3420
                (b'rev2',), ((b'rev1',)), None, lines)
 
3421
            # Another delta.
 
3422
            lines = serializer.delta_to_lines(b'rev1', b'rev3', delta)
 
3423
            yield versionedfile.ChunkedContentFactory(
 
3424
                (b'rev3',), ((b'rev1',)), None, lines)
 
3425
        return stream_with_inv_delta()
 
3426
 
 
3427
 
 
3428
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
 
3429
 
 
3430
    def test_unlocked_repo(self):
 
3431
        transport_path = 'quack'
 
3432
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3433
        client.add_expected_call(
 
3434
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3435
            b'success', (b'ok',))
 
3436
        client.add_expected_call(
 
3437
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3438
            b'success', (b'ok',))
 
3439
        self.checkInsertEmptyStream(repo, client)
 
3440
 
 
3441
    def test_locked_repo_with_no_lock_token(self):
 
3442
        transport_path = 'quack'
 
3443
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3444
        client.add_expected_call(
 
3445
            b'Repository.lock_write', (b'quack/', b''),
 
3446
            b'success', (b'ok', b''))
 
3447
        client.add_expected_call(
 
3448
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3449
            b'success', (b'ok',))
 
3450
        client.add_expected_call(
 
3451
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3452
            b'success', (b'ok',))
 
3453
        repo.lock_write()
 
3454
        self.checkInsertEmptyStream(repo, client)
 
3455
 
 
3456
    def test_locked_repo_with_lock_token(self):
 
3457
        transport_path = 'quack'
 
3458
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3459
        client.add_expected_call(
 
3460
            b'Repository.lock_write', (b'quack/', b''),
 
3461
            b'success', (b'ok', b'a token'))
 
3462
        client.add_expected_call(
 
3463
            b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
 
3464
            b'success', (b'ok',))
 
3465
        client.add_expected_call(
 
3466
            b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
 
3467
            b'success', (b'ok',))
 
3468
        repo.lock_write()
 
3469
        self.checkInsertEmptyStream(repo, client)
 
3470
 
 
3471
 
 
3472
class TestRepositoryTarball(TestRemoteRepository):
 
3473
 
 
3474
    # This is a canned tarball reponse we can validate against
 
3475
    tarball_content = base64.b64decode(
 
3476
        'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
 
3477
        'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
 
3478
        'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
 
3479
        'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
 
3480
        'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
 
3481
        'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
 
3482
        'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
 
3483
        'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
 
3484
        '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
 
3485
        'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
 
3486
        'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
 
3487
        'nWQ7QH/F3JFOFCQ0aSPfA='
 
3488
        )
 
3489
 
 
3490
    def test_repository_tarball(self):
 
3491
        # Test that Repository.tarball generates the right operations
 
3492
        transport_path = 'repo'
 
3493
        expected_calls = [('call_expecting_body', b'Repository.tarball',
 
3494
                           (b'repo/', b'bz2',),),
 
3495
            ]
 
3496
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3497
        client.add_success_response_with_body(self.tarball_content, b'ok')
 
3498
        # Now actually ask for the tarball
 
3499
        tarball_file = repo._get_tarball('bz2')
 
3500
        try:
 
3501
            self.assertEqual(expected_calls, client._calls)
 
3502
            self.assertEqual(self.tarball_content, tarball_file.read())
 
3503
        finally:
 
3504
            tarball_file.close()
 
3505
 
 
3506
 
 
3507
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
 
3508
    """RemoteRepository.copy_content_into optimizations"""
 
3509
 
 
3510
    def test_copy_content_remote_to_local(self):
 
3511
        self.transport_server = test_server.SmartTCPServer_for_testing
 
3512
        src_repo = self.make_repository('repo1')
 
3513
        src_repo = repository.Repository.open(self.get_url('repo1'))
 
3514
        # At the moment the tarball-based copy_content_into can't write back
 
3515
        # into a smart server.  It would be good if it could upload the
 
3516
        # tarball; once that works we'd have to create repositories of
 
3517
        # different formats. -- mbp 20070410
 
3518
        dest_url = self.get_vfs_only_url('repo2')
 
3519
        dest_bzrdir = BzrDir.create(dest_url)
 
3520
        dest_repo = dest_bzrdir.create_repository()
 
3521
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
 
3522
        self.assertTrue(isinstance(src_repo, RemoteRepository))
 
3523
        src_repo.copy_content_into(dest_repo)
 
3524
 
 
3525
 
 
3526
class _StubRealPackRepository(object):
 
3527
 
 
3528
    def __init__(self, calls):
 
3529
        self.calls = calls
 
3530
        self._pack_collection = _StubPackCollection(calls)
 
3531
 
 
3532
    def start_write_group(self):
 
3533
        self.calls.append(('start_write_group',))
 
3534
 
 
3535
    def is_in_write_group(self):
 
3536
        return False
 
3537
 
 
3538
    def refresh_data(self):
 
3539
        self.calls.append(('pack collection reload_pack_names',))
 
3540
 
 
3541
 
 
3542
class _StubPackCollection(object):
 
3543
 
 
3544
    def __init__(self, calls):
 
3545
        self.calls = calls
 
3546
 
 
3547
    def autopack(self):
 
3548
        self.calls.append(('pack collection autopack',))
 
3549
 
 
3550
 
 
3551
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
 
3552
    """Tests for RemoteRepository.autopack implementation."""
 
3553
 
 
3554
    def test_ok(self):
 
3555
        """When the server returns 'ok' and there's no _real_repository, then
 
3556
        nothing else happens: the autopack method is done.
 
3557
        """
 
3558
        transport_path = 'quack'
 
3559
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3560
        client.add_expected_call(
 
3561
            b'PackRepository.autopack', (b'quack/',), b'success', (b'ok',))
 
3562
        repo.autopack()
 
3563
        self.assertFinished(client)
 
3564
 
 
3565
    def test_ok_with_real_repo(self):
 
3566
        """When the server returns 'ok' and there is a _real_repository, then
 
3567
        the _real_repository's reload_pack_name's method will be called.
 
3568
        """
 
3569
        transport_path = 'quack'
 
3570
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3571
        client.add_expected_call(
 
3572
            b'PackRepository.autopack', (b'quack/',),
 
3573
            b'success', (b'ok',))
 
3574
        repo._real_repository = _StubRealPackRepository(client._calls)
 
3575
        repo.autopack()
 
3576
        self.assertEqual(
 
3577
            [('call', b'PackRepository.autopack', (b'quack/',)),
 
3578
             ('pack collection reload_pack_names',)],
 
3579
            client._calls)
 
3580
 
 
3581
    def test_backwards_compatibility(self):
 
3582
        """If the server does not recognise the PackRepository.autopack verb,
 
3583
        fallback to the real_repository's implementation.
 
3584
        """
 
3585
        transport_path = 'quack'
 
3586
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3587
        client.add_unknown_method_response(b'PackRepository.autopack')
 
3588
        def stub_ensure_real():
 
3589
            client._calls.append(('_ensure_real',))
 
3590
            repo._real_repository = _StubRealPackRepository(client._calls)
 
3591
        repo._ensure_real = stub_ensure_real
 
3592
        repo.autopack()
 
3593
        self.assertEqual(
 
3594
            [('call', b'PackRepository.autopack', (b'quack/',)),
 
3595
             ('_ensure_real',),
 
3596
             ('pack collection autopack',)],
 
3597
            client._calls)
 
3598
 
 
3599
    def test_oom_error_reporting(self):
 
3600
        """An out-of-memory condition on the server is reported clearly"""
 
3601
        transport_path = 'quack'
 
3602
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3603
        client.add_expected_call(
 
3604
            b'PackRepository.autopack', (b'quack/',),
 
3605
            b'error', (b'MemoryError',))
 
3606
        err = self.assertRaises(errors.BzrError, repo.autopack)
 
3607
        self.assertContainsRe(str(err), "^remote server out of mem")
 
3608
 
 
3609
 
 
3610
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
 
3611
    """Base class for unit tests for breezy.bzr.remote._translate_error."""
 
3612
 
 
3613
    def translateTuple(self, error_tuple, **context):
 
3614
        """Call _translate_error with an ErrorFromSmartServer built from the
 
3615
        given error_tuple.
 
3616
 
 
3617
        :param error_tuple: A tuple of a smart server response, as would be
 
3618
            passed to an ErrorFromSmartServer.
 
3619
        :kwargs context: context items to call _translate_error with.
 
3620
 
 
3621
        :returns: The error raised by _translate_error.
 
3622
        """
 
3623
        # Raise the ErrorFromSmartServer before passing it as an argument,
 
3624
        # because _translate_error may need to re-raise it with a bare 'raise'
 
3625
        # statement.
 
3626
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
3627
        translated_error = self.translateErrorFromSmartServer(
 
3628
            server_error, **context)
 
3629
        return translated_error
 
3630
 
 
3631
    def translateErrorFromSmartServer(self, error_object, **context):
 
3632
        """Like translateTuple, but takes an already constructed
 
3633
        ErrorFromSmartServer rather than a tuple.
 
3634
        """
 
3635
        try:
 
3636
            raise error_object
 
3637
        except errors.ErrorFromSmartServer as server_error:
 
3638
            translated_error = self.assertRaises(
 
3639
                errors.BzrError, remote._translate_error, server_error,
 
3640
                **context)
 
3641
        return translated_error
 
3642
 
 
3643
 
 
3644
class TestErrorTranslationSuccess(TestErrorTranslationBase):
 
3645
    """Unit tests for breezy.bzr.remote._translate_error.
 
3646
 
 
3647
    Given an ErrorFromSmartServer (which has an error tuple from a smart
 
3648
    server) and some context, _translate_error raises more specific errors from
 
3649
    breezy.errors.
 
3650
 
 
3651
    This test case covers the cases where _translate_error succeeds in
 
3652
    translating an ErrorFromSmartServer to something better.  See
 
3653
    TestErrorTranslationRobustness for other cases.
 
3654
    """
 
3655
 
 
3656
    def test_NoSuchRevision(self):
 
3657
        branch = self.make_branch('')
 
3658
        revid = b'revid'
 
3659
        translated_error = self.translateTuple(
 
3660
            (b'NoSuchRevision', revid), branch=branch)
 
3661
        expected_error = errors.NoSuchRevision(branch, revid)
 
3662
        self.assertEqual(expected_error, translated_error)
 
3663
 
 
3664
    def test_nosuchrevision(self):
 
3665
        repository = self.make_repository('')
 
3666
        revid = b'revid'
 
3667
        translated_error = self.translateTuple(
 
3668
            (b'nosuchrevision', revid), repository=repository)
 
3669
        expected_error = errors.NoSuchRevision(repository, revid)
 
3670
        self.assertEqual(expected_error, translated_error)
 
3671
 
 
3672
    def test_nobranch(self):
 
3673
        bzrdir = self.make_controldir('')
 
3674
        translated_error = self.translateTuple((b'nobranch',), bzrdir=bzrdir)
 
3675
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
 
3676
        self.assertEqual(expected_error, translated_error)
 
3677
 
 
3678
    def test_nobranch_one_arg(self):
 
3679
        bzrdir = self.make_controldir('')
 
3680
        translated_error = self.translateTuple(
 
3681
            (b'nobranch', b'extra detail'), bzrdir=bzrdir)
 
3682
        expected_error = errors.NotBranchError(
 
3683
            path=bzrdir.root_transport.base,
 
3684
            detail='extra detail')
 
3685
        self.assertEqual(expected_error, translated_error)
 
3686
 
 
3687
    def test_norepository(self):
 
3688
        bzrdir = self.make_controldir('')
 
3689
        translated_error = self.translateTuple((b'norepository',),
 
3690
            bzrdir=bzrdir)
 
3691
        expected_error = errors.NoRepositoryPresent(bzrdir)
 
3692
        self.assertEqual(expected_error, translated_error)
 
3693
 
 
3694
    def test_LockContention(self):
 
3695
        translated_error = self.translateTuple((b'LockContention',))
 
3696
        expected_error = errors.LockContention('(remote lock)')
 
3697
        self.assertEqual(expected_error, translated_error)
 
3698
 
 
3699
    def test_UnlockableTransport(self):
 
3700
        bzrdir = self.make_controldir('')
 
3701
        translated_error = self.translateTuple(
 
3702
            (b'UnlockableTransport',), bzrdir=bzrdir)
 
3703
        expected_error = errors.UnlockableTransport(bzrdir.root_transport)
 
3704
        self.assertEqual(expected_error, translated_error)
 
3705
 
 
3706
    def test_LockFailed(self):
 
3707
        lock = 'str() of a server lock'
 
3708
        why = 'str() of why'
 
3709
        translated_error = self.translateTuple((b'LockFailed', lock.encode('ascii'), why.encode('ascii')))
 
3710
        expected_error = errors.LockFailed(lock, why)
 
3711
        self.assertEqual(expected_error, translated_error)
 
3712
 
 
3713
    def test_TokenMismatch(self):
 
3714
        token = 'a lock token'
 
3715
        translated_error = self.translateTuple((b'TokenMismatch',), token=token)
 
3716
        expected_error = errors.TokenMismatch(token, '(remote token)')
 
3717
        self.assertEqual(expected_error, translated_error)
 
3718
 
 
3719
    def test_Diverged(self):
 
3720
        branch = self.make_branch('a')
 
3721
        other_branch = self.make_branch('b')
 
3722
        translated_error = self.translateTuple(
 
3723
            (b'Diverged',), branch=branch, other_branch=other_branch)
 
3724
        expected_error = errors.DivergedBranches(branch, other_branch)
 
3725
        self.assertEqual(expected_error, translated_error)
 
3726
 
 
3727
    def test_NotStacked(self):
 
3728
        branch = self.make_branch('')
 
3729
        translated_error = self.translateTuple((b'NotStacked',), branch=branch)
 
3730
        expected_error = errors.NotStacked(branch)
 
3731
        self.assertEqual(expected_error, translated_error)
 
3732
 
 
3733
    def test_ReadError_no_args(self):
 
3734
        path = 'a path'
 
3735
        translated_error = self.translateTuple((b'ReadError',), path=path)
 
3736
        expected_error = errors.ReadError(path)
 
3737
        self.assertEqual(expected_error, translated_error)
 
3738
 
 
3739
    def test_ReadError(self):
 
3740
        path = 'a path'
 
3741
        translated_error = self.translateTuple((b'ReadError', path.encode('utf-8')))
 
3742
        expected_error = errors.ReadError(path)
 
3743
        self.assertEqual(expected_error, translated_error)
 
3744
 
 
3745
    def test_IncompatibleRepositories(self):
 
3746
        translated_error = self.translateTuple((b'IncompatibleRepositories',
 
3747
            b"repo1", b"repo2", b"details here"))
 
3748
        expected_error = errors.IncompatibleRepositories("repo1", "repo2",
 
3749
            "details here")
 
3750
        self.assertEqual(expected_error, translated_error)
 
3751
 
 
3752
    def test_PermissionDenied_no_args(self):
 
3753
        path = 'a path'
 
3754
        translated_error = self.translateTuple((b'PermissionDenied',),
 
3755
            path=path)
 
3756
        expected_error = errors.PermissionDenied(path)
 
3757
        self.assertEqual(expected_error, translated_error)
 
3758
 
 
3759
    def test_PermissionDenied_one_arg(self):
 
3760
        path = 'a path'
 
3761
        translated_error = self.translateTuple((b'PermissionDenied', path.encode('utf-8')))
 
3762
        expected_error = errors.PermissionDenied(path)
 
3763
        self.assertEqual(expected_error, translated_error)
 
3764
 
 
3765
    def test_PermissionDenied_one_arg_and_context(self):
 
3766
        """Given a choice between a path from the local context and a path on
 
3767
        the wire, _translate_error prefers the path from the local context.
 
3768
        """
 
3769
        local_path = 'local path'
 
3770
        remote_path = 'remote path'
 
3771
        translated_error = self.translateTuple(
 
3772
            (b'PermissionDenied', remote_path.encode('utf-8')), path=local_path)
 
3773
        expected_error = errors.PermissionDenied(local_path)
 
3774
        self.assertEqual(expected_error, translated_error)
 
3775
 
 
3776
    def test_PermissionDenied_two_args(self):
 
3777
        path = 'a path'
 
3778
        extra = 'a string with extra info'
 
3779
        translated_error = self.translateTuple(
 
3780
            (b'PermissionDenied', path.encode('utf-8'), extra.encode('utf-8')))
 
3781
        expected_error = errors.PermissionDenied(path, extra)
 
3782
        self.assertEqual(expected_error, translated_error)
 
3783
 
 
3784
    # GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
 
3785
 
 
3786
    def test_NoSuchFile_context_path(self):
 
3787
        local_path = "local path"
 
3788
        translated_error = self.translateTuple((b'ReadError', b"remote path"),
 
3789
            path=local_path)
 
3790
        expected_error = errors.ReadError(local_path)
 
3791
        self.assertEqual(expected_error, translated_error)
 
3792
 
 
3793
    def test_NoSuchFile_without_context(self):
 
3794
        remote_path = "remote path"
 
3795
        translated_error = self.translateTuple((b'ReadError', remote_path.encode('utf-8')))
 
3796
        expected_error = errors.ReadError(remote_path)
 
3797
        self.assertEqual(expected_error, translated_error)
 
3798
 
 
3799
    def test_ReadOnlyError(self):
 
3800
        translated_error = self.translateTuple((b'ReadOnlyError',))
 
3801
        expected_error = errors.TransportNotPossible("readonly transport")
 
3802
        self.assertEqual(expected_error, translated_error)
 
3803
 
 
3804
    def test_MemoryError(self):
 
3805
        translated_error = self.translateTuple((b'MemoryError',))
 
3806
        self.assertStartsWith(str(translated_error),
 
3807
            "remote server out of memory")
 
3808
 
 
3809
    def test_generic_IndexError_no_classname(self):
 
3810
        err = errors.ErrorFromSmartServer((b'error', b"list index out of range"))
 
3811
        translated_error = self.translateErrorFromSmartServer(err)
 
3812
        expected_error = errors.UnknownErrorFromSmartServer(err)
 
3813
        self.assertEqual(expected_error, translated_error)
 
3814
 
 
3815
    # GZ 2011-03-02: TODO test generic non-ascii error string
 
3816
 
 
3817
    def test_generic_KeyError(self):
 
3818
        err = errors.ErrorFromSmartServer((b'error', b'KeyError', b"1"))
 
3819
        translated_error = self.translateErrorFromSmartServer(err)
 
3820
        expected_error = errors.UnknownErrorFromSmartServer(err)
 
3821
        self.assertEqual(expected_error, translated_error)
 
3822
 
 
3823
 
 
3824
class TestErrorTranslationRobustness(TestErrorTranslationBase):
 
3825
    """Unit tests for breezy.bzr.remote._translate_error's robustness.
 
3826
 
 
3827
    TestErrorTranslationSuccess is for cases where _translate_error can
 
3828
    translate successfully.  This class about how _translate_err behaves when
 
3829
    it fails to translate: it re-raises the original error.
 
3830
    """
 
3831
 
 
3832
    def test_unrecognised_server_error(self):
 
3833
        """If the error code from the server is not recognised, the original
 
3834
        ErrorFromSmartServer is propagated unmodified.
 
3835
        """
 
3836
        error_tuple = (b'An unknown error tuple',)
 
3837
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
3838
        translated_error = self.translateErrorFromSmartServer(server_error)
 
3839
        expected_error = errors.UnknownErrorFromSmartServer(server_error)
 
3840
        self.assertEqual(expected_error, translated_error)
 
3841
 
 
3842
    def test_context_missing_a_key(self):
 
3843
        """In case of a bug in the client, or perhaps an unexpected response
 
3844
        from a server, _translate_error returns the original error tuple from
 
3845
        the server and mutters a warning.
 
3846
        """
 
3847
        # To translate a NoSuchRevision error _translate_error needs a 'branch'
 
3848
        # in the context dict.  So let's give it an empty context dict instead
 
3849
        # to exercise its error recovery.
 
3850
        empty_context = {}
 
3851
        error_tuple = (b'NoSuchRevision', b'revid')
 
3852
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
3853
        translated_error = self.translateErrorFromSmartServer(server_error)
 
3854
        self.assertEqual(server_error, translated_error)
 
3855
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
3856
        # been muttered to the log file for developer to look at.
 
3857
        self.assertContainsRe(
 
3858
            self.get_log(),
 
3859
            "Missing key 'branch' in context")
 
3860
 
 
3861
    def test_path_missing(self):
 
3862
        """Some translations (PermissionDenied, ReadError) can determine the
 
3863
        'path' variable from either the wire or the local context.  If neither
 
3864
        has it, then an error is raised.
 
3865
        """
 
3866
        error_tuple = (b'ReadError',)
 
3867
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
3868
        translated_error = self.translateErrorFromSmartServer(server_error)
 
3869
        self.assertEqual(server_error, translated_error)
 
3870
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
3871
        # been muttered to the log file for developer to look at.
 
3872
        self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
 
3873
 
 
3874
 
 
3875
class TestStacking(tests.TestCaseWithTransport):
 
3876
    """Tests for operations on stacked remote repositories.
 
3877
 
 
3878
    The underlying format type must support stacking.
 
3879
    """
 
3880
 
 
3881
    def test_access_stacked_remote(self):
 
3882
        # based on <http://launchpad.net/bugs/261315>
 
3883
        # make a branch stacked on another repository containing an empty
 
3884
        # revision, then open it over hpss - we should be able to see that
 
3885
        # revision.
 
3886
        base_transport = self.get_transport()
 
3887
        base_builder = self.make_branch_builder('base', format='1.9')
 
3888
        base_builder.start_series()
 
3889
        base_revid = base_builder.build_snapshot(None,
 
3890
            [('add', ('', None, 'directory', None))],
 
3891
            'message', revision_id=b'rev-id')
 
3892
        base_builder.finish_series()
 
3893
        stacked_branch = self.make_branch('stacked', format='1.9')
 
3894
        stacked_branch.set_stacked_on_url('../base')
 
3895
        # start a server looking at this
 
3896
        smart_server = test_server.SmartTCPServer_for_testing()
 
3897
        self.start_server(smart_server)
 
3898
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
 
3899
        # can get its branch and repository
 
3900
        remote_branch = remote_bzrdir.open_branch()
 
3901
        remote_repo = remote_branch.repository
 
3902
        remote_repo.lock_read()
 
3903
        try:
 
3904
            # it should have an appropriate fallback repository, which should also
 
3905
            # be a RemoteRepository
 
3906
            self.assertLength(1, remote_repo._fallback_repositories)
 
3907
            self.assertIsInstance(remote_repo._fallback_repositories[0],
 
3908
                RemoteRepository)
 
3909
            # and it has the revision committed to the underlying repository;
 
3910
            # these have varying implementations so we try several of them
 
3911
            self.assertTrue(remote_repo.has_revisions([base_revid]))
 
3912
            self.assertTrue(remote_repo.has_revision(base_revid))
 
3913
            self.assertEqual(remote_repo.get_revision(base_revid).message,
 
3914
                'message')
 
3915
        finally:
 
3916
            remote_repo.unlock()
 
3917
 
 
3918
    def prepare_stacked_remote_branch(self):
 
3919
        """Get stacked_upon and stacked branches with content in each."""
 
3920
        self.setup_smart_server_with_call_log()
 
3921
        tree1 = self.make_branch_and_tree('tree1', format='1.9')
 
3922
        tree1.commit('rev1', rev_id=b'rev1')
 
3923
        tree2 = tree1.branch.controldir.sprout('tree2', stacked=True
 
3924
            ).open_workingtree()
 
3925
        local_tree = tree2.branch.create_checkout('local')
 
3926
        local_tree.commit('local changes make me feel good.')
 
3927
        branch2 = Branch.open(self.get_url('tree2'))
 
3928
        branch2.lock_read()
 
3929
        self.addCleanup(branch2.unlock)
 
3930
        return tree1.branch, branch2
 
3931
 
 
3932
    def test_stacked_get_parent_map(self):
 
3933
        # the public implementation of get_parent_map obeys stacking
 
3934
        _, branch = self.prepare_stacked_remote_branch()
 
3935
        repo = branch.repository
 
3936
        self.assertEqual({'rev1'}, set(repo.get_parent_map([b'rev1'])))
 
3937
 
 
3938
    def test_unstacked_get_parent_map(self):
 
3939
        # _unstacked_provider.get_parent_map ignores stacking
 
3940
        _, branch = self.prepare_stacked_remote_branch()
 
3941
        provider = branch.repository._unstacked_provider
 
3942
        self.assertEqual(set(), set(provider.get_parent_map([b'rev1'])))
 
3943
 
 
3944
    def fetch_stream_to_rev_order(self, stream):
 
3945
        result = []
 
3946
        for kind, substream in stream:
 
3947
            if not kind == 'revisions':
 
3948
                list(substream)
 
3949
            else:
 
3950
                for content in substream:
 
3951
                    result.append(content.key[-1])
 
3952
        return result
 
3953
 
 
3954
    def get_ordered_revs(self, format, order, branch_factory=None):
 
3955
        """Get a list of the revisions in a stream to format format.
 
3956
 
 
3957
        :param format: The format of the target.
 
3958
        :param order: the order that target should have requested.
 
3959
        :param branch_factory: A callable to create a trunk and stacked branch
 
3960
            to fetch from. If none, self.prepare_stacked_remote_branch is used.
 
3961
        :result: The revision ids in the stream, in the order seen,
 
3962
            the topological order of revisions in the source.
 
3963
        """
 
3964
        unordered_format = controldir.format_registry.get(format)()
 
3965
        target_repository_format = unordered_format.repository_format
 
3966
        # Cross check
 
3967
        self.assertEqual(order, target_repository_format._fetch_order)
 
3968
        if branch_factory is None:
 
3969
            branch_factory = self.prepare_stacked_remote_branch
 
3970
        _, stacked = branch_factory()
 
3971
        source = stacked.repository._get_source(target_repository_format)
 
3972
        tip = stacked.last_revision()
 
3973
        stacked.repository._ensure_real()
 
3974
        graph = stacked.repository.get_graph()
 
3975
        revs = [r for (r, ps) in graph.iter_ancestry([tip])
 
3976
                if r != NULL_REVISION]
 
3977
        revs.reverse()
 
3978
        search = vf_search.PendingAncestryResult([tip], stacked.repository)
 
3979
        self.reset_smart_call_log()
 
3980
        stream = source.get_stream(search)
 
3981
        # We trust that if a revision is in the stream the rest of the new
 
3982
        # content for it is too, as per our main fetch tests; here we are
 
3983
        # checking that the revisions are actually included at all, and their
 
3984
        # order.
 
3985
        return self.fetch_stream_to_rev_order(stream), revs
 
3986
 
 
3987
    def test_stacked_get_stream_unordered(self):
 
3988
        # Repository._get_source.get_stream() from a stacked repository with
 
3989
        # unordered yields the full data from both stacked and stacked upon
 
3990
        # sources.
 
3991
        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
 
3992
        self.assertEqual(set(expected_revs), set(rev_ord))
 
3993
        # Getting unordered results should have made a streaming data request
 
3994
        # from the server, then one from the backing branch.
 
3995
        self.assertLength(2, self.hpss_calls)
 
3996
 
 
3997
    def test_stacked_on_stacked_get_stream_unordered(self):
 
3998
        # Repository._get_source.get_stream() from a stacked repository which
 
3999
        # is itself stacked yields the full data from all three sources.
 
4000
        def make_stacked_stacked():
 
4001
            _, stacked = self.prepare_stacked_remote_branch()
 
4002
            tree = stacked.controldir.sprout('tree3', stacked=True
 
4003
                ).open_workingtree()
 
4004
            local_tree = tree.branch.create_checkout('local-tree3')
 
4005
            local_tree.commit('more local changes are better')
 
4006
            branch = Branch.open(self.get_url('tree3'))
 
4007
            branch.lock_read()
 
4008
            self.addCleanup(branch.unlock)
 
4009
            return None, branch
 
4010
        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
 
4011
            branch_factory=make_stacked_stacked)
 
4012
        self.assertEqual(set(expected_revs), set(rev_ord))
 
4013
        # Getting unordered results should have made a streaming data request
 
4014
        # from the server, and one from each backing repo
 
4015
        self.assertLength(3, self.hpss_calls)
 
4016
 
 
4017
    def test_stacked_get_stream_topological(self):
 
4018
        # Repository._get_source.get_stream() from a stacked repository with
 
4019
        # topological sorting yields the full data from both stacked and
 
4020
        # stacked upon sources in topological order.
 
4021
        rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
 
4022
        self.assertEqual(expected_revs, rev_ord)
 
4023
        # Getting topological sort requires VFS calls still - one of which is
 
4024
        # pushing up from the bound branch.
 
4025
        self.assertLength(14, self.hpss_calls)
 
4026
 
 
4027
    def test_stacked_get_stream_groupcompress(self):
 
4028
        # Repository._get_source.get_stream() from a stacked repository with
 
4029
        # groupcompress sorting yields the full data from both stacked and
 
4030
        # stacked upon sources in groupcompress order.
 
4031
        raise tests.TestSkipped('No groupcompress ordered format available')
 
4032
        rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
 
4033
        self.assertEqual(expected_revs, reversed(rev_ord))
 
4034
        # Getting unordered results should have made a streaming data request
 
4035
        # from the backing branch, and one from the stacked on branch.
 
4036
        self.assertLength(2, self.hpss_calls)
 
4037
 
 
4038
    def test_stacked_pull_more_than_stacking_has_bug_360791(self):
 
4039
        # When pulling some fixed amount of content that is more than the
 
4040
        # source has (because some is coming from a fallback branch, no error
 
4041
        # should be received. This was reported as bug 360791.
 
4042
        # Need three branches: a trunk, a stacked branch, and a preexisting
 
4043
        # branch pulling content from stacked and trunk.
 
4044
        self.setup_smart_server_with_call_log()
 
4045
        trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
 
4046
        r1 = trunk.commit('start')
 
4047
        stacked_branch = trunk.branch.create_clone_on_transport(
 
4048
            self.get_transport('stacked'), stacked_on=trunk.branch.base)
 
4049
        local = self.make_branch('local', format='1.9-rich-root')
 
4050
        local.repository.fetch(stacked_branch.repository,
 
4051
            stacked_branch.last_revision())
 
4052
 
 
4053
 
 
4054
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
 
4055
 
 
4056
    def setUp(self):
 
4057
        super(TestRemoteBranchEffort, self).setUp()
 
4058
        # Create a smart server that publishes whatever the backing VFS server
 
4059
        # does.
 
4060
        self.smart_server = test_server.SmartTCPServer_for_testing()
 
4061
        self.start_server(self.smart_server, self.get_server())
 
4062
        # Log all HPSS calls into self.hpss_calls.
 
4063
        _SmartClient.hooks.install_named_hook(
 
4064
            'call', self.capture_hpss_call, None)
 
4065
        self.hpss_calls = []
 
4066
 
 
4067
    def capture_hpss_call(self, params):
 
4068
        self.hpss_calls.append(params.method)
 
4069
 
 
4070
    def test_copy_content_into_avoids_revision_history(self):
 
4071
        local = self.make_branch('local')
 
4072
        builder = self.make_branch_builder('remote')
 
4073
        builder.build_commit(message="Commit.")
 
4074
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
4075
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
4076
        local.repository.fetch(remote_branch.repository)
 
4077
        self.hpss_calls = []
 
4078
        remote_branch.copy_content_into(local)
 
4079
        self.assertFalse(b'Branch.revision_history' in self.hpss_calls)
 
4080
 
 
4081
    def test_fetch_everything_needs_just_one_call(self):
 
4082
        local = self.make_branch('local')
 
4083
        builder = self.make_branch_builder('remote')
 
4084
        builder.build_commit(message="Commit.")
 
4085
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
4086
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
4087
        self.hpss_calls = []
 
4088
        local.repository.fetch(
 
4089
            remote_branch.repository,
 
4090
            fetch_spec=vf_search.EverythingResult(remote_branch.repository))
 
4091
        self.assertEqual([b'Repository.get_stream_1.19'], self.hpss_calls)
 
4092
 
 
4093
    def override_verb(self, verb_name, verb):
 
4094
        request_handlers = request.request_handlers
 
4095
        orig_verb = request_handlers.get(verb_name)
 
4096
        orig_info = request_handlers.get_info(verb_name)
 
4097
        request_handlers.register(verb_name, verb, override_existing=True)
 
4098
        self.addCleanup(request_handlers.register, verb_name, orig_verb,
 
4099
                override_existing=True, info=orig_info)
 
4100
 
 
4101
    def test_fetch_everything_backwards_compat(self):
 
4102
        """Can fetch with EverythingResult even with pre 2.4 servers.
 
4103
        
 
4104
        Pre-2.4 do not support 'everything' searches with the
 
4105
        Repository.get_stream_1.19 verb.
 
4106
        """
 
4107
        verb_log = []
 
4108
        class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
 
4109
            """A version of the Repository.get_stream_1.19 verb patched to
 
4110
            reject 'everything' searches the way 2.3 and earlier do.
 
4111
            """
 
4112
            def recreate_search(self, repository, search_bytes,
 
4113
                                discard_excess=False):
 
4114
                verb_log.append(search_bytes.split(b'\n', 1)[0])
 
4115
                if search_bytes == b'everything':
 
4116
                    return (None,
 
4117
                            request.FailedSmartServerResponse((b'BadSearch',)))
 
4118
                return super(OldGetStreamVerb,
 
4119
                        self).recreate_search(repository, search_bytes,
 
4120
                            discard_excess=discard_excess)
 
4121
        self.override_verb(b'Repository.get_stream_1.19', OldGetStreamVerb)
 
4122
        local = self.make_branch('local')
 
4123
        builder = self.make_branch_builder('remote')
 
4124
        builder.build_commit(message="Commit.")
 
4125
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
4126
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
4127
        self.hpss_calls = []
 
4128
        local.repository.fetch(
 
4129
            remote_branch.repository,
 
4130
            fetch_spec=vf_search.EverythingResult(remote_branch.repository))
 
4131
        # make sure the overridden verb was used
 
4132
        self.assertLength(1, verb_log)
 
4133
        # more than one HPSS call is needed, but because it's a VFS callback
 
4134
        # its hard to predict exactly how many.
 
4135
        self.assertTrue(len(self.hpss_calls) > 1)
 
4136
 
 
4137
 
 
4138
class TestUpdateBoundBranchWithModifiedBoundLocation(
 
4139
    tests.TestCaseWithTransport):
 
4140
    """Ensure correct handling of bound_location modifications.
 
4141
 
 
4142
    This is tested against a smart server as http://pad.lv/786980 was about a
 
4143
    ReadOnlyError (write attempt during a read-only transaction) which can only
 
4144
    happen in this context.
 
4145
    """
 
4146
 
 
4147
    def setUp(self):
 
4148
        super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
 
4149
        self.transport_server = test_server.SmartTCPServer_for_testing
 
4150
 
 
4151
    def make_master_and_checkout(self, master_name, checkout_name):
 
4152
        # Create the master branch and its associated checkout
 
4153
        self.master = self.make_branch_and_tree(master_name)
 
4154
        self.checkout = self.master.branch.create_checkout(checkout_name)
 
4155
        # Modify the master branch so there is something to update
 
4156
        self.master.commit('add stuff')
 
4157
        self.last_revid = self.master.commit('even more stuff')
 
4158
        self.bound_location = self.checkout.branch.get_bound_location()
 
4159
 
 
4160
    def assertUpdateSucceeds(self, new_location):
 
4161
        self.checkout.branch.set_bound_location(new_location)
 
4162
        self.checkout.update()
 
4163
        self.assertEqual(self.last_revid, self.checkout.last_revision())
 
4164
 
 
4165
    def test_without_final_slash(self):
 
4166
        self.make_master_and_checkout('master', 'checkout')
 
4167
        # For unclear reasons some users have a bound_location without a final
 
4168
        # '/', simulate that by forcing such a value
 
4169
        self.assertEndsWith(self.bound_location, '/')
 
4170
        self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
 
4171
 
 
4172
    def test_plus_sign(self):
 
4173
        self.make_master_and_checkout('+master', 'checkout')
 
4174
        self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
 
4175
 
 
4176
    def test_tilda(self):
 
4177
        # Embed ~ in the middle of the path just to avoid any $HOME
 
4178
        # interpretation
 
4179
        self.make_master_and_checkout('mas~ter', 'checkout')
 
4180
        self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
 
4181
 
 
4182
 
 
4183
class TestWithCustomErrorHandler(RemoteBranchTestCase):
 
4184
 
 
4185
    def test_no_context(self):
 
4186
        class OutOfCoffee(errors.BzrError):
 
4187
            """A dummy exception for testing."""
 
4188
 
 
4189
            def __init__(self, urgency):
 
4190
                self.urgency = urgency
 
4191
        remote.no_context_error_translators.register(b"OutOfCoffee",
 
4192
            lambda err: OutOfCoffee(err.error_args[0]))
 
4193
        transport = MemoryTransport()
 
4194
        client = FakeClient(transport.base)
 
4195
        client.add_expected_call(
 
4196
            b'Branch.get_stacked_on_url', (b'quack/',),
 
4197
            b'error', (b'NotStacked',))
 
4198
        client.add_expected_call(
 
4199
            b'Branch.last_revision_info',
 
4200
            (b'quack/',),
 
4201
            b'error', (b'OutOfCoffee', b'low'))
 
4202
        transport.mkdir('quack')
 
4203
        transport = transport.clone('quack')
 
4204
        branch = self.make_remote_branch(transport, client)
 
4205
        self.assertRaises(OutOfCoffee, branch.last_revision_info)
 
4206
        self.assertFinished(client)
 
4207
 
 
4208
    def test_with_context(self):
 
4209
        class OutOfTea(errors.BzrError):
 
4210
            def __init__(self, branch, urgency):
 
4211
                self.branch = branch
 
4212
                self.urgency = urgency
 
4213
        remote.error_translators.register(b"OutOfTea",
 
4214
            lambda err, find, path: OutOfTea(
 
4215
                err.error_args[0].decode('utf-8'),
 
4216
                find("branch")))
 
4217
        transport = MemoryTransport()
 
4218
        client = FakeClient(transport.base)
 
4219
        client.add_expected_call(
 
4220
            b'Branch.get_stacked_on_url', (b'quack/',),
 
4221
            b'error', (b'NotStacked',))
 
4222
        client.add_expected_call(
 
4223
            b'Branch.last_revision_info',
 
4224
            (b'quack/',),
 
4225
            b'error', (b'OutOfTea', b'low'))
 
4226
        transport.mkdir('quack')
 
4227
        transport = transport.clone('quack')
 
4228
        branch = self.make_remote_branch(transport, client)
 
4229
        self.assertRaises(OutOfTea, branch.last_revision_info)
 
4230
        self.assertFinished(client)
 
4231
 
 
4232
 
 
4233
class TestRepositoryPack(TestRemoteRepository):
 
4234
 
 
4235
    def test_pack(self):
 
4236
        transport_path = 'quack'
 
4237
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4238
        client.add_expected_call(
 
4239
            b'Repository.lock_write', (b'quack/', b''),
 
4240
            b'success', (b'ok', b'token'))
 
4241
        client.add_expected_call(
 
4242
            b'Repository.pack', (b'quack/', b'token', b'False'),
 
4243
            b'success', (b'ok',), )
 
4244
        client.add_expected_call(
 
4245
            b'Repository.unlock', (b'quack/', b'token'),
 
4246
            b'success', (b'ok', ))
 
4247
        repo.pack()
 
4248
 
 
4249
    def test_pack_with_hint(self):
 
4250
        transport_path = 'quack'
 
4251
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4252
        client.add_expected_call(
 
4253
            b'Repository.lock_write', (b'quack/', b''),
 
4254
            b'success', (b'ok', b'token'))
 
4255
        client.add_expected_call(
 
4256
            b'Repository.pack', (b'quack/', b'token', b'False'),
 
4257
            b'success', (b'ok',), )
 
4258
        client.add_expected_call(
 
4259
            b'Repository.unlock', (b'quack/', b'token', b'False'),
 
4260
            b'success', (b'ok', ))
 
4261
        repo.pack([b'hinta', b'hintb'])
 
4262
 
 
4263
 
 
4264
class TestRepositoryIterInventories(TestRemoteRepository):
 
4265
    """Test Repository.iter_inventories."""
 
4266
 
 
4267
    def _serialize_inv_delta(self, old_name, new_name, delta):
 
4268
        serializer = inventory_delta.InventoryDeltaSerializer(True, False)
 
4269
        return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
 
4270
 
 
4271
    def test_single_empty(self):
 
4272
        transport_path = 'quack'
 
4273
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4274
        fmt = controldir.format_registry.get('2a')().repository_format
 
4275
        repo._format = fmt
 
4276
        stream = [('inventory-deltas', [
 
4277
            versionedfile.FulltextContentFactory(b'somerevid', None, None,
 
4278
                self._serialize_inv_delta(b'null:', b'somerevid', []))])]
 
4279
        client.add_expected_call(
 
4280
            b'VersionedFileRepository.get_inventories', (b'quack/', b'unordered'),
 
4281
            b'success', (b'ok', ),
 
4282
            _stream_to_byte_stream(stream, fmt))
 
4283
        ret = list(repo.iter_inventories([b"somerevid"]))
 
4284
        self.assertLength(1, ret)
 
4285
        inv = ret[0]
 
4286
        self.assertEqual(b"somerevid", inv.revision_id)
 
4287
 
 
4288
    def test_empty(self):
 
4289
        transport_path = 'quack'
 
4290
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4291
        ret = list(repo.iter_inventories([]))
 
4292
        self.assertEqual(ret, [])
 
4293
 
 
4294
    def test_missing(self):
 
4295
        transport_path = 'quack'
 
4296
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4297
        client.add_expected_call(
 
4298
            b'VersionedFileRepository.get_inventories', (b'quack/', b'unordered'),
 
4299
            b'success', (b'ok', ), iter([]))
 
4300
        self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(
 
4301
            [b"somerevid"]))