/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: Martin
  • Date: 2018-11-16 19:10:17 UTC
  • mto: This revision was merged to the branch mainline in revision 7177.
  • Revision ID: gzlist@googlemail.com-20181116191017-kyedz1qck0ovon3h
Remove lazy_regexp reset in bt.test_source

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_backwards_compat_set_option(self):
 
2046
        self.setup_smart_server_with_call_log()
 
2047
        branch = self.make_branch('.')
 
2048
        verb = b'Branch.set_config_option'
 
2049
        self.disable_verb(verb)
 
2050
        branch.lock_write()
 
2051
        self.addCleanup(branch.unlock)
 
2052
        self.reset_smart_call_log()
 
2053
        branch._get_config().set_option('value', 'name')
 
2054
        self.assertLength(11, self.hpss_calls)
 
2055
        self.assertEqual('value', branch._get_config().get_option('name'))
 
2056
 
 
2057
    def test_backwards_compat_set_option_with_dict(self):
 
2058
        self.setup_smart_server_with_call_log()
 
2059
        branch = self.make_branch('.')
 
2060
        verb = b'Branch.set_config_option_dict'
 
2061
        self.disable_verb(verb)
 
2062
        branch.lock_write()
 
2063
        self.addCleanup(branch.unlock)
 
2064
        self.reset_smart_call_log()
 
2065
        config = branch._get_config()
 
2066
        value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
 
2067
        config.set_option(value_dict, 'name')
 
2068
        self.assertLength(11, self.hpss_calls)
 
2069
        self.assertEqual(value_dict, branch._get_config().get_option('name'))
 
2070
 
 
2071
 
 
2072
class TestBranchGetPutConfigStore(RemoteBranchTestCase):
 
2073
 
 
2074
    def test_get_branch_conf(self):
 
2075
        # in an empty branch we decode the response properly
 
2076
        client = FakeClient()
 
2077
        client.add_expected_call(
 
2078
            b'Branch.get_stacked_on_url', (b'memory:///',),
 
2079
            b'error', (b'NotStacked',),)
 
2080
        client.add_success_response_with_body(b'# config file body', b'ok')
 
2081
        transport = MemoryTransport()
 
2082
        branch = self.make_remote_branch(transport, client)
 
2083
        config = branch.get_config_stack()
 
2084
        config.get("email")
 
2085
        config.get("log_format")
 
2086
        self.assertEqual(
 
2087
            [('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
 
2088
             ('call_expecting_body', b'Branch.get_config_file', (b'memory:///',))],
 
2089
            client._calls)
 
2090
 
 
2091
    def test_set_branch_conf(self):
 
2092
        client = FakeClient()
 
2093
        client.add_expected_call(
 
2094
            b'Branch.get_stacked_on_url', (b'memory:///',),
 
2095
            b'error', (b'NotStacked',),)
 
2096
        client.add_expected_call(
 
2097
            b'Branch.lock_write', (b'memory:///', b'', b''),
 
2098
            b'success', (b'ok', b'branch token', b'repo token'))
 
2099
        client.add_expected_call(
 
2100
            b'Branch.get_config_file', (b'memory:///', ),
 
2101
            b'success', (b'ok', ), b"# line 1\n")
 
2102
        client.add_expected_call(
 
2103
            b'Branch.get_config_file', (b'memory:///', ),
 
2104
            b'success', (b'ok', ), b"# line 1\n")
 
2105
        client.add_expected_call(
 
2106
            b'Branch.put_config_file', (b'memory:///', b'branch token',
 
2107
                                        b'repo token'),
 
2108
            b'success', (b'ok',))
 
2109
        client.add_expected_call(
 
2110
            b'Branch.unlock', (b'memory:///', b'branch token', b'repo token'),
 
2111
            b'success', (b'ok',))
 
2112
        transport = MemoryTransport()
 
2113
        branch = self.make_remote_branch(transport, client)
 
2114
        branch.lock_write()
 
2115
        config = branch.get_config_stack()
 
2116
        config.set('email', 'The Dude <lebowski@example.com>')
 
2117
        branch.unlock()
 
2118
        self.assertFinished(client)
 
2119
        self.assertEqual(
 
2120
            [('call', b'Branch.get_stacked_on_url', (b'memory:///',)),
 
2121
             ('call', b'Branch.lock_write', (b'memory:///', b'', b'')),
 
2122
             ('call_expecting_body', b'Branch.get_config_file',
 
2123
                 (b'memory:///',)),
 
2124
             ('call_expecting_body', b'Branch.get_config_file',
 
2125
                 (b'memory:///',)),
 
2126
             ('call_with_body_bytes_expecting_body', b'Branch.put_config_file',
 
2127
                 (b'memory:///', b'branch token', b'repo token'),
 
2128
                 b'# line 1\nemail = The Dude <lebowski@example.com>\n'),
 
2129
             ('call', b'Branch.unlock',
 
2130
                 (b'memory:///', b'branch token', b'repo token'))],
 
2131
            client._calls)
 
2132
 
 
2133
 
 
2134
class TestBranchLockWrite(RemoteBranchTestCase):
 
2135
 
 
2136
    def test_lock_write_unlockable(self):
 
2137
        transport = MemoryTransport()
 
2138
        client = FakeClient(transport.base)
 
2139
        client.add_expected_call(
 
2140
            b'Branch.get_stacked_on_url', (b'quack/',),
 
2141
            b'error', (b'NotStacked',),)
 
2142
        client.add_expected_call(
 
2143
            b'Branch.lock_write', (b'quack/', b'', b''),
 
2144
            b'error', (b'UnlockableTransport',))
 
2145
        transport.mkdir('quack')
 
2146
        transport = transport.clone('quack')
 
2147
        branch = self.make_remote_branch(transport, client)
 
2148
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
 
2149
        self.assertFinished(client)
 
2150
 
 
2151
 
 
2152
class TestBranchRevisionIdToRevno(RemoteBranchTestCase):
 
2153
 
 
2154
    def test_simple(self):
 
2155
        transport = MemoryTransport()
 
2156
        client = FakeClient(transport.base)
 
2157
        client.add_expected_call(
 
2158
            b'Branch.get_stacked_on_url', (b'quack/',),
 
2159
            b'error', (b'NotStacked',),)
 
2160
        client.add_expected_call(
 
2161
            b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
 
2162
            b'success', (b'ok', b'0',),)
 
2163
        client.add_expected_call(
 
2164
            b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
 
2165
            b'error', (b'NoSuchRevision', b'unknown',),)
 
2166
        transport.mkdir('quack')
 
2167
        transport = transport.clone('quack')
 
2168
        branch = self.make_remote_branch(transport, client)
 
2169
        self.assertEqual(0, branch.revision_id_to_revno(b'null:'))
 
2170
        self.assertRaises(errors.NoSuchRevision,
 
2171
                          branch.revision_id_to_revno, b'unknown')
 
2172
        self.assertFinished(client)
 
2173
 
 
2174
    def test_dotted(self):
 
2175
        transport = MemoryTransport()
 
2176
        client = FakeClient(transport.base)
 
2177
        client.add_expected_call(
 
2178
            b'Branch.get_stacked_on_url', (b'quack/',),
 
2179
            b'error', (b'NotStacked',),)
 
2180
        client.add_expected_call(
 
2181
            b'Branch.revision_id_to_revno', (b'quack/', b'null:'),
 
2182
            b'success', (b'ok', b'0',),)
 
2183
        client.add_expected_call(
 
2184
            b'Branch.revision_id_to_revno', (b'quack/', b'unknown'),
 
2185
            b'error', (b'NoSuchRevision', b'unknown',),)
 
2186
        transport.mkdir('quack')
 
2187
        transport = transport.clone('quack')
 
2188
        branch = self.make_remote_branch(transport, client)
 
2189
        self.assertEqual((0, ), branch.revision_id_to_dotted_revno(b'null:'))
 
2190
        self.assertRaises(errors.NoSuchRevision,
 
2191
                          branch.revision_id_to_dotted_revno, b'unknown')
 
2192
        self.assertFinished(client)
 
2193
 
 
2194
    def test_dotted_no_smart_verb(self):
 
2195
        self.setup_smart_server_with_call_log()
 
2196
        branch = self.make_branch('.')
 
2197
        self.disable_verb(b'Branch.revision_id_to_revno')
 
2198
        self.reset_smart_call_log()
 
2199
        self.assertEqual((0, ),
 
2200
                         branch.revision_id_to_dotted_revno(b'null:'))
 
2201
        self.assertLength(8, self.hpss_calls)
 
2202
 
 
2203
 
 
2204
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
 
2205
 
 
2206
    def test__get_config(self):
 
2207
        client = FakeClient()
 
2208
        client.add_success_response_with_body(b'default_stack_on = /\n', b'ok')
 
2209
        transport = MemoryTransport()
 
2210
        bzrdir = self.make_remote_bzrdir(transport, client)
 
2211
        config = bzrdir.get_config()
 
2212
        self.assertEqual('/', config.get_default_stack_on())
 
2213
        self.assertEqual(
 
2214
            [('call_expecting_body', b'BzrDir.get_config_file',
 
2215
                (b'memory:///',))],
 
2216
            client._calls)
 
2217
 
 
2218
    def test_set_option_uses_vfs(self):
 
2219
        self.setup_smart_server_with_call_log()
 
2220
        bzrdir = self.make_controldir('.')
 
2221
        self.reset_smart_call_log()
 
2222
        config = bzrdir.get_config()
 
2223
        config.set_default_stack_on('/')
 
2224
        self.assertLength(4, self.hpss_calls)
 
2225
 
 
2226
    def test_backwards_compat_get_option(self):
 
2227
        self.setup_smart_server_with_call_log()
 
2228
        bzrdir = self.make_controldir('.')
 
2229
        verb = b'BzrDir.get_config_file'
 
2230
        self.disable_verb(verb)
 
2231
        self.reset_smart_call_log()
 
2232
        self.assertEqual(None,
 
2233
                         bzrdir._get_config().get_option('default_stack_on'))
 
2234
        self.assertLength(4, self.hpss_calls)
 
2235
 
 
2236
 
 
2237
class TestTransportIsReadonly(tests.TestCase):
 
2238
 
 
2239
    def test_true(self):
 
2240
        client = FakeClient()
 
2241
        client.add_success_response(b'yes')
 
2242
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
2243
                                    _client=client)
 
2244
        self.assertEqual(True, transport.is_readonly())
 
2245
        self.assertEqual(
 
2246
            [('call', b'Transport.is_readonly', ())],
 
2247
            client._calls)
 
2248
 
 
2249
    def test_false(self):
 
2250
        client = FakeClient()
 
2251
        client.add_success_response(b'no')
 
2252
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
2253
                                    _client=client)
 
2254
        self.assertEqual(False, transport.is_readonly())
 
2255
        self.assertEqual(
 
2256
            [('call', b'Transport.is_readonly', ())],
 
2257
            client._calls)
 
2258
 
 
2259
    def test_error_from_old_server(self):
 
2260
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
 
2261
 
 
2262
        Clients should treat it as a "no" response, because is_readonly is only
 
2263
        advisory anyway (a transport could be read-write, but then the
 
2264
        underlying filesystem could be readonly anyway).
 
2265
        """
 
2266
        client = FakeClient()
 
2267
        client.add_unknown_method_response(b'Transport.is_readonly')
 
2268
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
2269
                                    _client=client)
 
2270
        self.assertEqual(False, transport.is_readonly())
 
2271
        self.assertEqual(
 
2272
            [('call', b'Transport.is_readonly', ())],
 
2273
            client._calls)
 
2274
 
 
2275
 
 
2276
class TestTransportMkdir(tests.TestCase):
 
2277
 
 
2278
    def test_permissiondenied(self):
 
2279
        client = FakeClient()
 
2280
        client.add_error_response(
 
2281
            b'PermissionDenied', b'remote path', b'extra')
 
2282
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
2283
                                    _client=client)
 
2284
        exc = self.assertRaises(
 
2285
            errors.PermissionDenied, transport.mkdir, 'client path')
 
2286
        expected_error = errors.PermissionDenied('/client path', 'extra')
 
2287
        self.assertEqual(expected_error, exc)
 
2288
 
 
2289
 
 
2290
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
 
2291
 
 
2292
    def test_defaults_to_none(self):
 
2293
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
2294
        self.assertIs(None, t._get_credentials()[0])
 
2295
 
 
2296
    def test_uses_authentication_config(self):
 
2297
        conf = config.AuthenticationConfig()
 
2298
        conf._get_config().update(
 
2299
            {'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
 
2300
                             'example.com'}})
 
2301
        conf._save()
 
2302
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
2303
        self.assertEqual('bar', t._get_credentials()[0])
 
2304
 
 
2305
 
 
2306
class TestRemoteRepository(TestRemote):
 
2307
    """Base for testing RemoteRepository protocol usage.
 
2308
 
 
2309
    These tests contain frozen requests and responses.  We want any changes to
 
2310
    what is sent or expected to be require a thoughtful update to these tests
 
2311
    because they might break compatibility with different-versioned servers.
 
2312
    """
 
2313
 
 
2314
    def setup_fake_client_and_repository(self, transport_path):
 
2315
        """Create the fake client and repository for testing with.
 
2316
 
 
2317
        There's no real server here; we just have canned responses sent
 
2318
        back one by one.
 
2319
 
 
2320
        :param transport_path: Path below the root of the MemoryTransport
 
2321
            where the repository will be created.
 
