~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Martin Pool
  • Date: 2007-04-04 06:17:31 UTC
  • mto: This revision was merged to the branch mainline in revision 2397.
  • Revision ID: mbp@sourcefrog.net-20070404061731-tt2xrzllqhbodn83
Contents of TODO file moved into bug tracker

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008, 2009 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
 
 
29
 
from bzrlib import (
30
 
    bzrdir,
31
 
    config,
32
 
    errors,
33
 
    graph,
34
 
    pack,
35
 
    remote,
36
 
    repository,
37
 
    smart,
38
 
    tests,
39
 
    treebuilder,
40
 
    urlutils,
41
 
    )
42
 
from bzrlib.branch import Branch
43
 
from bzrlib.bzrdir import BzrDir, BzrDirFormat
44
 
from bzrlib.remote import (
45
 
    RemoteBranch,
46
 
    RemoteBranchFormat,
47
 
    RemoteBzrDir,
48
 
    RemoteBzrDirFormat,
49
 
    RemoteRepository,
50
 
    RemoteRepositoryFormat,
51
 
    )
52
 
from bzrlib.repofmt import groupcompress_repo, pack_repo
53
 
from bzrlib.revision import NULL_REVISION
54
 
from bzrlib.smart import server, medium
55
 
from bzrlib.smart.client import _SmartClient
56
 
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
57
 
from bzrlib.tests import (
58
 
    condition_isinstance,
59
 
    split_suite_by_condition,
60
 
    multiply_tests,
61
 
    )
62
 
from bzrlib.transport import get_transport, http
63
 
from bzrlib.transport.memory import MemoryTransport
64
 
from bzrlib.transport.remote import (
65
 
    RemoteTransport,
66
 
    RemoteSSHTransport,
67
 
    RemoteTCPTransport,
68
 
)
69
 
 
70
 
def load_tests(standard_tests, module, loader):
71
 
    to_adapt, result = split_suite_by_condition(
72
 
        standard_tests, condition_isinstance(BasicRemoteObjectTests))
73
 
    smart_server_version_scenarios = [
74
 
        ('HPSS-v2',
75
 
            {'transport_server': server.SmartTCPServer_for_testing_v2_only}),
76
 
        ('HPSS-v3',
77
 
            {'transport_server': server.SmartTCPServer_for_testing})]
78
 
    return multiply_tests(to_adapt, smart_server_version_scenarios, result)
79
 
 
80
 
 
81
 
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
82
 
 
83
 
    def setUp(self):
84
 
        super(BasicRemoteObjectTests, self).setUp()
85
 
        self.transport = self.get_transport()
86
 
        # make a branch that can be opened over the smart transport
87
 
        self.local_wt = BzrDir.create_standalone_workingtree('.')
88
 
 
89
 
    def tearDown(self):
90
 
        self.transport.disconnect()
91
 
        tests.TestCaseWithTransport.tearDown(self)
92
 
 
93
 
    def test_create_remote_bzrdir(self):
94
 
        b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
95
 
        self.assertIsInstance(b, BzrDir)
96
 
 
97
 
    def test_open_remote_branch(self):
98
 
        # open a standalone branch in the working directory
99
 
        b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
100
 
        branch = b.open_branch()
101
 
        self.assertIsInstance(branch, Branch)
102
 
 
103
 
    def test_remote_repository(self):
104
 
        b = BzrDir.open_from_transport(self.transport)
105
 
        repo = b.open_repository()
106
 
        revid = u'\xc823123123'.encode('utf8')
107
 
        self.assertFalse(repo.has_revision(revid))
108
 
        self.local_wt.commit(message='test commit', rev_id=revid)
109
 
        self.assertTrue(repo.has_revision(revid))
110
 
 
111
 
    def test_remote_branch_revision_history(self):
112
 
        b = BzrDir.open_from_transport(self.transport).open_branch()
113
 
        self.assertEqual([], b.revision_history())
114
 
        r1 = self.local_wt.commit('1st commit')
115
 
        r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
116
 
        self.assertEqual([r1, r2], b.revision_history())
117
 
 
118
 
    def test_find_correct_format(self):
119
 
        """Should open a RemoteBzrDir over a RemoteTransport"""
120
 
        fmt = BzrDirFormat.find_format(self.transport)
121
 
        self.assertTrue(RemoteBzrDirFormat
122
 
                        in BzrDirFormat._control_server_formats)
123
 
        self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
124
 
 
125
 
    def test_open_detected_smart_format(self):
126
 
        fmt = BzrDirFormat.find_format(self.transport)
127
 
        d = fmt.open(self.transport)
128
 
        self.assertIsInstance(d, BzrDir)
129
 
 
130
 
    def test_remote_branch_repr(self):
131
 
        b = BzrDir.open_from_transport(self.transport).open_branch()
132
 
        self.assertStartsWith(str(b), 'RemoteBranch(')
133
 
 
134
 
    def test_remote_branch_format_supports_stacking(self):
135
 
        t = self.transport
136
 
        self.make_branch('unstackable', format='pack-0.92')
137
 
        b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
138
 
        self.assertFalse(b._format.supports_stacking())
139
 
        self.make_branch('stackable', format='1.9')
140
 
        b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
141
 
        self.assertTrue(b._format.supports_stacking())
142
 
 
143
 
    def test_remote_repo_format_supports_external_references(self):
144
 
        t = self.transport
145
 
        bd = self.make_bzrdir('unstackable', format='pack-0.92')
146
 
        r = bd.create_repository()
147
 
        self.assertFalse(r._format.supports_external_lookups)
148
 
        r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
149
 
        self.assertFalse(r._format.supports_external_lookups)
150
 
        bd = self.make_bzrdir('stackable', format='1.9')
151
 
        r = bd.create_repository()
152
 
        self.assertTrue(r._format.supports_external_lookups)
153
 
        r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
154
 
        self.assertTrue(r._format.supports_external_lookups)
155
 
 
156
 
 
157
 
class FakeProtocol(object):
158
 
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
159
 
 
160
 
    def __init__(self, body, fake_client):
161
 
        self.body = body
162
 
        self._body_buffer = None
163
 
        self._fake_client = fake_client
164
 
 
165
 
    def read_body_bytes(self, count=-1):
166
 
        if self._body_buffer is None:
167
 
            self._body_buffer = StringIO(self.body)
168
 
        bytes = self._body_buffer.read(count)
169
 
        if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
170
 
            self._fake_client.expecting_body = False
171
 
        return bytes
172
 
 
173
 
    def cancel_read_body(self):
174
 
        self._fake_client.expecting_body = False
175
 
 
176
 
    def read_streamed_body(self):
177
 
        return self.body
178
 
 
179
 
 
180
 
class FakeClient(_SmartClient):
181
 
    """Lookalike for _SmartClient allowing testing."""
182
 
 
183
 
    def __init__(self, fake_medium_base='fake base'):
184
 
        """Create a FakeClient."""
185
 
        self.responses = []
186
 
        self._calls = []
187
 
        self.expecting_body = False
188
 
        # if non-None, this is the list of expected calls, with only the
189
 
        # method name and arguments included.  the body might be hard to
190
 
        # compute so is not included. If a call is None, that call can
191
 
        # be anything.
192
 
        self._expected_calls = None
193
 
        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
194
 
 
195
 
    def add_expected_call(self, call_name, call_args, response_type,
196
 
        response_args, response_body=None):
197
 
        if self._expected_calls is None:
198
 
            self._expected_calls = []
199
 
        self._expected_calls.append((call_name, call_args))
200
 
        self.responses.append((response_type, response_args, response_body))
201
 
 
202
 
    def add_success_response(self, *args):
203
 
        self.responses.append(('success', args, None))
204
 
 
205
 
    def add_success_response_with_body(self, body, *args):
206
 
        self.responses.append(('success', args, body))
207
 
        if self._expected_calls is not None:
208
 
            self._expected_calls.append(None)
209
 
 
210
 
    def add_error_response(self, *args):
211
 
        self.responses.append(('error', args))
212
 
 
213
 
    def add_unknown_method_response(self, verb):
214
 
        self.responses.append(('unknown', verb))
215
 
 
216
 
    def finished_test(self):
217
 
        if self._expected_calls:
218
 
            raise AssertionError("%r finished but was still expecting %r"
219
 
                % (self, self._expected_calls[0]))
220
 
 
221
 
    def _get_next_response(self):
222
 
        try:
223
 
            response_tuple = self.responses.pop(0)
224
 
        except IndexError, e:
225
 
            raise AssertionError("%r didn't expect any more calls"
226
 
                % (self,))
227
 
        if response_tuple[0] == 'unknown':
228
 
            raise errors.UnknownSmartMethod(response_tuple[1])
229
 
        elif response_tuple[0] == 'error':
230
 
            raise errors.ErrorFromSmartServer(response_tuple[1])
231
 
        return response_tuple
232
 
 
233
 
    def _check_call(self, method, args):
234
 
        if self._expected_calls is None:
235
 
            # the test should be updated to say what it expects
236
 
            return
237
 
        try:
238
 
            next_call = self._expected_calls.pop(0)
239
 
        except IndexError:
240
 
            raise AssertionError("%r didn't expect any more calls "
241
 
                "but got %r%r"
242
 
                % (self, method, args,))
243
 
        if next_call is None:
244
 
            return
245
 
        if method != next_call[0] or args != next_call[1]:
246
 
            raise AssertionError("%r expected %r%r "
247
 
                "but got %r%r"
248
 
                % (self, next_call[0], next_call[1], method, args,))
249
 
 
250
 
    def call(self, method, *args):
251
 
        self._check_call(method, args)
252
 
        self._calls.append(('call', method, args))
253
 
        return self._get_next_response()[1]
254
 
 
255
 
    def call_expecting_body(self, method, *args):
256
 
        self._check_call(method, args)
257
 
        self._calls.append(('call_expecting_body', method, args))
258
 
        result = self._get_next_response()
259
 
        self.expecting_body = True
260
 
        return result[1], FakeProtocol(result[2], self)
261
 
 
262
 
    def call_with_body_bytes_expecting_body(self, method, args, body):
263
 
        self._check_call(method, args)
264
 
        self._calls.append(('call_with_body_bytes_expecting_body', method,
265
 
            args, body))
266
 
        result = self._get_next_response()
267
 
        self.expecting_body = True
268
 
        return result[1], FakeProtocol(result[2], self)
269
 
 
270
 
    def call_with_body_stream(self, args, stream):
271
 
        # Explicitly consume the stream before checking for an error, because
272
 
        # that's what happens a real medium.
273
 
        stream = list(stream)
274
 
        self._check_call(args[0], args[1:])
275
 
        self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
276
 
        result = self._get_next_response()
277
 
        # The second value returned from call_with_body_stream is supposed to
278
 
        # be a response_handler object, but so far no tests depend on that.
279
 
        response_handler = None 
280
 
        return result[1], response_handler
281
 
 
282
 
 
283
 
class FakeMedium(medium.SmartClientMedium):
284
 
 
285
 
    def __init__(self, client_calls, base):
286
 
        medium.SmartClientMedium.__init__(self, base)
287
 
        self._client_calls = client_calls
288
 
 
289
 
    def disconnect(self):
290
 
        self._client_calls.append(('disconnect medium',))
291
 
 
292
 
 
293
 
class TestVfsHas(tests.TestCase):
294
 
 
295
 
    def test_unicode_path(self):
296
 
        client = FakeClient('/')
297
 
        client.add_success_response('yes',)
298
 
        transport = RemoteTransport('bzr://localhost/', _client=client)
299
 
        filename = u'/hell\u00d8'.encode('utf8')
300
 
        result = transport.has(filename)
301
 
        self.assertEqual(
302
 
            [('call', 'has', (filename,))],
303
 
            client._calls)
304
 
        self.assertTrue(result)
305
 
 
306
 
 
307
 
class TestRemote(tests.TestCaseWithMemoryTransport):
308
 
 
309
 
    def get_branch_format(self):
310
 
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
311
 
        return reference_bzrdir_format.get_branch_format()
312
 
 
313
 
    def get_repo_format(self):
314
 
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
315
 
        return reference_bzrdir_format.repository_format
316
 
 
317
 
    def disable_verb(self, verb):
318
 
        """Disable a verb for one test."""
319
 
        request_handlers = smart.request.request_handlers
320
 
        orig_method = request_handlers.get(verb)
321
 
        request_handlers.remove(verb)
322
 
        def restoreVerb():
323
 
            request_handlers.register(verb, orig_method)
324
 
        self.addCleanup(restoreVerb)
325
 
 
326
 
 
327
 
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
328
 
    """Tests for the behaviour of client_medium.remote_path_from_transport."""
329
 
 
330
 
    def assertRemotePath(self, expected, client_base, transport_base):
331
 
        """Assert that the result of
332
 
        SmartClientMedium.remote_path_from_transport is the expected value for
333
 
        a given client_base and transport_base.
334
 
        """
335
 
        client_medium = medium.SmartClientMedium(client_base)
336
 
        transport = get_transport(transport_base)
337
 
        result = client_medium.remote_path_from_transport(transport)
338
 
        self.assertEqual(expected, result)
339
 
 
340
 
    def test_remote_path_from_transport(self):
341
 
        """SmartClientMedium.remote_path_from_transport calculates a URL for
342
 
        the given transport relative to the root of the client base URL.
343
 
        """
344
 
        self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
