~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

Fix commit message template for non-ascii files, and add test for handling of
non-unicode.

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 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 RemoteBranchTestCase(TestRemote):
773
 
 
774
 
    def make_remote_branch(self, transport, client):
775
 
        """Make a RemoteBranch using 'client' as its _SmartClient.
776
 
 
777
 
        A RemoteBzrDir and RemoteRepository will also be created to fill out
778
 
        the RemoteBranch, albeit with stub values for some of their attributes.
779
 
        """
780
 
        # we do not want bzrdir to make any remote calls, so use False as its
781
 
        # _client.  If it tries to make a remote call, this will fail
782
 
        # immediately.
783
 
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
784
 
            _client=False)
785
 
        repo = RemoteRepository(bzrdir, None, _client=client)
786
 
        branch_format = self.get_branch_format()
787
 
        format = RemoteBranchFormat(network_name=branch_format.network_name())
788
 
        return RemoteBranch(bzrdir, repo, _client=client, format=format)
789
 
 
790
 
 
791
 
class TestBranchGetParent(RemoteBranchTestCase):
792
 
 
793
 
    def test_no_parent(self):
794
 
        # in an empty branch we decode the response properly
795
 
        transport = MemoryTransport()
796
 
        client = FakeClient(transport.base)
797
 
        client.add_expected_call(
798
 
            'Branch.get_stacked_on_url', ('quack/',),
799
 
            'error', ('NotStacked',))
800
 
        client.add_expected_call(
801
 
            'Branch.get_parent', ('quack/',),
802
 
            'success', ('',))
803
 
        transport.mkdir('quack')
804
 
        transport = transport.clone('quack')
805
 
        branch = self.make_remote_branch(transport, client)
806
 
        result = branch.get_parent()
807
 
        client.finished_test()
808
 
        self.assertEqual(None, result)
809
 
 
810
 
    def test_parent_relative(self):
811
 
        transport = MemoryTransport()
812
 
        client = FakeClient(transport.base)
813
 
        client.add_expected_call(
814
 
            'Branch.get_stacked_on_url', ('kwaak/',),
815
 
            'error', ('NotStacked',))
816
 
        client.add_expected_call(
817
 
            'Branch.get_parent', ('kwaak/',),
818
 
            'success', ('../foo/',))
819
 
        transport.mkdir('kwaak')
820
 
        transport = transport.clone('kwaak')
821
 
        branch = self.make_remote_branch(transport, client)
822
 
        result = branch.get_parent()
823
 
        self.assertEqual(transport.clone('../foo').base, result)
824
 
 
825
 
    def test_parent_absolute(self):
826
 
        transport = MemoryTransport()
827
 
        client = FakeClient(transport.base)
828
 
        client.add_expected_call(
829
 
            'Branch.get_stacked_on_url', ('kwaak/',),
830
 
            'error', ('NotStacked',))
831
 
        client.add_expected_call(
832
 
            'Branch.get_parent', ('kwaak/',),
833
 
            'success', ('http://foo/',))
834
 
        transport.mkdir('kwaak')
835
 
        transport = transport.clone('kwaak')
836
 
        branch = self.make_remote_branch(transport, client)
837
 
        result = branch.get_parent()
838
 
        self.assertEqual('http://foo/', result)
839
 
 
840
 
 
841
 
class TestBranchGetTagsBytes(RemoteBranchTestCase):
842
 
 
843
 
    def test_backwards_compat(self):
844
 
        self.setup_smart_server_with_call_log()
845
 
        branch = self.make_branch('.')
846
 
        self.reset_smart_call_log()
847
 
        verb = 'Branch.get_tags_bytes'
848
 
        self.disable_verb(verb)
849
 
        branch.tags.get_tag_dict()
850
 
        call_count = len([call for call in self.hpss_calls if
851
 
            call.call.method == verb])
852
 
        self.assertEqual(1, call_count)
853
 
 
854
 
    def test_trivial(self):
855
 
        transport = MemoryTransport()
856
 
        client = FakeClient(transport.base)
857
 
        client.add_expected_call(
858
 
            'Branch.get_stacked_on_url', ('quack/',),
859
 
            'error', ('NotStacked',))
860
 
        client.add_expected_call(
861
 
            'Branch.get_tags_bytes', ('quack/',),
862
 
            'success', ('',))
863
 
        transport.mkdir('quack')
864
 
        transport = transport.clone('quack')
865
 
        branch = self.make_remote_branch(transport, client)
866
 
        result = branch.tags.get_tag_dict()
867
 
        client.finished_test()
868
 
        self.assertEqual({}, result)
869
 
 
870
 
 
871
 
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
872
 
 
873
 
    def test_empty_branch(self):
874
 
        # in an empty branch we decode the response properly
875
 
        transport = MemoryTransport()
876
 
        client = FakeClient(transport.base)
877
 
        client.add_expected_call(
878
 
            'Branch.get_stacked_on_url', ('quack/',),
879
 
            'error', ('NotStacked',))
880
 
        client.add_expected_call(
881
 
            'Branch.last_revision_info', ('quack/',),
882
 
            'success', ('ok', '0', 'null:'))
883
 
        transport.mkdir('quack')
884
 
        transport = transport.clone('quack')
885
 
        branch = self.make_remote_branch(transport, client)
886
 
        result = branch.last_revision_info()
887
 
        client.finished_test()
888
 
        self.assertEqual((0, NULL_REVISION), result)
889
 
 
890
 
    def test_non_empty_branch(self):
891
 
        # in a non-empty branch we also decode the response properly
892
 
        revid = u'\xc8'.encode('utf8')
893
 
        transport = MemoryTransport()
894
 
        client = FakeClient(transport.base)
895
 
        client.add_expected_call(
896
 
            'Branch.get_stacked_on_url', ('kwaak/',),
897
 
            'error', ('NotStacked',))
898
 
        client.add_expected_call(
899
 
            'Branch.last_revision_info', ('kwaak/',),
900
 
            'success', ('ok', '2', revid))
901
 
        transport.mkdir('kwaak')
902
 
        transport = transport.clone('kwaak')
903
 
        branch = self.make_remote_branch(transport, client)
904
 
        result = branch.last_revision_info()
905
 
        self.assertEqual((2, revid), result)
906
 
 
907
 
 
908
 
class TestBranch_get_stacked_on_url(TestRemote):
909
 
    """Test Branch._get_stacked_on_url rpc"""
910
 
 
911
 
    def test_get_stacked_on_invalid_url(self):
912
 
        # test that asking for a stacked on url the server can't access works.
913
 
        # This isn't perfect, but then as we're in the same process there
914
 
        # really isn't anything we can do to be 100% sure that the server
915
 
        # doesn't just open in - this test probably needs to be rewritten using
916
 
        # a spawn()ed server.
917
 
        stacked_branch = self.make_branch('stacked', format='1.9')
918
 
        memory_branch = self.make_branch('base', format='1.9')
919
 
        vfs_url = self.get_vfs_only_url('base')
920
 
        stacked_branch.set_stacked_on_url(vfs_url)
921
 
        transport = stacked_branch.bzrdir.root_transport
922
 
        client = FakeClient(transport.base)
923
 
        client.add_expected_call(
924
 
            'Branch.get_stacked_on_url', ('stacked/',),
925
 
            'success', ('ok', vfs_url))
926
 
        # XXX: Multiple calls are bad, this second call documents what is
927
 
        # today.
928
 
        client.add_expected_call(
929
 
            'Branch.get_stacked_on_url', ('stacked/',),
930
 
            'success', ('ok', vfs_url))
931
 
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
932
 
            _client=client)
933
 
        repo_fmt = remote.RemoteRepositoryFormat()
934
 
        repo_fmt._custom_format = stacked_branch.repository._format
935
 
        branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
936
 
            _client=client)
937
 
        result = branch.get_stacked_on_url()
938
 
        self.assertEqual(vfs_url, result)
939
 
 
940
 
    def test_backwards_compatible(self):
941
 
        # like with bzr1.6 with no Branch.get_stacked_on_url rpc
942
 
        base_branch = self.make_branch('base', format='1.6')
943
 
        stacked_branch = self.make_branch('stacked', format='1.6')
944
 
        stacked_branch.set_stacked_on_url('../base')
945
 
        client = FakeClient(self.get_url())
946
 
        branch_network_name = self.get_branch_format().network_name()
947
 
        client.add_expected_call(
948
 
            'BzrDir.open_branchV2', ('stacked/',),
949
 
            'success', ('branch', branch_network_name))
950
 
        client.add_expected_call(
951
 
            'BzrDir.find_repositoryV3', ('stacked/',),
952
 
            'success', ('ok', '', 'no', 'no', 'yes',
953
 
                stacked_branch.repository._format.network_name()))
954
 
        # called twice, once from constructor and then again by us
955
 
        client.add_expected_call(
956
 
            'Branch.get_stacked_on_url', ('stacked/',),
957
 
            'unknown', ('Branch.get_stacked_on_url',))
958
 
        client.add_expected_call(
959
 
            'Branch.get_stacked_on_url', ('stacked/',),
960
 
            'unknown', ('Branch.get_stacked_on_url',))
961
 
        # this will also do vfs access, but that goes direct to the transport
962
 
        # and isn't seen by the FakeClient.
963
 
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
964
 
            remote.RemoteBzrDirFormat(), _client=client)
965
 
        branch = bzrdir.open_branch()
966
 
        result = branch.get_stacked_on_url()
967
 
        self.assertEqual('../base', result)
968
 
        client.finished_test()
969
 
        # it's in the fallback list both for the RemoteRepository and its vfs
970
 
        # repository
971
 
        self.assertEqual(1, len(branch.repository._fallback_repositories))
972
 
        self.assertEqual(1,
973
 
            len(branch.repository._real_repository._fallback_repositories))
974
 
 
975
 
    def test_get_stacked_on_real_branch(self):
976
 
        base_branch = self.make_branch('base', format='1.6')
977
 
        stacked_branch = self.make_branch('stacked', format='1.6')
978
 
        stacked_branch.set_stacked_on_url('../base')
979
 
        reference_format = self.get_repo_format()
980
 
        network_name = reference_format.network_name()
981
 
        client = FakeClient(self.get_url())
982
 
        branch_network_name = self.get_branch_format().network_name()
983
 
        client.add_expected_call(
984
 
            'BzrDir.open_branchV2', ('stacked/',),
985
 
            'success', ('branch', branch_network_name))