2322
        """
 
2323
        transport = MemoryTransport()
 
2324
        transport.mkdir(transport_path)
 
2325
        client = FakeClient(transport.base)
 
2326
        transport = transport.clone(transport_path)
 
2327
        # we do not want bzrdir to make any remote calls
 
2328
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
2329
                              _client=False)
 
2330
        repo = RemoteRepository(bzrdir, None, _client=client)
 
2331
        return repo, client
 
2332
 
 
2333
 
 
2334
def remoted_description(format):
 
2335
    return 'Remote: ' + format.get_format_description()
 
2336
 
 
2337
 
 
2338
class TestBranchFormat(tests.TestCase):
 
2339
 
 
2340
    def test_get_format_description(self):
 
2341
        remote_format = RemoteBranchFormat()
 
2342
        real_format = branch.format_registry.get_default()
 
2343
        remote_format._network_name = real_format.network_name()
 
2344
        self.assertEqual(remoted_description(real_format),
 
2345
                         remote_format.get_format_description())
 
2346
 
 
2347
 
 
2348
class TestRepositoryFormat(TestRemoteRepository):
 
2349
 
 
2350
    def test_fast_delta(self):
 
2351
        true_name = groupcompress_repo.RepositoryFormat2a().network_name()
 
2352
        true_format = RemoteRepositoryFormat()
 
2353
        true_format._network_name = true_name
 
2354
        self.assertEqual(True, true_format.fast_deltas)
 
2355
        false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
 
2356
        false_format = RemoteRepositoryFormat()
 
2357
        false_format._network_name = false_name
 
2358
        self.assertEqual(False, false_format.fast_deltas)
 
2359
 
 
2360
    def test_get_format_description(self):
 
2361
        remote_repo_format = RemoteRepositoryFormat()
 
2362
        real_format = repository.format_registry.get_default()
 
2363
        remote_repo_format._network_name = real_format.network_name()
 
2364
        self.assertEqual(remoted_description(real_format),
 
2365
                         remote_repo_format.get_format_description())
 
2366
 
 
2367
 
 
2368
class TestRepositoryAllRevisionIds(TestRemoteRepository):
 
2369
 
 
2370
    def test_empty(self):
 
2371
        transport_path = 'quack'
 
2372
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2373
        client.add_success_response_with_body(b'', b'ok')
 
2374
        self.assertEqual([], repo.all_revision_ids())
 
2375
        self.assertEqual(
 
2376
            [('call_expecting_body', b'Repository.all_revision_ids',
 
2377
              (b'quack/',))],
 
2378
            client._calls)
 
2379
 
 
2380
    def test_with_some_content(self):
 
2381
        transport_path = 'quack'
 
2382
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2383
        client.add_success_response_with_body(
 
2384
            b'rev1\nrev2\nanotherrev\n', b'ok')
 
2385
        self.assertEqual(
 
2386
            set([b"rev1", b"rev2", b"anotherrev"]),
 
2387
            set(repo.all_revision_ids()))
 
2388
        self.assertEqual(
 
2389
            [('call_expecting_body', b'Repository.all_revision_ids',
 
2390
              (b'quack/',))],
 
2391
            client._calls)
 
2392
 
 
2393
 
 
2394
class TestRepositoryGatherStats(TestRemoteRepository):
 
2395
 
 
2396
    def test_revid_none(self):
 
2397
        # ('ok',), body with revisions and size
 
2398
        transport_path = 'quack'
 
2399
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2400
        client.add_success_response_with_body(
 
2401
            b'revisions: 2\nsize: 18\n', b'ok')
 
2402
        result = repo.gather_stats(None)
 
2403
        self.assertEqual(
 
2404
            [('call_expecting_body', b'Repository.gather_stats',
 
2405
              (b'quack/', b'', b'no'))],
 
2406
            client._calls)
 
2407
        self.assertEqual({'revisions': 2, 'size': 18}, result)
 
2408
 
 
2409
    def test_revid_no_committers(self):
 
2410
        # ('ok',), body without committers
 
2411
        body = (b'firstrev: 123456.300 3600\n'
 
2412
                b'latestrev: 654231.400 0\n'
 
2413
                b'revisions: 2\n'
 
2414
                b'size: 18\n')
 
2415
        transport_path = 'quick'
 
2416
        revid = u'\xc8'.encode('utf8')
 
2417
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2418
        client.add_success_response_with_body(body, b'ok')
 
2419
        result = repo.gather_stats(revid)
 
2420
        self.assertEqual(
 
2421
            [('call_expecting_body', b'Repository.gather_stats',
 
2422
              (b'quick/', revid, b'no'))],
 
2423
            client._calls)
 
2424
        self.assertEqual({'revisions': 2, 'size': 18,
 
2425
                          'firstrev': (123456.300, 3600),
 
2426
                          'latestrev': (654231.400, 0), },
 
2427
                         result)
 
2428
 
 
2429
    def test_revid_with_committers(self):
 
2430
        # ('ok',), body with committers
 
2431
        body = (b'committers: 128\n'
 
2432
                b'firstrev: 123456.300 3600\n'
 
2433
                b'latestrev: 654231.400 0\n'
 
2434
                b'revisions: 2\n'
 
2435
                b'size: 18\n')
 
2436
        transport_path = 'buick'
 
2437
        revid = u'\xc8'.encode('utf8')
 
2438
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2439
        client.add_success_response_with_body(body, b'ok')
 
2440
        result = repo.gather_stats(revid, True)
 
2441
        self.assertEqual(
 
2442
            [('call_expecting_body', b'Repository.gather_stats',
 
2443
              (b'buick/', revid, b'yes'))],
 
2444
            client._calls)
 
2445
        self.assertEqual({'revisions': 2, 'size': 18,
 
2446
                          'committers': 128,
 
2447
                          'firstrev': (123456.300, 3600),
 
2448
                          'latestrev': (654231.400, 0), },
 
2449
                         result)
 
2450
 
 
2451
 
 
2452
class TestRepositoryBreakLock(TestRemoteRepository):
 
2453
 
 
2454
    def test_break_lock(self):
 
2455
        transport_path = 'quack'
 
2456
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2457
        client.add_success_response(b'ok')
 
2458
        repo.break_lock()
 
2459
        self.assertEqual(
 
2460
            [('call', b'Repository.break_lock', (b'quack/',))],
 
2461
            client._calls)
 
2462
 
 
2463
 
 
2464
class TestRepositoryGetSerializerFormat(TestRemoteRepository):
 
2465
 
 
2466
    def test_get_serializer_format(self):
 
2467
        transport_path = 'hill'
 
2468
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2469
        client.add_success_response(b'ok', b'7')
 
2470
        self.assertEqual(b'7', repo.get_serializer_format())
 
2471
        self.assertEqual(
 
2472
            [('call', b'VersionedFileRepository.get_serializer_format',
 
2473
              (b'hill/', ))],
 
2474
            client._calls)
 
2475
 
 
2476
 
 
2477
class TestRepositoryReconcile(TestRemoteRepository):
 
2478
 
 
2479
    def test_reconcile(self):
 
2480
        transport_path = 'hill'
 
2481
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2482
        body = (b"garbage_inventories: 2\n"
 
2483
                b"inconsistent_parents: 3\n")
 
2484
        client.add_expected_call(
 
2485
            b'Repository.lock_write', (b'hill/', b''),
 
2486
            b'success', (b'ok', b'a token'))
 
2487
        client.add_success_response_with_body(body, b'ok')
 
2488
        reconciler = repo.reconcile()
 
2489
        self.assertEqual(
 
2490
            [('call', b'Repository.lock_write', (b'hill/', b'')),
 
2491
             ('call_expecting_body', b'Repository.reconcile',
 
2492
                (b'hill/', b'a token'))],
 
2493
            client._calls)
 
2494
        self.assertEqual(2, reconciler.garbage_inventories)
 
2495
        self.assertEqual(3, reconciler.inconsistent_parents)
 
2496
 
 
2497
 
 
2498
class TestRepositoryGetRevisionSignatureText(TestRemoteRepository):
 
2499
 
 
2500
    def test_text(self):
 
2501
        # ('ok',), body with signature text
 
2502
        transport_path = 'quack'
 
2503
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2504
        client.add_success_response_with_body(
 
2505
            b'THETEXT', b'ok')
 
2506
        self.assertEqual(b"THETEXT", repo.get_signature_text(b"revid"))
 
2507
        self.assertEqual(
 
2508
            [('call_expecting_body', b'Repository.get_revision_signature_text',
 
2509
              (b'quack/', b'revid'))],
 
2510
            client._calls)
 
2511
 
 
2512
    def test_no_signature(self):
 
2513
        transport_path = 'quick'
 
2514
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2515
        client.add_error_response(b'nosuchrevision', b'unknown')
 
2516
        self.assertRaises(errors.NoSuchRevision, repo.get_signature_text,
 
2517
                          b"unknown")
 
2518
        self.assertEqual(
 
2519
            [('call_expecting_body', b'Repository.get_revision_signature_text',
 
2520
              (b'quick/', b'unknown'))],
 
2521
            client._calls)
 
2522
 
 
2523
 
 
2524
class TestRepositoryGetGraph(TestRemoteRepository):
 
2525
 
 
2526
    def test_get_graph(self):
 
2527
        # get_graph returns a graph with a custom parents provider.
 
2528
        transport_path = 'quack'
 
2529
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2530
        graph = repo.get_graph()
 
2531
        self.assertNotEqual(graph._parents_provider, repo)
 
2532
 
 
2533
 
 
2534
class TestRepositoryAddSignatureText(TestRemoteRepository):
 
2535
 
 
2536
    def test_add_signature_text(self):
 
2537
        transport_path = 'quack'
 
2538
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2539
        client.add_expected_call(
 
2540
            b'Repository.lock_write', (b'quack/', b''),
 
2541
            b'success', (b'ok', b'a token'))
 
2542
        client.add_expected_call(
 
2543
            b'Repository.start_write_group', (b'quack/', b'a token'),
 
2544
            b'success', (b'ok', (b'token1', )))
 
2545
        client.add_expected_call(
 
2546
            b'Repository.add_signature_text', (b'quack/', b'a token', b'rev1',
 
2547
                                               b'token1'),
 
2548
            b'success', (b'ok', ), None)
 
2549
        repo.lock_write()
 
2550
        repo.start_write_group()
 
2551
        self.assertIs(
 
2552
            None, repo.add_signature_text(b"rev1", b"every bloody emperor"))
 
2553
        self.assertEqual(
 
2554
            ('call_with_body_bytes_expecting_body',
 
2555
             b'Repository.add_signature_text',
 
2556
                (b'quack/', b'a token', b'rev1', b'token1'),
 
2557
             b'every bloody emperor'),
 
2558
            client._calls[-1])
 
2559
 
 
2560
 
 
2561
class TestRepositoryGetParentMap(TestRemoteRepository):
 
2562
 
 
2563
    def test_get_parent_map_caching(self):
 
2564
        # get_parent_map returns from cache until unlock()
 
2565
        # setup a reponse with two revisions
 
2566
        r1 = u'\u0e33'.encode('utf8')
 
2567
        r2 = u'\u0dab'.encode('utf8')
 
2568
        lines = [b' '.join([r2, r1]), r1]
 
2569
        encoded_body = bz2.compress(b'\n'.join(lines))
 
2570
 
 
2571
        transport_path = 'quack'
 
2572
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2573
        client.add_success_response_with_body(encoded_body, b'ok')
 
2574
        client.add_success_response_with_body(encoded_body, b'ok')
 
2575
        repo.lock_read()
 
2576
        graph = repo.get_graph()
 
2577
        parents = graph.get_parent_map([r2])
 
2578
        self.assertEqual({r2: (r1,)}, parents)
 
2579
        # locking and unlocking deeper should not reset
 
2580
        repo.lock_read()
 
2581
        repo.unlock()
 
2582
        parents = graph.get_parent_map([r1])
 
2583
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
2584
        self.assertEqual(
 
2585
            [('call_with_body_bytes_expecting_body',
 
2586
              b'Repository.get_parent_map', (b'quack/',
 
2587
                                             b'include-missing:', r2),
 
2588
              b'\n\n0')],
 
2589
            client._calls)
 
2590
        repo.unlock()
 
2591
        # now we call again, and it should use the second response.
 
2592
        repo.lock_read()
 
2593
        graph = repo.get_graph()
 
2594
        parents = graph.get_parent_map([r1])
 
2595
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
2596
        self.assertEqual(
 
2597
            [('call_with_body_bytes_expecting_body',
 
2598
              b'Repository.get_parent_map', (b'quack/',
 
2599
                                             b'include-missing:', r2),
 
2600
              b'\n\n0'),
 
2601
             ('call_with_body_bytes_expecting_body',
 
2602
              b'Repository.get_parent_map', (b'quack/',
 
2603
                                             b'include-missing:', r1),
 
2604
              b'\n\n0'),
 
2605
             ],
 
2606
            client._calls)
 
2607
        repo.unlock()
 
2608
 
 
2609
    def test_get_parent_map_reconnects_if_unknown_method(self):
 
2610
        transport_path = 'quack'
 
2611
        rev_id = b'revision-id'
 
2612
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2613
        client.add_unknown_method_response(b'Repository.get_parent_map')
 
2614
        client.add_success_response_with_body(rev_id, b'ok')
 
2615
        self.assertFalse(client._medium._is_remote_before((1, 2)))
 
2616
        parents = repo.get_parent_map([rev_id])
 
2617
        self.assertEqual(
 
2618
            [('call_with_body_bytes_expecting_body',
 
2619
              b'Repository.get_parent_map',
 
2620
              (b'quack/', b'include-missing:', rev_id), b'\n\n0'),
 
2621
             ('disconnect medium',),
 
2622
             ('call_expecting_body', b'Repository.get_revision_graph',
 
2623
              (b'quack/', b''))],
 
2624
            client._calls)
 
2625
        # The medium is now marked as being connected to an older server
 
2626
        self.assertTrue(client._medium._is_remote_before((1, 2)))
 
2627
        self.assertEqual({rev_id: (b'null:',)}, parents)
 
2628
 
 
2629
    def test_get_parent_map_fallback_parentless_node(self):
 
2630
        """get_parent_map falls back to get_revision_graph on old servers.  The
 
2631
        results from get_revision_graph are tweaked to match the get_parent_map
 
2632
        API.
 
2633
 
 
2634
        Specifically, a {key: ()} result from get_revision_graph means "no
 
2635
        parents" for that key, which in get_parent_map results should be
 
2636
        represented as {key: ('null:',)}.
 
2637
 
 
2638
        This is the test for https://bugs.launchpad.net/bzr/+bug/214894
 