345
 
        self.assertRemotePath(
346
 
            'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
347
 
 
348
 
    def assertRemotePathHTTP(self, expected, transport_base, relpath):
349
 
        """Assert that the result of
350
 
        HttpTransportBase.remote_path_from_transport is the expected value for
351
 
        a given transport_base and relpath of that transport.  (Note that
352
 
        HttpTransportBase is a subclass of SmartClientMedium)
353
 
        """
354
 
        base_transport = get_transport(transport_base)
355
 
        client_medium = base_transport.get_smart_medium()
356
 
        cloned_transport = base_transport.clone(relpath)
357
 
        result = client_medium.remote_path_from_transport(cloned_transport)
358
 
        self.assertEqual(expected, result)
359
 
 
360
 
    def test_remote_path_from_transport_http(self):
361
 
        """Remote paths for HTTP transports are calculated differently to other
362
 
        transports.  They are just relative to the client base, not the root
363
 
        directory of the host.
364
 
        """
365
 
        for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
366
 
            self.assertRemotePathHTTP(
367
 
                '../xyz/', scheme + '//host/path', '../xyz/')
368
 
            self.assertRemotePathHTTP(
369
 
                'xyz/', scheme + '//host/path', 'xyz/')
370
 
 
371
 
 
372
 
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
373
 
    """Tests for the behaviour of client_medium.remote_is_at_least."""
374
 
 
375
 
    def test_initially_unlimited(self):
376
 
        """A fresh medium assumes that the remote side supports all
377
 
        versions.
378
 
        """
379
 
        client_medium = medium.SmartClientMedium('dummy base')
380
 
        self.assertFalse(client_medium._is_remote_before((99, 99)))
381
 
 
382
 
    def test__remember_remote_is_before(self):
383
 
        """Calling _remember_remote_is_before ratchets down the known remote
384
 
        version.
385
 
        """
386
 
        client_medium = medium.SmartClientMedium('dummy base')
387
 
        # Mark the remote side as being less than 1.6.  The remote side may
388
 
        # still be 1.5.
389
 
        client_medium._remember_remote_is_before((1, 6))
390
 
        self.assertTrue(client_medium._is_remote_before((1, 6)))
391
 
        self.assertFalse(client_medium._is_remote_before((1, 5)))
392
 
        # Calling _remember_remote_is_before again with a lower value works.
393
 
        client_medium._remember_remote_is_before((1, 5))
394
 
        self.assertTrue(client_medium._is_remote_before((1, 5)))
395
 
        # You cannot call _remember_remote_is_before with a larger value.
396
 
        self.assertRaises(
397
 
            AssertionError, client_medium._remember_remote_is_before, (1, 9))
398
 
 
399
 
 
400
 
class TestBzrDirCloningMetaDir(TestRemote):
401
 
 
402
 
    def test_backwards_compat(self):
403
 
        self.setup_smart_server_with_call_log()
404
 
        a_dir = self.make_bzrdir('.')
405
 
        self.reset_smart_call_log()
406
 
        verb = 'BzrDir.cloning_metadir'
407
 
        self.disable_verb(verb)
408
 
        format = a_dir.cloning_metadir()
409
 
        call_count = len([call for call in self.hpss_calls if
410
 
            call.call.method == verb])
411
 
        self.assertEqual(1, call_count)
412
 
 
413
 
    def test_branch_reference(self):
414
 
        transport = self.get_transport('quack')
415
 
        referenced = self.make_branch('referenced')
416
 
        expected = referenced.bzrdir.cloning_metadir()
417
 
        client = FakeClient(transport.base)
418
 
        client.add_expected_call(
419
 
            'BzrDir.cloning_metadir', ('quack/', 'False'),
420
 
            'error', ('BranchReference',)),
421
 
        client.add_expected_call(
422
 
            'BzrDir.open_branchV2', ('quack/',),
423
 
            'success', ('ref', self.get_url('referenced'))),
424
 
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
425
 
            _client=client)
426
 
        result = a_bzrdir.cloning_metadir()
427
 
        # We should have got a control dir matching the referenced branch.
428
 
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
429
 
        self.assertEqual(expected._repository_format, result._repository_format)
430
 
        self.assertEqual(expected._branch_format, result._branch_format)
431
 
        client.finished_test()
432
 
 
433
 
    def test_current_server(self):
434
 
        transport = self.get_transport('.')
435
 
        transport = transport.clone('quack')
436
 
        self.make_bzrdir('quack')
437
 
        client = FakeClient(transport.base)
438
 
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
439
 
        control_name = reference_bzrdir_format.network_name()
440
 
        client.add_expected_call(
441
 
            'BzrDir.cloning_metadir', ('quack/', 'False'),
442
 
            'success', (control_name, '', ('branch', ''))),
443
 
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
444
 
            _client=client)
445
 
        result = a_bzrdir.cloning_metadir()
446
 
        # We should have got a reference control dir with default branch and
447
 
        # repository formats.
448
 
        # This pokes a little, just to be sure.
449
 
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
450
 
        self.assertEqual(None, result._repository_format)
451
 
        self.assertEqual(None, result._branch_format)
452
 
        client.finished_test()
453
 
 
454
 
 
455
 
class TestBzrDirOpenBranch(TestRemote):
456
 
 
457
 
    def test_backwards_compat(self):
458
 
        self.setup_smart_server_with_call_log()
459
 
        self.make_branch('.')
460
 
        a_dir = BzrDir.open(self.get_url('.'))
461
 
        self.reset_smart_call_log()
462
 
        verb = 'BzrDir.open_branchV2'
463
 
        self.disable_verb(verb)
464
 
        format = a_dir.open_branch()
465
 
        call_count = len([call for call in self.hpss_calls if
466
 
            call.call.method == verb])
467
 
        self.assertEqual(1, call_count)
468
 
 
469
 
    def test_branch_present(self):
470
 
        reference_format = self.get_repo_format()
471
 
        network_name = reference_format.network_name()
472
 
        branch_network_name = self.get_branch_format().network_name()
473
 
        transport = MemoryTransport()
474
 
        transport.mkdir('quack')
475
 
        transport = transport.clone('quack')
476
 
        client = FakeClient(transport.base)
477
 
        client.add_expected_call(
478
 
            'BzrDir.open_branchV2', ('quack/',),
479
 
            'success', ('branch', branch_network_name))
480
 
        client.add_expected_call(
481
 
            'BzrDir.find_repositoryV3', ('quack/',),
482
 
            'success', ('ok', '', 'no', 'no', 'no', network_name))
483
 
        client.add_expected_call(
484
 
            'Branch.get_stacked_on_url', ('quack/',),
485
 
            'error', ('NotStacked',))
486
 
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
487
 
            _client=client)
488
 
        result = bzrdir.open_branch()
489
 
        self.assertIsInstance(result, RemoteBranch)
490
 
        self.assertEqual(bzrdir, result.bzrdir)
491
 
        client.finished_test()
492
 
 
493
 
    def test_branch_missing(self):
494
 
        transport = MemoryTransport()
495
 
        transport.mkdir('quack')
496
 
        transport = transport.clone('quack')
497
 
        client = FakeClient(transport.base)
498
 
        client.add_error_response('nobranch')
499
 
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
500
 
            _client=client)
501
 
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
502
 
        self.assertEqual(
503
 
            [('call', 'BzrDir.open_branchV2', ('quack/',))],
504
 
            client._calls)
505
 
 
506
 
    def test__get_tree_branch(self):
507
 
        # _get_tree_branch is a form of open_branch, but it should only ask for
508
 
        # branch opening, not any other network requests.
509
 
        calls = []
510
 
        def open_branch():
511
 
            calls.append("Called")
512
 
            return "a-branch"
513
 
        transport = MemoryTransport()
514
 
        # no requests on the network - catches other api calls being made.
515
 
        client = FakeClient(transport.base)
516
 
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
517
 
            _client=client)
518
 
        # patch the open_branch call to record that it was called.
519
 
        bzrdir.open_branch = open_branch
520
 
        self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
521
 
        self.assertEqual(["Called"], calls)
522
 
        self.assertEqual([], client._calls)
523
 
 
524
 
    def test_url_quoting_of_path(self):
525
 
        # Relpaths on the wire should not be URL-escaped.  So "~" should be
526
 
        # transmitted as "~", not "%7E".
527
 
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
528
 
        client = FakeClient(transport.base)
529
 
        reference_format = self.get_repo_format()
530
 
        network_name = reference_format.network_name()
531
 
        branch_network_name = self.get_branch_format().network_name()
532
 
        client.add_expected_call(
533
 
            'BzrDir.open_branchV2', ('~hello/',),
534
 
            'success', ('branch', branch_network_name))
535
 
        client.add_expected_call(
536
 
            'BzrDir.find_repositoryV3', ('~hello/',),
537
 
            'success', ('ok', '', 'no', 'no', 'no', network_name))
538
 
        client.add_expected_call(
539
 
            'Branch.get_stacked_on_url', ('~hello/',),
540
 
            'error', ('NotStacked',))
541
 
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
542
 
            _client=client)
543
 
        result = bzrdir.open_branch()
544
 
        client.finished_test()
545
 
 
546
 
    def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
547
 
        reference_format = self.get_repo_format()
548
 
        network_name = reference_format.network_name()
549
 
        transport = MemoryTransport()
550
 
        transport.mkdir('quack')
551
 
        transport = transport.clone('quack')
552
 
        if rich_root:
553
 
            rich_response = 'yes'
554
 
        else:
555
 
            rich_response = 'no'
556
 
        if subtrees:
557
 
            subtree_response = 'yes'
558
 
        else:
559
 
            subtree_response = 'no'
560
 
        client = FakeClient(transport.base)
561
 
        client.add_success_response(
562
 
            'ok', '', rich_response, subtree_response, external_lookup,
563
 
            network_name)
564
 
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
565
 
            _client=client)
566
 
        result = bzrdir.open_repository()
567
 
        self.assertEqual(
568
 
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
569
 
            client._calls)
570
 
        self.assertIsInstance(result, RemoteRepository)
571
 
        self.assertEqual(bzrdir, result.bzrdir)
572
 
        self.assertEqual(rich_root, result._format.rich_root_data)
573
 
        self.assertEqual(subtrees, result._format.supports_tree_reference)
574
 
 
575
 
    def test_open_repository_sets_format_attributes(self):
576
 
        self.check_open_repository(True, True)
577
 
        self.check_open_repository(False, True)
578
 
        self.check_open_repository(True, False)
579
 
        self.check_open_repository(False, False)
580
 
        self.check_open_repository(False, False, 'yes')
581
 
 
582
 
    def test_old_server(self):
583
 
        """RemoteBzrDirFormat should fail to probe if the server version is too
584
 
        old.
585
 
        """
586
 
        self.assertRaises(errors.NotBranchError,
587
 
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
588
 
 
589
 
 
590
 
class TestBzrDirCreateBranch(TestRemote):
591
 
 
592
 
    def test_backwards_compat(self):
593
 
        self.setup_smart_server_with_call_log()
594
 
        repo = self.make_repository('.')
595
 
        self.reset_smart_call_log()
596
 
        self.disable_verb('BzrDir.create_branch')
597
 
        branch = repo.bzrdir.create_branch()
598
 
        create_branch_call_count = len([call for call in self.hpss_calls if
599
 
            call.call.method == 'BzrDir.create_branch'])
600
 
        self.assertEqual(1, create_branch_call_count)
601
 
 
602
 
    def test_current_server(self):
603
 
        transport = self.get_transport('.')
604
 
        transport = transport.clone('quack')
605
 
        self.make_repository('quack')
606
 
        client = FakeClient(transport.base)
607
 
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
608
 
        reference_format = reference_bzrdir_format.get_branch_format()
609
 
        network_name = reference_format.network_name()
610
 
        reference_repo_fmt = reference_bzrdir_format.repository_format
611
 
        reference_repo_name = reference_repo_fmt.network_name()
612
 
        client.add_expected_call(
613
 
            'BzrDir.create_branch', ('quack/', network_name),
614
 
            'success', ('ok', network_name, '', 'no', 'no', 'yes',
615
 
            reference_repo_name))
616
 
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
617
 
            _client=client)
618
 
        branch = a_bzrdir.create_branch()
619
 
        # We should have got a remote branch
620
 
        self.assertIsInstance(branch, remote.RemoteBranch)
621
 
        # its format should have the settings from the response
622
 
        format = branch._format
623
 
        self.assertEqual(network_name, format.network_name())
624
 
 
625
 
 
626
 
class TestBzrDirCreateRepository(TestRemote):
627
 
 
628
 
    def test_backwards_compat(self):
629
 
        self.setup_smart_server_with_call_log()
630
 
        bzrdir = self.make_bzrdir('.')
631
 
        self.reset_smart_call_log()
632
 
        self.disable_verb('BzrDir.create_repository')
633
 
        repo = bzrdir.create_repository()
634
 
        create_repo_call_count = len([call for call in self.hpss_calls if
635
 
            call.call.method == 'BzrDir.create_repository'])
636
 
        self.assertEqual(1, create_repo_call_count)
637
 
 
638
 
    def test_current_server(self):
639
 
        transport = self.get_transport('.')
640
 
        transport = transport.clone('quack')
641
 
        self.make_bzrdir('quack')
642
 
        client = FakeClient(transport.base)
643
 
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
644
 
        reference_format = reference_bzrdir_format.repository_format
645
 
        network_name = reference_format.network_name()
646
 
        client.add_expected_call(
647
 
            'BzrDir.create_repository', ('quack/',
648
 
                'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
649
 
            'success', ('ok', 'no', 'no', 'no', network_name))
650
 
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
651
 
            _client=client)
652
 
        repo = a_bzrdir.create_repository()
653
 
        # We should have got a remote repository
654
 
        self.assertIsInstance(repo, remote.RemoteRepository)
655
 
        # its format should have the settings from the response
656
 
        format = repo._format
657
 
        self.assertFalse(format.rich_root_data)
658
 
        self.assertFalse(format.supports_tree_reference)
659
 
        self.assertFalse(format.supports_external_lookups)
660
 
        self.assertEqual(network_name, format.network_name())
661
 
 
662
 
 
663
 
class TestBzrDirOpenRepository(TestRemote):
664
 
 
665
 
    def test_backwards_compat_1_2_3(self):
666
 
        # fallback all the way to the first version.
667
 
        reference_format = self.get_repo_format()
668
 
        network_name = reference_format.network_name()
669
 
        client = FakeClient('bzr://example.com/')
670
 
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
671
 
        client.add_unknown_method_response('BzrDir.find_repositoryV2')
672
 
        client.add_success_response('ok', '', 'no', 'no')
673
 
        # A real repository instance will be created to determine the network
674
 
        # name.
675
 
        client.add_success_response_with_body(
676
 
            "Bazaar-NG meta directory, format 1\n", 'ok')
677
 
        client.add_success_response_with_body(
678
 
            reference_format.get_format_string(), 'ok')
679
 
        # PackRepository wants to do a stat
680
 
        client.add_success_response('stat', '0', '65535')
681
 
        remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
682
 
            _client=client)
683
 
        bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
684
 
            _client=client)
685
 
        repo = bzrdir.open_repository()
686
 
        self.assertEqual(
687
 
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
688
 
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
689
 
             ('call', 'BzrDir.find_repository', ('quack/',)),
690
 
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
691
 
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
692
 
             ('call', 'stat', ('/quack/.bzr/repository',)),
693
 
             ],
694
 
            client._calls)
695
 
        self.assertEqual(network_name, repo._format.network_name())
696
 
 
697
 
    def test_backwards_compat_2(self):
698
 
        # fallback to find_repositoryV2
699
 
        reference_format = self.get_repo_format()
700
 
        network_name = reference_format.network_name()
701
 
        client = FakeClient('bzr://example.com/')
702
 
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
703
 
        client.add_success_response('ok', '', 'no', 'no', 'no')
704
 
        # A real repository instance will be created to determine the network
705
 
        # name.
