/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-07-23 15:59:57 UTC
  • mto: This revision was merged to the branch mainline in revision 6740.
  • Revision ID: jelmer@jelmer.uk-20170723155957-rw4kqurf44fqx4x0
Move AlreadyBuilding/NotBuilding errors.

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