2639
        """
 
2640
        rev_id = b'revision-id'
 
2641
        transport_path = 'quack'
 
2642
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2643
        client.add_success_response_with_body(rev_id, b'ok')
 
2644
        client._medium._remember_remote_is_before((1, 2))
 
2645
        parents = repo.get_parent_map([rev_id])
 
2646
        self.assertEqual(
 
2647
            [('call_expecting_body', b'Repository.get_revision_graph',
 
2648
              (b'quack/', b''))],
 
2649
            client._calls)
 
2650
        self.assertEqual({rev_id: (b'null:',)}, parents)
 
2651
 
 
2652
    def test_get_parent_map_unexpected_response(self):
 
2653
        repo, client = self.setup_fake_client_and_repository('path')
 
2654
        client.add_success_response(b'something unexpected!')
 
2655
        self.assertRaises(
 
2656
            errors.UnexpectedSmartServerResponse,
 
2657
            repo.get_parent_map, [b'a-revision-id'])
 
2658
 
 
2659
    def test_get_parent_map_negative_caches_missing_keys(self):
 
2660
        self.setup_smart_server_with_call_log()
 
2661
        repo = self.make_repository('foo')
 
2662
        self.assertIsInstance(repo, RemoteRepository)
 
2663
        repo.lock_read()
 
2664
        self.addCleanup(repo.unlock)
 
2665
        self.reset_smart_call_log()
 
2666
        graph = repo.get_graph()
 
2667
        self.assertEqual(
 
2668
            {}, graph.get_parent_map([b'some-missing', b'other-missing']))
 
2669
        self.assertLength(1, self.hpss_calls)
 
2670
        # No call if we repeat this
 
2671
        self.reset_smart_call_log()
 
2672
        graph = repo.get_graph()
 
2673
        self.assertEqual(
 
2674
            {}, graph.get_parent_map([b'some-missing', b'other-missing']))
 
2675
        self.assertLength(0, self.hpss_calls)
 
2676
        # Asking for more unknown keys makes a request.
 
2677
        self.reset_smart_call_log()
 
2678
        graph = repo.get_graph()
 
2679
        self.assertEqual(
 
2680
            {}, graph.get_parent_map([b'some-missing', b'other-missing',
 
2681
                                     b'more-missing']))
 
2682
        self.assertLength(1, self.hpss_calls)
 
2683
 
 
2684
    def disableExtraResults(self):
 
2685
        self.overrideAttr(SmartServerRepositoryGetParentMap,
 
2686
                          'no_extra_results', True)
 
2687
 
 
2688
    def test_null_cached_missing_and_stop_key(self):
 
2689
        self.setup_smart_server_with_call_log()
 
2690
        # Make a branch with a single revision.
 
2691
        builder = self.make_branch_builder('foo')
 
2692
        builder.start_series()
 
2693
        builder.build_snapshot(None, [
 
2694
            ('add', ('', b'root-id', 'directory', ''))],
 
2695
            revision_id=b'first')
 
2696
        builder.finish_series()
 
2697
        branch = builder.get_branch()
 
2698
        repo = branch.repository
 
2699
        self.assertIsInstance(repo, RemoteRepository)
 
2700
        # Stop the server from sending extra results.
 
2701
        self.disableExtraResults()
 
2702
        repo.lock_read()
 
2703
        self.addCleanup(repo.unlock)
 
2704
        self.reset_smart_call_log()
 
2705
        graph = repo.get_graph()
 
2706
        # Query for b'first' and b'null:'.  Because b'null:' is a parent of
 
2707
        # 'first' it will be a candidate for the stop_keys of subsequent
 
2708
        # requests, and because b'null:' was queried but not returned it will
 
2709
        # be cached as missing.
 
2710
        self.assertEqual({b'first': (b'null:',)},
 
2711
                         graph.get_parent_map([b'first', b'null:']))
 
2712
        # Now query for another key.  This request will pass along a recipe of
 
2713
        # start and stop keys describing the already cached results, and this
 
2714
        # recipe's revision count must be correct (or else it will trigger an
 
2715
        # error from the server).
 
2716
        self.assertEqual({}, graph.get_parent_map([b'another-key']))
 
2717
        # This assertion guards against disableExtraResults silently failing to
 
2718
        # work, thus invalidating the test.
 
2719
        self.assertLength(2, self.hpss_calls)
 
2720
 
 
2721
    def test_get_parent_map_gets_ghosts_from_result(self):
 
2722
        # asking for a revision should negatively cache close ghosts in its
 
2723
        # ancestry.
 
2724
        self.setup_smart_server_with_call_log()
 
2725
        tree = self.make_branch_and_memory_tree('foo')
 
2726
        with tree.lock_write():
 
2727
            builder = treebuilder.TreeBuilder()
 
2728
            builder.start_tree(tree)
 
2729
            builder.build([])
 
2730
            builder.finish_tree()
 
2731
            tree.set_parent_ids([b'non-existant'],
 
2732
                                allow_leftmost_as_ghost=True)
 
2733
            rev_id = tree.commit('')
 
2734
        tree.lock_read()
 
2735
        self.addCleanup(tree.unlock)
 
2736
        repo = tree.branch.repository
 
2737
        self.assertIsInstance(repo, RemoteRepository)
 
2738
        # ask for rev_id
 
2739
        repo.get_parent_map([rev_id])
 
2740
        self.reset_smart_call_log()
 
2741
        # Now asking for rev_id's ghost parent should not make calls
 
2742
        self.assertEqual({}, repo.get_parent_map([b'non-existant']))
 
2743
        self.assertLength(0, self.hpss_calls)
 
2744
 
 
2745
    def test_exposes_get_cached_parent_map(self):
 
2746
        """RemoteRepository exposes get_cached_parent_map from
 
2747
        _unstacked_provider
 
2748
        """
 
2749
        r1 = u'\u0e33'.encode('utf8')
 
2750
        r2 = u'\u0dab'.encode('utf8')
 
2751
        lines = [b' '.join([r2, r1]), r1]
 
2752
        encoded_body = bz2.compress(b'\n'.join(lines))
 
2753
 
 
2754
        transport_path = 'quack'
 
2755
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2756
        client.add_success_response_with_body(encoded_body, b'ok')
 
2757
        repo.lock_read()
 
2758
        # get_cached_parent_map should *not* trigger an RPC
 
2759
        self.assertEqual({}, repo.get_cached_parent_map([r1]))
 
2760
        self.assertEqual([], client._calls)
 
2761
        self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
 
2762
        self.assertEqual({r1: (NULL_REVISION,)},
 
2763
                         repo.get_cached_parent_map([r1]))
 
2764
        self.assertEqual(
 
2765
            [('call_with_body_bytes_expecting_body',
 
2766
              b'Repository.get_parent_map', (b'quack/',
 
2767
                                             b'include-missing:', r2),
 
2768
              b'\n\n0')],
 
2769
            client._calls)
 
2770
        repo.unlock()
 
2771
 
 
2772
 
 
2773
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
 
2774
 
 
2775
    def test_allows_new_revisions(self):
 
2776
        """get_parent_map's results can be updated by commit."""
 
2777
        smart_server = test_server.SmartTCPServer_for_testing()
 
2778
        self.start_server(smart_server)
 
2779
        self.make_branch('branch')
 
2780
        branch = Branch.open(smart_server.get_url() + '/branch')
 
2781
        tree = branch.create_checkout('tree', lightweight=True)
 
2782
        tree.lock_write()
 
2783
        self.addCleanup(tree.unlock)
 
2784
        graph = tree.branch.repository.get_graph()
 
2785
        # This provides an opportunity for the missing rev-id to be cached.
 
2786
        self.assertEqual({}, graph.get_parent_map([b'rev1']))
 
2787
        tree.commit('message', rev_id=b'rev1')
 
2788
        graph = tree.branch.repository.get_graph()
 
2789
        self.assertEqual({b'rev1': (b'null:',)},
 
2790
                         graph.get_parent_map([b'rev1']))
 
2791
 
 
2792
 
 
2793
class TestRepositoryGetRevisions(TestRemoteRepository):
 
2794
 
 
2795
    def test_hpss_missing_revision(self):
 
2796
        transport_path = 'quack'
 
2797
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2798
        client.add_success_response_with_body(
 
2799
            b'', b'ok', b'10')
 
2800
        self.assertRaises(errors.NoSuchRevision, repo.get_revisions,
 
2801
                          [b'somerev1', b'anotherrev2'])
 
2802
        self.assertEqual(
 
2803
            [('call_with_body_bytes_expecting_body',
 
2804
              b'Repository.iter_revisions', (b'quack/', ),
 
2805
              b"somerev1\nanotherrev2")],
 
2806
            client._calls)
 
2807
 
 
2808
    def test_hpss_get_single_revision(self):
 
2809
        transport_path = 'quack'
 
2810
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2811
        somerev1 = Revision(b"somerev1")
 
2812
        somerev1.committer = "Joe Committer <joe@example.com>"
 
2813
        somerev1.timestamp = 1321828927
 
2814
        somerev1.timezone = -60
 
2815
        somerev1.inventory_sha1 = b"691b39be74c67b1212a75fcb19c433aaed903c2b"
 
2816
        somerev1.message = "Message"
 
2817
        body = zlib.compress(chk_bencode_serializer.write_revision_to_string(
 
2818
            somerev1))
 
2819
        # Split up body into two bits to make sure the zlib compression object
 
2820
        # gets data fed twice.
 
2821
        client.add_success_response_with_body(
 
2822
            [body[:10], body[10:]], b'ok', b'10')
 
2823
        revs = repo.get_revisions([b'somerev1'])
 
2824
        self.assertEqual(revs, [somerev1])
 
2825
        self.assertEqual(
 
2826
            [('call_with_body_bytes_expecting_body',
 
2827
              b'Repository.iter_revisions',
 
2828
              (b'quack/', ), b"somerev1")],
 
2829
            client._calls)
 
2830
 
 
2831
 
 
2832
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
 
2833
 
 
2834
    def test_null_revision(self):
 
2835
        # a null revision has the predictable result {}, we should have no wire
 
2836
        # traffic when calling it with this argument
 
2837
        transport_path = 'empty'
 
2838
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2839
        client.add_success_response(b'notused')
 
2840
        # actual RemoteRepository.get_revision_graph is gone, but there's an
 
2841
        # equivalent private method for testing
 
2842
        result = repo._get_revision_graph(NULL_REVISION)
 
2843
        self.assertEqual([], client._calls)
 
2844
        self.assertEqual({}, result)
 
2845
 
 
2846
    def test_none_revision(self):
 
2847
        # with none we want the entire graph
 
2848
        r1 = u'\u0e33'.encode('utf8')
 
2849
        r2 = u'\u0dab'.encode('utf8')
 
2850
        lines = [b' '.join([r2, r1]), r1]
 
2851
        encoded_body = b'\n'.join(lines)
 
2852
 
 
2853
        transport_path = 'sinhala'
 
2854
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2855
        client.add_success_response_with_body(encoded_body, b'ok')
 
2856
        # actual RemoteRepository.get_revision_graph is gone, but there's an
 
2857
        # equivalent private method for testing
 
2858
        result = repo._get_revision_graph(None)
 
2859
        self.assertEqual(
 
2860
            [('call_expecting_body', b'Repository.get_revision_graph',
 
2861
              (b'sinhala/', b''))],
 
2862
            client._calls)
 
2863
        self.assertEqual({r1: (), r2: (r1, )}, result)
 
2864
 
 
2865
    def test_specific_revision(self):
 
2866
        # with a specific revision we want the graph for that
 
2867
        # with none we want the entire graph
 
2868
        r11 = u'\u0e33'.encode('utf8')
 
2869
        r12 = u'\xc9'.encode('utf8')
 
2870
        r2 = u'\u0dab'.encode('utf8')
 
2871
        lines = [b' '.join([r2, r11, r12]), r11, r12]
 
2872
        encoded_body = b'\n'.join(lines)
 
2873
 
 
2874
        transport_path = 'sinhala'
 
2875
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2876
        client.add_success_response_with_body(encoded_body, b'ok')
 
2877
        result = repo._get_revision_graph(r2)
 
2878
        self.assertEqual(
 
2879
            [('call_expecting_body', b'Repository.get_revision_graph',
 
2880
              (b'sinhala/', r2))],
 
2881
            client._calls)
 
2882
        self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
 
2883
 
 
2884
    def test_no_such_revision(self):
 
2885
        revid = b'123'
 
2886
        transport_path = 'sinhala'
 
2887
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2888
        client.add_error_response(b'nosuchrevision', revid)
 
2889
        # also check that the right revision is reported in the error
 
2890
        self.assertRaises(errors.NoSuchRevision,
 
2891
                          repo._get_revision_graph, revid)
 
2892
        self.assertEqual(
 
2893
            [('call_expecting_body', b'Repository.get_revision_graph',
 
2894
              (b'sinhala/', revid))],
 
2895
            client._calls)
 
2896
 
 
2897
    def test_unexpected_error(self):
 
2898
        revid = '123'
 
2899
        transport_path = 'sinhala'
 
2900
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2901
        client.add_error_response(b'AnUnexpectedError')
 
2902
        e = self.assertRaises(errors.UnknownErrorFromSmartServer,
 
2903
                              repo._get_revision_graph, revid)
 
2904
        self.assertEqual((b'AnUnexpectedError',), e.error_tuple)
 
2905
 
 
2906
 
 
2907
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
 
2908
 
 
2909
    def test_ok(self):
 
2910
        repo, client = self.setup_fake_client_and_repository('quack')
 
2911
        client.add_expected_call(
 
2912
            b'Repository.get_rev_id_for_revno', (b'quack/',
 
2913
                                                 5, (42, b'rev-foo')),
 
2914
            b'success', (b'ok', b'rev-five'))
 
2915
        result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
 
2916
        self.assertEqual((True, b'rev-five'), result)
 
2917
        self.assertFinished(client)
 
2918
 
 
2919
    def test_history_incomplete(self):
 
2920
        repo, client = self.setup_fake_client_and_repository('quack')
 
2921
        client.add_expected_call(
 
2922
            b'Repository.get_rev_id_for_revno', (b'quack/',
 
2923
                                                 5, (42, b'rev-foo')),
 
2924
            b'success', (b'history-incomplete', 10, b'rev-ten'))
 
2925
        result = repo.get_rev_id_for_revno(5, (42, b'rev-foo'))
 
2926
        self.assertEqual((False, (10, b'rev-ten')), result)
 
2927
        self.assertFinished(client)
 
2928
 
 
2929
    def test_history_incomplete_with_fallback(self):
 
2930
        """A 'history-incomplete' response causes the fallback repository to be
 
2931
        queried too, if one is set.
 
2932
        """
 
2933
        # Make a repo with a fallback repo, both using a FakeClient.
 
2934
        format = remote.response_tuple_to_repo_format(
 
2935
            (b'yes', b'no', b'yes', self.get_repo_format().network_name()))
 
2936
        repo, client = self.setup_fake_client_and_repository('quack')
 
2937
        repo._format = format
 
2938
        fallback_repo, ignored = self.setup_fake_client_and_repository(
 
2939
            'fallback')
 
