/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-06-10 16:40:42 UTC
  • mfrom: (6653.6.7 rename-controldir)
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170610164042-zrxqgy2htyduvke2
MergeĀ rename-controldirĀ branch.

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