/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/tests/test_remote.py

  • Committer: Jelmer Vernooij
  • Date: 2017-11-11 13:10:32 UTC
  • mto: This revision was merged to the branch mainline in revision 6804.
  • Revision ID: jelmer@jelmer.uk-20171111131032-31lgi8qmvlz8363d
Fix typos.

Show diffs side-by-side

added added

removed removed

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