2940
        fallback_repo._client = client
 
2941
        fallback_repo._format = format
 
2942
        repo.add_fallback_repository(fallback_repo)
 
2943
        # First the client should ask the primary repo
 
2944
        client.add_expected_call(
 
2945
            b'Repository.get_rev_id_for_revno', (b'quack/',
 
2946
                                                 1, (42, b'rev-foo')),
 
2947
            b'success', (b'history-incomplete', 2, b'rev-two'))
 
2948
        # Then it should ask the fallback, using revno/revid from the
 
2949
        # history-incomplete response as the known revno/revid.
 
2950
        client.add_expected_call(
 
2951
            b'Repository.get_rev_id_for_revno', (
 
2952
                b'fallback/', 1, (2, b'rev-two')),
 
2953
            b'success', (b'ok', b'rev-one'))
 
2954
        result = repo.get_rev_id_for_revno(1, (42, b'rev-foo'))
 
2955
        self.assertEqual((True, b'rev-one'), result)
 
2956
        self.assertFinished(client)
 
2957
 
 
2958
    def test_nosuchrevision(self):
 
2959
        # 'nosuchrevision' is returned when the known-revid is not found in the
 
2960
        # remote repo.  The client translates that response to NoSuchRevision.
 
2961
        repo, client = self.setup_fake_client_and_repository('quack')
 
2962
        client.add_expected_call(
 
2963
            b'Repository.get_rev_id_for_revno', (b'quack/',
 
2964
                                                 5, (42, b'rev-foo')),
 
2965
            b'error', (b'nosuchrevision', b'rev-foo'))
 
2966
        self.assertRaises(
 
2967
            errors.NoSuchRevision,
 
2968
            repo.get_rev_id_for_revno, 5, (42, b'rev-foo'))
 
2969
        self.assertFinished(client)
 
2970
 
 
2971
    def test_branch_fallback_locking(self):
 
2972
        """RemoteBranch.get_rev_id takes a read lock, and tries to call the
 
2973
        get_rev_id_for_revno verb.  If the verb is unknown the VFS fallback
 
2974
        will be invoked, which will fail if the repo is unlocked.
 
2975
        """
 
2976
        self.setup_smart_server_with_call_log()
 
2977
        tree = self.make_branch_and_memory_tree('.')
 
2978
        tree.lock_write()
 
2979
        tree.add('')
 
2980
        rev1 = tree.commit('First')
 
2981
        tree.commit('Second')
 
2982
        tree.unlock()
 
2983
        branch = tree.branch
 
2984
        self.assertFalse(branch.is_locked())
 
2985
        self.reset_smart_call_log()
 
2986
        verb = b'Repository.get_rev_id_for_revno'
 
2987
        self.disable_verb(verb)
 
2988
        self.assertEqual(rev1, branch.get_rev_id(1))
 
2989
        self.assertLength(1, [call for call in self.hpss_calls if
 
2990
                              call.call.method == verb])
 
2991
 
 
2992
 
 
2993
class TestRepositoryHasSignatureForRevisionId(TestRemoteRepository):
 
2994
 
 
2995
    def test_has_signature_for_revision_id(self):
 
2996
        # ('yes', ) for Repository.has_signature_for_revision_id -> 'True'.
 
2997
        transport_path = 'quack'
 
2998
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2999
        client.add_success_response(b'yes')
 
3000
        result = repo.has_signature_for_revision_id(b'A')
 
3001
        self.assertEqual(
 
3002
            [('call', b'Repository.has_signature_for_revision_id',
 
3003
              (b'quack/', b'A'))],
 
3004
            client._calls)
 
3005
        self.assertEqual(True, result)
 
3006
 
 
3007
    def test_is_not_shared(self):
 
3008
        # ('no', ) for Repository.has_signature_for_revision_id -> 'False'.
 
3009
        transport_path = 'qwack'
 
3010
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3011
        client.add_success_response(b'no')
 
3012
        result = repo.has_signature_for_revision_id(b'A')
 
3013
        self.assertEqual(
 
3014
            [('call', b'Repository.has_signature_for_revision_id',
 
3015
              (b'qwack/', b'A'))],
 
3016
            client._calls)
 
3017
        self.assertEqual(False, result)
 
3018
 
 
3019
 
 
3020
class TestRepositoryPhysicalLockStatus(TestRemoteRepository):
 
3021
 
 
3022
    def test_get_physical_lock_status_yes(self):
 
3023
        transport_path = 'qwack'
 
3024
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3025
        client.add_success_response(b'yes')
 
3026
        result = repo.get_physical_lock_status()
 
3027
        self.assertEqual(
 
3028
            [('call', b'Repository.get_physical_lock_status',
 
3029
              (b'qwack/', ))],
 
3030
            client._calls)
 
3031
        self.assertEqual(True, result)
 
3032
 
 
3033
    def test_get_physical_lock_status_no(self):
 
3034
        transport_path = 'qwack'
 
3035
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3036
        client.add_success_response(b'no')
 
3037
        result = repo.get_physical_lock_status()
 
3038
        self.assertEqual(
 
3039
            [('call', b'Repository.get_physical_lock_status',
 
3040
              (b'qwack/', ))],
 
3041
            client._calls)
 
3042
        self.assertEqual(False, result)
 
3043
 
 
3044
 
 
3045
class TestRepositoryIsShared(TestRemoteRepository):
 
3046
 
 
3047
    def test_is_shared(self):
 
3048
        # ('yes', ) for Repository.is_shared -> 'True'.
 
3049
        transport_path = 'quack'
 
3050
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3051
        client.add_success_response(b'yes')
 
3052
        result = repo.is_shared()
 
3053
        self.assertEqual(
 
3054
            [('call', b'Repository.is_shared', (b'quack/',))],
 
3055
            client._calls)
 
3056
        self.assertEqual(True, result)
 
3057
 
 
3058
    def test_is_not_shared(self):
 
3059
        # ('no', ) for Repository.is_shared -> 'False'.
 
3060
        transport_path = 'qwack'
 
3061
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3062
        client.add_success_response(b'no')
 
3063
        result = repo.is_shared()
 
3064
        self.assertEqual(
 
3065
            [('call', b'Repository.is_shared', (b'qwack/',))],
 
3066
            client._calls)
 
3067
        self.assertEqual(False, result)
 
3068
 
 
3069
 
 
3070
class TestRepositoryMakeWorkingTrees(TestRemoteRepository):
 
3071
 
 
3072
    def test_make_working_trees(self):
 
3073
        # ('yes', ) for Repository.make_working_trees -> 'True'.
 
3074
        transport_path = 'quack'
 
3075
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3076
        client.add_success_response(b'yes')
 
3077
        result = repo.make_working_trees()
 
3078
        self.assertEqual(
 
3079
            [('call', b'Repository.make_working_trees', (b'quack/',))],
 
3080
            client._calls)
 
3081
        self.assertEqual(True, result)
 
3082
 
 
3083
    def test_no_working_trees(self):
 
3084
        # ('no', ) for Repository.make_working_trees -> 'False'.
 
3085
        transport_path = 'qwack'
 
3086
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3087
        client.add_success_response(b'no')
 
3088
        result = repo.make_working_trees()
 
3089
        self.assertEqual(
 
3090
            [('call', b'Repository.make_working_trees', (b'qwack/',))],
 
3091
            client._calls)
 
3092
        self.assertEqual(False, result)
 
3093
 
 
3094
 
 
3095
class TestRepositoryLockWrite(TestRemoteRepository):
 
3096
 
 
3097
    def test_lock_write(self):
 
3098
        transport_path = 'quack'
 
3099
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3100
        client.add_success_response(b'ok', b'a token')
 
3101
        token = repo.lock_write().repository_token
 
3102
        self.assertEqual(
 
3103
            [('call', b'Repository.lock_write', (b'quack/', b''))],
 
3104
            client._calls)
 
3105
        self.assertEqual(b'a token', token)
 
3106
 
 
3107
    def test_lock_write_already_locked(self):
 
3108
        transport_path = 'quack'
 
3109
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3110
        client.add_error_response(b'LockContention')
 
3111
        self.assertRaises(errors.LockContention, repo.lock_write)
 
3112
        self.assertEqual(
 
3113
            [('call', b'Repository.lock_write', (b'quack/', b''))],
 
3114
            client._calls)
 
3115
 
 
3116
    def test_lock_write_unlockable(self):
 
3117
        transport_path = 'quack'
 
3118
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3119
        client.add_error_response(b'UnlockableTransport')
 
3120
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
 
3121
        self.assertEqual(
 
3122
            [('call', b'Repository.lock_write', (b'quack/', b''))],
 
3123
            client._calls)
 
3124
 
 
3125
 
 
3126
class TestRepositoryWriteGroups(TestRemoteRepository):
 
3127
 
 
3128
    def test_start_write_group(self):
 
3129
        transport_path = 'quack'
 
3130
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3131
        client.add_expected_call(
 
3132
            b'Repository.lock_write', (b'quack/', b''),
 
3133
            b'success', (b'ok', b'a token'))
 
3134
        client.add_expected_call(
 
3135
            b'Repository.start_write_group', (b'quack/', b'a token'),
 
3136
            b'success', (b'ok', (b'token1', )))
 
3137
        repo.lock_write()
 
3138
        repo.start_write_group()
 
3139
 
 
3140
    def test_start_write_group_unsuspendable(self):
 
3141
        # Some repositories do not support suspending write
 
3142
        # groups. For those, fall back to the "real" repository.
 
3143
        transport_path = 'quack'
 
3144
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3145
 
 
3146
        def stub_ensure_real():
 
3147
            client._calls.append(('_ensure_real',))
 
3148
            repo._real_repository = _StubRealPackRepository(client._calls)
 
3149
        repo._ensure_real = stub_ensure_real
 
3150
        client.add_expected_call(
 
3151
            b'Repository.lock_write', (b'quack/', b''),
 
3152
            b'success', (b'ok', b'a token'))
 
3153
        client.add_expected_call(
 
3154
            b'Repository.start_write_group', (b'quack/', b'a token'),
 
3155
            b'error', (b'UnsuspendableWriteGroup',))
 
3156
        repo.lock_write()
 
3157
        repo.start_write_group()
 
3158
        self.assertEqual(client._calls[-2:], [
 
3159
            ('_ensure_real',),
 
3160
            ('start_write_group',)])
 
3161
 
 
3162
    def test_commit_write_group(self):
 
3163
        transport_path = 'quack'
 
3164
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3165
        client.add_expected_call(
 
3166
            b'Repository.lock_write', (b'quack/', b''),
 
3167
            b'success', (b'ok', b'a token'))
 
3168
        client.add_expected_call(
 
3169
            b'Repository.start_write_group', (b'quack/', b'a token'),
 
3170
            b'success', (b'ok', [b'token1']))
 
3171
        client.add_expected_call(
 
3172
            b'Repository.commit_write_group', (b'quack/',
 
3173
                                               b'a token', [b'token1']),
 
3174
            b'success', (b'ok',))
 
3175
        repo.lock_write()
 
3176
        repo.start_write_group()
 
3177
        repo.commit_write_group()
 
3178
 
 
3179
    def test_abort_write_group(self):
 
3180
        transport_path = 'quack'
 
3181
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3182
        client.add_expected_call(
 
3183
            b'Repository.lock_write', (b'quack/', b''),
 
3184
            b'success', (b'ok', b'a token'))
 
3185
        client.add_expected_call(
 
3186
            b'Repository.start_write_group', (b'quack/', b'a token'),
 
3187
            b'success', (b'ok', [b'token1']))
 
3188
        client.add_expected_call(
 
3189
            b'Repository.abort_write_group', (b'quack/',
 
3190
                                              b'a token', [b'token1']),
 
3191
            b'success', (b'ok',))
 
3192
        repo.lock_write()
 
3193
        repo.start_write_group()
 
3194
        repo.abort_write_group(False)
 
3195
 
 
3196
    def test_suspend_write_group(self):
 
3197
        transport_path = 'quack'
 
3198
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3199
        self.assertEqual([], repo.suspend_write_group())
 
3200
 
 
3201
    def test_resume_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.check_write_group', (b'quack/',
 
3209
                                              b'a token', [b'token1']),
 
3210
            b'success', (b'ok',))
 
3211
        repo.lock_write()
 
3212
        repo.resume_write_group(['token1'])
 
3213
 
 
3214
 
 
3215
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
 
3216
 
 
3217
    def test_backwards_compat(self):
 
3218
        self.setup_smart_server_with_call_log()
 
3219
        repo = self.make_repository('.')
 
3220
        self.reset_smart_call_log()
 
3221
        verb = b'Repository.set_make_working_trees'
 
3222
        self.disable_verb(verb)
 
3223
        repo.set_make_working_trees(True)
 
3224
        call_count = len([call for call in self.hpss_calls if
 
3225
                          call.call.method == verb])
 
3226
        self.assertEqual(1, call_count)
 
3227
 
 
3228
    def test_current(self):
 
3229
        transport_path = 'quack'
 
3230
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3231
        client.add_expected_call(
 
3232
            b'Repository.set_make_working_trees', (b'quack/', b'True'),
 
3233
            b'success', (b'ok',))
 
3234
        client.add_expected_call(
 
3235
            b'Repository.set_make_working_trees', (b'quack/', b'False'),
 
3236
            b'success', (b'ok',))
 
3237
        repo.set_make_working_trees(True)
 
3238
        repo.set_make_working_trees(False)
 
3239
 
 
3240
 
 
3241
class TestRepositoryUnlock(TestRemoteRepository):
 
3242
 
 
3243
    def test_unlock(self):
 
3244
        transport_path = 'quack'
 
3245
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3246
        client.add_success_response(b'ok', b'a token')
 
3247
        client.add_success_response(b'ok')
 
3248
        repo.lock_write()
 
3249
        repo.unlock()
 
3250
        self.assertEqual(
 
3251
            [('call', b'Repository.lock_write', (b'quack/', b'')),
 
3252
             ('call', b'Repository.unlock', (b'quack/', b'a token'))],
 
3253
            client._calls)
 
3254
 
 
3255
    def test_unlock_wrong_token(self):
 
3256
        # If somehow the token is wrong, unlock will raise TokenMismatch.
 
3257
        transport_path = 'quack'
 
3258
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3259
        client.add_success_response(b'ok', b'a token')
 
3260
        client.add_error_response(b'TokenMismatch')
 
3261
        repo.lock_write()
 
