~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Jelmer Vernooij
  • Date: 2011-11-30 20:02:16 UTC
  • mto: This revision was merged to the branch mainline in revision 6333.
  • Revision ID: jelmer@samba.org-20111130200216-aoju21pdl20d1gkd
Consistently pass tree path when exporting.

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