/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: 2019-06-29 19:54:32 UTC
  • mto: This revision was merged to the branch mainline in revision 7378.
  • Revision ID: jelmer@jelmer.uk-20190629195432-xuqzgxejnzq6gs2n
Use more ExitStacks.

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