3262
        self.assertRaises(errors.TokenMismatch, repo.unlock)
 
3263
 
 
3264
 
 
3265
class TestRepositoryHasRevision(TestRemoteRepository):
 
3266
 
 
3267
    def test_none(self):
 
3268
        # repo.has_revision(None) should not cause any traffic.
 
3269
        transport_path = 'quack'
 
3270
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3271
 
 
3272
        # The null revision is always there, so has_revision(None) == True.
 
3273
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
 
3274
 
 
3275
        # The remote repo shouldn't be accessed.
 
3276
        self.assertEqual([], client._calls)
 
3277
 
 
3278
 
 
3279
class TestRepositoryIterFilesBytes(TestRemoteRepository):
 
3280
    """Test Repository.iter_file_bytes."""
 
3281
 
 
3282
    def test_single(self):
 
3283
        transport_path = 'quack'
 
3284
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3285
        client.add_expected_call(
 
3286
            b'Repository.iter_files_bytes', (b'quack/', ),
 
3287
            b'success', (b'ok',), iter([b"ok\x000", b"\n", zlib.compress(b"mydata" * 10)]))
 
3288
        for (identifier, byte_stream) in repo.iter_files_bytes([(b"somefile",
 
3289
                                                                 b"somerev", b"myid")]):
 
3290
            self.assertEqual(b"myid", identifier)
 
3291
            self.assertEqual(b"".join(byte_stream), b"mydata" * 10)
 
3292
 
 
3293
    def test_missing(self):
 
3294
        transport_path = 'quack'
 
3295
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3296
        client.add_expected_call(
 
3297
            b'Repository.iter_files_bytes',
 
3298
            (b'quack/', ),
 
3299
            b'error', (b'RevisionNotPresent', b'somefile', b'somerev'),
 
3300
            iter([b"absent\0somefile\0somerev\n"]))
 
3301
        self.assertRaises(errors.RevisionNotPresent, list,
 
3302
                          repo.iter_files_bytes(
 
3303
                              [(b"somefile", b"somerev", b"myid")]))
 
3304
 
 
3305
 
 
3306
class TestRepositoryInsertStreamBase(TestRemoteRepository):
 
3307
    """Base class for Repository.insert_stream and .insert_stream_1.19
 
3308
    tests.
 
3309
    """
 
3310
 
 
3311
    def checkInsertEmptyStream(self, repo, client):
 
3312
        """Insert an empty stream, checking the result.
 
3313
 
 
3314
        This checks that there are no resume_tokens or missing_keys, and that
 
3315
        the client is finished.
 
3316
        """
 
3317
        sink = repo._get_sink()
 
3318
        fmt = repository.format_registry.get_default()
 
3319
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
 
3320
        self.assertEqual([], resume_tokens)
 
3321
        self.assertEqual(set(), missing_keys)
 
3322
        self.assertFinished(client)
 
3323
 
 
3324
 
 
3325
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
 
3326
    """Tests for using Repository.insert_stream verb when the _1.19 variant is
 
3327
    not available.
 
3328
 
 
3329
    This test case is very similar to TestRepositoryInsertStream_1_19.
 
3330
    """
 
3331
 
 
3332
    def setUp(self):
 
3333
        super(TestRepositoryInsertStream, self).setUp()
 
3334
        self.disable_verb(b'Repository.insert_stream_1.19')
 
3335
 
 
3336
    def test_unlocked_repo(self):
 
3337
        transport_path = 'quack'
 
3338
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3339
        client.add_expected_call(
 
3340
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3341
            b'unknown', (b'Repository.insert_stream_1.19',))
 
3342
        client.add_expected_call(
 
3343
            b'Repository.insert_stream', (b'quack/', b''),
 
3344
            b'success', (b'ok',))
 
3345
        client.add_expected_call(
 
3346
            b'Repository.insert_stream', (b'quack/', b''),
 
3347
            b'success', (b'ok',))
 
3348
        self.checkInsertEmptyStream(repo, client)
 
3349
 
 
3350
    def test_locked_repo_with_no_lock_token(self):
 
3351
        transport_path = 'quack'
 
3352
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3353
        client.add_expected_call(
 
3354
            b'Repository.lock_write', (b'quack/', b''),
 
3355
            b'success', (b'ok', b''))
 
3356
        client.add_expected_call(
 
3357
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3358
            b'unknown', (b'Repository.insert_stream_1.19',))
 
3359
        client.add_expected_call(
 
3360
            b'Repository.insert_stream', (b'quack/', b''),
 
3361
            b'success', (b'ok',))
 
3362
        client.add_expected_call(
 
3363
            b'Repository.insert_stream', (b'quack/', b''),
 
3364
            b'success', (b'ok',))
 
3365
        repo.lock_write()
 
3366
        self.checkInsertEmptyStream(repo, client)
 
3367
 
 
3368
    def test_locked_repo_with_lock_token(self):
 
3369
        transport_path = 'quack'
 
3370
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3371
        client.add_expected_call(
 
3372
            b'Repository.lock_write', (b'quack/', b''),
 
3373
            b'success', (b'ok', b'a token'))
 
3374
        client.add_expected_call(
 
3375
            b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
 
3376
            b'unknown', (b'Repository.insert_stream_1.19',))
 
3377
        client.add_expected_call(
 
3378
            b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
 
3379
            b'success', (b'ok',))
 
3380
        client.add_expected_call(
 
3381
            b'Repository.insert_stream_locked', (b'quack/', b'', b'a token'),
 
3382
            b'success', (b'ok',))
 
3383
        repo.lock_write()
 
3384
        self.checkInsertEmptyStream(repo, client)
 
3385
 
 
3386
    def test_stream_with_inventory_deltas(self):
 
3387
        """'inventory-deltas' substreams cannot be sent to the
 
3388
        Repository.insert_stream verb, because not all servers that implement
 
3389
        that verb will accept them.  So when one is encountered the RemoteSink
 
3390
        immediately stops using that verb and falls back to VFS insert_stream.
 
3391
        """
 
3392
        transport_path = 'quack'
 
3393
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3394
        client.add_expected_call(
 
3395
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3396
            b'unknown', (b'Repository.insert_stream_1.19',))
 
3397
        client.add_expected_call(
 
3398
            b'Repository.insert_stream', (b'quack/', b''),
 
3399
            b'success', (b'ok',))
 
3400
        client.add_expected_call(
 
3401
            b'Repository.insert_stream', (b'quack/', b''),
 
3402
            b'success', (b'ok',))
 
3403
        # Create a fake real repository for insert_stream to fall back on, so
 
3404
        # that we can directly see the records the RemoteSink passes to the
 
3405
        # real sink.
 
3406
 
 
3407
        class FakeRealSink:
 
3408
            def __init__(self):
 
3409
                self.records = []
 
3410
 
 
3411
            def insert_stream(self, stream, src_format, resume_tokens):
 
3412
                for substream_kind, substream in stream:
 
3413
                    self.records.append(
 
3414
                        (substream_kind, [record.key for record in substream]))
 
3415
                return [b'fake tokens'], [b'fake missing keys']
 
3416
        fake_real_sink = FakeRealSink()
 
3417
 
 
3418
        class FakeRealRepository:
 
3419
            def _get_sink(self):
 
3420
                return fake_real_sink
 
3421
 
 
3422
            def is_in_write_group(self):
 
3423
                return False
 
3424
 
 
3425
            def refresh_data(self):
 
3426
                return True
 
3427
        repo._real_repository = FakeRealRepository()
 
3428
        sink = repo._get_sink()
 
3429
        fmt = repository.format_registry.get_default()
 
3430
        stream = self.make_stream_with_inv_deltas(fmt)
 
3431
        resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
 
3432
        # Every record from the first inventory delta should have been sent to
 
3433
        # the VFS sink.
 
3434
        expected_records = [
 
3435
            ('inventory-deltas', [(b'rev2',), (b'rev3',)]),
 
3436
            ('texts', [(b'some-rev', b'some-file')])]
 
3437
        self.assertEqual(expected_records, fake_real_sink.records)
 
3438
        # The return values from the real sink's insert_stream are propagated
 
3439
        # back to the original caller.
 
3440
        self.assertEqual([b'fake tokens'], resume_tokens)
 
3441
        self.assertEqual([b'fake missing keys'], missing_keys)
 
3442
        self.assertFinished(client)
 
3443
 
 
3444
    def make_stream_with_inv_deltas(self, fmt):
 
3445
        """Make a simple stream with an inventory delta followed by more
 
3446
        records and more substreams to test that all records and substreams
 
3447
        from that point on are used.
 
3448
 
 
3449
        This sends, in order:
 
3450
           * inventories substream: rev1, rev2, rev3.  rev2 and rev3 are
 
3451
             inventory-deltas.
 
3452
           * texts substream: (some-rev, some-file)
 
3453
        """
 
3454
        # Define a stream using generators so that it isn't rewindable.
 
3455
        inv = inventory.Inventory(revision_id=b'rev1')
 
3456
        inv.root.revision = b'rev1'
 
3457
 
 
3458
        def stream_with_inv_delta():
 
3459
            yield ('inventories', inventories_substream())
 
3460
            yield ('inventory-deltas', inventory_delta_substream())
 
3461
            yield ('texts', [
 
3462
                versionedfile.FulltextContentFactory(
 
3463
                    (b'some-rev', b'some-file'), (), None, b'content')])
 
3464
 
 
3465
        def inventories_substream():
 
3466
            # An empty inventory fulltext.  This will be streamed normally.
 
3467
            text = fmt._serializer.write_inventory_to_string(inv)
 
3468
            yield versionedfile.FulltextContentFactory(
 
3469
                (b'rev1',), (), None, text)
 
3470
 
 
3471
        def inventory_delta_substream():
 
3472
            # An inventory delta.  This can't be streamed via this verb, so it
 
3473
            # will trigger a fallback to VFS insert_stream.
 
3474
            entry = inv.make_entry(
 
3475
                'directory', 'newdir', inv.root.file_id, b'newdir-id')
 
3476
            entry.revision = b'ghost'
 
3477
            delta = [(None, 'newdir', b'newdir-id', entry)]
 
3478
            serializer = inventory_delta.InventoryDeltaSerializer(
 
3479
                versioned_root=True, tree_references=False)
 
3480
            lines = serializer.delta_to_lines(b'rev1', b'rev2', delta)
 
3481
            yield versionedfile.ChunkedContentFactory(
 
3482
                (b'rev2',), ((b'rev1',)), None, lines)
 
3483
            # Another delta.
 
3484
            lines = serializer.delta_to_lines(b'rev1', b'rev3', delta)
 
3485
            yield versionedfile.ChunkedContentFactory(
 
3486
                (b'rev3',), ((b'rev1',)), None, lines)
 
3487
        return stream_with_inv_delta()
 
3488
 
 
3489
 
 
3490
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
 
3491
 
 
3492
    def test_unlocked_repo(self):
 
3493
        transport_path = 'quack'
 
3494
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3495
        client.add_expected_call(
 
3496
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3497
            b'success', (b'ok',))
 
3498
        client.add_expected_call(
 
3499
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3500
            b'success', (b'ok',))
 
3501
        self.checkInsertEmptyStream(repo, client)
 
3502
 
 
3503
    def test_locked_repo_with_no_lock_token(self):
 
3504
        transport_path = 'quack'
 
3505
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3506
        client.add_expected_call(
 
3507
            b'Repository.lock_write', (b'quack/', b''),
 
3508
            b'success', (b'ok', b''))
 
3509
        client.add_expected_call(
 
3510
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3511
            b'success', (b'ok',))
 
3512
        client.add_expected_call(
 
3513
            b'Repository.insert_stream_1.19', (b'quack/', b''),
 
3514
            b'success', (b'ok',))
 
3515
        repo.lock_write()
 
3516
        self.checkInsertEmptyStream(repo, client)
 
3517
 
 
3518
    def test_locked_repo_with_lock_token(self):
 
3519
        transport_path = 'quack'
 
3520
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3521
        client.add_expected_call(
 
3522
            b'Repository.lock_write', (b'quack/', b''),
 
3523
            b'success', (b'ok', b'a token'))
 
3524
        client.add_expected_call(
 
3525
            b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
 
3526
            b'success', (b'ok',))
 
3527
        client.add_expected_call(
 
3528
            b'Repository.insert_stream_1.19', (b'quack/', b'', b'a token'),
 
3529
            b'success', (b'ok',))
 
3530
        repo.lock_write()
 
3531
        self.checkInsertEmptyStream(repo, client)
 
3532
 
 
3533
 
 
3534
class TestRepositoryTarball(TestRemoteRepository):
 
3535
 
 
3536
    # This is a canned tarball reponse we can validate against
 
3537
    tarball_content = base64.b64decode(
 
3538
        'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
 
3539
        'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
 
3540
        'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
 
3541
        'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
 
3542
        'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
 
3543
        'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
 
3544
        'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
 
3545
        'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
 
3546
        '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
 
3547
        'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
 
3548
        'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
 
3549
        'nWQ7QH/F3JFOFCQ0aSPfA='
 
3550
        )
 
3551
 
 
3552
    def test_repository_tarball(self):
 
3553
        # Test that Repository.tarball generates the right operations
 
3554
        transport_path = 'repo'
 
3555
        expected_calls = [('call_expecting_body', b'Repository.tarball',
 
3556
                           (b'repo/', b'bz2',),),
 
3557
                          ]
 
3558
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3559
        client.add_success_response_with_body(self.tarball_content, b'ok')
 
3560
        # Now actually ask for the tarball
 
3561
        tarball_file = repo._get_tarball('bz2')
 
3562
        try:
 
3563
            self.assertEqual(expected_calls, client._calls)
 
3564
            self.assertEqual(self.tarball_content, tarball_file.read())
 
3565
        finally:
 
3566
            tarball_file.close()
 
3567
 
 
3568
 
 
3569
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
 
3570
    """RemoteRepository.copy_content_into optimizations"""
 
3571
 
 
3572
    def test_copy_content_remote_to_local(self):
 
3573
        self.transport_server = test_server.SmartTCPServer_for_testing
 
3574
        src_repo = self.make_repository('repo1')
 
3575
        src_repo = repository.Repository.open(self.get_url('repo1'))
 
3576
        # At the moment the tarball-based copy_content_into can't write back
 
3577
        # into a smart server.  It would be good if it could upload the
 
3578
        # tarball; once that works we'd have to create repositories of
 
3579
        # different formats. -- mbp 20070410
 