986
 
        client.add_expected_call(
987
 
            'BzrDir.find_repositoryV3', ('stacked/',),
988
 
            'success', ('ok', '', 'no', 'no', 'yes', network_name))
989
 
        # called twice, once from constructor and then again by us
990
 
        client.add_expected_call(
991
 
            'Branch.get_stacked_on_url', ('stacked/',),
992
 
            'success', ('ok', '../base'))
993
 
        client.add_expected_call(
994
 
            'Branch.get_stacked_on_url', ('stacked/',),
995
 
            'success', ('ok', '../base'))
996
 
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
997
 
            remote.RemoteBzrDirFormat(), _client=client)
998
 
        branch = bzrdir.open_branch()
999
 
        result = branch.get_stacked_on_url()
1000
 
        self.assertEqual('../base', result)
1001
 
        client.finished_test()
1002
 
        # it's in the fallback list both for the RemoteRepository.
1003
 
        self.assertEqual(1, len(branch.repository._fallback_repositories))
1004
 
        # And we haven't had to construct a real repository.
1005
 
        self.assertEqual(None, branch.repository._real_repository)
1006
 
 
1007
 
 
1008
 
class TestBranchSetLastRevision(RemoteBranchTestCase):
1009
 
 
1010
 
    def test_set_empty(self):
1011
 
        # set_revision_history([]) is translated to calling
1012
 
        # Branch.set_last_revision(path, '') on the wire.
1013
 
        transport = MemoryTransport()
1014
 
        transport.mkdir('branch')
1015
 
        transport = transport.clone('branch')
1016
 
 
1017
 
        client = FakeClient(transport.base)
1018
 
        client.add_expected_call(
1019
 
            'Branch.get_stacked_on_url', ('branch/',),
1020
 
            'error', ('NotStacked',))
1021
 
        client.add_expected_call(
1022
 
            'Branch.lock_write', ('branch/', '', ''),
1023
 
            'success', ('ok', 'branch token', 'repo token'))
1024
 
        client.add_expected_call(
1025
 
            'Branch.last_revision_info',
1026
 
            ('branch/',),
1027
 
            'success', ('ok', '0', 'null:'))
1028
 
        client.add_expected_call(
1029
 
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
1030
 
            'success', ('ok',))
1031
 
        client.add_expected_call(
1032
 
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1033
 
            'success', ('ok',))
1034
 
        branch = self.make_remote_branch(transport, client)
1035
 
        # This is a hack to work around the problem that RemoteBranch currently
1036
 
        # unnecessarily invokes _ensure_real upon a call to lock_write.
1037
 
        branch._ensure_real = lambda: None
1038
 
        branch.lock_write()
1039
 
        result = branch.set_revision_history([])
1040
 
        branch.unlock()
1041
 
        self.assertEqual(None, result)
1042
 
        client.finished_test()
1043
 
 
1044
 
    def test_set_nonempty(self):
1045
 
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
1046
 
        # Branch.set_last_revision(path, rev-idN) on the wire.
1047
 
        transport = MemoryTransport()
1048
 
        transport.mkdir('branch')
1049
 
        transport = transport.clone('branch')
1050
 
 
1051
 
        client = FakeClient(transport.base)
1052
 
        client.add_expected_call(
1053
 
            'Branch.get_stacked_on_url', ('branch/',),
1054
 
            'error', ('NotStacked',))
1055
 
        client.add_expected_call(
1056
 
            'Branch.lock_write', ('branch/', '', ''),
1057
 
            'success', ('ok', 'branch token', 'repo token'))
1058
 
        client.add_expected_call(
1059
 
            'Branch.last_revision_info',
1060
 
            ('branch/',),
1061
 
            'success', ('ok', '0', 'null:'))
1062
 
        lines = ['rev-id2']
1063
 
        encoded_body = bz2.compress('\n'.join(lines))
1064
 
        client.add_success_response_with_body(encoded_body, 'ok')
1065
 
        client.add_expected_call(
1066
 
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
1067
 
            'success', ('ok',))
1068
 
        client.add_expected_call(
1069
 
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1070
 
            'success', ('ok',))
1071
 
        branch = self.make_remote_branch(transport, client)
1072
 
        # This is a hack to work around the problem that RemoteBranch currently
1073
 
        # unnecessarily invokes _ensure_real upon a call to lock_write.
1074
 
        branch._ensure_real = lambda: None
1075
 
        # Lock the branch, reset the record of remote calls.
1076
 
        branch.lock_write()
1077
 
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
1078
 
        branch.unlock()
1079
 
        self.assertEqual(None, result)
1080
 
        client.finished_test()
1081
 
 
1082
 
    def test_no_such_revision(self):
1083
 
        transport = MemoryTransport()
1084
 
        transport.mkdir('branch')
1085
 
        transport = transport.clone('branch')
1086
 
        # A response of 'NoSuchRevision' is translated into an exception.
1087
 
        client = FakeClient(transport.base)
1088
 
        client.add_expected_call(
1089
 
            'Branch.get_stacked_on_url', ('branch/',),
1090
 
            'error', ('NotStacked',))
1091
 
        client.add_expected_call(
1092
 
            'Branch.lock_write', ('branch/', '', ''),
1093
 
            'success', ('ok', 'branch token', 'repo token'))
1094
 
        client.add_expected_call(
1095
 
            'Branch.last_revision_info',
1096
 
            ('branch/',),
1097
 
            'success', ('ok', '0', 'null:'))
1098
 
        # get_graph calls to construct the revision history, for the set_rh
1099
 
        # hook
1100
 
        lines = ['rev-id']
1101
 
        encoded_body = bz2.compress('\n'.join(lines))
1102
 
        client.add_success_response_with_body(encoded_body, 'ok')
1103
 
        client.add_expected_call(
1104
 
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1105
 
            'error', ('NoSuchRevision', 'rev-id'))
1106
 
        client.add_expected_call(
1107
 
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1108
 
            'success', ('ok',))
1109
 
 
1110
 
        branch = self.make_remote_branch(transport, client)
1111
 
        branch.lock_write()
