~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Martin Packman
  • Date: 2011-12-13 17:10:47 UTC
  • mto: This revision was merged to the branch mainline in revision 6367.
  • Revision ID: martin.packman@canonical.com-20111213171047-esvi1kyfbuehmhrm
Minor tweaks including normalising _fs_enc value

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