3580
        dest_url = self.get_vfs_only_url('repo2')
 
3581
        dest_bzrdir = BzrDir.create(dest_url)
 
3582
        dest_repo = dest_bzrdir.create_repository()
 
3583
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
 
3584
        self.assertTrue(isinstance(src_repo, RemoteRepository))
 
3585
        src_repo.copy_content_into(dest_repo)
 
3586
 
 
3587
 
 
3588
class _StubRealPackRepository(object):
 
3589
 
 
3590
    def __init__(self, calls):
 
3591
        self.calls = calls
 
3592
        self._pack_collection = _StubPackCollection(calls)
 
3593
 
 
3594
    def start_write_group(self):
 
3595
        self.calls.append(('start_write_group',))
 
3596
 
 
3597
    def is_in_write_group(self):
 
3598
        return False
 
3599
 
 
3600
    def refresh_data(self):
 
3601
        self.calls.append(('pack collection reload_pack_names',))
 
3602
 
 
3603
 
 
3604
class _StubPackCollection(object):
 
3605
 
 
3606
    def __init__(self, calls):
 
3607
        self.calls = calls
 
3608
 
 
3609
    def autopack(self):
 
3610
        self.calls.append(('pack collection autopack',))
 
3611
 
 
3612
 
 
3613
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
 
3614
    """Tests for RemoteRepository.autopack implementation."""
 
3615
 
 
3616
    def test_ok(self):
 
3617
        """When the server returns 'ok' and there's no _real_repository, then
 
3618
        nothing else happens: the autopack method is done.
 
3619
        """
 
3620
        transport_path = 'quack'
 
3621
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3622
        client.add_expected_call(
 
3623
            b'PackRepository.autopack', (b'quack/',), b'success', (b'ok',))
 
3624
        repo.autopack()
 
3625
        self.assertFinished(client)
 
3626
 
 
3627
    def test_ok_with_real_repo(self):
 
3628
        """When the server returns 'ok' and there is a _real_repository, then
 
3629
        the _real_repository's reload_pack_name's method will be called.
 
3630
        """
 
3631
        transport_path = 'quack'
 
3632
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3633
        client.add_expected_call(
 
3634
            b'PackRepository.autopack', (b'quack/',),
 
3635
            b'success', (b'ok',))
 
3636
        repo._real_repository = _StubRealPackRepository(client._calls)
 
3637
        repo.autopack()
 
3638
        self.assertEqual(
 
3639
            [('call', b'PackRepository.autopack', (b'quack/',)),
 
3640
             ('pack collection reload_pack_names',)],
 
3641
            client._calls)
 
3642
 
 
3643
    def test_backwards_compatibility(self):
 
3644
        """If the server does not recognise the PackRepository.autopack verb,
 
3645
        fallback to the real_repository's implementation.
 
3646
        """
 
3647
        transport_path = 'quack'
 
3648
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3649
        client.add_unknown_method_response(b'PackRepository.autopack')
 
3650
 
 
3651
        def stub_ensure_real():
 
3652
            client._calls.append(('_ensure_real',))
 
3653
            repo._real_repository = _StubRealPackRepository(client._calls)
 
3654
        repo._ensure_real = stub_ensure_real
 
3655
        repo.autopack()
 
3656
        self.assertEqual(
 
3657
            [('call', b'PackRepository.autopack', (b'quack/',)),
 
3658
             ('_ensure_real',),
 
3659
             ('pack collection autopack',)],
 
3660
            client._calls)
 
3661
 
 
3662
    def test_oom_error_reporting(self):
 
3663
        """An out-of-memory condition on the server is reported clearly"""
 
3664
        transport_path = 'quack'
 
3665
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
3666
        client.add_expected_call(
 
3667
            b'PackRepository.autopack', (b'quack/',),
 
3668
            b'error', (b'MemoryError',))
 
3669
        err = self.assertRaises(errors.BzrError, repo.autopack)
 
3670
        self.assertContainsRe(str(err), "^remote server out of mem")
 
3671
 
 
3672
 
 
3673
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
 
3674
    """Base class for unit tests for breezy.bzr.remote._translate_error."""
 
3675
 
 
3676
    def translateTuple(self, error_tuple, **context):
 
3677
        """Call _translate_error with an ErrorFromSmartServer built from the
 
3678
        given error_tuple.
 
3679
 
 
3680
        :param error_tuple: A tuple of a smart server response, as would be
 
3681
            passed to an ErrorFromSmartServer.
 
3682
        :kwargs context: context items to call _translate_error with.
 
3683
 
 
3684
        :returns: The error raised by _translate_error.
 
3685
        """
 
3686
        # Raise the ErrorFromSmartServer before passing it as an argument,
 
3687
        # because _translate_error may need to re-raise it with a bare 'raise'
 
3688
        # statement.
 
3689
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
3690
        translated_error = self.translateErrorFromSmartServer(
 
3691
            server_error, **context)
 
3692
        return translated_error
 
3693
 
 
3694
    def translateErrorFromSmartServer(self, error_object, **context):
 
3695
        """Like translateTuple, but takes an already constructed
 
3696
        ErrorFromSmartServer rather than a tuple.
 
3697
        """
 
3698
        try:
 
3699
            raise error_object
 
3700
        except errors.ErrorFromSmartServer as server_error:
 
3701
            translated_error = self.assertRaises(
 
3702
                errors.BzrError, remote._translate_error, server_error,
 
3703
                **context)
 
3704
        return translated_error
 
3705
 
 
3706
 
 
3707
class TestErrorTranslationSuccess(TestErrorTranslationBase):
 
3708
    """Unit tests for breezy.bzr.remote._translate_error.
 
3709
 
 
3710
    Given an ErrorFromSmartServer (which has an error tuple from a smart
 
3711
    server) and some context, _translate_error raises more specific errors from
 
3712
    breezy.errors.
 
3713
 
 
3714
    This test case covers the cases where _translate_error succeeds in
 
3715
    translating an ErrorFromSmartServer to something better.  See
 
3716
    TestErrorTranslationRobustness for other cases.
 
3717
    """
 
3718
 
 
3719
    def test_NoSuchRevision(self):
 
3720
        branch = self.make_branch('')
 
3721
        revid = b'revid'
 
3722
        translated_error = self.translateTuple(
 
3723
            (b'NoSuchRevision', revid), branch=branch)
 
3724
        expected_error = errors.NoSuchRevision(branch, revid)
 
3725
        self.assertEqual(expected_error, translated_error)
 
3726
 
 
3727
    def test_nosuchrevision(self):
 
3728
        repository = self.make_repository('')
 
3729
        revid = b'revid'
 
3730
        translated_error = self.translateTuple(
 
3731
            (b'nosuchrevision', revid), repository=repository)
 
3732
        expected_error = errors.NoSuchRevision(repository, revid)
 
3733
        self.assertEqual(expected_error, translated_error)
 
3734
 
 
3735
    def test_nobranch(self):
 
3736
        bzrdir = self.make_controldir('')
 
3737
        translated_error = self.translateTuple((b'nobranch',), bzrdir=bzrdir)
 
3738
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
 
3739
        self.assertEqual(expected_error, translated_error)
 
3740
 
 
3741
    def test_nobranch_one_arg(self):
 
3742
        bzrdir = self.make_controldir('')
 
3743
        translated_error = self.translateTuple(
 
3744
            (b'nobranch', b'extra detail'), bzrdir=bzrdir)
 
3745
        expected_error = errors.NotBranchError(
 
3746
            path=bzrdir.root_transport.base,
 
3747
            detail='extra detail')
 
3748
        self.assertEqual(expected_error, translated_error)
 
3749
 
 
3750
    def test_norepository(self):
 
3751
        bzrdir = self.make_controldir('')
 
3752
        translated_error = self.translateTuple((b'norepository',),
 
3753
                                               bzrdir=bzrdir)
 
3754
        expected_error = errors.NoRepositoryPresent(bzrdir)
 
3755
        self.assertEqual(expected_error, translated_error)
 
3756
 
 
3757
    def test_LockContention(self):
 
3758
        translated_error = self.translateTuple((b'LockContention',))
 
3759
        expected_error = errors.LockContention('(remote lock)')
 
3760
        self.assertEqual(expected_error, translated_error)
 
3761
 
 
3762
    def test_UnlockableTransport(self):
 
3763
        bzrdir = self.make_controldir('')
 
3764
        translated_error = self.translateTuple(
 
3765
            (b'UnlockableTransport',), bzrdir=bzrdir)
 
3766
        expected_error = errors.UnlockableTransport(bzrdir.root_transport)
 
3767
        self.assertEqual(expected_error, translated_error)
 
3768
 
 
3769
    def test_LockFailed(self):
 
3770
        lock = 'str() of a server lock'
 
3771
        why = 'str() of why'
 
3772
        translated_error = self.translateTuple(
 
3773
            (b'LockFailed', lock.encode('ascii'), why.encode('ascii')))
 
3774
        expected_error = errors.LockFailed(lock, why)
 
3775
        self.assertEqual(expected_error, translated_error)
 
3776
 
 
3777
    def test_TokenMismatch(self):
 
3778
        token = 'a lock token'
 
3779
        translated_error = self.translateTuple(
 
3780
            (b'TokenMismatch',), token=token)
 
3781
        expected_error = errors.TokenMismatch(token, '(remote token)')
 
3782
        self.assertEqual(expected_error, translated_error)
 
3783
 
 
3784
    def test_Diverged(self):
 
3785
        branch = self.make_branch('a')
 
3786
        other_branch = self.make_branch('b')
 
3787
        translated_error = self.translateTuple(
 
3788
            (b'Diverged',), branch=branch, other_branch=other_branch)
 
3789
        expected_error = errors.DivergedBranches(branch, other_branch)
 
3790
        self.assertEqual(expected_error, translated_error)
 
3791
 
 
3792
    def test_NotStacked(self):
 
3793
        branch = self.make_branch('')
 
3794
        translated_error = self.translateTuple((b'NotStacked',), branch=branch)
 
3795
        expected_error = errors.NotStacked(branch)
 
3796
        self.assertEqual(expected_error, translated_error)
 
3797
 
 
3798
    def test_ReadError_no_args(self):
 
3799
        path = 'a path'
 
3800
        translated_error = self.translateTuple((b'ReadError',), path=path)
 
3801
        expected_error = errors.ReadError(path)
 
3802
        self.assertEqual(expected_error, translated_error)
 
3803
 
 
3804
    def test_ReadError(self):
 
3805
        path = 'a path'
 
3806
        translated_error = self.translateTuple(
 
3807
            (b'ReadError', path.encode('utf-8')))
 
3808
        expected_error = errors.ReadError(path)
 
3809
        self.assertEqual(expected_error, translated_error)
 
3810
 
 
3811
    def test_IncompatibleRepositories(self):
 
3812
        translated_error = self.translateTuple((b'IncompatibleRepositories',
 
3813
                                                b"repo1", b"repo2", b"details here"))
 
3814
        expected_error = errors.IncompatibleRepositories("repo1", "repo2",
 
3815
                                                         "details here")
 
3816
        self.assertEqual(expected_error, translated_error)
 
3817
 
 
3818
    def test_GhostRevisionsHaveNoRevno(self):
 
3819
        translated_error = self.translateTuple((b'GhostRevisionsHaveNoRevno',
 
3820
                                                b"revid1", b"revid2"))
 
3821
        expected_error = errors.GhostRevisionsHaveNoRevno(b"revid1", b"revid2")
 
3822
        self.assertEqual(expected_error, translated_error)
 
3823
 
 
3824
    def test_PermissionDenied_no_args(self):
 
3825
        path = 'a path'
 
3826
        translated_error = self.translateTuple((b'PermissionDenied',),
 
3827
                                               path=path)
 
3828
        expected_error = errors.PermissionDenied(path)
 
3829
        self.assertEqual(expected_error, translated_error)
 
3830
 
 
3831
    def test_PermissionDenied_one_arg(self):
 
3832
        path = 'a path'
 
3833
        translated_error = self.translateTuple(
 
3834
            (b'PermissionDenied', path.encode('utf-8')))
 
3835
        expected_error = errors.PermissionDenied(path)
 
3836
        self.assertEqual(expected_error, translated_error)
 
3837
 
 
3838
    def test_PermissionDenied_one_arg_and_context(self):
 
3839
        """Given a choice between a path from the local context and a path on
 
3840
        the wire, _translate_error prefers the path from the local context.
 
3841
        """
 
3842
        local_path = 'local path'
 
3843
        remote_path = 'remote path'
 
3844
        translated_error = self.translateTuple(
 
3845
            (b'PermissionDenied', remote_path.encode('utf-8')), path=local_path)
 
3846
        expected_error = errors.PermissionDenied(local_path)
 
3847
        self.assertEqual(expected_error, translated_error)
 
3848
 
 
3849
    def test_PermissionDenied_two_args(self):
 
3850
        path = 'a path'
 
3851
        extra = 'a string with extra info'
 
3852
        translated_error = self.translateTuple(
 
3853
            (b'PermissionDenied', path.encode('utf-8'), extra.encode('utf-8')))
 
3854
        expected_error = errors.PermissionDenied(path, extra)
 
3855
        self.assertEqual(expected_error, translated_error)
 
3856
 
 
3857
    # GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
 
3858
 
 
3859
    def test_NoSuchFile_context_path(self):
 
3860
        local_path = "local path"
 
3861
        translated_error = self.translateTuple((b'ReadError', b"remote path"),
 
3862
                                               path=local_path)
 
3863
        expected_error = errors.ReadError(local_path)
 
3864
        self.assertEqual(expected_error, translated_error)
 
3865
 
 
3866
    def test_NoSuchFile_without_context(self):
 
3867
        remote_path = "remote path"
 
3868
        translated_error = self.translateTuple(
 
3869
            (b'ReadError', remote_path.encode('utf-8')))
 
3870
        expected_error = errors.ReadError(remote_path)
 
3871
        self.assertEqual(expected_error, translated_error)
 
3872
 
 
3873
    def test_ReadOnlyError(self):
 
3874
        translated_error = self.translateTuple((b'ReadOnlyError',))
 
3875
        expected_error = errors.TransportNotPossible("readonly transport")
 
3876
        self.assertEqual(expected_error, translated_error)
 
3877
 
 
3878
    def test_MemoryError(self):
 
3879
        translated_error = self.translateTuple((b'MemoryError',))
 
