~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Jelmer Vernooij
  • Date: 2012-02-07 00:49:58 UTC
  • mto: This revision was merged to the branch mainline in revision 6465.
  • Revision ID: jelmer@samba.org-20120207004958-rdtzmipi10p1oq97
Migrate 'bugtracker' setting to config stacks.

Show diffs side-by-side

added added

removed removed

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