1112
 
        self.assertRaises(
1113
 
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
1114
 
        branch.unlock()
1115
 
        client.finished_test()
1116
 
 
1117
 
    def test_tip_change_rejected(self):
1118
 
        """TipChangeRejected responses cause a TipChangeRejected exception to
1119
 
        be raised.
1120
 
        """
1121
 
        transport = MemoryTransport()
1122
 
        transport.mkdir('branch')
1123
 
        transport = transport.clone('branch')
1124
 
        client = FakeClient(transport.base)
1125
 
        rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
1126
 
        rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
1127
 
        client.add_expected_call(
1128
 
            'Branch.get_stacked_on_url', ('branch/',),
1129
 
            'error', ('NotStacked',))
1130
 
        client.add_expected_call(
1131
 
            'Branch.lock_write', ('branch/', '', ''),
1132
 
            'success', ('ok', 'branch token', 'repo token'))
1133
 
        client.add_expected_call(
1134
 
            'Branch.last_revision_info',
1135
 
            ('branch/',),
1136
 
            'success', ('ok', '0', 'null:'))
1137
 
        lines = ['rev-id']
1138
 
        encoded_body = bz2.compress('\n'.join(lines))
1139
 
        client.add_success_response_with_body(encoded_body, 'ok')
1140
 
        client.add_expected_call(
1141
 
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
1142
 
            'error', ('TipChangeRejected', rejection_msg_utf8))
1143
 
        client.add_expected_call(
1144
 
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
1145
 
            'success', ('ok',))
1146
 
        branch = self.make_remote_branch(transport, client)
1147
 
        branch._ensure_real = lambda: None
1148
 
        branch.lock_write()
1149
 
        # The 'TipChangeRejected' error response triggered by calling
1150
 
        # set_revision_history causes a TipChangeRejected exception.
1151
 
        err = self.assertRaises(
1152
 
            errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
1153
 
        # The UTF-8 message from the response has been decoded into a unicode
1154
 
        # object.
1155
 
        self.assertIsInstance(err.msg, unicode)
1156
 
        self.assertEqual(rejection_msg_unicode, err.msg)
1157
 
        branch.unlock()
1158
 
        client.finished_test()
1159
 
 
1160
 
 
1161
 
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
1162
 
 
1163
 
    def test_set_last_revision_info(self):
1164
 
        # set_last_revision_info(num, 'rev-id') is translated to calling
1165
 
        # Branch.set_last_revision_info(num, 'rev-id') on the wire.
1166
 
        transport = MemoryTransport()
1167
 
        transport.mkdir('branch')
1168
 
        transport = transport.clone('branch')
1169
 
        client = FakeClient(transport.base)
1170
 
        # get_stacked_on_url
1171
 
        client.add_error_response('NotStacked')
1172
 
        # lock_write
1173
 
        client.add_success_response('ok', 'branch token', 'repo token')
1174
 
        # query the current revision
1175
 
        client.add_success_response('ok', '0', 'null:')
1176
 
        # set_last_revision
1177
 
        client.add_success_response('ok')
1178
 
        # unlock
1179
 
        client.add_success_response('ok')
1180
 
 
1181
 
        branch = self.make_remote_branch(transport, client)
1182
 
        # Lock the branch, reset the record of remote calls.
1183
 
        branch.lock_write()
1184
 
        client._calls = []
1185
 
        result = branch.set_last_revision_info(1234, 'a-revision-id')
1186
 
        self.assertEqual(
1187
 
            [('call', 'Branch.last_revision_info', ('branch/',)),
1188
 
             ('call', 'Branch.set_last_revision_info',
1189
 
                ('branch/', 'branch token', 'repo token',
1190
 
                 '1234', 'a-revision-id'))],
1191
 
            client._calls)
1192
 
        self.assertEqual(None, result)
1193
 
 
1194
 
    def test_no_such_revision(self):
1195
 
        # A response of 'NoSuchRevision' is translated into an exception.
1196
 
        transport = MemoryTransport()
1197
 
        transport.mkdir('branch')
1198
 
        transport = transport.clone('branch')
1199
 
        client = FakeClient(transport.base)
1200
 
        # get_stacked_on_url
1201
 
        client.add_error_response('NotStacked')
1202
 
        # lock_write
1203
 
        client.add_success_response('ok', 'branch token', 'repo token')
1204
 
        # set_last_revision
1205
 
        client.add_error_response('NoSuchRevision', 'revid')
1206
 
        # unlock
1207
 
        client.add_success_response('ok')
1208
 
 
1209
 
        branch = self.make_remote_branch(transport, client)
1210
 
        # Lock the branch, reset the record of remote calls.
1211
 
        branch.lock_write()
1212
 
        client._calls = []
1213
 
 
1214
 
        self.assertRaises(
1215
 
            errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
1216
 
        branch.unlock()
1217
 
 
1218
 
    def lock_remote_branch(self, branch):
1219
 
        """Trick a RemoteBranch into thinking it is locked."""
1220
 
        branch._lock_mode = 'w'
1221
 
        branch._lock_count = 2
1222
 
        branch._lock_token = 'branch token'
1223
 
        branch._repo_lock_token = 'repo token'
1224
 
        branch.repository._lock_mode = 'w'
1225
 
        branch.repository._lock_count = 2
1226
 
        branch.repository._lock_token = 'repo token'
1227
 
 
1228
 
    def test_backwards_compatibility(self):
1229
 
        """If the server does not support the Branch.set_last_revision_info
1230
 
        verb (which is new in 1.4), then the client falls back to VFS methods.
1231
 
        """
1232
 
        # This test is a little messy.  Unlike most tests in this file, it
1233
 
        # doesn't purely test what a Remote* object sends over the wire, and
1234
 
        # how it reacts to responses from the wire.  It instead relies partly
1235
 
        # on asserting that the RemoteBranch will call
1236
 
        # self._real_branch.set_last_revision_info(...).
1237
 
 
1238
 
        # First, set up our RemoteBranch with a FakeClient that raises
1239
 
        # UnknownSmartMethod, and a StubRealBranch that logs how it is called.
1240
 
        transport = MemoryTransport()
1241
 
        transport.mkdir('branch')
1242
 
        transport = transport.clone('branch')
1243
 
        client = FakeClient(transport.base)
1244
 
        client.add_expected_call(
1245
 
            'Branch.get_stacked_on_url', ('branch/',),
1246
 
            'error', ('NotStacked',))
1247
 
        client.add_expected_call(
1248
 
            'Branch.last_revision_info',
1249
 
            ('branch/',),
1250
 
            'success', ('ok', '0', 'null:'))
1251
 
        client.add_expected_call(
1252
 
            'Branch.set_last_revision_info',
1253
 
            ('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
1254
 
            'unknown', 'Branch.set_last_revision_info')
1255
 
 
1256
 
        branch = self.make_remote_branch(transport, client)
1257
 
        class StubRealBranch(object):
1258
 
            def __init__(self):
1259
 
                self.calls = []
1260
 
            def set_last_revision_info(self, revno, revision_id):
1261
 
                self.calls.append(
1262
 
                    ('set_last_revision_info', revno, revision_id))
1263
 
            def _clear_cached_state(self):
1264
 
                pass
1265
 
        real_branch = StubRealBranch()
1266
 
        branch._real_branch = real_branch
1267
 
        self.lock_remote_branch(branch)
1268
 
 
1269
 
        # Call set_last_revision_info, and verify it behaved as expected.
1270
 
        result = branch.set_last_revision_info(1234, 'a-revision-id')
1271
 
        self.assertEqual(
1272
 
            [('set_last_revision_info', 1234, 'a-revision-id')],
1273
 
            real_branch.calls)
1274
 
        client.finished_test()
1275
 
 
1276
 
    def test_unexpected_error(self):
1277
 
        # If the server sends an error the client doesn't understand, it gets
1278
 
        # turned into an UnknownErrorFromSmartServer, which is presented as a
1279
 
        # non-internal error to the user.
1280
 
        transport = MemoryTransport()
1281
 
        transport.mkdir('branch')
1282
 
        transport = transport.clone('branch')
1283
 
        client = FakeClient(transport.base)
1284
 
        # get_stacked_on_url
1285
 
        client.add_error_response('NotStacked')
1286
 
        # lock_write
1287
 
        client.add_success_response('ok', 'branch token', 'repo token')
1288
 
        # set_last_revision
1289
 
        client.add_error_response('UnexpectedError')
1290
 
        # unlock
1291
 
        client.add_success_response('ok')
1292
 
 
1293
 
        branch = self.make_remote_branch(transport, client)
1294
 
        # Lock the branch, reset the record of remote calls.
1295
 
        branch.lock_write()
1296
 
        client._calls = []
1297
 
 
1298
 
        err = self.assertRaises(
1299
 
            errors.UnknownErrorFromSmartServer,
1300
 
            branch.set_last_revision_info, 123, 'revid')
1301
 
        self.assertEqual(('UnexpectedError',), err.error_tuple)
1302
 
        branch.unlock()
1303
 
 
1304
 
    def test_tip_change_rejected(self):
1305
 
        """TipChangeRejected responses cause a TipChangeRejected exception to
1306
 
        be raised.
1307
 
        """
1308
 
        transport = MemoryTransport()
1309
 
        transport.mkdir('branch')
1310
 
        transport = transport.clone('branch')
1311
 
        client = FakeClient(transport.base)
1312
 
        # get_stacked_on_url
1313
 
        client.add_error_response('NotStacked')
1314
 
        # lock_write
1315
 
        client.add_success_response('ok', 'branch token', 'repo token')
1316
 
        # set_last_revision
1317
 
        client.add_error_response('TipChangeRejected', 'rejection message')
1318
 
        # unlock
1319
 
        client.add_success_response('ok')
1320
 
 
1321
 
        branch = self.make_remote_branch(transport, client)
1322
 
        # Lock the branch, reset the record of remote calls.
1323
 
        branch.lock_write()
1324
 
        self.addCleanup(branch.unlock)
1325
 
        client._calls = []
1326
 
 
1327
 
        # The 'TipChangeRejected' error response triggered by calling
1328
 
        # set_last_revision_info causes a TipChangeRejected exception.
1329
 
        err = self.assertRaises(
1330
 
            errors.TipChangeRejected,
1331
 
            branch.set_last_revision_info, 123, 'revid')
1332
 
        self.assertEqual('rejection message', err.msg)
1333
 
 
1334
 
 
1335
 
class TestBranchGetSetConfig(RemoteBranchTestCase):
1336
 
 
1337
 
    def test_get_branch_conf(self):
1338
 
        # in an empty branch we decode the response properly
1339
 
        client = FakeClient()
1340
 
        client.add_expected_call(
1341
 
            'Branch.get_stacked_on_url', ('memory:///',),
1342
 
            'error', ('NotStacked',),)
1343
 
        client.add_success_response_with_body('# config file body', 'ok')
1344
 
        transport = MemoryTransport()
1345
 
        branch = self.make_remote_branch(transport, client)
1346
 
        config = branch.get_config()
1347
 
        config.has_explicit_nickname()
1348
 
        self.assertEqual(
1349
 
            [('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1350
 
             ('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1351
 
            client._calls)
1352
 
 
1353
 
    def test_get_multi_line_branch_conf(self):
1354
 
        # Make sure that multiple-line branch.conf files are supported
1355
 
        #
1356
 
        # https://bugs.edge.launchpad.net/bzr/+bug/354075
1357
 
        client = FakeClient()
1358
 
        client.add_expected_call(
1359
 
            'Branch.get_stacked_on_url', ('memory:///',),
1360
 
            'error', ('NotStacked',),)
1361
 
        client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1362
 
        transport = MemoryTransport()
1363
 
        branch = self.make_remote_branch(transport, client)
1364
 
        config = branch.get_config()
1365
 
        self.assertEqual(u'2', config.get_user_option('b'))
1366
 
 
1367
 
    def test_set_option(self):
1368
 
        client = FakeClient()
1369
 
        client.add_expected_call(
1370
 
            'Branch.get_stacked_on_url', ('memory:///',),
1371
 
            'error', ('NotStacked',),)
1372
 
        client.add_expected_call(
1373
 
            'Branch.lock_write', ('memory:///', '', ''),
1374
 
            'success', ('ok', 'branch token', 'repo token'))
1375
 
        client.add_expected_call(
1376
 
            'Branch.set_config_option', ('memory:///', 'branch token',
1377
 
            'repo token', 'foo', 'bar', ''),
1378
 
            'success', ())
1379
 
        client.add_expected_call(
1380
 
            'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1381
 
            'success', ('ok',))
1382
 
        transport = MemoryTransport()
1383
 
        branch = self.make_remote_branch(transport, client)
1384
 
        branch.lock_write()
1385
 
        config = branch._get_config()
1386
 
        config.set_option('foo', 'bar')
1387
 
        branch.unlock()
1388
 
        client.finished_test()
1389
 
 
1390
 
    def test_backwards_compat_set_option(self):
1391
 
        self.setup_smart_server_with_call_log()
1392
 
        branch = self.make_branch('.')
1393
 
        verb = 'Branch.set_config_option'
1394
 
        self.disable_verb(verb)
1395
 
        branch.lock_write()
1396
 
        self.addCleanup(branch.unlock)
1397
 
        self.reset_smart_call_log()
1398
 
        branch._get_config().set_option('value', 'name')
1399
 
        self.assertLength(10, self.hpss_calls)
1400
 
        self.assertEqual('value', branch._get_config().get_option('name'))
1401
 
 
1402
 
 
1403
 
class TestBranchLockWrite(RemoteBranchTestCase):
1404
 
 
1405
 
    def test_lock_write_unlockable(self):
1406
 
        transport = MemoryTransport()
1407
 
        client = FakeClient(transport.base)
1408
 
        client.add_expected_call(
1409
 
            'Branch.get_stacked_on_url', ('quack/',),
1410
 
            'error', ('NotStacked',),)
1411
 
        client.add_expected_call(
1412
 
            'Branch.lock_write', ('quack/', '', ''),
1413
 
            'error', ('UnlockableTransport',))
1414
 
        transport.mkdir('quack')
1415
 
        transport = transport.clone('quack')
1416
 
        branch = self.make_remote_branch(transport, client)
1417
 
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
1418
 
        client.finished_test()
1419
 
 
1420
 
 
1421
 
class TestTransportIsReadonly(tests.TestCase):
1422
 
 
1423
 
    def test_true(self):
1424
 
        client = FakeClient()
1425
 
        client.add_success_response('yes')
1426
 
        transport = RemoteTransport('bzr://example.com/', medium=False,
1427
 
                                    _client=client)
1428
 
        self.assertEqual(True, transport.is_readonly())
1429
 
        self.assertEqual(
1430
 
            [('call', 'Transport.is_readonly', ())],
1431
 
            client._calls)
1432
 
 
1433
 
    def test_false(self):
1434
 
        client = FakeClient()
1435
 
        client.add_success_response('no')
1436
 
        transport = RemoteTransport('bzr://example.com/', medium=False,
1437
 
                                    _client=client)
1438
 
        self.assertEqual(False, transport.is_readonly())
1439
 
        self.assertEqual(
1440
 
            [('call', 'Transport.is_readonly', ())],
1441
 
            client._calls)
1442
 
 
1443
 
    def test_error_from_old_server(self):
1444
 
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1445
 
 
1446
 
        Clients should treat it as a "no" response, because is_readonly is only
1447
 
        advisory anyway (a transport could be read-write, but then the
1448
 
        underlying filesystem could be readonly anyway).
1449
 
        """
1450
 
        client = FakeClient()
1451
 
        client.add_unknown_method_response('Transport.is_readonly')
1452
 
        transport = RemoteTransport('bzr://example.com/', medium=False,
1453
 
                                    _client=client)
1454
 
        self.assertEqual(False, transport.is_readonly())
1455
 
        self.assertEqual(
1456
 
            [('call', 'Transport.is_readonly', ())],
1457
 
            client._calls)
1458
 
 
1459
 
 
1460
 
class TestTransportMkdir(tests.TestCase):
1461
 
 
1462
 
    def test_permissiondenied(self):
1463
 
        client = FakeClient()
1464
 
        client.add_error_response('PermissionDenied', 'remote path', 'extra')
1465
 
        transport = RemoteTransport('bzr://example.com/', medium=False,
1466
 
                                    _client=client)
1467
 
        exc = self.assertRaises(
1468
 
            errors.PermissionDenied, transport.mkdir, 'client path')
1469
 
        expected_error = errors.PermissionDenied('/client path', 'extra')
1470
 
        self.assertEqual(expected_error, exc)
1471
 
 
1472
 
 
1473
 
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
1474
 
 
1475
 
    def test_defaults_to_none(self):
1476
 
        t = RemoteSSHTransport('bzr+ssh://example.com')
1477
 
        self.assertIs(None, t._get_credentials()[0])
1478
 
 
1479
 
    def test_uses_authentication_config(self):
1480
 
        conf = config.AuthenticationConfig()
1481
 
        conf._get_config().update(
1482
 
            {'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
1483
 
            'example.com'}})
1484
 
        conf._save()
1485
 
        t = RemoteSSHTransport('bzr+ssh://example.com')
1486
 
        self.assertEqual('bar', t._get_credentials()[0])
1487
 
 
1488
 
 
1489
 
class TestRemoteRepository(TestRemote):
1490
 
    """Base for testing RemoteRepository protocol usage.
1491
 
 
1492
 
    These tests contain frozen requests and responses.  We want any changes to
1493
 
    what is sent or expected to be require a thoughtful update to these tests
1494
 
    because they might break compatibility with different-versioned servers.
1495
 
    """
1496
 
 
1497
 
    def setup_fake_client_and_repository(self, transport_path):
1498
 
        """Create the fake client and repository for testing with.
1499
 
 
1500
 
        There's no real server here; we just have canned responses sent
1501
 
        back one by one.
1502
 
 
1503
 
        :param transport_path: Path below the root of the MemoryTransport
1504
 
            where the repository will be created.
1505
 
        """
1506
 
        transport = MemoryTransport()
1507
 
        transport.mkdir(transport_path)
1508
 
        client = FakeClient(transport.base)
1509
 
        transport = transport.clone(transport_path)
1510
 
        # we do not want bzrdir to make any remote calls
1511
 
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
1512
 
            _client=False)
1513
 
        repo = RemoteRepository(bzrdir, None, _client=client)
1514
 
        return repo, client
1515
 
 
1516
 
 
1517
 
class TestRepositoryFormat(TestRemoteRepository):
1518
 
 
1519
 
    def test_fast_delta(self):
1520
 
        true_name = pack_repo.RepositoryFormatPackDevelopment2().network_name()
1521
 
        true_format = RemoteRepositoryFormat()
1522
 
        true_format._network_name = true_name
1523
 
        self.assertEqual(True, true_format.fast_deltas)
1524
 
        false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
1525
 
        false_format = RemoteRepositoryFormat()
1526
 
        false_format._network_name = false_name
1527
 
        self.assertEqual(False, false_format.fast_deltas)
1528
 
 
1529
 
 
1530
 
class TestRepositoryGatherStats(TestRemoteRepository):
1531
 
 
1532
 
    def test_revid_none(self):
1533
 
        # ('ok',), body with revisions and size
1534
 
        transport_path = 'quack'
1535
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1536
 
        client.add_success_response_with_body(
1537
 
            'revisions: 2\nsize: 18\n', 'ok')
1538
 
        result = repo.gather_stats(None)
1539
 
        self.assertEqual(
1540
 
            [('call_expecting_body', 'Repository.gather_stats',
1541
 
             ('quack/','','no'))],
1542
 
            client._calls)
1543
 
        self.assertEqual({'revisions': 2, 'size': 18}, result)
1544
 
 
1545
 
    def test_revid_no_committers(self):
1546
 
        # ('ok',), body without committers
1547
 
        body = ('firstrev: 123456.300 3600\n'
1548
 
                'latestrev: 654231.400 0\n'
1549
 
                'revisions: 2\n'
1550
 
                'size: 18\n')
1551
 
        transport_path = 'quick'
1552
 
        revid = u'\xc8'.encode('utf8')
1553
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1554
 
        client.add_success_response_with_body(body, 'ok')
1555
 
        result = repo.gather_stats(revid)
1556
 
        self.assertEqual(
1557
 
            [('call_expecting_body', 'Repository.gather_stats',
1558
 
              ('quick/', revid, 'no'))],
1559
 
            client._calls)
1560
 
        self.assertEqual({'revisions': 2, 'size': 18,
1561
 
                          'firstrev': (123456.300, 3600),
1562
 
                          'latestrev': (654231.400, 0),},
1563
 
                         result)
1564
 
 
1565
 
    def test_revid_with_committers(self):
1566
 
        # ('ok',), body with committers
1567
 
        body = ('committers: 128\n'
1568
 
                'firstrev: 123456.300 3600\n'
1569
 
                'latestrev: 654231.400 0\n'
1570
 
                'revisions: 2\n'
1571
 
                'size: 18\n')
1572
 
        transport_path = 'buick'
1573
 
        revid = u'\xc8'.encode('utf8')
1574
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1575
 
        client.add_success_response_with_body(body, 'ok')
1576
 
        result = repo.gather_stats(revid, True)
1577
 
        self.assertEqual(
1578
 
            [('call_expecting_body', 'Repository.gather_stats',
1579
 
              ('buick/', revid, 'yes'))],
1580
 
            client._calls)
1581
 
        self.assertEqual({'revisions': 2, 'size': 18,
1582
 
                          'committers': 128,
1583
 
                          'firstrev': (123456.300, 3600),
1584
 
                          'latestrev': (654231.400, 0),},
1585
 
                         result)
1586
 
 
1587
 
 
1588
 
class TestRepositoryGetGraph(TestRemoteRepository):
1589
 
 
1590
 
    def test_get_graph(self):
1591
 
        # get_graph returns a graph with a custom parents provider.
1592
 
        transport_path = 'quack'
1593
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1594
 
        graph = repo.get_graph()
1595
 
        self.assertNotEqual(graph._parents_provider, repo)
1596
 
 
1597
 
 
1598
 
class TestRepositoryGetParentMap(TestRemoteRepository):
1599
 
 
1600
 
    def test_get_parent_map_caching(self):
1601
 
        # get_parent_map returns from cache until unlock()
1602
 
        # setup a reponse with two revisions
1603
 
        r1 = u'\u0e33'.encode('utf8')
1604
 
        r2 = u'\u0dab'.encode('utf8')
1605
 
        lines = [' '.join([r2, r1]), r1]
1606
 
        encoded_body = bz2.compress('\n'.join(lines))
1607
 
 
1608
 
        transport_path = 'quack'
1609
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1610
 
        client.add_success_response_with_body(encoded_body, 'ok')
1611
 
        client.add_success_response_with_body(encoded_body, 'ok')
1612
 
        repo.lock_read()
1613
 
        graph = repo.get_graph()
1614
 
        parents = graph.get_parent_map([r2])
1615
 
        self.assertEqual({r2: (r1,)}, parents)
1616
 
        # locking and unlocking deeper should not reset
1617
 
        repo.lock_read()
1618
 
        repo.unlock()
1619
 
        parents = graph.get_parent_map([r1])
1620
 
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
1621
 
        self.assertEqual(
1622
 
            [('call_with_body_bytes_expecting_body',
1623
 
              'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1624
 
              '\n\n0')],
1625
 
            client._calls)
1626
 
        repo.unlock()
1627
 
        # now we call again, and it should use the second response.
1628
 
        repo.lock_read()
1629
 
        graph = repo.get_graph()
1630
 
        parents = graph.get_parent_map([r1])
1631
 
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
1632
 
        self.assertEqual(
1633
 
            [('call_with_body_bytes_expecting_body',
1634
 
              'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
1635
 
              '\n\n0'),
1636
 
             ('call_with_body_bytes_expecting_body',
1637
 
              'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
1638
 
              '\n\n0'),
1639
 
            ],
1640
 
            client._calls)
1641
 
        repo.unlock()
1642
 
 
1643
 
    def test_get_parent_map_reconnects_if_unknown_method(self):
1644
 
        transport_path = 'quack'
1645
 
        rev_id = 'revision-id'
1646
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1647
 
        client.add_unknown_method_response('Repository.get_parent_map')
1648
 
        client.add_success_response_with_body(rev_id, 'ok')
1649
 
        self.assertFalse(client._medium._is_remote_before((1, 2)))
1650
 
        parents = repo.get_parent_map([rev_id])
1651
 
        self.assertEqual(
1652
 
            [('call_with_body_bytes_expecting_body',
1653
 
              'Repository.get_parent_map', ('quack/', 'include-missing:',
1654
 
              rev_id), '\n\n0'),
1655
 
             ('disconnect medium',),
1656
 
             ('call_expecting_body', 'Repository.get_revision_graph',
1657
 
              ('quack/', ''))],
1658
 
            client._calls)
1659
 
        # The medium is now marked as being connected to an older server
1660
 
        self.assertTrue(client._medium._is_remote_before((1, 2)))
1661
 
        self.assertEqual({rev_id: ('null:',)}, parents)
1662
 
 
1663
 
    def test_get_parent_map_fallback_parentless_node(self):
1664
 
        """get_parent_map falls back to get_revision_graph on old servers.  The
1665
 
        results from get_revision_graph are tweaked to match the get_parent_map
1666
 
        API.
1667
 
 
1668
 
        Specifically, a {key: ()} result from get_revision_graph means "no
1669
 
        parents" for that key, which in get_parent_map results should be
1670
 
        represented as {key: ('null:',)}.
1671
 
 
1672
 
        This is the test for https://bugs.launchpad.net/bzr/+bug/214894
1673
 
        """
1674
 
        rev_id = 'revision-id'
1675
 
        transport_path = 'quack'
1676
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1677
 
        client.add_success_response_with_body(rev_id, 'ok')
1678
 
        client._medium._remember_remote_is_before((1, 2))
1679
 
        parents = repo.get_parent_map([rev_id])
1680
 
        self.assertEqual(
1681
 
            [('call_expecting_body', 'Repository.get_revision_graph',
1682
 
             ('quack/', ''))],
1683
 
            client._calls)
1684
 
        self.assertEqual({rev_id: ('null:',)}, parents)
1685
 
 
1686
 
    def test_get_parent_map_unexpected_response(self):
1687
 
        repo, client = self.setup_fake_client_and_repository('path')
1688
 
        client.add_success_response('something unexpected!')
1689
 
        self.assertRaises(
1690
 
            errors.UnexpectedSmartServerResponse,
1691
 
            repo.get_parent_map, ['a-revision-id'])
1692
 
 
1693
 
    def test_get_parent_map_negative_caches_missing_keys(self):
1694
 
        self.setup_smart_server_with_call_log()
1695
 
        repo = self.make_repository('foo')
1696
 
        self.assertIsInstance(repo, RemoteRepository)
1697
 
        repo.lock_read()
1698
 
        self.addCleanup(repo.unlock)
1699
 
        self.reset_smart_call_log()
1700
 
        graph = repo.get_graph()
1701
 
        self.assertEqual({},
1702
 
            graph.get_parent_map(['some-missing', 'other-missing']))
1703
 
        self.assertLength(1, self.hpss_calls)
1704
 
        # No call if we repeat this
1705
 
        self.reset_smart_call_log()
1706
 
        graph = repo.get_graph()
1707
 
        self.assertEqual({},
1708
 
            graph.get_parent_map(['some-missing', 'other-missing']))
1709
 
        self.assertLength(0, self.hpss_calls)
1710
 
        # Asking for more unknown keys makes a request.
1711
 
        self.reset_smart_call_log()
1712
 
        graph = repo.get_graph()
1713
 
        self.assertEqual({},
1714
 
            graph.get_parent_map(['some-missing', 'other-missing',
1715
 
                'more-missing']))
1716
 
        self.assertLength(1, self.hpss_calls)
1717
 
 
1718
 
    def disableExtraResults(self):
1719
 
        old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1720
 
        SmartServerRepositoryGetParentMap.no_extra_results = True
1721
 
        def reset_values():
1722
 
            SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1723
 
        self.addCleanup(reset_values)
1724
 
 
1725
 
    def test_null_cached_missing_and_stop_key(self):
1726
 
        self.setup_smart_server_with_call_log()
1727
 
        # Make a branch with a single revision.
1728
 
        builder = self.make_branch_builder('foo')
1729
 
        builder.start_series()
1730
 
        builder.build_snapshot('first', None, [
1731
 
            ('add', ('', 'root-id', 'directory', ''))])
1732
 
        builder.finish_series()
1733
 
        branch = builder.get_branch()
1734
 
        repo = branch.repository
1735
 
        self.assertIsInstance(repo, RemoteRepository)
1736
 
        # Stop the server from sending extra results.
1737
 
        self.disableExtraResults()
1738
 
        repo.lock_read()
1739
 
        self.addCleanup(repo.unlock)
1740
 
        self.reset_smart_call_log()
1741
 
        graph = repo.get_graph()
1742
 
        # Query for 'first' and 'null:'.  Because 'null:' is a parent of
1743
 
        # 'first' it will be a candidate for the stop_keys of subsequent
1744
 
        # requests, and because 'null:' was queried but not returned it will be
1745
 
        # cached as missing.
1746
 
        self.assertEqual({'first': ('null:',)},
1747
 
            graph.get_parent_map(['first', 'null:']))
1748
 
        # Now query for another key.  This request will pass along a recipe of
1749
 
        # start and stop keys describing the already cached results, and this
1750
 
        # recipe's revision count must be correct (or else it will trigger an
1751
 
        # error from the server).
1752
 
        self.assertEqual({}, graph.get_parent_map(['another-key']))
1753
 
        # This assertion guards against disableExtraResults silently failing to
1754
 
        # work, thus invalidating the test.
1755
 
        self.assertLength(2, self.hpss_calls)
1756
 
 
1757
 
    def test_get_parent_map_gets_ghosts_from_result(self):
1758
 
        # asking for a revision should negatively cache close ghosts in its
1759
 
        # ancestry.
1760
 
        self.setup_smart_server_with_call_log()
1761
 
        tree = self.make_branch_and_memory_tree('foo')
1762
 
        tree.lock_write()
1763
 
        try:
1764
 
            builder = treebuilder.TreeBuilder()
1765
 
            builder.start_tree(tree)
1766
 
            builder.build([])
1767
 
            builder.finish_tree()
1768
 
            tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
1769
 
            rev_id = tree.commit('')
1770
 
        finally:
1771
 
            tree.unlock()
1772
 
        tree.lock_read()
1773
 
        self.addCleanup(tree.unlock)
1774
 
        repo = tree.branch.repository
1775
 
        self.assertIsInstance(repo, RemoteRepository)
1776
 
        # ask for rev_id
1777
 
        repo.get_parent_map([rev_id])
1778
 
        self.reset_smart_call_log()
1779
 
        # Now asking for rev_id's ghost parent should not make calls
1780
 
        self.assertEqual({}, repo.get_parent_map(['non-existant']))
1781
 
        self.assertLength(0, self.hpss_calls)
1782
 
 
1783
 
 
1784
 
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1785
 
 
1786
 
    def test_allows_new_revisions(self):
1787
 
        """get_parent_map's results can be updated by commit."""
1788
 
        smart_server = server.SmartTCPServer_for_testing()
1789
 
        smart_server.setUp()
1790
 
        self.addCleanup(smart_server.tearDown)
1791
 
        self.make_branch('branch')
1792
 
        branch = Branch.open(smart_server.get_url() + '/branch')
1793
 
        tree = branch.create_checkout('tree', lightweight=True)
1794
 
        tree.lock_write()
1795
 
        self.addCleanup(tree.unlock)
1796
 
        graph = tree.branch.repository.get_graph()
1797
 
        # This provides an opportunity for the missing rev-id to be cached.
1798
 
        self.assertEqual({}, graph.get_parent_map(['rev1']))
1799
 
        tree.commit('message', rev_id='rev1')
1800
 
        graph = tree.branch.repository.get_graph()
1801
 
        self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
1802
 
 
1803
 
 
1804
 
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1805
 
 
1806
 
    def test_null_revision(self):
1807
 
        # a null revision has the predictable result {}, we should have no wire
1808
 
        # traffic when calling it with this argument
1809
 
        transport_path = 'empty'
1810
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1811
 
        client.add_success_response('notused')
1812
 
        # actual RemoteRepository.get_revision_graph is gone, but there's an
1813
 
        # equivalent private method for testing
1814
 
        result = repo._get_revision_graph(NULL_REVISION)
1815
 
        self.assertEqual([], client._calls)
1816
 
        self.assertEqual({}, result)
1817
 
 
1818
 
    def test_none_revision(self):
1819
 
        # with none we want the entire graph
1820
 
        r1 = u'\u0e33'.encode('utf8')
1821
 
        r2 = u'\u0dab'.encode('utf8')
1822
 
        lines = [' '.join([r2, r1]), r1]
1823
 
        encoded_body = '\n'.join(lines)
1824
 
 
1825
 
        transport_path = 'sinhala'
1826
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1827
 
        client.add_success_response_with_body(encoded_body, 'ok')
1828
 
        # actual RemoteRepository.get_revision_graph is gone, but there's an
1829
 
        # equivalent private method for testing
1830
 
        result = repo._get_revision_graph(None)
1831
 
        self.assertEqual(
1832
 
            [('call_expecting_body', 'Repository.get_revision_graph',
1833
 
             ('sinhala/', ''))],
1834
 
            client._calls)
1835
 
        self.assertEqual({r1: (), r2: (r1, )}, result)
1836
 
 
1837
 
    def test_specific_revision(self):
1838
 
        # with a specific revision we want the graph for that
1839
 
        # with none we want the entire graph
1840
 
        r11 = u'\u0e33'.encode('utf8')
1841
 
        r12 = u'\xc9'.encode('utf8')
1842
 
        r2 = u'\u0dab'.encode('utf8')
1843
 
        lines = [' '.join([r2, r11, r12]), r11, r12]
1844
 
        encoded_body = '\n'.join(lines)
1845
 
 
1846
 
        transport_path = 'sinhala'
1847
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1848
 
        client.add_success_response_with_body(encoded_body, 'ok')
1849
 
        result = repo._get_revision_graph(r2)
1850
 
        self.assertEqual(
1851
 
            [('call_expecting_body', 'Repository.get_revision_graph',
1852
 
             ('sinhala/', r2))],
1853
 
            client._calls)
1854
 
        self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
1855
 
 
1856
 
    def test_no_such_revision(self):
1857
 
        revid = '123'
1858
 
        transport_path = 'sinhala'
1859
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1860
 
        client.add_error_response('nosuchrevision', revid)
1861
 
        # also check that the right revision is reported in the error
1862
 
        self.assertRaises(errors.NoSuchRevision,
1863
 
            repo._get_revision_graph, revid)
1864
 
        self.assertEqual(
1865
 
            [('call_expecting_body', 'Repository.get_revision_graph',
1866
 
             ('sinhala/', revid))],
1867
 
            client._calls)
1868
 
 
1869
 
    def test_unexpected_error(self):
1870
 
        revid = '123'
1871
 
        transport_path = 'sinhala'
1872
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1873
 
        client.add_error_response('AnUnexpectedError')
1874
 
        e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1875
 
            repo._get_revision_graph, revid)
1876
 
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1877
 
 
1878
 
 
1879
 
class TestRepositoryIsShared(TestRemoteRepository):
1880
 
 
1881
 
    def test_is_shared(self):
1882
 
        # ('yes', ) for Repository.is_shared -> 'True'.
1883
 
        transport_path = 'quack'
1884
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1885
 
        client.add_success_response('yes')
1886
 
        result = repo.is_shared()
1887
 
        self.assertEqual(
1888
 
            [('call', 'Repository.is_shared', ('quack/',))],
1889
 
            client._calls)
1890
 
        self.assertEqual(True, result)
1891
 
 
1892
 
    def test_is_not_shared(self):
1893
 
        # ('no', ) for Repository.is_shared -> 'False'.
1894
 
        transport_path = 'qwack'
1895
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1896
 
        client.add_success_response('no')
1897
 
        result = repo.is_shared()
1898
 
        self.assertEqual(
1899
 
            [('call', 'Repository.is_shared', ('qwack/',))],
1900
 
            client._calls)
1901
 
        self.assertEqual(False, result)
1902
 
 
1903
 
 
1904
 
class TestRepositoryLockWrite(TestRemoteRepository):
1905
 
 
1906
 
    def test_lock_write(self):
1907
 
        transport_path = 'quack'
1908
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1909
 
        client.add_success_response('ok', 'a token')
1910
 
        result = repo.lock_write()
1911
 
        self.assertEqual(
1912
 
            [('call', 'Repository.lock_write', ('quack/', ''))],
1913
 
            client._calls)
1914
 
        self.assertEqual('a token', result)
1915
 
 
1916
 
    def test_lock_write_already_locked(self):
1917
 
        transport_path = 'quack'
1918
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1919
 
        client.add_error_response('LockContention')
1920
 
        self.assertRaises(errors.LockContention, repo.lock_write)
1921
 
        self.assertEqual(
1922
 
            [('call', 'Repository.lock_write', ('quack/', ''))],
1923
 
            client._calls)
1924
 
 
1925
 
    def test_lock_write_unlockable(self):
1926
 
        transport_path = 'quack'
1927
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1928
 
        client.add_error_response('UnlockableTransport')
1929
 
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
1930
 
        self.assertEqual(
1931
 
            [('call', 'Repository.lock_write', ('quack/', ''))],
1932
 
            client._calls)
1933
 
 
1934
 
 
1935
 
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
1936
 
 
1937
 
    def test_backwards_compat(self):
1938
 
        self.setup_smart_server_with_call_log()
1939
 
        repo = self.make_repository('.')
1940
 
        self.reset_smart_call_log()
1941
 
        verb = 'Repository.set_make_working_trees'
1942
 
        self.disable_verb(verb)
1943
 
        repo.set_make_working_trees(True)
1944
 
        call_count = len([call for call in self.hpss_calls if
1945
 
            call.call.method == verb])
1946
 
        self.assertEqual(1, call_count)
1947
 
 
1948
 
    def test_current(self):
1949
 
        transport_path = 'quack'
1950
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1951
 
        client.add_expected_call(
1952
 
            'Repository.set_make_working_trees', ('quack/', 'True'),
1953
 
            'success', ('ok',))
1954
 
        client.add_expected_call(
1955
 
            'Repository.set_make_working_trees', ('quack/', 'False'),
1956
 
            'success', ('ok',))
1957
 
        repo.set_make_working_trees(True)
1958
 
        repo.set_make_working_trees(False)
1959
 
 
1960
 
 
1961
 
class TestRepositoryUnlock(TestRemoteRepository):
1962
 
 
1963
 
    def test_unlock(self):
1964
 
        transport_path = 'quack'
1965
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1966
 
        client.add_success_response('ok', 'a token')
1967
 
        client.add_success_response('ok')
1968
 
        repo.lock_write()
1969
 
        repo.unlock()
1970
 
        self.assertEqual(
1971
 
            [('call', 'Repository.lock_write', ('quack/', '')),
1972
 
             ('call', 'Repository.unlock', ('quack/', 'a token'))],
1973
 
            client._calls)
1974
 
 
1975
 
    def test_unlock_wrong_token(self):
1976
 
        # If somehow the token is wrong, unlock will raise TokenMismatch.
1977
 
        transport_path = 'quack'
1978
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1979
 
        client.add_success_response('ok', 'a token')
1980
 
        client.add_error_response('TokenMismatch')
1981
 
        repo.lock_write()
1982
 
        self.assertRaises(errors.TokenMismatch, repo.unlock)
1983
 
 
1984
 
 
1985
 
class TestRepositoryHasRevision(TestRemoteRepository):
1986
 
 
1987
 
    def test_none(self):
1988
 
        # repo.has_revision(None) should not cause any traffic.
1989
 
        transport_path = 'quack'
1990
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1991
 
 
1992
 
        # The null revision is always there, so has_revision(None) == True.
1993
 
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
1994
 
 
1995
 
        # The remote repo shouldn't be accessed.
1996
 
        self.assertEqual([], client._calls)
1997
 
 
1998
 
 
1999
 
class TestRepositoryInsertStream(TestRemoteRepository):
2000
 
 
2001
 
    def test_unlocked_repo(self):
2002
 
        transport_path = 'quack'
2003
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2004
 
        client.add_expected_call(
2005
 
            'Repository.insert_stream', ('quack/', ''),
2006
 
            'success', ('ok',))
2007
 
        client.add_expected_call(
2008
 
            'Repository.insert_stream', ('quack/', ''),
2009
 
            'success', ('ok',))
2010
 
        sink = repo._get_sink()
2011
 
        fmt = repository.RepositoryFormat.get_default_format()
2012
 
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2013
 
        self.assertEqual([], resume_tokens)
2014
 
        self.assertEqual(set(), missing_keys)
2015
 
        client.finished_test()
2016
 
 
2017
 
    def test_locked_repo_with_no_lock_token(self):
2018
 
        transport_path = 'quack'
2019
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2020
 
        client.add_expected_call(
2021
 
            'Repository.lock_write', ('quack/', ''),
2022
 
            'success', ('ok', ''))
2023
 
        client.add_expected_call(
2024
 
            'Repository.insert_stream', ('quack/', ''),
2025
 
            'success', ('ok',))
2026
 
        client.add_expected_call(
2027
 
            'Repository.insert_stream', ('quack/', ''),
2028
 
            'success', ('ok',))
2029
 
        repo.lock_write()
2030
 
        sink = repo._get_sink()
2031
 
        fmt = repository.RepositoryFormat.get_default_format()
2032
 
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2033
 
        self.assertEqual([], resume_tokens)
2034
 
        self.assertEqual(set(), missing_keys)
2035
 
        client.finished_test()
2036
 
 
2037
 
    def test_locked_repo_with_lock_token(self):
2038
 
        transport_path = 'quack'
2039
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2040
 
        client.add_expected_call(
2041
 
            'Repository.lock_write', ('quack/', ''),
2042
 
            'success', ('ok', 'a token'))
2043
 
        client.add_expected_call(
2044
 
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2045
 
            'success', ('ok',))
2046
 
        client.add_expected_call(
2047
 
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2048
 
            'success', ('ok',))
2049
 
        repo.lock_write()
2050
 
        sink = repo._get_sink()
2051
 
        fmt = repository.RepositoryFormat.get_default_format()
2052
 
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2053
 
        self.assertEqual([], resume_tokens)
2054
 
        self.assertEqual(set(), missing_keys)
2055
 
        client.finished_test()
2056
 
 
2057
 
 
2058
 
class TestRepositoryTarball(TestRemoteRepository):
2059
 
 
2060
 
    # This is a canned tarball reponse we can validate against
2061
 
    tarball_content = (
2062
 
        'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
2063
 
        'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
2064
 
        'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
2065
 
        'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
2066
 
        'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
2067
 
        'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
2068
 
        'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
2069
 
        'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
2070
 
        '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
2071
 
        'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
2072
 
        'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
2073
 
        'nWQ7QH/F3JFOFCQ0aSPfA='
2074
 
        ).decode('base64')
2075
 
 
2076
 
    def test_repository_tarball(self):
2077
 
        # Test that Repository.tarball generates the right operations
2078
 
        transport_path = 'repo'
2079
 
        expected_calls = [('call_expecting_body', 'Repository.tarball',
2080
 
                           ('repo/', 'bz2',),),
2081
 
            ]
2082
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2083
 
        client.add_success_response_with_body(self.tarball_content, 'ok')
2084
 
        # Now actually ask for the tarball
2085
 
        tarball_file = repo._get_tarball('bz2')
2086
 
        try:
2087
 
            self.assertEqual(expected_calls, client._calls)
2088
 
            self.assertEqual(self.tarball_content, tarball_file.read())
2089
 
        finally:
2090
 
            tarball_file.close()
2091
 
 
2092
 
 
2093
 
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
2094
 
    """RemoteRepository.copy_content_into optimizations"""
2095
 
 
2096
 
    def test_copy_content_remote_to_local(self):
2097
 
        self.transport_server = server.SmartTCPServer_for_testing
2098
 
        src_repo = self.make_repository('repo1')
2099
 
        src_repo = repository.Repository.open(self.get_url('repo1'))
2100
 
        # At the moment the tarball-based copy_content_into can't write back
2101
 
        # into a smart server.  It would be good if it could upload the
2102
 
        # tarball; once that works we'd have to create repositories of
2103
 
        # different formats. -- mbp 20070410
2104
 
        dest_url = self.get_vfs_only_url('repo2')
2105
 
        dest_bzrdir = BzrDir.create(dest_url)
2106
 
        dest_repo = dest_bzrdir.create_repository()
2107
 
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
2108
 
        self.assertTrue(isinstance(src_repo, RemoteRepository))
2109
 
        src_repo.copy_content_into(dest_repo)
2110
 
 
2111
 
 
2112
 
class _StubRealPackRepository(object):
2113
 
 
2114
 
    def __init__(self, calls):
2115
 
        self.calls = calls
2116
 
        self._pack_collection = _StubPackCollection(calls)
2117
 
 
2118
 
    def is_in_write_group(self):
2119
 
        return False
2120
 
 
2121
 
    def refresh_data(self):
2122
 
        self.calls.append(('pack collection reload_pack_names',))
2123
 
 
2124
 
 
2125
 
class _StubPackCollection(object):
2126
 
 
2127
 
    def __init__(self, calls):
2128
 
        self.calls = calls
2129
 
 
2130
 
    def autopack(self):
2131
 
        self.calls.append(('pack collection autopack',))
2132
 
 
2133
 
 
2134
 
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
2135
 
    """Tests for RemoteRepository.autopack implementation."""
2136
 
 
2137
 
    def test_ok(self):
2138
 
        """When the server returns 'ok' and there's no _real_repository, then
2139
 
        nothing else happens: the autopack method is done.
2140
 
        """
2141
 
        transport_path = 'quack'
2142
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2143
 
        client.add_expected_call(
2144
 
            'PackRepository.autopack', ('quack/',), 'success', ('ok',))
2145
 
        repo.autopack()
2146
 
        client.finished_test()
2147
 
 
2148
 
    def test_ok_with_real_repo(self):
2149
 
        """When the server returns 'ok' and there is a _real_repository, then
2150
 
        the _real_repository's reload_pack_name's method will be called.
2151
 
        """
2152
 
        transport_path = 'quack'
2153
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2154
 
        client.add_expected_call(
2155
 
            'PackRepository.autopack', ('quack/',),
2156
 
            'success', ('ok',))
2157
 
        repo._real_repository = _StubRealPackRepository(client._calls)
2158
 
        repo.autopack()
2159
 
        self.assertEqual(
2160
 
            [('call', 'PackRepository.autopack', ('quack/',)),
2161
 
             ('pack collection reload_pack_names',)],
2162
 
            client._calls)
2163
 
 
2164
 
    def test_backwards_compatibility(self):
2165
 
        """If the server does not recognise the PackRepository.autopack verb,
2166
 
        fallback to the real_repository's implementation.
2167
 
        """
2168
 
        transport_path = 'quack'
2169
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
2170
 
        client.add_unknown_method_response('PackRepository.autopack')
2171
 
        def stub_ensure_real():
2172
 
            client._calls.append(('_ensure_real',))
2173
 
            repo._real_repository = _StubRealPackRepository(client._calls)
2174
 
        repo._ensure_real = stub_ensure_real
2175
 
        repo.autopack()
2176
 
        self.assertEqual(
2177
 
            [('call', 'PackRepository.autopack', ('quack/',)),
2178
 
             ('_ensure_real',),
2179
 
             ('pack collection autopack',)],
2180
 
            client._calls)
2181
 
 
2182
 
 
2183
 
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
2184
 
    """Base class for unit tests for bzrlib.remote._translate_error."""
2185
 
 
2186
 
    def translateTuple(self, error_tuple, **context):
2187
 
        """Call _translate_error with an ErrorFromSmartServer built from the
2188
 
        given error_tuple.
2189
 
 
2190
 
        :param error_tuple: A tuple of a smart server response, as would be
2191
 
            passed to an ErrorFromSmartServer.
2192
 
        :kwargs context: context items to call _translate_error with.
2193
 
 
2194
 
        :returns: The error raised by _translate_error.
2195
 
        """
2196
 
        # Raise the ErrorFromSmartServer before passing it as an argument,
2197
 
        # because _translate_error may need to re-raise it with a bare 'raise'
2198
 
        # statement.
2199
 
        server_error = errors.ErrorFromSmartServer(error_tuple)
2200
 
        translated_error = self.translateErrorFromSmartServer(
2201
 
            server_error, **context)
2202
 
        return translated_error
2203
 
 
2204
 
    def translateErrorFromSmartServer(self, error_object, **context):
2205
 
        """Like translateTuple, but takes an already constructed
2206
 
        ErrorFromSmartServer rather than a tuple.
2207
 
        """
2208
 
        try:
2209
 
            raise error_object
2210
 
        except errors.ErrorFromSmartServer, server_error:
2211
 
            translated_error = self.assertRaises(
2212
 
                errors.BzrError, remote._translate_error, server_error,
2213
 
                **context)
2214
 
        return translated_error
2215
 
 
2216
 
 
2217
 
class TestErrorTranslationSuccess(TestErrorTranslationBase):
2218
 
    """Unit tests for bzrlib.remote._translate_error.
2219
 
 
2220
 
    Given an ErrorFromSmartServer (which has an error tuple from a smart
2221
 
    server) and some context, _translate_error raises more specific errors from
2222
 
    bzrlib.errors.
2223
 
 
2224
 
    This test case covers the cases where _translate_error succeeds in
2225
 
    translating an ErrorFromSmartServer to something better.  See
2226
 
    TestErrorTranslationRobustness for other cases.
2227
 
    """
2228
 
 
2229
 
    def test_NoSuchRevision(self):
2230
 
        branch = self.make_branch('')
2231
 
        revid = 'revid'
2232
 
        translated_error = self.translateTuple(
2233
 
            ('NoSuchRevision', revid), branch=branch)
2234
 
        expected_error = errors.NoSuchRevision(branch, revid)
2235
 
        self.assertEqual(expected_error, translated_error)
2236
 
 
2237
 
    def test_nosuchrevision(self):
2238
 
        repository = self.make_repository('')
2239
 
        revid = 'revid'
2240
 
        translated_error = self.translateTuple(
2241
 
            ('nosuchrevision', revid), repository=repository)
2242
 
        expected_error = errors.NoSuchRevision(repository, revid)
2243
 
        self.assertEqual(expected_error, translated_error)
2244
 
 
2245
 
    def test_nobranch(self):
2246
 
        bzrdir = self.make_bzrdir('')
2247
 
        translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
2248
 
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2249
 
        self.assertEqual(expected_error, translated_error)
2250
 
 
2251
 
    def test_LockContention(self):
2252
 
        translated_error = self.translateTuple(('LockContention',))
2253
 
        expected_error = errors.LockContention('(remote lock)')
2254
 
        self.assertEqual(expected_error, translated_error)
2255
 
 
2256
 
    def test_UnlockableTransport(self):
2257
 
        bzrdir = self.make_bzrdir('')
2258
 
        translated_error = self.translateTuple(
2259
 
            ('UnlockableTransport',), bzrdir=bzrdir)
2260
 
        expected_error = errors.UnlockableTransport(bzrdir.root_transport)
2261
 
        self.assertEqual(expected_error, translated_error)
2262
 
 
2263
 
    def test_LockFailed(self):
2264
 
        lock = 'str() of a server lock'
2265
 
        why = 'str() of why'
2266
 
        translated_error = self.translateTuple(('LockFailed', lock, why))
2267
 
        expected_error = errors.LockFailed(lock, why)
2268
 
        self.assertEqual(expected_error, translated_error)
2269
 
 
2270
 
    def test_TokenMismatch(self):
2271
 
        token = 'a lock token'
2272
 
        translated_error = self.translateTuple(('TokenMismatch',), token=token)
2273
 
        expected_error = errors.TokenMismatch(token, '(remote token)')
2274
 
        self.assertEqual(expected_error, translated_error)
2275
 
 
2276
 
    def test_Diverged(self):
2277
 
        branch = self.make_branch('a')
2278
 
        other_branch = self.make_branch('b')
2279
 
        translated_error = self.translateTuple(
2280
 
            ('Diverged',), branch=branch, other_branch=other_branch)
2281
 
        expected_error = errors.DivergedBranches(branch, other_branch)
2282
 
        self.assertEqual(expected_error, translated_error)
2283
 
 
2284
 
    def test_ReadError_no_args(self):
2285
 
        path = 'a path'
2286
 
        translated_error = self.translateTuple(('ReadError',), path=path)
2287
 
        expected_error = errors.ReadError(path)
2288
 
        self.assertEqual(expected_error, translated_error)
2289
 
 
2290
 
    def test_ReadError(self):
2291
 
        path = 'a path'
2292
 
        translated_error = self.translateTuple(('ReadError', path))
2293
 
        expected_error = errors.ReadError(path)
2294
 
        self.assertEqual(expected_error, translated_error)
2295
 
 
2296
 
    def test_PermissionDenied_no_args(self):
2297
 
        path = 'a path'
2298
 
        translated_error = self.translateTuple(('PermissionDenied',), path=path)
2299
 
        expected_error = errors.PermissionDenied(path)
2300
 
        self.assertEqual(expected_error, translated_error)
2301
 
 
2302
 
    def test_PermissionDenied_one_arg(self):
2303
 
        path = 'a path'
2304
 
        translated_error = self.translateTuple(('PermissionDenied', path))
2305
 
        expected_error = errors.PermissionDenied(path)
2306
 
        self.assertEqual(expected_error, translated_error)
2307
 
 
2308
 
    def test_PermissionDenied_one_arg_and_context(self):
2309
 
        """Given a choice between a path from the local context and a path on
2310
 
        the wire, _translate_error prefers the path from the local context.
2311
 
        """
2312
 
        local_path = 'local path'
2313
 
        remote_path = 'remote path'
2314
 
        translated_error = self.translateTuple(
2315
 
            ('PermissionDenied', remote_path), path=local_path)
2316
 
        expected_error = errors.PermissionDenied(local_path)
2317
 
        self.assertEqual(expected_error, translated_error)
2318
 
 
2319
 
    def test_PermissionDenied_two_args(self):
2320
 
        path = 'a path'
2321
 
        extra = 'a string with extra info'
2322
 
        translated_error = self.translateTuple(
2323
 
            ('PermissionDenied', path, extra))
2324
 
        expected_error = errors.PermissionDenied(path, extra)
2325
 
        self.assertEqual(expected_error, translated_error)
2326
 
 
2327
 
 
2328
 
class TestErrorTranslationRobustness(TestErrorTranslationBase):
2329
 
    """Unit tests for bzrlib.remote._translate_error's robustness.
2330
 
 
2331
 
    TestErrorTranslationSuccess is for cases where _translate_error can
2332
 
    translate successfully.  This class about how _translate_err behaves when
2333
 
    it fails to translate: it re-raises the original error.
2334
 
    """
2335
 
 
2336
 
    def test_unrecognised_server_error(self):
2337
 
        """If the error code from the server is not recognised, the original
2338
 
        ErrorFromSmartServer is propagated unmodified.
2339
 
        """
2340
 
        error_tuple = ('An unknown error tuple',)
2341
 
        server_error = errors.ErrorFromSmartServer(error_tuple)
2342
 
        translated_error = self.translateErrorFromSmartServer(server_error)
2343
 
        expected_error = errors.UnknownErrorFromSmartServer(server_error)
2344
 
        self.assertEqual(expected_error, translated_error)
2345
 
 
2346
 
    def test_context_missing_a_key(self):
2347
 
        """In case of a bug in the client, or perhaps an unexpected response
2348
 
        from a server, _translate_error returns the original error tuple from
2349
 
        the server and mutters a warning.
2350
 
        """
2351
 
        # To translate a NoSuchRevision error _translate_error needs a 'branch'
2352
 
        # in the context dict.  So let's give it an empty context dict instead
2353
 
        # to exercise its error recovery.
2354
 
        empty_context = {}
2355
 
        error_tuple = ('NoSuchRevision', 'revid')
2356
 
        server_error = errors.ErrorFromSmartServer(error_tuple)
2357
 
        translated_error = self.translateErrorFromSmartServer(server_error)
2358
 
        self.assertEqual(server_error, translated_error)
2359
 
        # In addition to re-raising ErrorFromSmartServer, some debug info has
2360
 
        # been muttered to the log file for developer to look at.
2361
 
        self.assertContainsRe(
2362
 
            self._get_log(keep_log_file=True),
2363
 
            "Missing key 'branch' in context")
2364
 
 
2365
 
    def test_path_missing(self):
2366
 
        """Some translations (PermissionDenied, ReadError) can determine the
2367
 
        'path' variable from either the wire or the local context.  If neither
2368
 
        has it, then an error is raised.
2369
 
        """
2370
 
        error_tuple = ('ReadError',)
2371
 
        server_error = errors.ErrorFromSmartServer(error_tuple)
2372
 
        translated_error = self.translateErrorFromSmartServer(server_error)
2373
 
        self.assertEqual(server_error, translated_error)
2374
 
        # In addition to re-raising ErrorFromSmartServer, some debug info has
2375
 
        # been muttered to the log file for developer to look at.
2376
 
        self.assertContainsRe(
2377
 
            self._get_log(keep_log_file=True), "Missing key 'path' in context")
2378
 
 
2379
 
 
2380
 
class TestStacking(tests.TestCaseWithTransport):
2381
 
    """Tests for operations on stacked remote repositories.
2382
 
 
2383
 
    The underlying format type must support stacking.
2384
 
    """
2385
 
 
2386
 
    def test_access_stacked_remote(self):
2387
 
        # based on <http://launchpad.net/bugs/261315>
2388
 
        # make a branch stacked on another repository containing an empty
2389
 
        # revision, then open it over hpss - we should be able to see that
2390
 
        # revision.
2391
 
        base_transport = self.get_transport()
2392
 
        base_builder = self.make_branch_builder('base', format='1.9')
2393
 
        base_builder.start_series()
2394
 
        base_revid = base_builder.build_snapshot('rev-id', None,
2395
 
            [('add', ('', None, 'directory', None))],
2396
 
            'message')
2397
 
        base_builder.finish_series()
2398
 
        stacked_branch = self.make_branch('stacked', format='1.9')
2399
 
        stacked_branch.set_stacked_on_url('../base')
2400
 
        # start a server looking at this
2401
 
        smart_server = server.SmartTCPServer_for_testing()
2402
 
        smart_server.setUp()
2403
 
        self.addCleanup(smart_server.tearDown)
2404
 
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2405
 
        # can get its branch and repository
2406
 
        remote_branch = remote_bzrdir.open_branch()
2407
 
        remote_repo = remote_branch.repository
2408
 
        remote_repo.lock_read()
2409
 
        try:
2410
 
            # it should have an appropriate fallback repository, which should also
2411
 
            # be a RemoteRepository
2412
 
            self.assertEquals(len(remote_repo._fallback_repositories), 1)
2413
 
            self.assertIsInstance(remote_repo._fallback_repositories[0],
2414
 
                RemoteRepository)
2415
 
            # and it has the revision committed to the underlying repository;
2416
 
            # these have varying implementations so we try several of them
2417
 
            self.assertTrue(remote_repo.has_revisions([base_revid]))
2418
 
            self.assertTrue(remote_repo.has_revision(base_revid))
2419
 
            self.assertEqual(remote_repo.get_revision(base_revid).message,
2420
 
                'message')
2421
 
        finally:
2422
 
            remote_repo.unlock()
2423
 
 
2424
 
    def prepare_stacked_remote_branch(self):
2425
 
        """Get stacked_upon and stacked branches with content in each."""
2426
 
        self.setup_smart_server_with_call_log()
2427
 
        tree1 = self.make_branch_and_tree('tree1', format='1.9')
2428
 
        tree1.commit('rev1', rev_id='rev1')
2429
 
        tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
2430
 
            ).open_workingtree()
2431
 
        tree2.commit('local changes make me feel good.')
2432
 
        branch2 = Branch.open(self.get_url('tree2'))
2433
 
        branch2.lock_read()
2434
 
        self.addCleanup(branch2.unlock)
2435
 
        return tree1.branch, branch2
2436
 
 
2437
 
    def test_stacked_get_parent_map(self):
2438
 
        # the public implementation of get_parent_map obeys stacking
2439
 
        _, branch = self.prepare_stacked_remote_branch()
2440
 
        repo = branch.repository
2441
 
        self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
2442
 
 
2443
 
    def test_unstacked_get_parent_map(self):
2444
 
        # _unstacked_provider.get_parent_map ignores stacking
2445
 
        _, branch = self.prepare_stacked_remote_branch()
2446
 
        provider = branch.repository._unstacked_provider
2447
 
        self.assertEqual([], provider.get_parent_map(['rev1']).keys())
2448
 
 
2449
 
    def fetch_stream_to_rev_order(self, stream):
2450
 
        result = []
2451
 
        for kind, substream in stream:
2452
 
            if not kind == 'revisions':
2453
 
                list(substream)
2454
 
            else:
2455
 
                for content in substream:
2456
 
                    result.append(content.key[-1])
2457
 
        return result
2458
 
 
2459
 
    def get_ordered_revs(self, format, order):
2460
 
        """Get a list of the revisions in a stream to format format.
2461
 
 
2462
 
        :param format: The format of the target.
2463
 
        :param order: the order that target should have requested.
2464
 
        :result: The revision ids in the stream, in the order seen,
2465
 
            the topological order of revisions in the source.
2466
 
        """
2467
 
        unordered_format = bzrdir.format_registry.get(format)()
2468
 
        target_repository_format = unordered_format.repository_format
2469
 
        # Cross check
2470
 
        self.assertEqual(order, target_repository_format._fetch_order)
2471
 
        trunk, stacked = self.prepare_stacked_remote_branch()
2472
 
        source = stacked.repository._get_source(target_repository_format)
2473
 
        tip = stacked.last_revision()
2474
 
        revs = stacked.repository.get_ancestry(tip)
2475
 
        search = graph.PendingAncestryResult([tip], stacked.repository)
2476
 
        self.reset_smart_call_log()
2477
 
        stream = source.get_stream(search)
2478
 
        if None in revs:
2479
 
            revs.remove(None)
2480
 
        # We trust that if a revision is in the stream the rest of the new
2481
 
        # content for it is too, as per our main fetch tests; here we are
2482
 
        # checking that the revisions are actually included at all, and their
2483
 
        # order.
2484
 
        return self.fetch_stream_to_rev_order(stream), revs
2485
 
 
2486
 
    def test_stacked_get_stream_unordered(self):
2487
 
        # Repository._get_source.get_stream() from a stacked repository with
2488
 
        # unordered yields the full data from both stacked and stacked upon
2489
 
        # sources.
2490
 
        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
2491
 
        self.assertEqual(set(expected_revs), set(rev_ord))
2492
 
        # Getting unordered results should have made a streaming data request
2493
 
        # from the server, then one from the backing branch.
2494
 
        self.assertLength(2, self.hpss_calls)
2495
 
 
2496
 
    def test_stacked_get_stream_topological(self):
2497
 
        # Repository._get_source.get_stream() from a stacked repository with
2498
 
        # topological sorting yields the full data from both stacked and
2499
 
        # stacked upon sources in topological order.
2500
 
        rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
2501
 
        self.assertEqual(expected_revs, rev_ord)
2502
 
        # Getting topological sort requires VFS calls still
2503
 
        self.assertLength(12, self.hpss_calls)
2504
 
 
2505
 
    def test_stacked_get_stream_groupcompress(self):
2506
 
        # Repository._get_source.get_stream() from a stacked repository with
2507
 
        # groupcompress sorting yields the full data from both stacked and
2508
 
        # stacked upon sources in groupcompress order.
2509
 
        raise tests.TestSkipped('No groupcompress ordered format available')
2510
 
        rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
2511
 
        self.assertEqual(expected_revs, reversed(rev_ord))
2512
 
        # Getting unordered results should have made a streaming data request
2513
 
        # from the backing branch, and one from the stacked on branch.
2514
 
        self.assertLength(2, self.hpss_calls)
2515
 
 
2516
 
 
2517
 
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
2518
 
 
2519
 
    def setUp(self):
2520
 
        super(TestRemoteBranchEffort, self).setUp()
2521
 
        # Create a smart server that publishes whatever the backing VFS server
2522
 
        # does.
2523
 
        self.smart_server = server.SmartTCPServer_for_testing()
2524
 
        self.smart_server.setUp(self.get_server())
2525
 
        self.addCleanup(self.smart_server.tearDown)
2526
 
        # Log all HPSS calls into self.hpss_calls.
2527
 
        _SmartClient.hooks.install_named_hook(
2528
 
            'call', self.capture_hpss_call, None)
2529
 
        self.hpss_calls = []
2530
 
 
2531
 
    def capture_hpss_call(self, params):
2532
 
        self.hpss_calls.append(params.method)
2533
 
 
2534
 
    def test_copy_content_into_avoids_revision_history(self):
2535
 
        local = self.make_branch('local')
2536
 
        remote_backing_tree = self.make_branch_and_tree('remote')
2537
 
        remote_backing_tree.commit("Commit.")
2538
 
        remote_branch_url = self.smart_server.get_url() + 'remote'
2539
 
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
2540
 
        local.repository.fetch(remote_branch.repository)
2541
 
        self.hpss_calls = []
2542
 
        remote_branch.copy_content_into(local)
2543
 
        self.assertFalse('Branch.revision_history' in self.hpss_calls)