/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 bzrlib/tests/test_remote.py

  • Committer: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

Show diffs side-by-side

added added

removed removed

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