3880
        self.assertStartsWith(str(translated_error),
 
3881
                              "remote server out of memory")
 
3882
 
 
3883
    def test_generic_IndexError_no_classname(self):
 
3884
        err = errors.ErrorFromSmartServer(
 
3885
            (b'error', b"list index out of range"))
 
3886
        translated_error = self.translateErrorFromSmartServer(err)
 
3887
        expected_error = errors.UnknownErrorFromSmartServer(err)
 
3888
        self.assertEqual(expected_error, translated_error)
 
3889
 
 
3890
    # GZ 2011-03-02: TODO test generic non-ascii error string
 
3891
 
 
3892
    def test_generic_KeyError(self):
 
3893
        err = errors.ErrorFromSmartServer((b'error', b'KeyError', b"1"))
 
3894
        translated_error = self.translateErrorFromSmartServer(err)
 
3895
        expected_error = errors.UnknownErrorFromSmartServer(err)
 
3896
        self.assertEqual(expected_error, translated_error)
 
3897
 
 
3898
 
 
3899
class TestErrorTranslationRobustness(TestErrorTranslationBase):
 
3900
    """Unit tests for breezy.bzr.remote._translate_error's robustness.
 
3901
 
 
3902
    TestErrorTranslationSuccess is for cases where _translate_error can
 
3903
    translate successfully.  This class about how _translate_err behaves when
 
3904
    it fails to translate: it re-raises the original error.
 
3905
    """
 
3906
 
 
3907
    def test_unrecognised_server_error(self):
 
3908
        """If the error code from the server is not recognised, the original
 
3909
        ErrorFromSmartServer is propagated unmodified.
 
3910
        """
 
3911
        error_tuple = (b'An unknown error tuple',)
 
3912
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
3913
        translated_error = self.translateErrorFromSmartServer(server_error)
 
3914
        expected_error = errors.UnknownErrorFromSmartServer(server_error)
 
3915
        self.assertEqual(expected_error, translated_error)
 
3916
 
 
3917
    def test_context_missing_a_key(self):
 
3918
        """In case of a bug in the client, or perhaps an unexpected response
 
3919
        from a server, _translate_error returns the original error tuple from
 
3920
        the server and mutters a warning.
 
3921
        """
 
3922
        # To translate a NoSuchRevision error _translate_error needs a 'branch'
 
3923
        # in the context dict.  So let's give it an empty context dict instead
 
3924
        # to exercise its error recovery.
 
3925
        error_tuple = (b'NoSuchRevision', b'revid')
 
3926
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
3927
        translated_error = self.translateErrorFromSmartServer(server_error)
 
3928
        self.assertEqual(server_error, translated_error)
 
3929
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
3930
        # been muttered to the log file for developer to look at.
 
3931
        self.assertContainsRe(
 
3932
            self.get_log(),
 
3933
            "Missing key 'branch' in context")
 
3934
 
 
3935
    def test_path_missing(self):
 
3936
        """Some translations (PermissionDenied, ReadError) can determine the
 
3937
        'path' variable from either the wire or the local context.  If neither
 
3938
        has it, then an error is raised.
 
3939
        """
 
3940
        error_tuple = (b'ReadError',)
 
3941
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
3942
        translated_error = self.translateErrorFromSmartServer(server_error)
 
3943
        self.assertEqual(server_error, translated_error)
 
3944
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
3945
        # been muttered to the log file for developer to look at.
 
3946
        self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
 
3947
 
 
3948
 
 
3949
class TestStacking(tests.TestCaseWithTransport):
 
3950
    """Tests for operations on stacked remote repositories.
 
3951
 
 
3952
    The underlying format type must support stacking.
 
3953
    """
 
3954
 
 
3955
    def test_access_stacked_remote(self):
 
3956
        # based on <http://launchpad.net/bugs/261315>
 
3957
        # make a branch stacked on another repository containing an empty
 
3958
        # revision, then open it over hpss - we should be able to see that
 
3959
        # revision.
 
3960
        base_builder = self.make_branch_builder('base', format='1.9')
 
3961
        base_builder.start_series()
 
3962
        base_revid = base_builder.build_snapshot(None,
 
3963
                                                 [('add', ('', None, 'directory', None))],
 
3964
                                                 'message', revision_id=b'rev-id')
 
3965
        base_builder.finish_series()
 
3966
        stacked_branch = self.make_branch('stacked', format='1.9')
 
3967
        stacked_branch.set_stacked_on_url('../base')
 
3968
        # start a server looking at this
 
3969
        smart_server = test_server.SmartTCPServer_for_testing()
 
3970
        self.start_server(smart_server)
 
3971
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
 
3972
        # can get its branch and repository
 
3973
        remote_branch = remote_bzrdir.open_branch()
 
3974
        remote_repo = remote_branch.repository
 
3975
        remote_repo.lock_read()
 
3976
        try:
 
3977
            # it should have an appropriate fallback repository, which should also
 
3978
            # be a RemoteRepository
 
3979
            self.assertLength(1, remote_repo._fallback_repositories)
 
3980
            self.assertIsInstance(remote_repo._fallback_repositories[0],
 
3981
                                  RemoteRepository)
 
3982
            # and it has the revision committed to the underlying repository;
 
3983
            # these have varying implementations so we try several of them
 
3984
            self.assertTrue(remote_repo.has_revisions([base_revid]))
 
3985
            self.assertTrue(remote_repo.has_revision(base_revid))
 
3986
            self.assertEqual(remote_repo.get_revision(base_revid).message,
 
3987
                             'message')
 
3988
        finally:
 
3989
            remote_repo.unlock()
 
3990
 
 
3991
    def prepare_stacked_remote_branch(self):
 
3992
        """Get stacked_upon and stacked branches with content in each."""
 
3993
        self.setup_smart_server_with_call_log()
 
3994
        tree1 = self.make_branch_and_tree('tree1', format='1.9')
 
3995
        tree1.commit('rev1', rev_id=b'rev1')
 
3996
        tree2 = tree1.branch.controldir.sprout('tree2', stacked=True
 
3997
                                               ).open_workingtree()
 
3998
        local_tree = tree2.branch.create_checkout('local')
 
3999
        local_tree.commit('local changes make me feel good.')
 
4000
        branch2 = Branch.open(self.get_url('tree2'))
 
4001
        branch2.lock_read()
 
4002
        self.addCleanup(branch2.unlock)
 
4003
        return tree1.branch, branch2
 
4004
 
 
4005
    def test_stacked_get_parent_map(self):
 
4006
        # the public implementation of get_parent_map obeys stacking
 
4007
        _, branch = self.prepare_stacked_remote_branch()
 
4008
        repo = branch.repository
 
4009
        self.assertEqual({b'rev1'}, set(repo.get_parent_map([b'rev1'])))
 
4010
 
 
4011
    def test_unstacked_get_parent_map(self):
 
4012
        # _unstacked_provider.get_parent_map ignores stacking
 
4013
        _, branch = self.prepare_stacked_remote_branch()
 
4014
        provider = branch.repository._unstacked_provider
 
4015
        self.assertEqual(set(), set(provider.get_parent_map([b'rev1'])))
 
4016
 
 
4017
    def fetch_stream_to_rev_order(self, stream):
 
4018
        result = []
 
4019
        for kind, substream in stream:
 
4020
            if not kind == 'revisions':
 
4021
                list(substream)
 
4022
            else:
 
4023
                for content in substream:
 
4024
                    result.append(content.key[-1])
 
4025
        return result
 
4026
 
 
4027
    def get_ordered_revs(self, format, order, branch_factory=None):
 
4028
        """Get a list of the revisions in a stream to format format.
 
4029
 
 
4030
        :param format: The format of the target.
 
4031
        :param order: the order that target should have requested.
 
4032
        :param branch_factory: A callable to create a trunk and stacked branch
 
4033
            to fetch from. If none, self.prepare_stacked_remote_branch is used.
 
4034
        :result: The revision ids in the stream, in the order seen,
 
4035
            the topological order of revisions in the source.
 
4036
        """
 
4037
        unordered_format = controldir.format_registry.get(format)()
 
4038
        target_repository_format = unordered_format.repository_format
 
4039
        # Cross check
 
4040
        self.assertEqual(order, target_repository_format._fetch_order)
 
4041
        if branch_factory is None:
 
4042
            branch_factory = self.prepare_stacked_remote_branch
 
4043
        _, stacked = branch_factory()
 
4044
        source = stacked.repository._get_source(target_repository_format)
 
4045
        tip = stacked.last_revision()
 
4046
        stacked.repository._ensure_real()
 
4047
        graph = stacked.repository.get_graph()
 
4048
        revs = [r for (r, ps) in graph.iter_ancestry([tip])
 
4049
                if r != NULL_REVISION]
 
4050
        revs.reverse()
 
4051
        search = vf_search.PendingAncestryResult([tip], stacked.repository)
 
4052
        self.reset_smart_call_log()
 
4053
        stream = source.get_stream(search)
 
4054
        # We trust that if a revision is in the stream the rest of the new
 
4055
        # content for it is too, as per our main fetch tests; here we are
 
4056
        # checking that the revisions are actually included at all, and their
 
4057
        # order.
 
4058
        return self.fetch_stream_to_rev_order(stream), revs
 
4059
 
 
4060
    def test_stacked_get_stream_unordered(self):
 
4061
        # Repository._get_source.get_stream() from a stacked repository with
 
4062
        # unordered yields the full data from both stacked and stacked upon
 
4063
        # sources.
 
4064
        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
 
4065
        self.assertEqual(set(expected_revs), set(rev_ord))
 
4066
        # Getting unordered results should have made a streaming data request
 
4067
        # from the server, then one from the backing branch.
 
4068
        self.assertLength(2, self.hpss_calls)
 
4069
 
 
4070
    def test_stacked_on_stacked_get_stream_unordered(self):
 
4071
        # Repository._get_source.get_stream() from a stacked repository which
 
4072
        # is itself stacked yields the full data from all three sources.
 
4073
        def make_stacked_stacked():
 
4074
            _, stacked = self.prepare_stacked_remote_branch()
 
4075
            tree = stacked.controldir.sprout('tree3', stacked=True
 
4076
                                             ).open_workingtree()
 
4077
            local_tree = tree.branch.create_checkout('local-tree3')
 
4078
            local_tree.commit('more local changes are better')
 
4079
            branch = Branch.open(self.get_url('tree3'))
 
4080
            branch.lock_read()
 
4081
            self.addCleanup(branch.unlock)
 
4082
            return None, branch
 
4083
        rev_ord, expected_revs = self.get_ordered_revs(
 
4084
            '1.9', 'unordered', branch_factory=make_stacked_stacked)
 
4085
        self.assertEqual(set(expected_revs), set(rev_ord))
 
4086
        # Getting unordered results should have made a streaming data request
 
4087
        # from the server, and one from each backing repo
 
4088
        self.assertLength(3, self.hpss_calls)
 
4089
 
 
4090
    def test_stacked_get_stream_topological(self):
 
4091
        # Repository._get_source.get_stream() from a stacked repository with
 
4092
        # topological sorting yields the full data from both stacked and
 
4093
        # stacked upon sources in topological order.
 
4094
        rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
 
4095
        self.assertEqual(expected_revs, rev_ord)
 
4096
        # Getting topological sort requires VFS calls still - one of which is
 
4097
        # pushing up from the bound branch.
 
4098
        self.assertLength(14, self.hpss_calls)
 
4099
 
 
4100
    def test_stacked_get_stream_groupcompress(self):
 
4101
        # Repository._get_source.get_stream() from a stacked repository with
 
4102
        # groupcompress sorting yields the full data from both stacked and
 
4103
        # stacked upon sources in groupcompress order.
 
4104
        raise tests.TestSkipped('No groupcompress ordered format available')
 
4105
        rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
 
4106
        self.assertEqual(expected_revs, reversed(rev_ord))
 
4107
        # Getting unordered results should have made a streaming data request
 
4108
        # from the backing branch, and one from the stacked on branch.
 
4109
        self.assertLength(2, self.hpss_calls)
 
4110
 
 
4111
    def test_stacked_pull_more_than_stacking_has_bug_360791(self):
 
4112
        # When pulling some fixed amount of content that is more than the
 
4113
        # source has (because some is coming from a fallback branch, no error
 
4114
        # should be received. This was reported as bug 360791.
 
4115
        # Need three branches: a trunk, a stacked branch, and a preexisting
 
4116
        # branch pulling content from stacked and trunk.
 
4117
        self.setup_smart_server_with_call_log()
 
4118
        trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
 
4119
        trunk.commit('start')
 
4120
        stacked_branch = trunk.branch.create_clone_on_transport(
 
4121
            self.get_transport('stacked'), stacked_on=trunk.branch.base)
 
4122
        local = self.make_branch('local', format='1.9-rich-root')
 
4123
        local.repository.fetch(stacked_branch.repository,
 
4124
                               stacked_branch.last_revision())
 
4125
 
 
4126
 
 
4127
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
 
4128
 
 
4129
    def setUp(self):
 
4130
        super(TestRemoteBranchEffort, self).setUp()
 
4131
        # Create a smart server that publishes whatever the backing VFS server
 
4132
        # does.
 
4133
        self.smart_server = test_server.SmartTCPServer_for_testing()
 
4134
        self.start_server(self.smart_server, self.get_server())
 
4135
        # Log all HPSS calls into self.hpss_calls.
 
4136
        _SmartClient.hooks.install_named_hook(
 
4137
            'call', self.capture_hpss_call, None)
 
4138
        self.hpss_calls = []
 
4139
 
 
4140
    def capture_hpss_call(self, params):
 
4141
        self.hpss_calls.append(params.method)
 
4142
 
 
4143
    def test_copy_content_into_avoids_revision_history(self):
 
4144
        local = self.make_branch('local')
 
4145
        builder = self.make_branch_builder('remote')
 
4146
        builder.build_commit(message="Commit.")
 
4147
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
4148
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
4149
        local.repository.fetch(remote_branch.repository)
 
4150
        self.hpss_calls = []
 
4151
        remote_branch.copy_content_into(local)
 
4152
        self.assertFalse(b'Branch.revision_history' in self.hpss_calls)
 
4153
 
 
4154
    def test_fetch_everything_needs_just_one_call(self):
 
4155
        local = self.make_branch('local')
 
4156
        builder = self.make_branch_builder('remote')
 
4157
        builder.build_commit(message="Commit.")
 
4158
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
4159
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
4160
        self.hpss_calls = []
 