706
 
        client.add_success_response_with_body(
707
 
            "Bazaar-NG meta directory, format 1\n", 'ok')
708
 
        client.add_success_response_with_body(
709
 
            reference_format.get_format_string(), 'ok')
710
 
        # PackRepository wants to do a stat
711
 
        client.add_success_response('stat', '0', '65535')
712
 
        remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
713
 
            _client=client)
714
 
        bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
715
 
            _client=client)
716
 
        repo = bzrdir.open_repository()
717
 
        self.assertEqual(
718
 
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
719
 
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
720
 
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
721
 
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
722
 
             ('call', 'stat', ('/quack/.bzr/repository',)),
723
 
             ],
724
 
            client._calls)
725
 
        self.assertEqual(network_name, repo._format.network_name())
726
 
 
727
 
    def test_current_server(self):
728
 
        reference_format = self.get_repo_format()
729
 
        network_name = reference_format.network_name()
730
 
        transport = MemoryTransport()
731
 
        transport.mkdir('quack')
732
 
        transport = transport.clone('quack')
733
 
        client = FakeClient(transport.base)
734
 
        client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
735
 
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
736
 
            _client=client)
737
 
        repo = bzrdir.open_repository()
738
 
        self.assertEqual(
739
 
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
740
 
            client._calls)
741
 
        self.assertEqual(network_name, repo._format.network_name())
742
 
 
743
 
 
744
 
class OldSmartClient(object):
745
 
    """A fake smart client for test_old_version that just returns a version one
746
 
    response to the 'hello' (query version) command.
747
 
    """
748
 
 
749
 
    def get_request(self):
750
 
        input_file = StringIO('ok\x011\n')
751
 
        output_file = StringIO()
752
 
        client_medium = medium.SmartSimplePipesClientMedium(
753
 
            input_file, output_file)
754
 
        return medium.SmartClientStreamMediumRequest(client_medium)
755
 
 
756
 
    def protocol_version(self):
757
 
        return 1
758
 
 
759
 
 
760
 
class OldServerTransport(object):
761
 
    """A fake transport for test_old_server that reports it's smart server
762
 
    protocol version as version one.
763
 
    """
764
 
 
765
 
    def __init__(self):
766
 
        self.base = 'fake:'
767
 
 
768
 
    def get_smart_client(self):
769
 
        return OldSmartClient()
770
 
 
771
 
 
772
 
class RemoteBzrDirTestCase(TestRemote):
773
 
 
774
 
    def make_remote_bzrdir(self, transport, client):
775
 
        """Make a RemotebzrDir using 'client' as the _client."""
776
 
        return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
777
 
            _client=client)
778
 
 
779
 
 
780
 
class RemoteBranchTestCase(RemoteBzrDirTestCase):
781
 
 
782
 
    def make_remote_branch(self, transport, client):
783
 
        """Make a RemoteBranch using 'client' as its _SmartClient.
784
 
 
785
 
        A RemoteBzrDir and RemoteRepository will also be created to fill out
786
 
        the RemoteBranch, albeit with stub values for some of their attributes.
787
 
        """
788
 
        # we do not want bzrdir to make any remote calls, so use False as its
789
 
        # _client.  If it tries to make a remote call, this will fail
790
 
        # immediately.
791
 
        bzrdir = self.make_remote_bzrdir(transport, False)
792
 
        repo = RemoteRepository(bzrdir, None, _client=client)
793
 
        branch_format = self.get_branch_format()
794
 
        format = RemoteBranchFormat(network_name=branch_format.network_name())
795
 
        return RemoteBranch(bzrdir, repo, _client=client, format=format)
796
 
 
797
 
 
798
 
class TestBranchGetParent(RemoteBranchTestCase):
799
 
 
800
 
    def test_no_parent(self):
801
 
        # in an empty branch we decode the response properly
802
 
        transport = MemoryTransport()
803
 
        client = FakeClient(transport.base)
804
 
        client.add_expected_call(
805
 
            'Branch.get_stacked_on_url', ('quack/',),
806
 
            'error', ('NotStacked',))
807
 
        client.add_expected_call(
808
 
            'Branch.get_parent', ('quack/',),
809
 
            'success', ('',))
810
 
        transport.mkdir('quack')
811
 
        transport = transport.clone('quack')
812
 
        branch = self.make_remote_branch(transport, client)
813
 
        result = branch.get_parent()
814
 
        client.finished_test()
815
 
        self.assertEqual(None, result)
816
 
 
817
 
    def test_parent_relative(self):
818
 
        transport = MemoryTransport()
819
 
        client = FakeClient(transport.base)
820
 
        client.add_expected_call(
821
 
            'Branch.get_stacked_on_url', ('kwaak/',),
822
 
            'error', ('NotStacked',))
823
 
        client.add_expected_call(
824
 
            'Branch.get_parent', ('kwaak/',),
825
 
            'success', ('../foo/',))
826
 
        transport.mkdir('kwaak')
827
 
        transport = transport.clone('kwaak')
828
 
        branch = self.make_remote_branch(transport, client)
829
 
        result = branch.get_parent()
830
 
        self.assertEqual(transport.clone('../foo').base, result)
831
 
 
832
 
    def test_parent_absolute(self):
833
 
        transport = MemoryTransport()
834
 
        client = FakeClient(transport.base)
835
 
        client.add_expected_call(
836
 
            'Branch.get_stacked_on_url', ('kwaak/',),
837
 
            'error', ('NotStacked',))
838
 
        client.add_expected_call(
839
 
            'Branch.get_parent', ('kwaak/',),
840
 
            'success', ('http://foo/',))
841
 
        transport.mkdir('kwaak')
842
 
        transport = transport.clone('kwaak')
843
 
        branch = self.make_remote_branch(transport, client)
844
 
        result = branch.get_parent()
845
 
        self.assertEqual('http://foo/', result)
846
 
        client.finished_test()
847
 
 
848
 
 
849
 
class TestBranchSetParentLocation(RemoteBranchTestCase):
850
 
 
851
 
    def test_no_parent(self):
852
 
        # We call the verb when setting parent to None
853
 
        transport = MemoryTransport()
854
 
        client = FakeClient(transport.base)
855
 
        client.add_expected_call(
856
 
            'Branch.get_stacked_on_url', ('quack/',),
857
 
            'error', ('NotStacked',))
858
 
        client.add_expected_call(
859
 
            'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
860
 
            'success', ())
861
 
        transport.mkdir('quack')
862
 
        transport = transport.clone('quack')
863
 
        branch = self.make_remote_branch(transport, client)
864
 
        branch._lock_token = 'b'
865
 
        branch._repo_lock_token = 'r'
866
 
        branch._set_parent_location(None)
867
 
        client.finished_test()
868
 
 
869
 
    def test_parent(self):
870
 
        transport = MemoryTransport()
871
 
        client = FakeClient(transport.base)
872
 
        client.add_expected_call(
873
 
            'Branch.get_stacked_on_url', ('kwaak/',),
874
 
            'error', ('NotStacked',))
875
 
        client.add_expected_call(
876
 
            'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
877
 
            'success', ())
878
 
        transport.mkdir('kwaak')
879
 
        transport = transport.clone('kwaak')
880
 
        branch = self.make_remote_branch(transport, client)
881
 
        branch._lock_token = 'b'
882
 
        branch._repo_lock_token = 'r'
883
 
        branch._set_parent_location('foo')
884
 
        client.finished_test()
885
 
 
886
 
    def test_backwards_compat(self):
887
 
        self.setup_smart_server_with_call_log()
888
 
        branch = self.make_branch('.')
889
 
        self.reset_smart_call_log()
890
 
        verb = 'Branch.set_parent_location'
891
 
        self.disable_verb(verb)
892
 
        branch.set_parent('http://foo/')
893
 
        self.assertLength(12, self.hpss_calls)
894
 
 
895
 
 
896
 
class TestBranchGetTagsBytes(RemoteBranchTestCase):
897
 
 
898
 
    def test_backwards_compat(self):
899
 
        self.setup_smart_server_with_call_log()
900
 
        branch = self.make_branch('.')
901
 
        self.reset_smart_call_log()
902
 
        verb = 'Branch.get_tags_bytes'
903
 
        self.disable_verb(verb)
904
 
        branch.tags.get_tag_dict()
905
 
        call_count = len([call for call in self.hpss_calls if
906
 
            call.call.method == verb])
907
 
        self.assertEqual(1, call_count)
908
 
 
909
 
    def test_trivial(self):
910
 
        transport = MemoryTransport()
911
 
        client = FakeClient(transport.base)
912
 
        client.add_expected_call(
913
 
            'Branch.get_stacked_on_url', ('quack/',),
914
 
            'error', ('NotStacked',))
915
 
        client.add_expected_call(
916
 
            'Branch.get_tags_bytes', ('quack/',),
917
 
            'success', ('',))
918
 
        transport.mkdir('quack')
919
 
        transport = transport.clone('quack')
920
 
        branch = self.make_remote_branch(transport, client)
921
 
        result = branch.tags.get_tag_dict()
922
 
        client.finished_test()
923
 
        self.assertEqual({}, result)
924
 
 
925
 
 
926
 
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
927
 
 
928
 
    def test_empty_branch(self):
929
 
        # in an empty branch we decode the response properly
930
 
        transport = MemoryTransport()
931
 
        client = FakeClient(transport.base)
932
 
        client.add_expected_call(
933
 
            'Branch.get_stacked_on_url', ('quack/',),
934
 
            'error', ('NotStacked',))
935
 
        client.add_expected_call(
936
 
            'Branch.last_revision_info', ('quack/',),
937
 
            'success', ('ok', '0', 'null:'))
938
 
        transport.mkdir('quack')
939
 
        transport = transport.clone('quack')
940
 
        branch = self.make_remote_branch(transport, client)
941
 
        result = branch.last_revision_info()
942
 
        client.finished_test()
943
 
        self.assertEqual((0, NULL_REVISION), result)
944
 
 
945
 
    def test_non_empty_branch(self):
946
 
        # in a non-empty branch we also decode the response properly
947
 
        revid = u'\xc8'.encode('utf8')
948
 
        transport = MemoryTransport()
949
 
        client = FakeClient(transport.base)
950
 
        client.add_expected_call(
951
 
            'Branch.get_stacked_on_url', ('kwaak/',),
952
 
            'error', ('NotStacked',))
953
 
        client.add_expected_call(
954
 
            'Branch.last_revision_info', ('kwaak/',),
955
 
            'success', ('ok', '2', revid))
956
 
        transport.mkdir('kwaak')
957
 
        transport = transport.clone('kwaak')
958
 
        branch = self.make_remote_branch(transport, client)
959
 
        result = branch.last_revision_info()
960
 
        self.assertEqual((2, revid), result)
961
 
 
962
 
 
963
 
class TestBranch_get_stacked_on_url(TestRemote):
964
 
    """Test Branch._get_stacked_on_url rpc"""
965
 
 
966
 
    def test_get_stacked_on_invalid_url(self):
967
 
        # test that asking for a stacked on url the server can't access works.
968
 
        # This isn't perfect, but then as we're in the same process there
969
 
        # really isn't anything we can do to be 100% sure that the server
970
 
        # doesn't just open in - this test probably needs to be rewritten using
971
 
        # a spawn()ed server.
972
 
        stacked_branch = self.make_branch('stacked', format='1.9')
973
 
        memory_branch = self.make_branch('base', format='1.9')
974
 
        vfs_url = self.get_vfs_only_url('base')
975
 
        stacked_branch.set_stacked_on_url(vfs_url)
976
 
        transport = stacked_branch.bzrdir.root_transport
977
 
        client = FakeClient(transport.base)
978
 
        client.add_expected_call(
979
 
            'Branch.get_stacked_on_url', ('stacked/',),
980
 
            'success', ('ok', vfs_url))
981
 
        # XXX: Multiple calls are bad, this second call documents what is
982
 
        # today.
983
 
        client.add_expected_call(
984
 
            'Branch.get_stacked_on_url', ('stacked/',),
985
 
            'success', ('ok', vfs_url))
986
 
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
987
 
            _client=client)
988
 
        repo_fmt = remote.RemoteRepositoryFormat()
989
 
        repo_fmt._custom_format = stacked_branch.repository._format
990
 
        branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
991
 
            _client=client)
992
 
        result = branch.get_stacked_on_url()
993
 
        self.assertEqual(vfs_url, result)
994
 
 
995
 
    def test_backwards_compatible(self):
996
 
        # like with bzr1.6 with no Branch.get_stacked_on_url rpc
997
 
        base_branch = self.make_branch('base', format='1.6')
998
 
        stacked_branch = self.make_branch('stacked', format='1.6')
999
 
        stacked_branch.set_stacked_on_url('../base')
1000
 
        client = FakeClient(self.get_url())
1001
 
        branch_network_name = self.get_branch_format().network_name()
1002
 
        client.add_expected_call(
1003
 
            'BzrDir.open_branchV2', ('stacked/',),
1004
 
            'success', ('branch', branch_network_name))
1005
 
        client.add_expected_call(
1006
 
            'BzrDir.find_repositoryV3', ('stacked/',),
1007
 
            'success', ('ok', '', 'no', 'no', 'yes',
1008
 
                stacked_branch.repository._format.network_name()))
1009
 
        # called twice, once from constructor and then again by us
1010
 
        client.add_expected_call(
1011
 
            'Branch.get_stacked_on_url', ('stacked/',),
1012
 
            'unknown', ('Branch.get_stacked_on_url',))
1013
 
        client.add_expected_call(
1014
 
            'Branch.get_stacked_on_url', ('stacked/',),
1015
 
            'unknown', ('Branch.get_stacked_on_url',))
1016
 
        # this will also do vfs access, but that goes direct to the transport
1017
 
        # and isn't seen by the FakeClient.
1018
 
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1019
 
            remote.RemoteBzrDirFormat(), _client=client)
1020
 
        branch = bzrdir.open_branch()
1021
 
        result = branch.get_stacked_on_url()
1022
 
        self.assertEqual('../base', result)
1023
 
        client.finished_test()
1024
 
        # it's in the fallback list both for the RemoteRepository and its vfs
1025
 
        # repository
1026
 
        self.assertEqual(1, len(branch.repository._fallback_repositories))
1027
 
        self.assertEqual(1,
1028
 
            len(branch.repository._real_repository._fallback_repositories))
1029
 
 
1030
 
    def test_get_stacked_on_real_branch(self):
1031
 
        base_branch = self.make_branch('base', format='1.6')
1032
 
        stacked_branch = self.make_branch('stacked', format='1.6')
1033
 
        stacked_branch.set_stacked_on_url('../base')
1034
 
        reference_format = self.get_repo_format()
1035
 
        network_name = reference_format.network_name()