4161
        local.repository.fetch(
 
4162
            remote_branch.repository,
 
4163
            fetch_spec=vf_search.EverythingResult(remote_branch.repository))
 
4164
        self.assertEqual([b'Repository.get_stream_1.19'], self.hpss_calls)
 
4165
 
 
4166
    def override_verb(self, verb_name, verb):
 
4167
        request_handlers = request.request_handlers
 
4168
        orig_verb = request_handlers.get(verb_name)
 
4169
        orig_info = request_handlers.get_info(verb_name)
 
4170
        request_handlers.register(verb_name, verb, override_existing=True)
 
4171
        self.addCleanup(request_handlers.register, verb_name, orig_verb,
 
4172
                        override_existing=True, info=orig_info)
 
4173
 
 
4174
    def test_fetch_everything_backwards_compat(self):
 
4175
        """Can fetch with EverythingResult even with pre 2.4 servers.
 
4176
 
 
4177
        Pre-2.4 do not support 'everything' searches with the
 
4178
        Repository.get_stream_1.19 verb.
 
4179
        """
 
4180
        verb_log = []
 
4181
 
 
4182
        class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
 
4183
            """A version of the Repository.get_stream_1.19 verb patched to
 
4184
            reject 'everything' searches the way 2.3 and earlier do.
 
4185
            """
 
4186
 
 
4187
            def recreate_search(self, repository, search_bytes,
 
4188
                                discard_excess=False):
 
4189
                verb_log.append(search_bytes.split(b'\n', 1)[0])
 
4190
                if search_bytes == b'everything':
 
4191
                    return (None,
 
4192
                            request.FailedSmartServerResponse((b'BadSearch',)))
 
4193
                return super(OldGetStreamVerb,
 
4194
                             self).recreate_search(repository, search_bytes,
 
4195
                                                   discard_excess=discard_excess)
 
4196
        self.override_verb(b'Repository.get_stream_1.19', OldGetStreamVerb)
 
4197
        local = self.make_branch('local')
 
4198
        builder = self.make_branch_builder('remote')
 
4199
        builder.build_commit(message="Commit.")
 
4200
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
4201
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
4202
        self.hpss_calls = []
 
4203
        local.repository.fetch(
 
4204
            remote_branch.repository,
 
4205
            fetch_spec=vf_search.EverythingResult(remote_branch.repository))
 
4206
        # make sure the overridden verb was used
 
4207
        self.assertLength(1, verb_log)
 
4208
        # more than one HPSS call is needed, but because it's a VFS callback
 
4209
        # its hard to predict exactly how many.
 
4210
        self.assertTrue(len(self.hpss_calls) > 1)
 
4211
 
 
4212
 
 
4213
class TestUpdateBoundBranchWithModifiedBoundLocation(
 
4214
        tests.TestCaseWithTransport):
 
4215
    """Ensure correct handling of bound_location modifications.
 
4216
 
 
4217
    This is tested against a smart server as http://pad.lv/786980 was about a
 
4218
    ReadOnlyError (write attempt during a read-only transaction) which can only
 
4219
    happen in this context.
 
4220
    """
 
4221
 
 
4222
    def setUp(self):
 
4223
        super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
 
4224
        self.transport_server = test_server.SmartTCPServer_for_testing
 
4225
 
 
4226
    def make_master_and_checkout(self, master_name, checkout_name):
 
4227
        # Create the master branch and its associated checkout
 
4228
        self.master = self.make_branch_and_tree(master_name)
 
4229
        self.checkout = self.master.branch.create_checkout(checkout_name)
 
4230
        # Modify the master branch so there is something to update
 
4231
        self.master.commit('add stuff')
 
4232
        self.last_revid = self.master.commit('even more stuff')
 
4233
        self.bound_location = self.checkout.branch.get_bound_location()
 
4234
 
 
4235
    def assertUpdateSucceeds(self, new_location):
 
4236
        self.checkout.branch.set_bound_location(new_location)
 
4237
        self.checkout.update()
 
4238
        self.assertEqual(self.last_revid, self.checkout.last_revision())
 
4239
 
 
4240
    def test_without_final_slash(self):
 
4241
        self.make_master_and_checkout('master', 'checkout')
 
4242
        # For unclear reasons some users have a bound_location without a final
 
4243
        # '/', simulate that by forcing such a value
 
4244
        self.assertEndsWith(self.bound_location, '/')
 
4245
        self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
 
4246
 
 
4247
    def test_plus_sign(self):
 
4248
        self.make_master_and_checkout('+master', 'checkout')
 
4249
        self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
 
4250
 
 
4251
    def test_tilda(self):
 
4252
        # Embed ~ in the middle of the path just to avoid any $HOME
 
4253
        # interpretation
 
4254
        self.make_master_and_checkout('mas~ter', 'checkout')
 
4255
        self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))
 
4256
 
 
4257
 
 
4258
class TestWithCustomErrorHandler(RemoteBranchTestCase):
 
4259
 
 
4260
    def test_no_context(self):
 
4261
        class OutOfCoffee(errors.BzrError):
 
4262
            """A dummy exception for testing."""
 
4263
 
 
4264
            def __init__(self, urgency):
 
4265
                self.urgency = urgency
 
4266
        remote.no_context_error_translators.register(b"OutOfCoffee",
 
4267
                                                     lambda err: OutOfCoffee(err.error_args[0]))
 
4268
        transport = MemoryTransport()
 
4269
        client = FakeClient(transport.base)
 
4270
        client.add_expected_call(
 
4271
            b'Branch.get_stacked_on_url', (b'quack/',),
 
4272
            b'error', (b'NotStacked',))
 
4273
        client.add_expected_call(
 
4274
            b'Branch.last_revision_info',
 
4275
            (b'quack/',),
 
4276
            b'error', (b'OutOfCoffee', b'low'))
 
4277
        transport.mkdir('quack')
 
4278
        transport = transport.clone('quack')
 
4279
        branch = self.make_remote_branch(transport, client)
 
4280
        self.assertRaises(OutOfCoffee, branch.last_revision_info)
 
4281
        self.assertFinished(client)
 
4282
 
 
4283
    def test_with_context(self):
 
4284
        class OutOfTea(errors.BzrError):
 
4285
            def __init__(self, branch, urgency):
 
4286
                self.branch = branch
 
4287
                self.urgency = urgency
 
4288
        remote.error_translators.register(b"OutOfTea",
 
4289
                                          lambda err, find, path: OutOfTea(
 
4290
                                              err.error_args[0].decode(
 
4291
                                                  'utf-8'),
 
4292
                                              find("branch")))
 
4293
        transport = MemoryTransport()
 
4294
        client = FakeClient(transport.base)
 
4295
        client.add_expected_call(
 
4296
            b'Branch.get_stacked_on_url', (b'quack/',),
 
4297
            b'error', (b'NotStacked',))
 
4298
        client.add_expected_call(
 
4299
            b'Branch.last_revision_info',
 
4300
            (b'quack/',),
 
4301
            b'error', (b'OutOfTea', b'low'))
 
4302
        transport.mkdir('quack')
 
4303
        transport = transport.clone('quack')
 
4304
        branch = self.make_remote_branch(transport, client)
 
4305
        self.assertRaises(OutOfTea, branch.last_revision_info)
 
4306
        self.assertFinished(client)
 
4307
 
 
4308
 
 
4309
class TestRepositoryPack(TestRemoteRepository):
 
4310
 
 
4311
    def test_pack(self):
 
4312
        transport_path = 'quack'
 
4313
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4314
        client.add_expected_call(
 
4315
            b'Repository.lock_write', (b'quack/', b''),
 
4316
            b'success', (b'ok', b'token'))
 
4317
        client.add_expected_call(
 
4318
            b'Repository.pack', (b'quack/', b'token', b'False'),
 
4319
            b'success', (b'ok',), )
 
4320
        client.add_expected_call(
 
4321
            b'Repository.unlock', (b'quack/', b'token'),
 
4322
            b'success', (b'ok', ))
 
4323
        repo.pack()
 
4324
 
 
4325
    def test_pack_with_hint(self):
 
4326
        transport_path = 'quack'
 
4327
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4328
        client.add_expected_call(
 
4329
            b'Repository.lock_write', (b'quack/', b''),
 
4330
            b'success', (b'ok', b'token'))
 
4331
        client.add_expected_call(
 
4332
            b'Repository.pack', (b'quack/', b'token', b'False'),
 
4333
            b'success', (b'ok',), )
 
4334
        client.add_expected_call(
 
4335
            b'Repository.unlock', (b'quack/', b'token', b'False'),
 
4336
            b'success', (b'ok', ))
 
4337
        repo.pack(['hinta', 'hintb'])
 
4338
 
 
4339
 
 
4340
class TestRepositoryIterInventories(TestRemoteRepository):
 
4341
    """Test Repository.iter_inventories."""
 
4342
 
 
4343
    def _serialize_inv_delta(self, old_name, new_name, delta):
 
4344
        serializer = inventory_delta.InventoryDeltaSerializer(True, False)
 
4345
        return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
 
4346
 
 
4347
    def test_single_empty(self):
 
4348
        transport_path = 'quack'
 
4349
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4350
        fmt = controldir.format_registry.get('2a')().repository_format
 
4351
        repo._format = fmt
 
4352
        stream = [('inventory-deltas', [
 
4353
            versionedfile.FulltextContentFactory(b'somerevid', None, None,
 
4354
                                                 self._serialize_inv_delta(b'null:', b'somerevid', []))])]
 
4355
        client.add_expected_call(
 
4356
            b'VersionedFileRepository.get_inventories', (
 
4357
                b'quack/', b'unordered'),
 
4358
            b'success', (b'ok', ),
 
4359
            _stream_to_byte_stream(stream, fmt))
 
4360
        ret = list(repo.iter_inventories([b"somerevid"]))
 
4361
        self.assertLength(1, ret)
 
4362
        inv = ret[0]
 
4363
        self.assertEqual(b"somerevid", inv.revision_id)
 
4364
 
 
4365
    def test_empty(self):
 
4366
        transport_path = 'quack'
 
4367
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4368
        ret = list(repo.iter_inventories([]))
 
4369
        self.assertEqual(ret, [])
 
4370
 
 
4371
    def test_missing(self):
 
4372
        transport_path = 'quack'
 
4373
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4374
        client.add_expected_call(
 
4375
            b'VersionedFileRepository.get_inventories', (
 
4376
                b'quack/', b'unordered'),
 
4377
            b'success', (b'ok', ), iter([]))
 
4378
        self.assertRaises(errors.NoSuchRevision, list, repo.iter_inventories(
 
4379
            [b"somerevid"]))
 
4380
 
 
4381
 
 
4382
class TestRepositoryRevisionTreeArchive(TestRemoteRepository):
 
4383
    """Test Repository.iter_inventories."""
 
4384
 
 
4385
    def _serialize_inv_delta(self, old_name, new_name, delta):
 
4386
        serializer = inventory_delta.InventoryDeltaSerializer(True, False)
 
4387
        return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
 
4388
 
 
4389
    def test_simple(self):
 
4390
        transport_path = 'quack'
 
4391
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4392
        fmt = controldir.format_registry.get('2a')().repository_format
 
4393
        repo._format = fmt
 
4394
        stream = [('inventory-deltas', [
 
4395
            versionedfile.FulltextContentFactory(b'somerevid', None, None,
 
4396
                                                 self._serialize_inv_delta(b'null:', b'somerevid', []))])]
 
4397
        client.add_expected_call(
 
4398
            b'VersionedFileRepository.get_inventories', (
 
4399
                b'quack/', b'unordered'),
 
4400
            b'success', (b'ok', ),
 
4401
            _stream_to_byte_stream(stream, fmt))
 
4402
        f = BytesIO()
 
4403
        with tarfile.open(mode='w', fileobj=f) as tf:
 
4404
            info = tarfile.TarInfo('somefile')
 
4405
            info.mtime = 432432
 
4406
            contents = b'some data'
 
4407
            info.type = tarfile.REGTYPE
 
4408
            info.mode = 0o644
 
4409
            info.size = len(contents)
 
4410
            tf.addfile(info, BytesIO(contents))
 
4411
        client.add_expected_call(
 
4412
            b'Repository.revision_archive', (b'quack/',
 
4413
                                             b'somerevid', b'tar', b'foo.tar', b'', b'', None),
 
4414
            b'success', (b'ok', ),
 
4415
            f.getvalue())
 
4416
        tree = repo.revision_tree(b'somerevid')
 
4417
        self.assertEqual(f.getvalue(), b''.join(
 
4418
            tree.archive('tar', 'foo.tar')))
 
4419
 
 
4420
 
 
4421
class TestRepositoryAnnotate(TestRemoteRepository):
 
4422
    """Test RemoteRevisionTree.annotate.."""
 
4423
 
 
4424
    def _serialize_inv_delta(self, old_name, new_name, delta):
 
4425
        serializer = inventory_delta.InventoryDeltaSerializer(True, False)
 
4426
        return b"".join(serializer.delta_to_lines(old_name, new_name, delta))
 
4427
 
 
4428
    def test_simple(self):
 
4429
        transport_path = 'quack'
 
4430
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
4431
        fmt = controldir.format_registry.get('2a')().repository_format
 
4432
        repo._format = fmt
 
4433
        stream = [
 
4434
            ('inventory-deltas', [
 
4435
                versionedfile.FulltextContentFactory(
 
4436
                    b'somerevid', None, None,
 
4437
                    self._serialize_inv_delta(b'null:', b'somerevid', []))])]
 
4438
        client.add_expected_call(
 
4439
            b'VersionedFileRepository.get_inventories', (
 
4440
                b'quack/', b'unordered'),
 
4441
            b'success', (b'ok', ),
 
4442
            _stream_to_byte_stream(stream, fmt))
 
4443
        client.add_expected_call(
 
4444
            b'Repository.annotate_file_revision',
 
4445
            (b'quack/', b'somerevid', b'filename', b'', b'current:'),
 
4446
            b'success', (b'ok', ),
 
4447
            bencode.bencode([[b'baserevid', b'line 1\n'],
 
4448
                             [b'somerevid', b'line2\n']]))
 
4449
        tree = repo.revision_tree(b'somerevid')
 
4450
        self.assertEqual([
 
4451
            (b'baserevid', b'line 1\n'),
 
4452
            (b'somerevid', b'line2\n')],
 
4453
            list(tree.annotate_iter('filename')))