1036
 
        client = FakeClient(self.get_url())
1037
 
        branch_network_name = self.get_branch_format().network_name()
1038
 
        client.add_expected_call(
1039
 
            'BzrDir.open_branchV2', ('stacked/',),
1040
 
            'success', ('branch', branch_network_name))
1041
 
        client.add_expected_call(
1042
 
            'BzrDir.find_repositoryV3', ('stacked/',),
1043
 
            'success', ('ok', '', 'no', 'no', 'yes', network_name))
1044
 
        # called twice, once from constructor and then again by us
1045
 
        client.add_expected_call(
1046
 
            'Branch.get_stacked_on_url', ('stacked/',),
1047
 
            'success', ('ok', '../base'))
1048
 
        client.add_expected_call(
1049
 
            'Branch.get_stacked_on_url', ('stacked/',),
1050
 
            'success', ('ok', '../base'))
1051
 
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
1052
 
            remote.RemoteBzrDirFormat(), _client=client)
1053
 
        branch = bzrdir.open_branch()
1054
 
        result = branch.get_stacked_on_url()
1055
 
        self.assertEqual('../base', result)
1056
 
        client.finished_test()
1057
 
        # it's in the fallback list both for the RemoteRepository.
1058
 
        self.assertEqual(1, len(branch.repository._fallback_repositories))
1059
 
        # And we haven't had to construct a real repository.
1060
 
        self.assertEqual(None, branch.repository._real_repository)
1061
 
 
1062
 
 
1063
 
class TestBranchSetLastRevision(RemoteBranchTestCase):
1064
 
 
1065
 
    def test_set_empty(self):
1066
 
        # set_revision_history([]) is translated to calling
1067
 
        # Branch.set_last_revision(path, '') on the wire.
1068
 
        transport = MemoryTransport()
1069
 
        transport.mkdir('branch')
1070
 
        transport = transport.clone('branch')
1071
 
 
1072
 
        client = FakeClient(transport.base)
1073
 
        client.add_expected_call(
1074
 
            'Branch.get_stacked_on_url', ('branch/',),
1075
 
            'error', ('NotStacked',))
1076
 
        client.add_expected_call(
1077
 
            'Branch.lock_write', ('branch/', '', ''),
1078
 
            'success', ('ok', 'branch token', 'repo token'))
1079
 
        client.add_expected_call(
1080
 
            'Branch.last_revision_info',
1081
 
            ('branch/',),
1082
 
            'success', ('ok', '0', 'null:'))
1083
 
        client.add_expected_call(
1084
 
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1085
 
            'success', ('ok',))
1086
 
        client.add_expected_call(
1087
 
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1088
 
            'success', ('ok',))
1089
 
        branch = self.make_remote_branch(transport, client)
1090
 
        # This is a hack to work around the problem that RemoteBranch currently
1091
 
        # unnecessarily invokes _ensure_real upon a call to lock_write.
1092
 
        branch._ensure_real = lambda: None
1093
 
        branch.lock_write()
1094
 
        result = branch.set_revision_history([])
1095
 
        branch.unlock()
1096
 
        self.assertEqual(None, result)
1097
 
        client.finished_test()
1098
 
 
1099
 
    def test_set_nonempty(self):
1100
 
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1101
 
        # Branch.set_last_revision(path, rev-idN) on the wire.
1102
 
        transport = MemoryTransport()
1103
 
        transport.mkdir('branch')
1104
 
        transport = transport.clone('branch')
1105
 
 
1106
 
        client = FakeClient(transport.base)
1107
 
        client.add_expected_call(
1108
 
            'Branch.get_stacked_on_url', ('branch/',),
1109
 
            'error', ('NotStacked',))
1110
 
        client.add_expected_call(
1111
 
            'Branch.lock_write', ('branch/', '', ''),
1112
 
            'success', ('ok', 'branch token', 'repo token'))
1113
 
        client.add_expected_call(
1114
 
            'Branch.last_revision_info',
1115
 
            ('branch/',),
1116
 
            'success', ('ok', '0', 'null:'))
1117
 
        lines = ['rev-id2']
1118
 
        encoded_body = bz2.compress('\n'.join(lines))
1119
 
        client.add_success_response_with_body(encoded_body, 'ok')
1120
 
        client.add_expected_call(
1121
 
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1122
 
            'success', ('ok',))
1123
 
        client.add_expected_call(
1124
 
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1125
 
            'success', ('ok',))
1126
 
        branch = self.make_remote_branch(transport, client)
1127
 
        # This is a hack to work around the problem that RemoteBranch currently
1128
 
        # unnecessarily invokes _ensure_real upon a call to lock_write.
1129
 
        branch._ensure_real = lambda: None
1130
 
        # Lock the branch, reset the record of remote calls.
1131
 
        branch.lock_write()
1132
 
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1133
 
        branch.unlock()
1134
 
        self.assertEqual(None, result)
1135
 
        client.finished_test()
1136
 
 
1137
 
    def test_no_such_revision(self):
1138
 
        transport = MemoryTransport()
1139
 
        transport.mkdir('branch')
1140
 
        transport = transport.clone('branch')
1141
 
        # A response of 'NoSuchRevision' is translated into an exception.
1142
 
        client = FakeClient(transport.base)
1143
 
        client.add_expected_call(
1144
 
            'Branch.get_stacked_on_url', ('branch/',),
1145
 
            'error', ('NotStacked',))
1146
 
        client.add_expected_call(
1147
 
            'Branch.lock_write', ('branch/', '', ''),
1148
 
            'success', ('ok', 'branch token', 'repo token'))
1149
 
        client.add_expected_call(
1150
 
            'Branch.last_revision_info',
1151
 
            ('branch/',),
1152
 
            'success', ('ok', '0', 'null:'))
1153
 
        # get_graph calls to construct the revision history, for the set_rh
1154
 
        # hook
1155
 
        lines = ['rev-id']
1156
 
        encoded_body = bz2.compress('\n'.join(lines))
1157
 
        client.add_success_response_with_body(encoded_body, 'ok')
1158
 
        client.add_expected_call(
1159
 
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1160
 
            'error', ('NoSuchRevision', 'rev-id'))
1161
 
        client.add_expected_call(
1162
 
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1163
 
            'success', ('ok',))
1164
 
 
1165
 
        branch = self.make_remote_branch(transport, client)
1166
 
        branch.lock_write()
1167
 
        self.assertRaises(
1168
 
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1169
 
        branch.unlock()
1170
 
        client.finished_test()
1171
 
 
1172
 
    def test_tip_change_rejected(self):
1173
 
        """TipChangeRejected responses cause a TipChangeRejected exception to
1174
 
        be raised.
1175
 
        """
1176
 
        transport = MemoryTransport()
1177
 
        transport.mkdir('branch')
1178
 
        transport = transport.clone('branch')
1179
 
        client = FakeClient(transport.base)
1180
 
        rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1181
 
        rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1182
 
        client.add_expected_call(
1183
 
            'Branch.get_stacked_on_url', ('branch/',),
1184
 
            'error', ('NotStacked',))
1185
 
        client.add_expected_call(
1186
 
            'Branch.lock_write', ('branch/', '', ''),
1187
 
            'success', ('ok', 'branch token', 'repo token'))
1188
 
        client.add_expected_call(
1189
 
            'Branch.last_revision_info',
1190
 
            ('branch/',),
1191
 
            'success', ('ok', '0', 'null:'))
1192
 
        lines = ['rev-id']
1193
 
        encoded_body = bz2.compress('\n'.join(lines))
1194
 
        client.add_success_response_with_body(encoded_body, 'ok')
1195
 
        client.add_expected_call(
1196
 
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1197
 
            'error', ('TipChangeRejected', rejection_msg_utf8))
1198
 
        client.add_expected_call(
1199
 
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1200
 
            'success', ('ok',))
1201
 
        branch = self.make_remote_branch(transport, client)
1202
 
        branch._ensure_real = lambda: None
1203
 
        branch.lock_write()
1204
 
        # The 'TipChangeRejected' error response triggered by calling
1205
 
        # set_revision_history causes a TipChangeRejected exception.
1206
 
        err = self.assertRaises(
1207
 
            errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1208
 
        # The UTF-8 message from the response has been decoded into a unicode
1209
 
        # object.
1210
 
        self.assertIsInstance(err.msg, unicode)
1211
 
        self.assertEqual(rejection_msg_unicode, err.msg)
1212
 
        branch.unlock()
1213
 
        client.finished_test()
1214
 
 
1215
 
 
1216
 
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1217
 
 
1218
 
    def test_set_last_revision_info(self):
1219
 
        # set_last_revision_info(num, 'rev-id') is translated to calling
1220
 
        # Branch.set_last_revision_info(num, 'rev-id') on the wire.
1221
 
        transport = MemoryTransport()
1222
 
        transport.mkdir('branch')
1223
 
        transport = transport.clone('branch')
1224
 
        client = FakeClient(transport.base)
1225
 
        # get_stacked_on_url
1226
 
        client.add_error_response('NotStacked')
1227
 
        # lock_write
1228
 
        client.add_success_response('ok', 'branch token', 'repo token')
1229
 
        # query the current revision
1230
 
        client.add_success_response('ok', '0', 'null:')
1231
 
        # set_last_revision
1232
 
        client.add_success_response('ok')
1233
 
        # unlock
1234
 
        client.add_success_response('ok')
1235
 
 
1236
 
        branch = self.make_remote_branch(transport, client)
1237
 
        # Lock the branch, reset the record of remote calls.
1238
 
        branch.lock_write()
1239
 
        client._calls = []
1240
 
        result = branch.set_last_revision_info(1234, 'a-revision-id')
1241
 
        self.assertEqual(
1242
 
            [('call', 'Branch.last_revision_info', ('branch/',)),
1243
 
             ('call', 'Branch.set_last_revision_info',
1244
 
                ('branch/', 'branch token', 'repo token',
1245
 
                 '1234', 'a-revision-id'))],
1246
 
            client._calls)
1247
 
        self.assertEqual(None, result)
1248
 
 
1249
 
    def test_no_such_revision(self):
1250
 
        # A response of 'NoSuchRevision' is translated into an exception.
1251
 
        transport = MemoryTransport()
1252
 
        transport.mkdir('branch')
1253
 
        transport = transport.clone('branch')
1254
 
        client = FakeClient(transport.base)
1255
 
        # get_stacked_on_url
1256
 
        client.add_error_response('NotStacked')
1257
 
        # lock_write
1258
 
        client.add_success_response('ok', 'branch token', 'repo token')
1259
 
        # set_last_revision
1260
 
        client.add_error_response('NoSuchRevision', 'revid')
1261
 
        # unlock
1262
 
        client.add_success_response('ok')
1263
 
 
1264
 
        branch = self.make_remote_branch(transport, client)
1265
 
        # Lock the branch, reset the record of remote calls.
1266
 
        branch.lock_write()
1267
 
        client._calls = []
1268
 
 
1269
 
        self.assertRaises(
1270
 
            errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1271
 
        branch.unlock()
1272
 
 
1273
 
    def lock_remote_branch(self, branch):
1274
 
        """Trick a RemoteBranch into thinking it is locked."""
1275
 
        branch._lock_mode = 'w'
1276
 
        branch._lock_count = 2
1277
 
        branch._lock_token = 'branch token'
1278
 
        branch._repo_lock_token = 'repo token'
1279
 
        branch.repository._lock_mode = 'w'
1280
 
        branch.repository._lock_count = 2
1281
 
        branch.repository._lock_token = 'repo token'
1282
 
 
1283
 
    def test_backwards_compatibility(self):
1284
 
        """If the server does not support the Branch.set_last_revision_info
1285
 
        verb (which is new in 1.4), then the client falls back to VFS methods.
1286
 
        """
1287
 
        # This test is a little messy.  Unlike most tests in this file, it
1288
 
        # doesn't purely test what a Remote* object sends over the wire, and
1289
 
        # how it reacts to responses from the wire.  It instead relies partly
1290
 
        # on asserting that the RemoteBranch will call
1291
 
        # self._real_branch.set_last_revision_info(...).
1292
 
 
1293
 
        # First, set up our RemoteBranch with a FakeClient that raises
1294
 
        # UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1295
 
        transport = MemoryTransport()
1296
 
        transport.mkdir('branch')
1297
 
        transport = transport.clone('branch')
1298
 
        client = FakeClient(transport.base)
1299
 
        client.add_expected_call(
1300
 
            'Branch.get_stacked_on_url', ('branch/',),
1301
 
            'error', ('NotStacked',))
1302
 
        client.add_expected_call(
1303
 
            'Branch.last_revision_info',
1304
 
            ('branch/',),
1305
 
            'success', ('ok', '0', 'null:'))
1306
 
        client.add_expected_call(
1307
 
            'Branch.set_last_revision_info',
1308
 
            ('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1309
 
            'unknown', 'Branch.set_last_revision_info')
1310
 
 
1311
 
        branch = self.make_remote_branch(transport, client)
1312
 
        class StubRealBranch(object):
1313
 
            def __init__(self):
1314
 
                self.calls = []
1315
 
            def set_last_revision_info(self, revno, revision_id):
1316
 
                self.calls.append(
1317
 
                    ('set_last_revision_info', revno, revision_id))
1318
 
            def _clear_cached_state(self):
1319
 
                pass
1320
 
        real_branch = StubRealBranch()
1321
 
        branch._real_branch = real_branch
1322
 
        self.lock_remote_branch(branch)
1323
 
 
1324
 
        # Call set_last_revision_info, and verify it behaved as expected.
1325
 
        result = branch.set_last_revision_info(1234, 'a-revision-id')
1326
 
        self.assertEqual(
1327
 
            [('set_last_revision_info', 1234, 'a-revision-id')],
1328
 
            real_branch.calls)
1329
 
        client.finished_test()
1330
 
 
1331
 
    def test_unexpected_error(self):
1332
 
        # If the server sends an error the client doesn't understand, it gets
1333
 
        # turned into an UnknownErrorFromSmartServer, which is presented as a
1334
 
        # non-internal error to the user.
1335
 
        transport = MemoryTransport()
1336
 
        transport.mkdir('branch')
1337
 
        transport = transport.clone('branch')
1338
 
        client = FakeClient(transport.base)
1339
 
        # get_stacked_on_url
1340
 
        client.add_error_response('NotStacked')
1341
 
        # lock_write
1342
 
        client.add_success_response('ok', 'branch token', 'repo token')
1343
 
        # set_last_revision
1344
 
        client.add_error_response('UnexpectedError')
1345
 
        # unlock
1346
 
        client.add_success_response('ok')
1347
 
 
1348
 
        branch = self.make_remote_branch(transport, client)
1349
 
        # Lock the branch, reset the record of remote calls.
1350
 
        branch.lock_write()
1351
 
        client._calls = []
1352
 
 
1353
 
        err = self.assertRaises(
1354
 
            errors.UnknownErrorFromSmartServer,
1355
 
            branch.set_last_revision_info, 123, 'revid')
1356
 
        self.assertEqual(('UnexpectedError',), err.error_tuple)
1357
 
        branch.unlock()
1358
 
 
1359
 
    def test_tip_change_rejected(self):
1360
 
        """TipChangeRejected responses cause a TipChangeRejected exception to
1361
 
        be raised.
1362
 
        """
1363
 
        transport = MemoryTransport()
1364
 
        transport.mkdir('branch')
1365
 
        transport = transport.clone('branch')
1366
 
        client = FakeClient(transport.base)
1367
 
        # get_stacked_on_url
1368
 
        client.add_error_response('NotStacked')
1369
 
        # lock_write
1370
 
        client.add_success_response('ok', 'branch token', 'repo token')
1371
 
        # set_last_revision
1372
 
        client.add_error_response('TipChangeRejected', 'rejection message')
1373
 
        # unlock
1374
 
        client.add_success_response('ok')
1375
 
 
1376
 
        branch = self.make_remote_branch(transport, client)
1377
 
        # Lock the branch, reset the record of remote calls.
1378
 
        branch.lock_write()
1379
 
        self.addCleanup(branch.unlock)
1380
 
        client._calls = []
1381
 
 
1382
 
        # The 'TipChangeRejected' error response triggered by calling
1383
 
        # set_last_revision_info causes a TipChangeRejected exception.
1384
 
        err = self.assertRaises(
1385
 
            errors.TipChangeRejected,
1386
 
            branch.set_last_revision_info, 123, 'revid')
1387
 
        self.assertEqual('rejection message', err.msg)
1388
 
 
1389
 
 
1390
 
class TestBranchGetSetConfig(RemoteBranchTestCase):
1391
 
 
1392
 
    def test_get_branch_conf(self):
1393
 
        # in an empty branch we decode the response properly
1394
 
        client = FakeClient()
1395
 
        client.add_expected_call(
1396
 
            'Branch.get_stacked_on_url', ('memory:///',),
1397
 
            'error', ('NotStacked',),)
1398
 
        client.add_success_response_with_body('# config file body', 'ok')
1399
 
        transport = MemoryTransport()
1400
 
        branch = self.make_remote_branch(transport, client)
1401
 
        config = branch.get_config()
1402
 
        config.has_explicit_nickname()
1403
 
        self.assertEqual(
1404
 
            [('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1405
 
             ('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1406
 
            client._calls)
1407
 
 
1408
 
    def test_get_multi_line_branch_conf(self):
1409
 
        # Make sure that multiple-line branch.conf files are supported
1410
 
        #
1411
 
        # https://bugs.edge.launchpad.net/bzr/+bug/354075
1412
 
        client = FakeClient()
1413
 
        client.add_expected_call(
1414
 
            'Branch.get_stacked_on_url', ('memory:///',),
1415
 
            'error', ('NotStacked',),)
1416
 
        client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1417
 
        transport = MemoryTransport()
1418
 
        branch = self.make_remote_branch(transport, client)
1419
 
        config = branch.get_config()
1420
 
        self.assertEqual(u'2', config.get_user_option('b'))
1421
 
 
1422
 
    def test_set_option(self):
1423
 
        client = FakeClient()
1424
 
        client.add_expected_call(
1425
 
            'Branch.get_stacked_on_url', ('memory:///',),
1426
 
            'error', ('NotStacked',),)
1427
 
        client.add_expected_call(
1428
 
            'Branch.lock_write', ('memory:///', '', ''),
1429
 
            'success', ('ok', 'branch token', 'repo token'))
1430
 
        client.add_expected_call(
1431
 
            'Branch.set_config_option', ('memory:///', 'branch token',
1432
 
            'repo token', 'foo', 'bar', ''),
1433
 
            'success', ())
1434
 
        client.add_expected_call(
1435
 
            'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1436
 
            'success', ('ok',))
1437
 
        transport = MemoryTransport()
1438
 
        branch = self.make_remote_branch(transport, client)
1439
 
        branch.lock_write()
1440
 
        config = branch._get_config()
1441
 
        config.set_option('foo', 'bar')
1442
 
        branch.unlock()
1443
 
        client.finished_test()
1444
 
 
1445
 
    def test_backwards_compat_set_option(self):
1446
 
        self.setup_smart_server_with_call_log()
1447
 
        branch = self.make_branch('.')
1448
 
        verb = 'Branch.set_config_option'
1449
 
        self.disable_verb(verb)
1450
 
        branch.lock_write()
1451
 
        self.addCleanup(branch.unlock)
1452
 
        self.reset_smart_call_log()
1453
 
        branch._get_config().set_option('value', 'name')
1454
 
        self.assertLength(10, self.hpss_calls)
1455
 
        self.assertEqual('value', branch._get_config().get_option('name'))
1456
 
 
1457
 
 
1458
 
class TestBranchLockWrite(RemoteBranchTestCase):
1459
 
 
1460
 
    def test_lock_write_unlockable(self):
1461
 
        transport = MemoryTransport()
1462
 
        client = FakeClient(transport.base)
1463
 
        client.add_expected_call(
1464
 
            'Branch.get_stacked_on_url', ('quack/',),
1465
 
            'error', ('NotStacked',),)
1466
 
        client.add_expected_call(
1467
 
            'Branch.lock_write', ('quack/', '', ''),
1468
 
            'error', ('UnlockableTransport',))
1469
 
        transport.mkdir('quack')
1470
 
        transport = transport.clone('quack')
1471
 
        branch = self.make_remote_branch(transport, client)
1472
 
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1473
 
        client.finished_test()
1474
 
 
1475
 
 
1476
 
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
1477
 
 
1478
 
    def test__get_config(self):
1479
 
        client = FakeClient()
1480
 
        client.add_success_response_with_body('default_stack_on = /\n', 'ok')
1481
 
        transport = MemoryTransport()
1482
 
        bzrdir = self.make_remote_bzrdir(transport, client)
1483
 
        config = bzrdir.get_config()
1484
 
        self.assertEqual('/', config.get_default_stack_on())
1485
 
        self.assertEqual(
1486
 
            [('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
1487
 
            client._calls)
1488
 
 
1489
 
    def test_set_option_uses_vfs(self):
1490
 
        self.setup_smart_server_with_call_log()
1491
 
        bzrdir = self.make_bzrdir('.')
1492
 
        self.reset_smart_call_log()
1493
 
        config = bzrdir.get_config()
1494
 
        config.set_default_stack_on('/')
1495
 
        self.assertLength(3, self.hpss_calls)
1496
 
 
1497
 
    def test_backwards_compat_get_option(self):
1498
 
        self.setup_smart_server_with_call_log()
1499
 
        bzrdir = self.make_bzrdir('.')
1500
 
        verb = 'BzrDir.get_config_file'
1501
 
        self.disable_verb(verb)
1502
 
        self.reset_smart_call_log()
1503
 
        self.assertEqual(None,
1504
 
            bzrdir._get_config().get_option('default_stack_on'))
1505
 
        self.assertLength(3, self.hpss_calls)
1506
 
 
1507
 
 
1508
 
class TestTransportIsReadonly(tests.TestCase):
1509
 
 
1510
 
    def test_true(self):
1511
 
        client = FakeClient()
1512
 
        client.add_success_response('yes')
1513
 
        transport = RemoteTransport('bzr://example.com/', medium=False,
1514
 
                                    _client=client)
1515
 
        self.assertEqual(True, transport.is_readonly())
1516
 
        self.assertEqual(
1517
 
            [('call', 'Transport.is_readonly', ())],
1518
 
            client._calls)
1519
 
 
1520
 
    def test_false(self):
1521
 
        client = FakeClient()
1522
 
        client.add_success_response('no')
1523
 
        transport = RemoteTransport('bzr://example.com/', medium=False,
1524
 
                                    _client=client)
1525
 
        self.assertEqual(False, transport.is_readonly())
1526
 
        self.assertEqual(
1527
 
            [('call', 'Transport.is_readonly', ())],
1528
 
            client._calls)
1529
 
 
1530
 
    def test_error_from_old_server(self):
1531
 
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1532
 
 
1533
 
        Clients should treat it as a "no" response, because is_readonly is only
1534
 
        advisory anyway (a transport could be read-write, but then the
1535
 
        underlying filesystem could be readonly anyway).
1536
 
        """
1537
 
        client = FakeClient()
1538
 
        client.add_unknown_method_response('Transport.is_readonly')
1539
 
        transport = RemoteTransport('bzr://example.com/', medium=False,
1540
 
                                    _client=client)
1541
 
        self.assertEqual(False, transport.is_readonly())
1542
 
        self.assertEqual(
1543
 
            [('call', 'Transport.is_readonly', ())],
1544
 
            client._calls)
1545
 
 
1546
 
 
1547
 
class TestTransportMkdir(tests.TestCase):
1548
 
 
1549
 
    def test_permissiondenied(self):
1550
 
        client = FakeClient()
1551
 
        client.add_error_response('PermissionDenied', 'remote path', 'extra')
1552
 
        transport = RemoteTransport('bzr://example.com/', medium=False,
1553
 
                                    _client=client)
1554
 
        exc = self.assertRaises(
1555
 
            errors.PermissionDenied, transport.mkdir, 'client path')
1556
 
        expected_error = errors.PermissionDenied('/client path', 'extra')
1557
 
        self.assertEqual(expected_error, exc)
1558
 
 
1559
 
 
1560
 
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1561
 
 
1562
 
    def test_defaults_to_none(self):
1563
 
        t = RemoteSSHTransport('bzr+ssh://example.com')
1564
 
        self.assertIs(None, t._get_credentials()[0])
1565
 
 
1566
 
    def test_uses_authentication_config(self):
1567
 
        conf = config.AuthenticationConfig()
1568
 
        conf._get_config().update(
1569
 
            {'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1570
 
            'example.com'}})
1571
 
        conf._save()
1572
 
        t = RemoteSSHTransport('bzr+ssh://example.com')
1573
 
        self.assertEqual('bar', t._get_credentials()[0])
1574
 
 
1575
 
 
1576
 
class TestRemoteRepository(TestRemote):
1577
 
    """Base for testing RemoteRepository protocol usage.
1578
 
 
1579
 
    These tests contain frozen requests and responses.  We want any changes to
1580
 
    what is sent or expected to be require a thoughtful update to these tests
1581
 
    because they might break compatibility with different-versioned servers.
1582
 
    """
1583
 
 
1584
 
    def setup_fake_client_and_repository(self, transport_path):
1585
 
        """Create the fake client and repository for testing with.
1586
 
 
1587
 
        There's no real server here; we just have canned responses sent
1588
 
        back one by one.
1589
 
 
1590
 
        :param transport_path: Path below the root of the MemoryTransport
1591
 
            where the repository will be created.
1592
 
        """
1593
 
        transport = MemoryTransport()
1594
 
        transport.mkdir(transport_path)
1595
 
        client = FakeClient(transport.base)
1596
 
        transport = transport.clone(transport_path)
1597
 
        # we do not want bzrdir to make any remote calls
1598
 
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1599
 
            _client=False)
1600
 
        repo = RemoteRepository(bzrdir, None, _client=client)
1601
 
        return repo, client
1602
 
 
1603
 
 
1604
 
class TestRepositoryFormat(TestRemoteRepository):
1605
 
 
1606
 
    def test_fast_delta(self):
1607
 
        true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
1608
 
        true_format = RemoteRepositoryFormat()
1609
 
        true_format._network_name = true_name
1610
 
        self.assertEqual(True, true_format.fast_deltas)
1611
 
        false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1612
 
        false_format = RemoteRepositoryFormat()
1613
 
        false_format._network_name = false_name
1614
 
        self.assertEqual(False, false_format.fast_deltas)
1615
 
 
1616
 
 
1617
 
class TestRepositoryGatherStats(TestRemoteRepository):
1618
 
 
1619
 
    def test_revid_none(self):
1620
 
        # ('ok',), body with revisions and size
1621
 
        transport_path = 'quack'
1622
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1623
 
        client.add_success_response_with_body(
1624
 
            'revisions: 2\nsize: 18\n', 'ok')
1625
 
        result = repo.gather_stats(None)
1626
 
        self.assertEqual(
1627
 
            [('call_expecting_body', 'Repository.gather_stats',
1628
 
             ('quack/','','no'))],
1629
 
            client._calls)
1630
 
        self.assertEqual({'revisions': 2, 'size': 18}, result)
1631
 
 
1632
 
    def test_revid_no_committers(self):
1633
 
        # ('ok',), body without committers
1634
 
        body = ('firstrev: 123456.300 3600\n'
1635
 
                'latestrev: 654231.400 0\n'
1636
 
                'revisions: 2\n'
1637
 
                'size: 18\n')
1638
 
        transport_path = 'quick'
1639
 
        revid = u'\xc8'.encode('utf8')
1640
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1641
 
        client.add_success_response_with_body(body, 'ok')
1642
 
        result = repo.gather_stats(revid)
1643
 
        self.assertEqual(
1644
 
            [('call_expecting_body', 'Repository.gather_stats',
1645
 
              ('quick/', revid, 'no'))],
1646
 
            client._calls)
1647
 
        self.assertEqual({'revisions': 2, 'size': 18,
1648
 
                          'firstrev': (123456.300, 3600),
1649
 
                          'latestrev': (654231.400, 0),},
1650
 
                         result)
1651
 
 
1652
 
    def test_revid_with_committers(self):
1653
 
        # ('ok',), body with committers
1654
 
        body = ('committers: 128\n'
1655
 
                'firstrev: 123456.300 3600\n'
1656
 
                'latestrev: 654231.400 0\n'
1657
 
                'revisions: 2\n'
1658
 
                'size: 18\n')
1659
 
        transport_path = 'buick'
1660
 
        revid = u'\xc8'.encode('utf8')
1661
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1662
 
        client.add_success_response_with_body(body, 'ok')
1663
 
        result = repo.gather_stats(revid, True)
1664
 
        self.assertEqual(
1665
 
            [('call_expecting_body', 'Repository.gather_stats',
1666
 
              ('buick/', revid, 'yes'))],
1667
 
            client._calls)
1668
 
        self.assertEqual({'revisions': 2, 'size': 18,
1669
 
                          'committers': 128,
1670
 
                          'firstrev': (123456.300, 3600),
1671
 
                          'latestrev': (654231.400, 0),},
1672
 
                         result)
1673
 
 
1674
 
 
1675
 
class TestRepositoryGetGraph(TestRemoteRepository):
1676
 
 
1677
 
    def test_get_graph(self):
1678
 
        # get_graph returns a graph with a custom parents provider.
1679
 
        transport_path = 'quack'
1680
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1681
 
        graph = repo.get_graph()
1682
 
        self.assertNotEqual(graph._parents_provider, repo)
1683
 
 
1684
 
 
1685
 
class TestRepositoryGetParentMap(TestRemoteRepository):
1686
 
 
1687
 
    def test_get_parent_map_caching(self):
1688
 
        # get_parent_map returns from cache until unlock()
1689
 
        # setup a reponse with two revisions
1690
 
        r1 = u'\u0e33'.encode('utf8')
1691
 
        r2 = u'\u0dab'.encode('utf8')
1692
 
        lines = [' '.join([r2, r1]), r1]
1693
 
        encoded_body = bz2.compress('\n'.join(lines))
1694
 
 
1695
 
        transport_path = 'quack'
1696
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1697
 
        client.add_success_response_with_body(encoded_body, 'ok')
1698
 
        client.add_success_response_with_body(encoded_body, 'ok')
1699
 
        repo.lock_read()
1700
 
        graph = repo.get_graph()
1701
 
        parents = graph.get_parent_map([r2])
1702
 
        self.assertEqual({r2: (r1,)}, parents)
1703
 
        # locking and unlocking deeper should not reset
1704
 
        repo.lock_read()
1705
 
        repo.unlock()
1706
 
        parents = graph.get_parent_map([r1])
1707
 
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
1708
 
        self.assertEqual(
1709
 
            [('call_with_body_bytes_expecting_body',
1710
 
              'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1711
 
              '\n\n0')],
1712
 
            client._calls)
1713
 
        repo.unlock()
1714
 
        # now we call again, and it should use the second response.
1715
 
        repo.lock_read()
1716
 
        graph = repo.get_graph()
1717
 
        parents = graph.get_parent_map([r1])
1718
 
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
1719
 
        self.assertEqual(
1720
 
            [('call_with_body_bytes_expecting_body',
1721
 
              'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1722
 
              '\n\n0'),
1723
 
             ('call_with_body_bytes_expecting_body',
1724
 
              'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1725
 
              '\n\n0'),
1726
 
            ],
1727
 
            client._calls)
1728
 
        repo.unlock()
1729
 
 
1730
 
    def test_get_parent_map_reconnects_if_unknown_method(self):
1731
 
        transport_path = 'quack'
1732
 
        rev_id = 'revision-id'
1733
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1734
 
        client.add_unknown_method_response('Repository.get_parent_map')
1735
 
        client.add_success_response_with_body(rev_id, 'ok')
1736
 
        self.assertFalse(client._medium._is_remote_before((1, 2)))
1737
 
        parents = repo.get_parent_map([rev_id])
1738
 
        self.assertEqual(
1739
 
            [('call_with_body_bytes_expecting_body',
1740
 
              'Repository.get_parent_map', ('quack/', 'include-missing:',
1741
 
              rev_id), '\n\n0'),
1742
 
             ('disconnect medium',),
1743
 
             ('call_expecting_body', 'Repository.get_revision_graph',
1744
 
              ('quack/', ''))],
1745
 
            client._calls)
1746
 
        # The medium is now marked as being connected to an older server
1747
 
        self.assertTrue(client._medium._is_remote_before((1, 2)))
1748
 
        self.assertEqual({rev_id: ('null:',)}, parents)
1749
 
 
1750
 
    def test_get_parent_map_fallback_parentless_node(self):
1751
 
        """get_parent_map falls back to get_revision_graph on old servers.  The
1752
 
        results from get_revision_graph are tweaked to match the get_parent_map
1753
 
        API.
1754
 
 
1755
 
        Specifically, a {key: ()} result from get_revision_graph means "no
1756
 
        parents" for that key, which in get_parent_map results should be
1757
 
        represented as {key: ('null:',)}.
1758
 
 
1759
 
        This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1760
 
        """
1761
 
        rev_id = 'revision-id'
1762
 
        transport_path = 'quack'
1763
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1764
 
        client.add_success_response_with_body(rev_id, 'ok')
1765
 
        client._medium._remember_remote_is_before((1, 2))
1766
 
        parents = repo.get_parent_map([rev_id])
1767
 
        self.assertEqual(
1768
 
            [('call_expecting_body', 'Repository.get_revision_graph',
1769
 
             ('quack/', ''))],
1770
 
            client._calls)
1771
 
        self.assertEqual({rev_id: ('null:',)}, parents)
1772
 
 
1773
 
    def test_get_parent_map_unexpected_response(self):
1774
 
        repo, client = self.setup_fake_client_and_repository('path')
1775
 
        client.add_success_response('something unexpected!')
1776
 
        self.assertRaises(
1777
 
            errors.UnexpectedSmartServerResponse,
1778
 
            repo.get_parent_map, ['a-revision-id'])
1779
 
 
1780
 
    def test_get_parent_map_negative_caches_missing_keys(self):
1781
 
        self.setup_smart_server_with_call_log()
1782
 
        repo = self.make_repository('foo')
1783
 
        self.assertIsInstance(repo, RemoteRepository)
1784
 
        repo.lock_read()
1785
 
        self.addCleanup(repo.unlock)
1786
 
        self.reset_smart_call_log()
1787
 
        graph = repo.get_graph()
1788
 
        self.assertEqual({},
1789
 
            graph.get_parent_map(['some-missing', 'other-missing']))
1790
 
        self.assertLength(1, self.hpss_calls)
1791
 
        # No call if we repeat this
1792
 
        self.reset_smart_call_log()
1793
 
        graph = repo.get_graph()
1794
 
        self.assertEqual({},
1795
 
            graph.get_parent_map(['some-missing', 'other-missing']))
1796
 
        self.assertLength(0, self.hpss_calls)
1797
 
        # Asking for more unknown keys makes a request.
1798
 
        self.reset_smart_call_log()
1799
 
        graph = repo.get_graph()
1800
 
        self.assertEqual({},
1801
 
            graph.get_parent_map(['some-missing', 'other-missing',
1802
 
                'more-missing']))
1803
 
        self.assertLength(1, self.hpss_calls)
1804
 
 
1805
 
    def disableExtraResults(self):
1806
 
        old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1807
 
        SmartServerRepositoryGetParentMap.no_extra_results = True
1808
 
        def reset_values():
1809
 
            SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1810
 
        self.addCleanup(reset_values)
1811
 
 
1812
 
    def test_null_cached_missing_and_stop_key(self):
1813
 
        self.setup_smart_server_with_call_log()
1814
 
        # Make a branch with a single revision.
1815
 
        builder = self.make_branch_builder('foo')
1816
 
        builder.start_series()
1817
 
        builder.build_snapshot('first', None, [
1818
 
            ('add', ('', 'root-id', 'directory', ''))])
1819
 
        builder.finish_series()
1820
 
        branch = builder.get_branch()
1821
 
        repo = branch.repository
1822
 
        self.assertIsInstance(repo, RemoteRepository)
1823
 
        # Stop the server from sending extra results.
1824
 
        self.disableExtraResults()
1825
 
        repo.lock_read()
1826
 
        self.addCleanup(repo.unlock)
1827
 
        self.reset_smart_call_log()
1828
 
        graph = repo.get_graph()
1829
 
        # Query for 'first' and 'null:'.  Because 'null:' is a parent of
1830
 
        # 'first' it will be a candidate for the stop_keys of subsequent
1831
 
        # requests, and because 'null:' was queried but not returned it will be
1832
 
        # cached as missing.
1833
 
        self.assertEqual({'first': ('null:',)},
1834
 
            graph.get_parent_map(['first', 'null:']))
1835
 
        # Now query for another key.  This request will pass along a recipe of
1836
 
        # start and stop keys describing the already cached results, and this
1837
 
        # recipe's revision count must be correct (or else it will trigger an
1838
 
        # error from the server).
1839
 
        self.assertEqual({}, graph.get_parent_map(['another-key']))
1840
 
        # This assertion guards against disableExtraResults silently failing to
1841
 
        # work, thus invalidating the test.
1842
 
        self.assertLength(2, self.hpss_calls)
1843
 
 
1844
 
    def test_get_parent_map_gets_ghosts_from_result(self):
1845
 
        # asking for a revision should negatively cache close ghosts in its
1846
 
        # ancestry.
1847
 
        self.setup_smart_server_with_call_log()
1848
 
        tree = self.make_branch_and_memory_tree('foo')
1849
 
        tree.lock_write()
1850
 
        try:
1851
 
            builder = treebuilder.TreeBuilder()
1852
 
            builder.start_tree(tree)
1853
 
            builder.build([])
1854
 
            builder.finish_tree()
1855
 
            tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1856
 
            rev_id = tree.commit('')
1857
 
        finally:
1858
 
            tree.unlock()
1859
 
        tree.lock_read()
1860
 
        self.addCleanup(tree.unlock)
1861
 
        repo = tree.branch.repository
1862
 
        self.assertIsInstance(repo, RemoteRepository)
1863
 
        # ask for rev_id
1864
 
        repo.get_parent_map([rev_id])
1865
 
        self.reset_smart_call_log()
1866
 
        # Now asking for rev_id's ghost parent should not make calls
1867
 
        self.assertEqual({}, repo.get_parent_map(['non-existant']))
1868
 
        self.assertLength(0, self.hpss_calls)
1869
 
 
1870
 
 
1871
 
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1872
 
 
1873
 
    def test_allows_new_revisions(self):
1874
 
        """get_parent_map's results can be updated by commit."""
1875
 
        smart_server = server.SmartTCPServer_for_testing()
1876
 
        smart_server.setUp()
1877
 
        self.addCleanup(smart_server.tearDown)
1878
 
        self.make_branch('branch')
1879
 
        branch = Branch.open(smart_server.get_url() + '/branch')
1880
 
        tree = branch.create_checkout('tree', lightweight=True)
1881
 
        tree.lock_write()
1882
 
        self.addCleanup(tree.unlock)
1883
 
        graph = tree.branch.repository.get_graph()
1884
 
        # This provides an opportunity for the missing rev-id to be cached.
1885
 
        self.assertEqual({}, graph.get_parent_map(['rev1']))
1886
 
        tree.commit('message', rev_id='rev1')
1887
 
        graph = tree.branch.repository.get_graph()
1888
 
        self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1889
 
 
1890
 
 
1891
 
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1892
 
 
1893
 
    def test_null_revision(self):
1894
 
        # a null revision has the predictable result {}, we should have no wire
1895
 
        # traffic when calling it with this argument
1896
 
        transport_path = 'empty'
1897
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1898
 
        client.add_success_response('notused')
1899
 
        # actual RemoteRepository.get_revision_graph is gone, but there's an
1900
 
        # equivalent private method for testing
1901
 
        result = repo._get_revision_graph(NULL_REVISION)
1902
 
        self.assertEqual([], client._calls)
1903
 
        self.assertEqual({}, result)
1904
 
 
1905
 
    def test_none_revision(self):
1906
 
        # with none we want the entire graph
1907
 
        r1 = u'\u0e33'.encode('utf8')
1908
 
        r2 = u'\u0dab'.encode('utf8')
1909
 
        lines = [' '.join([r2, r1]), r1]
1910
 
        encoded_body = '\n'.join(lines)
1911
 
 
1912
 
        transport_path = 'sinhala'
1913
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1914
 
        client.add_success_response_with_body(encoded_body, 'ok')
1915
 
        # actual RemoteRepository.get_revision_graph is gone, but there's an
1916
 
        # equivalent private method for testing
1917
 
        result = repo._get_revision_graph(None)
1918
 
        self.assertEqual(
1919
 
            [('call_expecting_body', 'Repository.get_revision_graph',
1920
 
             ('sinhala/', ''))],
1921
 
            client._calls)
1922
 
        self.assertEqual({r1: (), r2: (r1, )}, result)
1923
 
 
1924
 
    def test_specific_revision(self):
1925
 
        # with a specific revision we want the graph for that
1926
 
        # with none we want the entire graph
1927
 
        r11 = u'\u0e33'.encode('utf8')
1928
 
        r12 = u'\xc9'.encode('utf8')
1929
 
        r2 = u'\u0dab'.encode('utf8')
1930
 
        lines = [' '.join([r2, r11, r12]), r11, r12]
1931
 
        encoded_body = '\n'.join(lines)
1932
 
 
1933
 
        transport_path = 'sinhala'
1934
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1935
 
        client.add_success_response_with_body(encoded_body, 'ok')
1936
 
        result = repo._get_revision_graph(r2)
1937
 
        self.assertEqual(
1938
 
            [('call_expecting_body', 'Repository.get_revision_graph',
1939
 
             ('sinhala/', r2))],
1940
 
            client._calls)
1941
 
        self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1942
 
 
1943
 
    def test_no_such_revision(self):
1944
 
        revid = '123'
1945
 
        transport_path = 'sinhala'
1946
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1947
 
        client.add_error_response('nosuchrevision', revid)
1948
 
        # also check that the right revision is reported in the error
1949
 
        self.assertRaises(errors.NoSuchRevision,
1950
 
            repo._get_revision_graph, revid)
1951
 
        self.assertEqual(
1952
 
            [('call_expecting_body', 'Repository.get_revision_graph',
1953
 
             ('sinhala/', revid))],
1954
 
            client._calls)
1955
 
 
1956
 
    def test_unexpected_error(self):
1957
 
        revid = '123'
1958
 
        transport_path = 'sinhala'
1959
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1960
 
        client.add_error_response('AnUnexpectedError')
1961
 
        e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1962
 
            repo._get_revision_graph, revid)
1963
 
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1964
 
 
1965
 
 
1966
 
class TestRepositoryIsShared(TestRemoteRepository):
1967
 
 
1968
 
    def test_is_shared(self):
1969
 
        # ('yes', ) for Repository.is_shared -> 'True'.
1970
 
        transport_path = 'quack'
1971
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1972
 
        client.add_success_response('yes')
1973
 
        result = repo.is_shared()
1974
 
        self.assertEqual(
1975
 
            [('call', 'Repository.is_shared', ('quack/',))],
1976
 
            client._calls)
1977
 
        self.assertEqual(True, result)
1978
 
 
1979
 
    def test_is_not_shared(self):
1980
 
        # ('no', ) for Repository.is_shared -> 'False'.
1981
 
        transport_path = 'qwack'
1982
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1983
 
        client.add_success_response('no')
1984
 
        result = repo.is_shared()
1985
 
        self.assertEqual(
1986
 
            [('call', 'Repository.is_shared', ('qwack/',))],
1987
 
            client._calls)
1988
 
        self.assertEqual(False, result)
1989
 
 
1990
 
 
1991
 
class TestRepositoryLockWrite(TestRemoteRepository):
1992
 
 
1993
 
    def test_lock_write(self):
1994
 
        transport_path = 'quack'
1995
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1996
 
        client.add_success_response('ok', 'a token')
1997
 
        result = repo.lock_write()
1998
 
        self.assertEqual(
1999
 
            [('call', 'Repository.lock_write', ('quack/', ''))],
2000
 
            client._calls)
2001
 
        self.assertEqual('a token', result)
2002
 
 
2003
 
    def test_lock_write_already_locked(self):
2004
 
        transport_path = 'quack'
2005
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2006
 
        client.add_error_response('LockContention')
2007
 
        self.assertRaises(errors.LockContention, repo.lock_write)
2008
 
        self.assertEqual(
2009
 
            [('call', 'Repository.lock_write', ('quack/', ''))],
2010
 
            client._calls)
2011
 
 
2012
 
    def test_lock_write_unlockable(self):
2013
 
        transport_path = 'quack'
2014
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2015
 
        client.add_error_response('UnlockableTransport')
2016
 
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
2017
 
        self.assertEqual(
2018
 
            [('call', 'Repository.lock_write', ('quack/', ''))],
2019
 
            client._calls)
2020
 
 
2021
 
 
2022
 
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
2023
 
 
2024
 
    def test_backwards_compat(self):
2025
 
        self.setup_smart_server_with_call_log()
2026
 
        repo = self.make_repository('.')
2027
 
        self.reset_smart_call_log()
2028
 
        verb = 'Repository.set_make_working_trees'
2029
 
        self.disable_verb(verb)
2030
 
        repo.set_make_working_trees(True)
2031
 
        call_count = len([call for call in self.hpss_calls if
2032
 
            call.call.method == verb])
2033
 
        self.assertEqual(1, call_count)
2034
 
 
2035
 
    def test_current(self):
2036
 
        transport_path = 'quack'
2037
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2038
 
        client.add_expected_call(
2039
 
            'Repository.set_make_working_trees', ('quack/', 'True'),
2040
 
            'success', ('ok',))
2041
 
        client.add_expected_call(
2042
 
            'Repository.set_make_working_trees', ('quack/', 'False'),
2043
 
            'success', ('ok',))
2044
 
        repo.set_make_working_trees(True)
2045
 
        repo.set_make_working_trees(False)
2046
 
 
2047
 
 
2048
 
class TestRepositoryUnlock(TestRemoteRepository):
2049
 
 
2050
 
    def test_unlock(self):
2051
 
        transport_path = 'quack'
2052
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2053
 
        client.add_success_response('ok', 'a token')
2054
 
        client.add_success_response('ok')
2055
 
        repo.lock_write()
2056
 
        repo.unlock()
2057
 
        self.assertEqual(
2058
 
            [('call', 'Repository.lock_write', ('quack/', '')),
2059
 
             ('call', 'Repository.unlock', ('quack/', 'a token'))],
2060
 
            client._calls)
2061
 
 
2062
 
    def test_unlock_wrong_token(self):
2063
 
        # If somehow the token is wrong, unlock will raise TokenMismatch.
2064
 
        transport_path = 'quack'
2065
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2066
 
        client.add_success_response('ok', 'a token')
2067
 
        client.add_error_response('TokenMismatch')
2068
 
        repo.lock_write()
2069
 
        self.assertRaises(errors.TokenMismatch, repo.unlock)
2070
 
 
2071
 
 
2072
 
class TestRepositoryHasRevision(TestRemoteRepository):
2073
 
 
2074
 
    def test_none(self):
2075
 
        # repo.has_revision(None) should not cause any traffic.
2076
 
        transport_path = 'quack'
2077
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2078
 
 
2079
 
        # The null revision is always there, so has_revision(None) == True.
2080
 
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
2081
 
 
2082
 
        # The remote repo shouldn't be accessed.
2083
 
        self.assertEqual([], client._calls)
2084
 
 
2085
 
 
2086
 
class TestRepositoryInsertStream(TestRemoteRepository):
2087
 
 
2088
 
    def test_unlocked_repo(self):
2089
 
        transport_path = 'quack'
2090
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2091
 
        client.add_expected_call(
2092
 
            'Repository.insert_stream', ('quack/', ''),
2093
 
            'success', ('ok',))
2094
 
        client.add_expected_call(
2095
 
            'Repository.insert_stream', ('quack/', ''),
2096
 
            'success', ('ok',))
2097
 
        sink = repo._get_sink()
2098
 
        fmt = repository.RepositoryFormat.get_default_format()
2099
 
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2100
 
        self.assertEqual([], resume_tokens)
2101
 
        self.assertEqual(set(), missing_keys)
2102
 
        client.finished_test()
2103
 
 
2104
 
    def test_locked_repo_with_no_lock_token(self):
2105
 
        transport_path = 'quack'
2106
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2107
 
        client.add_expected_call(
2108
 
            'Repository.lock_write', ('quack/', ''),
2109
 
            'success', ('ok', ''))
2110
 
        client.add_expected_call(
2111
 
            'Repository.insert_stream', ('quack/', ''),
2112
 
            'success', ('ok',))
2113
 
        client.add_expected_call(
2114
 
            'Repository.insert_stream', ('quack/', ''),
2115
 
            'success', ('ok',))
2116
 
        repo.lock_write()
2117
 
        sink = repo._get_sink()
2118
 
        fmt = repository.RepositoryFormat.get_default_format()
2119
 
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2120
 
        self.assertEqual([], resume_tokens)
2121
 
        self.assertEqual(set(), missing_keys)
2122
 
        client.finished_test()
2123
 
 
2124
 
    def test_locked_repo_with_lock_token(self):
2125
 
        transport_path = 'quack'
2126
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2127
 
        client.add_expected_call(
2128
 
            'Repository.lock_write', ('quack/', ''),
2129
 
            'success', ('ok', 'a token'))
2130
 
        client.add_expected_call(
2131
 
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2132
 
            'success', ('ok',))
2133
 
        client.add_expected_call(
2134
 
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2135
 
            'success', ('ok',))
2136
 
        repo.lock_write()
2137
 
        sink = repo._get_sink()
2138
 
        fmt = repository.RepositoryFormat.get_default_format()
2139
 
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2140
 
        self.assertEqual([], resume_tokens)
2141
 
        self.assertEqual(set(), missing_keys)
2142
 
        client.finished_test()
2143
 
 
2144
 
 
2145
 
class TestRepositoryTarball(TestRemoteRepository):
2146
 
 
2147
 
    # This is a canned tarball reponse we can validate against
2148
 
    tarball_content = (
2149
 
        'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2150
 
        'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2151
 
        'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2152
 
        'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2153
 
        'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2154
 
        'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2155
 
        'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2156
 
        'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2157
 
        '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2158
 
        'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2159
 
        'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2160
 
        'nWQ7QH/F3JFOFCQ0aSPfA='
2161
 
        ).decode('base64')
2162
 
 
2163
 
    def test_repository_tarball(self):
2164
 
        # Test that Repository.tarball generates the right operations
2165
 
        transport_path = 'repo'
2166
 
        expected_calls = [('call_expecting_body', 'Repository.tarball',
2167
 
                           ('repo/', 'bz2',),),
2168
 
            ]
2169
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2170
 
        client.add_success_response_with_body(self.tarball_content, 'ok')
2171
 
        # Now actually ask for the tarball
2172
 
        tarball_file = repo._get_tarball('bz2')
2173
 
        try:
2174
 
            self.assertEqual(expected_calls, client._calls)
2175
 
            self.assertEqual(self.tarball_content, tarball_file.read())
2176
 
        finally:
2177
 
            tarball_file.close()
2178
 
 
2179
 
 
2180
 
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2181
 
    """RemoteRepository.copy_content_into optimizations"""
2182
 
 
2183
 
    def test_copy_content_remote_to_local(self):
2184
 
        self.transport_server = server.SmartTCPServer_for_testing
2185
 
        src_repo = self.make_repository('repo1')
2186
 
        src_repo = repository.Repository.open(self.get_url('repo1'))
2187
 
        # At the moment the tarball-based copy_content_into can't write back
2188
 
        # into a smart server.  It would be good if it could upload the
2189
 
        # tarball; once that works we'd have to create repositories of
2190
 
        # different formats. -- mbp 20070410
2191
 
        dest_url = self.get_vfs_only_url('repo2')
2192
 
        dest_bzrdir = BzrDir.create(dest_url)
2193
 
        dest_repo = dest_bzrdir.create_repository()
2194
 
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
2195
 
        self.assertTrue(isinstance(src_repo, RemoteRepository))
2196
 
        src_repo.copy_content_into(dest_repo)
2197
 
 
2198
 
 
2199
 
class _StubRealPackRepository(object):
2200
 
 
2201
 
    def __init__(self, calls):
2202
 
        self.calls = calls
2203
 
        self._pack_collection = _StubPackCollection(calls)
2204
 
 
2205
 
    def is_in_write_group(self):
2206
 
        return False
2207
 
 
2208
 
    def refresh_data(self):
2209
 
        self.calls.append(('pack collection reload_pack_names',))
2210
 
 
2211
 
 
2212
 
class _StubPackCollection(object):
2213
 
 
2214
 
    def __init__(self, calls):
2215
 
        self.calls = calls
2216
 
 
2217
 
    def autopack(self):
2218
 
        self.calls.append(('pack collection autopack',))
2219
 
 
2220
 
 
2221
 
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2222
 
    """Tests for RemoteRepository.autopack implementation."""
2223
 
 
2224
 
    def test_ok(self):
2225
 
        """When the server returns 'ok' and there's no _real_repository, then
2226
 
        nothing else happens: the autopack method is done.
2227
 
        """
2228
 
        transport_path = 'quack'
2229
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2230
 
        client.add_expected_call(
2231
 
            'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2232
 
        repo.autopack()
2233
 
        client.finished_test()
2234
 
 
2235
 
    def test_ok_with_real_repo(self):
2236
 
        """When the server returns 'ok' and there is a _real_repository, then
2237
 
        the _real_repository's reload_pack_name's method will be called.
2238
 
        """
2239
 
        transport_path = 'quack'
2240
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2241
 
        client.add_expected_call(
2242
 
            'PackRepository.autopack', ('quack/',),
2243
 
            'success', ('ok',))
2244
 
        repo._real_repository = _StubRealPackRepository(client._calls)
2245
 
        repo.autopack()
2246
 
        self.assertEqual(
2247
 
            [('call', 'PackRepository.autopack', ('quack/',)),
2248
 
             ('pack collection reload_pack_names',)],
2249
 
            client._calls)
2250
 
 
2251
 
    def test_backwards_compatibility(self):
2252
 
        """If the server does not recognise the PackRepository.autopack verb,
2253
 
        fallback to the real_repository's implementation.
2254
 
        """
2255
 
        transport_path = 'quack'
2256
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2257
 
        client.add_unknown_method_response('PackRepository.autopack')
2258
 
        def stub_ensure_real():
2259
 
            client._calls.append(('_ensure_real',))
2260
 
            repo._real_repository = _StubRealPackRepository(client._calls)
2261
 
        repo._ensure_real = stub_ensure_real
2262
 
        repo.autopack()
2263
 
        self.assertEqual(
2264
 
            [('call', 'PackRepository.autopack', ('quack/',)),
2265
 
             ('_ensure_real',),
2266
 
             ('pack collection autopack',)],
2267
 
            client._calls)
2268
 
 
2269
 
 
2270
 
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2271
 
    """Base class for unit tests for bzrlib.remote._translate_error."""
2272
 
 
2273
 
    def translateTuple(self, error_tuple, **context):
2274
 
        """Call _translate_error with an ErrorFromSmartServer built from the
2275
 
        given error_tuple.
2276
 
 
2277
 
        :param error_tuple: A tuple of a smart server response, as would be
2278
 
            passed to an ErrorFromSmartServer.
2279
 
        :kwargs context: context items to call _translate_error with.
2280
 
 
2281
 
        :returns: The error raised by _translate_error.
2282
 
        """
2283
 
        # Raise the ErrorFromSmartServer before passing it as an argument,
2284
 
        # because _translate_error may need to re-raise it with a bare 'raise'
2285
 
        # statement.
2286
 
        server_error = errors.ErrorFromSmartServer(error_tuple)
2287
 
        translated_error = self.translateErrorFromSmartServer(
2288
 
            server_error, **context)
2289
 
        return translated_error
2290
 
 
2291
 
    def translateErrorFromSmartServer(self, error_object, **context):
2292
 
        """Like translateTuple, but takes an already constructed
2293
 
        ErrorFromSmartServer rather than a tuple.
2294
 
        """
2295
 
        try:
2296
 
            raise error_object
2297
 
        except errors.ErrorFromSmartServer, server_error:
2298
 
            translated_error = self.assertRaises(
2299
 
                errors.BzrError, remote._translate_error, server_error,
2300
 
                **context)
2301
 
        return translated_error
2302
 
 
2303
 
 
2304
 
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2305
 
    """Unit tests for bzrlib.remote._translate_error.
2306
 
 
2307
 
    Given an ErrorFromSmartServer (which has an error tuple from a smart
2308
 
    server) and some context, _translate_error raises more specific errors from
2309
 
    bzrlib.errors.
2310
 
 
2311
 
    This test case covers the cases where _translate_error succeeds in
2312
 
    translating an ErrorFromSmartServer to something better.  See
2313
 
    TestErrorTranslationRobustness for other cases.
2314
 
    """
2315
 
 
2316
 
    def test_NoSuchRevision(self):
2317
 
        branch = self.make_branch('')
2318
 
        revid = 'revid'
2319
 
        translated_error = self.translateTuple(
2320
 
            ('NoSuchRevision', revid), branch=branch)
2321
 
        expected_error = errors.NoSuchRevision(branch, revid)
2322
 
        self.assertEqual(expected_error, translated_error)
2323
 
 
2324
 
    def test_nosuchrevision(self):
2325
 
        repository = self.make_repository('')
2326
 
        revid = 'revid'
2327
 
        translated_error = self.translateTuple(
2328
 
            ('nosuchrevision', revid), repository=repository)
2329
 
        expected_error = errors.NoSuchRevision(repository, revid)
2330
 
        self.assertEqual(expected_error, translated_error)
2331
 
 
2332
 
    def test_nobranch(self):
2333
 
        bzrdir = self.make_bzrdir('')
2334
 
        translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2335
 
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2336
 
        self.assertEqual(expected_error, translated_error)
2337
 
 
2338
 
    def test_LockContention(self):
2339
 
        translated_error = self.translateTuple(('LockContention',))
2340
 
        expected_error = errors.LockContention('(remote lock)')
2341
 
        self.assertEqual(expected_error, translated_error)
2342
 
 
2343
 
    def test_UnlockableTransport(self):
2344
 
        bzrdir = self.make_bzrdir('')
2345
 
        translated_error = self.translateTuple(
2346
 
            ('UnlockableTransport',), bzrdir=bzrdir)
2347
 
        expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2348
 
        self.assertEqual(expected_error, translated_error)
2349
 
 
2350
 
    def test_LockFailed(self):
2351
 
        lock = 'str() of a server lock'
2352
 
        why = 'str() of why'
2353
 
        translated_error = self.translateTuple(('LockFailed', lock, why))
2354
 
        expected_error = errors.LockFailed(lock, why)
2355
 
        self.assertEqual(expected_error, translated_error)
2356
 
 
2357
 
    def test_TokenMismatch(self):
2358
 
        token = 'a lock token'
2359
 
        translated_error = self.translateTuple(('TokenMismatch',), token=token)
2360
 
        expected_error = errors.TokenMismatch(token, '(remote token)')
2361
 
        self.assertEqual(expected_error, translated_error)
2362
 
 
2363
 
    def test_Diverged(self):
2364
 
        branch = self.make_branch('a')
2365
 
        other_branch = self.make_branch('b')
2366
 
        translated_error = self.translateTuple(
2367
 
            ('Diverged',), branch=branch, other_branch=other_branch)
2368
 
        expected_error = errors.DivergedBranches(branch, other_branch)
2369
 
        self.assertEqual(expected_error, translated_error)
2370
 
 
2371
 
    def test_ReadError_no_args(self):
2372
 
        path = 'a path'
2373
 
        translated_error = self.translateTuple(('ReadError',), path=path)
2374
 
        expected_error = errors.ReadError(path)
2375
 
        self.assertEqual(expected_error, translated_error)
2376
 
 
2377
 
    def test_ReadError(self):
2378
 
        path = 'a path'
2379
 
        translated_error = self.translateTuple(('ReadError', path))
2380
 
        expected_error = errors.ReadError(path)
2381
 
        self.assertEqual(expected_error, translated_error)
2382
 
 
2383
 
    def test_PermissionDenied_no_args(self):
2384
 
        path = 'a path'
2385
 
        translated_error = self.translateTuple(('PermissionDenied',), path=path)
2386
 
        expected_error = errors.PermissionDenied(path)
2387
 
        self.assertEqual(expected_error, translated_error)
2388
 
 
2389
 
    def test_PermissionDenied_one_arg(self):
2390
 
        path = 'a path'
2391
 
        translated_error = self.translateTuple(('PermissionDenied', path))
2392
 
        expected_error = errors.PermissionDenied(path)
2393
 
        self.assertEqual(expected_error, translated_error)
2394
 
 
2395
 
    def test_PermissionDenied_one_arg_and_context(self):
2396
 
        """Given a choice between a path from the local context and a path on
2397
 
        the wire, _translate_error prefers the path from the local context.
2398
 
        """
2399
 
        local_path = 'local path'
2400
 
        remote_path = 'remote path'
2401
 
        translated_error = self.translateTuple(
2402
 
            ('PermissionDenied', remote_path), path=local_path)
2403
 
        expected_error = errors.PermissionDenied(local_path)
2404
 
        self.assertEqual(expected_error, translated_error)
2405
 
 
2406
 
    def test_PermissionDenied_two_args(self):
2407
 
        path = 'a path'
2408
 
        extra = 'a string with extra info'
2409
 
        translated_error = self.translateTuple(
2410
 
            ('PermissionDenied', path, extra))
2411
 
        expected_error = errors.PermissionDenied(path, extra)
2412
 
        self.assertEqual(expected_error, translated_error)
2413
 
 
2414
 
 
2415
 
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2416
 
    """Unit tests for bzrlib.remote._translate_error's robustness.
2417
 
 
2418
 
    TestErrorTranslationSuccess is for cases where _translate_error can
2419
 
    translate successfully.  This class about how _translate_err behaves when
2420
 
    it fails to translate: it re-raises the original error.
2421
 
    """
2422
 
 
2423
 
    def test_unrecognised_server_error(self):
2424
 
        """If the error code from the server is not recognised, the original
2425
 
        ErrorFromSmartServer is propagated unmodified.
2426
 
        """
2427
 
        error_tuple = ('An unknown error tuple',)
2428
 
        server_error = errors.ErrorFromSmartServer(error_tuple)
2429
 
        translated_error = self.translateErrorFromSmartServer(server_error)
2430
 
        expected_error = errors.UnknownErrorFromSmartServer(server_error)
2431
 
        self.assertEqual(expected_error, translated_error)
2432
 
 
2433
 
    def test_context_missing_a_key(self):
2434
 
        """In case of a bug in the client, or perhaps an unexpected response
2435
 
        from a server, _translate_error returns the original error tuple from
2436
 
        the server and mutters a warning.
2437
 
        """
2438
 
        # To translate a NoSuchRevision error _translate_error needs a 'branch'
2439
 
        # in the context dict.  So let's give it an empty context dict instead
2440
 
        # to exercise its error recovery.
2441
 
        empty_context = {}
2442
 
        error_tuple = ('NoSuchRevision', 'revid')
2443
 
        server_error = errors.ErrorFromSmartServer(error_tuple)
2444
 
        translated_error = self.translateErrorFromSmartServer(server_error)
2445
 
        self.assertEqual(server_error, translated_error)
2446
 
        # In addition to re-raising ErrorFromSmartServer, some debug info has
2447
 
        # been muttered to the log file for developer to look at.
2448
 
        self.assertContainsRe(
2449
 
            self._get_log(keep_log_file=True),
2450
 
            "Missing key 'branch' in context")
2451
 
 
2452
 
    def test_path_missing(self):
2453
 
        """Some translations (PermissionDenied, ReadError) can determine the
2454
 
        'path' variable from either the wire or the local context.  If neither
2455
 
        has it, then an error is raised.
2456
 
        """
2457
 
        error_tuple = ('ReadError',)
2458
 
        server_error = errors.ErrorFromSmartServer(error_tuple)
2459
 
        translated_error = self.translateErrorFromSmartServer(server_error)
2460
 
        self.assertEqual(server_error, translated_error)
2461
 
        # In addition to re-raising ErrorFromSmartServer, some debug info has
2462
 
        # been muttered to the log file for developer to look at.
2463
 
        self.assertContainsRe(
2464
 
            self._get_log(keep_log_file=True), "Missing key 'path' in context")
2465
 
 
2466
 
 
2467
 
class TestStacking(tests.TestCaseWithTransport):
2468
 
    """Tests for operations on stacked remote repositories.
2469
 
 
2470
 
    The underlying format type must support stacking.
2471
 
    """
2472
 
 
2473
 
    def test_access_stacked_remote(self):
2474
 
        # based on <http://launchpad.net/bugs/261315>
2475
 
        # make a branch stacked on another repository containing an empty
2476
 
        # revision, then open it over hpss - we should be able to see that
2477
 
        # revision.
2478
 
        base_transport = self.get_transport()
2479
 
        base_builder = self.make_branch_builder('base', format='1.9')
2480
 
        base_builder.start_series()
2481
 
        base_revid = base_builder.build_snapshot('rev-id', None,
2482
 
            [('add', ('', None, 'directory', None))],
2483
 
            'message')
2484
 
        base_builder.finish_series()
2485
 
        stacked_branch = self.make_branch('stacked', format='1.9')
2486
 
        stacked_branch.set_stacked_on_url('../base')
2487
 
        # start a server looking at this
2488
 
        smart_server = server.SmartTCPServer_for_testing()
2489
 
        smart_server.setUp()
2490
 
        self.addCleanup(smart_server.tearDown)
2491
 
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2492
 
        # can get its branch and repository
2493
 
        remote_branch = remote_bzrdir.open_branch()
2494
 
        remote_repo = remote_branch.repository
2495
 
        remote_repo.lock_read()
2496
 
        try:
2497
 
            # it should have an appropriate fallback repository, which should also
2498
 
            # be a RemoteRepository
2499
 
            self.assertLength(1, remote_repo._fallback_repositories)
2500
 
            self.assertIsInstance(remote_repo._fallback_repositories[0],
2501
 
                RemoteRepository)
2502
 
            # and it has the revision committed to the underlying repository;
2503
 
            # these have varying implementations so we try several of them
2504
 
            self.assertTrue(remote_repo.has_revisions([base_revid]))
2505
 
            self.assertTrue(remote_repo.has_revision(base_revid))
2506
 
            self.assertEqual(remote_repo.get_revision(base_revid).message,
2507
 
                'message')
2508
 
        finally:
2509
 
            remote_repo.unlock()
2510
 
 
2511
 
    def prepare_stacked_remote_branch(self):
2512
 
        """Get stacked_upon and stacked branches with content in each."""
2513
 
        self.setup_smart_server_with_call_log()
2514
 
        tree1 = self.make_branch_and_tree('tree1', format='1.9')
2515
 
        tree1.commit('rev1', rev_id='rev1')
2516
 
        tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2517
 
            ).open_workingtree()
2518
 
        tree2.commit('local changes make me feel good.')
2519
 
        branch2 = Branch.open(self.get_url('tree2'))
2520
 
        branch2.lock_read()
2521
 
        self.addCleanup(branch2.unlock)
2522
 
        return tree1.branch, branch2
2523
 
 
2524
 
    def test_stacked_get_parent_map(self):
2525
 
        # the public implementation of get_parent_map obeys stacking
2526
 
        _, branch = self.prepare_stacked_remote_branch()
2527
 
        repo = branch.repository
2528
 
        self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2529
 
 
2530
 
    def test_unstacked_get_parent_map(self):
2531
 
        # _unstacked_provider.get_parent_map ignores stacking
2532
 
        _, branch = self.prepare_stacked_remote_branch()
2533
 
        provider = branch.repository._unstacked_provider
2534
 
        self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2535
 
 
2536
 
    def fetch_stream_to_rev_order(self, stream):
2537
 
        result = []
2538
 
        for kind, substream in stream:
2539
 
            if not kind == 'revisions':
2540
 
                list(substream)
2541
 
            else:
2542
 
                for content in substream:
2543
 
                    result.append(content.key[-1])
2544
 
        return result
2545
 
 
2546
 
    def get_ordered_revs(self, format, order):
2547
 
        """Get a list of the revisions in a stream to format format.
2548
 
 
2549
 
        :param format: The format of the target.
2550
 
        :param order: the order that target should have requested.
2551
 
        :result: The revision ids in the stream, in the order seen,
2552
 
            the topological order of revisions in the source.
2553
 
        """
2554
 
        unordered_format = bzrdir.format_registry.get(format)()
2555
 
        target_repository_format = unordered_format.repository_format
2556
 
        # Cross check
2557
 
        self.assertEqual(order, target_repository_format._fetch_order)
2558
 
        trunk, stacked = self.prepare_stacked_remote_branch()
2559
 
        source = stacked.repository._get_source(target_repository_format)
2560
 
        tip = stacked.last_revision()
2561
 
        revs = stacked.repository.get_ancestry(tip)
2562
 
        search = graph.PendingAncestryResult([tip], stacked.repository)
2563
 
        self.reset_smart_call_log()
2564
 
        stream = source.get_stream(search)
2565
 
        if None in revs:
2566
 
            revs.remove(None)
2567
 
        # We trust that if a revision is in the stream the rest of the new
2568
 
        # content for it is too, as per our main fetch tests; here we are
2569
 
        # checking that the revisions are actually included at all, and their
2570
 
        # order.
2571
 
        return self.fetch_stream_to_rev_order(stream), revs
2572
 
 
2573
 
    def test_stacked_get_stream_unordered(self):
2574
 
        # Repository._get_source.get_stream() from a stacked repository with
2575
 
        # unordered yields the full data from both stacked and stacked upon
2576
 
        # sources.
2577
 
        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2578
 
        self.assertEqual(set(expected_revs), set(rev_ord))
2579
 
        # Getting unordered results should have made a streaming data request
2580
 
        # from the server, then one from the backing branch.
2581
 
        self.assertLength(2, self.hpss_calls)
2582
 
 
2583
 
    def test_stacked_get_stream_topological(self):
2584
 
        # Repository._get_source.get_stream() from a stacked repository with
2585
 
        # topological sorting yields the full data from both stacked and
2586
 
        # stacked upon sources in topological order.
2587
 
        rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2588
 
        self.assertEqual(expected_revs, rev_ord)
2589
 
        # Getting topological sort requires VFS calls still
2590
 
        self.assertLength(12, self.hpss_calls)
2591
 
 
2592
 
    def test_stacked_get_stream_groupcompress(self):
2593
 
        # Repository._get_source.get_stream() from a stacked repository with
2594
 
        # groupcompress sorting yields the full data from both stacked and
2595
 
        # stacked upon sources in groupcompress order.
2596
 
        raise tests.TestSkipped('No groupcompress ordered format available')
2597
 
        rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2598
 
        self.assertEqual(expected_revs, reversed(rev_ord))
2599
 
        # Getting unordered results should have made a streaming data request
2600
 
        # from the backing branch, and one from the stacked on branch.
2601
 
        self.assertLength(2, self.hpss_calls)
2602
 
 
2603
 
 
2604
 
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2605
 
 
2606
 
    def setUp(self):
2607
 
        super(TestRemoteBranchEffort, self).setUp()
2608
 
        # Create a smart server that publishes whatever the backing VFS server
2609
 
        # does.
2610
 
        self.smart_server = server.SmartTCPServer_for_testing()
2611
 
        self.smart_server.setUp(self.get_server())
2612
 
        self.addCleanup(self.smart_server.tearDown)
2613
 
        # Log all HPSS calls into self.hpss_calls.
2614
 
        _SmartClient.hooks.install_named_hook(
2615
 
            'call', self.capture_hpss_call, None)
2616
 
        self.hpss_calls = []
2617
 
 
2618
 
    def capture_hpss_call(self, params):
2619
 
        self.hpss_calls.append(params.method)
2620
 
 
2621
 
    def test_copy_content_into_avoids_revision_history(self):
2622
 
        local = self.make_branch('local')
2623
 
        remote_backing_tree = self.make_branch_and_tree('remote')
2624
 
        remote_backing_tree.commit("Commit.")
2625
 
        remote_branch_url = self.smart_server.get_url() + 'remote'
2626
 
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2627
 
        local.repository.fetch(remote_branch.repository)
2628
 
        self.hpss_calls = []
2629
 
        remote_branch.copy_content_into(local)
2630
 
        self.assertFalse('Branch.revision_history' in self.hpss_calls)