~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Vincent Ladeuil
  • Date: 2009-06-22 12:52:39 UTC
  • mto: (4471.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 4472.
  • Revision ID: v.ladeuil+lp@free.fr-20090622125239-kabo9smxt9c3vnir
Use a consistent scheme for naming pyrex source files.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tests for remote bzrdir/branch/repo/etc
18
18
 
19
19
These are proxy objects which act on remote objects by sending messages
20
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. 
 
21
the object given a transport that supports smartserver rpc operations.
22
22
 
23
23
These tests correspond to tests.test_smart, which exercises the server side.
24
24
"""
27
27
from cStringIO import StringIO
28
28
 
29
29
from bzrlib import (
 
30
    bzrdir,
 
31
    config,
30
32
    errors,
31
33
    graph,
32
34
    pack,
33
35
    remote,
34
36
    repository,
 
37
    smart,
35
38
    tests,
 
39
    treebuilder,
 
40
    urlutils,
36
41
    )
37
42
from bzrlib.branch import Branch
38
43
from bzrlib.bzrdir import BzrDir, BzrDirFormat
39
44
from bzrlib.remote import (
40
45
    RemoteBranch,
 
46
    RemoteBranchFormat,
41
47
    RemoteBzrDir,
42
48
    RemoteBzrDirFormat,
43
49
    RemoteRepository,
 
50
    RemoteRepositoryFormat,
44
51
    )
 
52
from bzrlib.repofmt import groupcompress_repo, pack_repo
45
53
from bzrlib.revision import NULL_REVISION
46
54
from bzrlib.smart import server, medium
47
55
from bzrlib.smart.client import _SmartClient
48
 
from bzrlib.symbol_versioning import one_four
 
56
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
 
57
from bzrlib.tests import (
 
58
    condition_isinstance,
 
59
    split_suite_by_condition,
 
60
    multiply_tests,
 
61
    KnownFailure,
 
62
    )
49
63
from bzrlib.transport import get_transport, http
50
64
from bzrlib.transport.memory import MemoryTransport
51
 
from bzrlib.transport.remote import RemoteTransport, RemoteTCPTransport
 
65
from bzrlib.transport.remote import (
 
66
    RemoteTransport,
 
67
    RemoteSSHTransport,
 
68
    RemoteTCPTransport,
 
69
)
 
70
 
 
71
def load_tests(standard_tests, module, loader):
 
72
    to_adapt, result = split_suite_by_condition(
 
73
        standard_tests, condition_isinstance(BasicRemoteObjectTests))
 
74
    smart_server_version_scenarios = [
 
75
        ('HPSS-v2',
 
76
            {'transport_server': server.SmartTCPServer_for_testing_v2_only}),
 
77
        ('HPSS-v3',
 
78
            {'transport_server': server.SmartTCPServer_for_testing})]
 
79
    return multiply_tests(to_adapt, smart_server_version_scenarios, result)
52
80
 
53
81
 
54
82
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
55
83
 
56
84
    def setUp(self):
57
 
        self.transport_server = server.SmartTCPServer_for_testing
58
85
        super(BasicRemoteObjectTests, self).setUp()
59
86
        self.transport = self.get_transport()
60
87
        # make a branch that can be opened over the smart transport
65
92
        tests.TestCaseWithTransport.tearDown(self)
66
93
 
67
94
    def test_create_remote_bzrdir(self):
68
 
        b = remote.RemoteBzrDir(self.transport)
 
95
        b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
69
96
        self.assertIsInstance(b, BzrDir)
70
97
 
71
98
    def test_open_remote_branch(self):
72
99
        # open a standalone branch in the working directory
73
 
        b = remote.RemoteBzrDir(self.transport)
 
100
        b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
74
101
        branch = b.open_branch()
75
102
        self.assertIsInstance(branch, Branch)
76
103
 
105
132
        b = BzrDir.open_from_transport(self.transport).open_branch()
106
133
        self.assertStartsWith(str(b), 'RemoteBranch(')
107
134
 
 
135
    def test_remote_branch_format_supports_stacking(self):
 
136
        t = self.transport
 
137
        self.make_branch('unstackable', format='pack-0.92')
 
138
        b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
 
139
        self.assertFalse(b._format.supports_stacking())
 
140
        self.make_branch('stackable', format='1.9')
 
141
        b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
 
142
        self.assertTrue(b._format.supports_stacking())
 
143
 
 
144
    def test_remote_repo_format_supports_external_references(self):
 
145
        t = self.transport
 
146
        bd = self.make_bzrdir('unstackable', format='pack-0.92')
 
147
        r = bd.create_repository()
 
148
        self.assertFalse(r._format.supports_external_lookups)
 
149
        r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
 
150
        self.assertFalse(r._format.supports_external_lookups)
 
151
        bd = self.make_bzrdir('stackable', format='1.9')
 
152
        r = bd.create_repository()
 
153
        self.assertTrue(r._format.supports_external_lookups)
 
154
        r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
 
155
        self.assertTrue(r._format.supports_external_lookups)
 
156
 
 
157
    def test_remote_branch_set_append_revisions_only(self):
 
158
        # Make a format 1.9 branch, which supports append_revisions_only
 
159
        branch = self.make_branch('branch', format='1.9')
 
160
        config = branch.get_config()
 
161
        branch.set_append_revisions_only(True)
 
162
        self.assertEqual(
 
163
            'True', config.get_user_option('append_revisions_only'))
 
164
        branch.set_append_revisions_only(False)
 
165
        self.assertEqual(
 
166
            'False', config.get_user_option('append_revisions_only'))
 
167
 
 
168
    def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
 
169
        branch = self.make_branch('branch', format='knit')
 
170
        config = branch.get_config()
 
171
        self.assertRaises(
 
172
            errors.UpgradeRequired, branch.set_append_revisions_only, True)
 
173
 
108
174
 
109
175
class FakeProtocol(object):
110
176
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
131
197
 
132
198
class FakeClient(_SmartClient):
133
199
    """Lookalike for _SmartClient allowing testing."""
134
 
    
 
200
 
135
201
    def __init__(self, fake_medium_base='fake base'):
136
 
        """Create a FakeClient.
137
 
 
138
 
        :param responses: A list of response-tuple, body-data pairs to be sent
139
 
            back to callers.  A special case is if the response-tuple is
140
 
            'unknown verb', then a UnknownSmartMethod will be raised for that
141
 
            call, using the second element of the tuple as the verb in the
142
 
            exception.
143
 
        """
 
202
        """Create a FakeClient."""
144
203
        self.responses = []
145
204
        self._calls = []
146
205
        self.expecting_body = False
 
206
        # if non-None, this is the list of expected calls, with only the
 
207
        # method name and arguments included.  the body might be hard to
 
208
        # compute so is not included. If a call is None, that call can
 
209
        # be anything.
 
210
        self._expected_calls = None
147
211
        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
148
212
 
 
213
    def add_expected_call(self, call_name, call_args, response_type,
 
214
        response_args, response_body=None):
 
215
        if self._expected_calls is None:
 
216
            self._expected_calls = []
 
217
        self._expected_calls.append((call_name, call_args))
 
218
        self.responses.append((response_type, response_args, response_body))
 
219
 
149
220
    def add_success_response(self, *args):
150
221
        self.responses.append(('success', args, None))
151
222
 
152
223
    def add_success_response_with_body(self, body, *args):
153
224
        self.responses.append(('success', args, body))
 
225
        if self._expected_calls is not None:
 
226
            self._expected_calls.append(None)
154
227
 
155
228
    def add_error_response(self, *args):
156
229
        self.responses.append(('error', args))
158
231
    def add_unknown_method_response(self, verb):
159
232
        self.responses.append(('unknown', verb))
160
233
 
 
234
    def finished_test(self):
 
235
        if self._expected_calls:
 
236
            raise AssertionError("%r finished but was still expecting %r"
 
237
                % (self, self._expected_calls[0]))
 
238
 
161
239
    def _get_next_response(self):
162
 
        response_tuple = self.responses.pop(0)
 
240
        try:
 
241
            response_tuple = self.responses.pop(0)
 
242
        except IndexError, e:
 
243
            raise AssertionError("%r didn't expect any more calls"
 
244
                % (self,))
163
245
        if response_tuple[0] == 'unknown':
164
246
            raise errors.UnknownSmartMethod(response_tuple[1])
165
247
        elif response_tuple[0] == 'error':
166
248
            raise errors.ErrorFromSmartServer(response_tuple[1])
167
249
        return response_tuple
168
250
 
 
251
    def _check_call(self, method, args):
 
252
        if self._expected_calls is None:
 
253
            # the test should be updated to say what it expects
 
254
            return
 
255
        try:
 
256
            next_call = self._expected_calls.pop(0)
 
257
        except IndexError:
 
258
            raise AssertionError("%r didn't expect any more calls "
 
259
                "but got %r%r"
 
260
                % (self, method, args,))
 
261
        if next_call is None:
 
262
            return
 
263
        if method != next_call[0] or args != next_call[1]:
 
264
            raise AssertionError("%r expected %r%r "
 
265
                "but got %r%r"
 
266
                % (self, next_call[0], next_call[1], method, args,))
 
267
 
169
268
    def call(self, method, *args):
 
269
        self._check_call(method, args)
170
270
        self._calls.append(('call', method, args))
171
271
        return self._get_next_response()[1]
172
272
 
173
273
    def call_expecting_body(self, method, *args):
 
274
        self._check_call(method, args)
174
275
        self._calls.append(('call_expecting_body', method, args))
175
276
        result = self._get_next_response()
176
277
        self.expecting_body = True
177
278
        return result[1], FakeProtocol(result[2], self)
178
279
 
179
280
    def call_with_body_bytes_expecting_body(self, method, args, body):
 
281
        self._check_call(method, args)
180
282
        self._calls.append(('call_with_body_bytes_expecting_body', method,
181
283
            args, body))
182
284
        result = self._get_next_response()
183
285
        self.expecting_body = True
184
286
        return result[1], FakeProtocol(result[2], self)
185
287
 
 
288
    def call_with_body_stream(self, args, stream):
 
289
        # Explicitly consume the stream before checking for an error, because
 
290
        # that's what happens a real medium.
 
291
        stream = list(stream)
 
292
        self._check_call(args[0], args[1:])
 
293
        self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
 
294
        result = self._get_next_response()
 
295
        # The second value returned from call_with_body_stream is supposed to
 
296
        # be a response_handler object, but so far no tests depend on that.
 
297
        response_handler = None 
 
298
        return result[1], response_handler
 
299
 
186
300
 
187
301
class FakeMedium(medium.SmartClientMedium):
188
302
 
189
303
    def __init__(self, client_calls, base):
190
 
        self._remote_is_at_least_1_2 = True
 
304
        medium.SmartClientMedium.__init__(self, base)
191
305
        self._client_calls = client_calls
192
 
        self.base = base
193
306
 
194
307
    def disconnect(self):
195
308
        self._client_calls.append(('disconnect medium',))
209
322
        self.assertTrue(result)
210
323
 
211
324
 
 
325
class TestRemote(tests.TestCaseWithMemoryTransport):
 
326
 
 
327
    def get_branch_format(self):
 
328
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
329
        return reference_bzrdir_format.get_branch_format()
 
330
 
 
331
    def get_repo_format(self):
 
332
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
333
        return reference_bzrdir_format.repository_format
 
334
 
 
335
    def disable_verb(self, verb):
 
336
        """Disable a verb for one test."""
 
337
        request_handlers = smart.request.request_handlers
 
338
        orig_method = request_handlers.get(verb)
 
339
        request_handlers.remove(verb)
 
340
        def restoreVerb():
 
341
            request_handlers.register(verb, orig_method)
 
342
        self.addCleanup(restoreVerb)
 
343
 
 
344
 
212
345
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
213
346
    """Tests for the behaviour of client_medium.remote_path_from_transport."""
214
347
 
241
374
        cloned_transport = base_transport.clone(relpath)
242
375
        result = client_medium.remote_path_from_transport(cloned_transport)
243
376
        self.assertEqual(expected, result)
244
 
        
 
377
 
245
378
    def test_remote_path_from_transport_http(self):
246
379
        """Remote paths for HTTP transports are calculated differently to other
247
380
        transports.  They are just relative to the client base, not the root
254
387
                'xyz/', scheme + '//host/path', 'xyz/')
255
388
 
256
389
 
257
 
class TestBzrDirOpenBranch(tests.TestCase):
 
390
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
 
391
    """Tests for the behaviour of client_medium.remote_is_at_least."""
 
392
 
 
393
    def test_initially_unlimited(self):
 
394
        """A fresh medium assumes that the remote side supports all
 
395
        versions.
 
396
        """
 
397
        client_medium = medium.SmartClientMedium('dummy base')
 
398
        self.assertFalse(client_medium._is_remote_before((99, 99)))
 
399
 
 
400
    def test__remember_remote_is_before(self):
 
401
        """Calling _remember_remote_is_before ratchets down the known remote
 
402
        version.
 
403
        """
 
404
        client_medium = medium.SmartClientMedium('dummy base')
 
405
        # Mark the remote side as being less than 1.6.  The remote side may
 
406
        # still be 1.5.
 
407
        client_medium._remember_remote_is_before((1, 6))
 
408
        self.assertTrue(client_medium._is_remote_before((1, 6)))
 
409
        self.assertFalse(client_medium._is_remote_before((1, 5)))
 
410
        # Calling _remember_remote_is_before again with a lower value works.
 
411
        client_medium._remember_remote_is_before((1, 5))
 
412
        self.assertTrue(client_medium._is_remote_before((1, 5)))
 
413
        # You cannot call _remember_remote_is_before with a larger value.
 
414
        self.assertRaises(
 
415
            AssertionError, client_medium._remember_remote_is_before, (1, 9))
 
416
 
 
417
 
 
418
class TestBzrDirCloningMetaDir(TestRemote):
 
419
 
 
420
    def test_backwards_compat(self):
 
421
        self.setup_smart_server_with_call_log()
 
422
        a_dir = self.make_bzrdir('.')
 
423
        self.reset_smart_call_log()
 
424
        verb = 'BzrDir.cloning_metadir'
 
425
        self.disable_verb(verb)
 
426
        format = a_dir.cloning_metadir()
 
427
        call_count = len([call for call in self.hpss_calls if
 
428
            call.call.method == verb])
 
429
        self.assertEqual(1, call_count)
 
430
 
 
431
    def test_branch_reference(self):
 
432
        transport = self.get_transport('quack')
 
433
        referenced = self.make_branch('referenced')
 
434
        expected = referenced.bzrdir.cloning_metadir()
 
435
        client = FakeClient(transport.base)
 
436
        client.add_expected_call(
 
437
            'BzrDir.cloning_metadir', ('quack/', 'False'),
 
438
            'error', ('BranchReference',)),
 
439
        client.add_expected_call(
 
440
            'BzrDir.open_branchV2', ('quack/',),
 
441
            'success', ('ref', self.get_url('referenced'))),
 
442
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
443
            _client=client)
 
444
        result = a_bzrdir.cloning_metadir()
 
445
        # We should have got a control dir matching the referenced branch.
 
446
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
 
447
        self.assertEqual(expected._repository_format, result._repository_format)
 
448
        self.assertEqual(expected._branch_format, result._branch_format)
 
449
        client.finished_test()
 
450
 
 
451
    def test_current_server(self):
 
452
        transport = self.get_transport('.')
 
453
        transport = transport.clone('quack')
 
454
        self.make_bzrdir('quack')
 
455
        client = FakeClient(transport.base)
 
456
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
457
        control_name = reference_bzrdir_format.network_name()
 
458
        client.add_expected_call(
 
459
            'BzrDir.cloning_metadir', ('quack/', 'False'),
 
460
            'success', (control_name, '', ('branch', ''))),
 
461
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
462
            _client=client)
 
463
        result = a_bzrdir.cloning_metadir()
 
464
        # We should have got a reference control dir with default branch and
 
465
        # repository formats.
 
466
        # This pokes a little, just to be sure.
 
467
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
 
468
        self.assertEqual(None, result._repository_format)
 
469
        self.assertEqual(None, result._branch_format)
 
470
        client.finished_test()
 
471
 
 
472
 
 
473
class TestBzrDirOpenBranch(TestRemote):
 
474
 
 
475
    def test_backwards_compat(self):
 
476
        self.setup_smart_server_with_call_log()
 
477
        self.make_branch('.')
 
478
        a_dir = BzrDir.open(self.get_url('.'))
 
479
        self.reset_smart_call_log()
 
480
        verb = 'BzrDir.open_branchV2'
 
481
        self.disable_verb(verb)
 
482
        format = a_dir.open_branch()
 
483
        call_count = len([call for call in self.hpss_calls if
 
484
            call.call.method == verb])
 
485
        self.assertEqual(1, call_count)
258
486
 
259
487
    def test_branch_present(self):
 
488
        reference_format = self.get_repo_format()
 
489
        network_name = reference_format.network_name()
 
490
        branch_network_name = self.get_branch_format().network_name()
260
491
        transport = MemoryTransport()
261
492
        transport.mkdir('quack')
262
493
        transport = transport.clone('quack')
263
494
        client = FakeClient(transport.base)
264
 
        client.add_success_response('ok', '')
265
 
        client.add_success_response('ok', '', 'no', 'no', 'no')
266
 
        bzrdir = RemoteBzrDir(transport, _client=client)
 
495
        client.add_expected_call(
 
496
            'BzrDir.open_branchV2', ('quack/',),
 
497
            'success', ('branch', branch_network_name))
 
498
        client.add_expected_call(
 
499
            'BzrDir.find_repositoryV3', ('quack/',),
 
500
            'success', ('ok', '', 'no', 'no', 'no', network_name))
 
501
        client.add_expected_call(
 
502
            'Branch.get_stacked_on_url', ('quack/',),
 
503
            'error', ('NotStacked',))
 
504
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
505
            _client=client)
267
506
        result = bzrdir.open_branch()
268
 
        self.assertEqual(
269
 
            [('call', 'BzrDir.open_branch', ('quack/',)),
270
 
             ('call', 'BzrDir.find_repositoryV2', ('quack/',))],
271
 
            client._calls)
272
507
        self.assertIsInstance(result, RemoteBranch)
273
508
        self.assertEqual(bzrdir, result.bzrdir)
 
509
        client.finished_test()
274
510
 
275
511
    def test_branch_missing(self):
276
512
        transport = MemoryTransport()
278
514
        transport = transport.clone('quack')
279
515
        client = FakeClient(transport.base)
280
516
        client.add_error_response('nobranch')
281
 
        bzrdir = RemoteBzrDir(transport, _client=client)
 
517
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
518
            _client=client)
282
519
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
283
520
        self.assertEqual(
284
 
            [('call', 'BzrDir.open_branch', ('quack/',))],
 
521
            [('call', 'BzrDir.open_branchV2', ('quack/',))],
285
522
            client._calls)
286
523
 
287
524
    def test__get_tree_branch(self):
294
531
        transport = MemoryTransport()
295
532
        # no requests on the network - catches other api calls being made.
296
533
        client = FakeClient(transport.base)
297
 
        bzrdir = RemoteBzrDir(transport, _client=client)
 
534
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
535
            _client=client)
298
536
        # patch the open_branch call to record that it was called.
299
537
        bzrdir.open_branch = open_branch
300
538
        self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
306
544
        # transmitted as "~", not "%7E".
307
545
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
308
546
        client = FakeClient(transport.base)
309
 
        client.add_success_response('ok', '')
310
 
        client.add_success_response('ok', '', 'no', 'no', 'no')
311
 
        bzrdir = RemoteBzrDir(transport, _client=client)
 
547
        reference_format = self.get_repo_format()
 
548
        network_name = reference_format.network_name()
 
549
        branch_network_name = self.get_branch_format().network_name()
 
550
        client.add_expected_call(
 
551
            'BzrDir.open_branchV2', ('~hello/',),
 
552
            'success', ('branch', branch_network_name))
 
553
        client.add_expected_call(
 
554
            'BzrDir.find_repositoryV3', ('~hello/',),
 
555
            'success', ('ok', '', 'no', 'no', 'no', network_name))
 
556
        client.add_expected_call(
 
557
            'Branch.get_stacked_on_url', ('~hello/',),
 
558
            'error', ('NotStacked',))
 
559
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
560
            _client=client)
312
561
        result = bzrdir.open_branch()
313
 
        self.assertEqual(
314
 
            [('call', 'BzrDir.open_branch', ('~hello/',)),
315
 
             ('call', 'BzrDir.find_repositoryV2', ('~hello/',))],
316
 
            client._calls)
 
562
        client.finished_test()
317
563
 
318
564
    def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
 
565
        reference_format = self.get_repo_format()
 
566
        network_name = reference_format.network_name()
319
567
        transport = MemoryTransport()
320
568
        transport.mkdir('quack')
321
569
        transport = transport.clone('quack')
329
577
            subtree_response = 'no'
330
578
        client = FakeClient(transport.base)
331
579
        client.add_success_response(
332
 
            'ok', '', rich_response, subtree_response, external_lookup)
333
 
        bzrdir = RemoteBzrDir(transport, _client=client)
 
580
            'ok', '', rich_response, subtree_response, external_lookup,
 
581
            network_name)
 
582
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
583
            _client=client)
334
584
        result = bzrdir.open_repository()
335
585
        self.assertEqual(
336
 
            [('call', 'BzrDir.find_repositoryV2', ('quack/',))],
 
586
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
337
587
            client._calls)
338
588
        self.assertIsInstance(result, RemoteRepository)
339
589
        self.assertEqual(bzrdir, result.bzrdir)
355
605
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
356
606
 
357
607
 
358
 
class TestBzrDirOpenRepository(tests.TestCase):
359
 
 
360
 
    def test_backwards_compat_1_2(self):
361
 
        transport = MemoryTransport()
362
 
        transport.mkdir('quack')
363
 
        transport = transport.clone('quack')
364
 
        client = FakeClient(transport.base)
365
 
        client.add_unknown_method_response('RemoteRepository.find_repositoryV2')
 
608
class TestBzrDirCreateBranch(TestRemote):
 
609
 
 
610
    def test_backwards_compat(self):
 
611
        self.setup_smart_server_with_call_log()
 
612
        repo = self.make_repository('.')
 
613
        self.reset_smart_call_log()
 
614
        self.disable_verb('BzrDir.create_branch')
 
615
        branch = repo.bzrdir.create_branch()
 
616
        create_branch_call_count = len([call for call in self.hpss_calls if
 
617
            call.call.method == 'BzrDir.create_branch'])
 
618
        self.assertEqual(1, create_branch_call_count)
 
619
 
 
620
    def test_current_server(self):
 
621
        transport = self.get_transport('.')
 
622
        transport = transport.clone('quack')
 
623
        self.make_repository('quack')
 
624
        client = FakeClient(transport.base)
 
625
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
626
        reference_format = reference_bzrdir_format.get_branch_format()
 
627
        network_name = reference_format.network_name()
 
628
        reference_repo_fmt = reference_bzrdir_format.repository_format
 
629
        reference_repo_name = reference_repo_fmt.network_name()
 
630
        client.add_expected_call(
 
631
            'BzrDir.create_branch', ('quack/', network_name),
 
632
            'success', ('ok', network_name, '', 'no', 'no', 'yes',
 
633
            reference_repo_name))
 
634
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
635
            _client=client)
 
636
        branch = a_bzrdir.create_branch()
 
637
        # We should have got a remote branch
 
638
        self.assertIsInstance(branch, remote.RemoteBranch)
 
639
        # its format should have the settings from the response
 
640
        format = branch._format
 
641
        self.assertEqual(network_name, format.network_name())
 
642
 
 
643
 
 
644
class TestBzrDirCreateRepository(TestRemote):
 
645
 
 
646
    def test_backwards_compat(self):
 
647
        self.setup_smart_server_with_call_log()
 
648
        bzrdir = self.make_bzrdir('.')
 
649
        self.reset_smart_call_log()
 
650
        self.disable_verb('BzrDir.create_repository')
 
651
        repo = bzrdir.create_repository()
 
652
        create_repo_call_count = len([call for call in self.hpss_calls if
 
653
            call.call.method == 'BzrDir.create_repository'])
 
654
        self.assertEqual(1, create_repo_call_count)
 
655
 
 
656
    def test_current_server(self):
 
657
        transport = self.get_transport('.')
 
658
        transport = transport.clone('quack')
 
659
        self.make_bzrdir('quack')
 
660
        client = FakeClient(transport.base)
 
661
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
662
        reference_format = reference_bzrdir_format.repository_format
 
663
        network_name = reference_format.network_name()
 
664
        client.add_expected_call(
 
665
            'BzrDir.create_repository', ('quack/',
 
666
                'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
 
667
            'success', ('ok', 'no', 'no', 'no', network_name))
 
668
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
669
            _client=client)
 
670
        repo = a_bzrdir.create_repository()
 
671
        # We should have got a remote repository
 
672
        self.assertIsInstance(repo, remote.RemoteRepository)
 
673
        # its format should have the settings from the response
 
674
        format = repo._format
 
675
        self.assertFalse(format.rich_root_data)
 
676
        self.assertFalse(format.supports_tree_reference)
 
677
        self.assertFalse(format.supports_external_lookups)
 
678
        self.assertEqual(network_name, format.network_name())
 
679
 
 
680
 
 
681
class TestBzrDirOpenRepository(TestRemote):
 
682
 
 
683
    def test_backwards_compat_1_2_3(self):
 
684
        # fallback all the way to the first version.
 
685
        reference_format = self.get_repo_format()
 
686
        network_name = reference_format.network_name()
 
687
        client = FakeClient('bzr://example.com/')
 
688
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
 
689
        client.add_unknown_method_response('BzrDir.find_repositoryV2')
366
690
        client.add_success_response('ok', '', 'no', 'no')
367
 
        bzrdir = RemoteBzrDir(transport, _client=client)
368
 
        repo = bzrdir.open_repository()
369
 
        self.assertEqual(
370
 
            [('call', 'BzrDir.find_repositoryV2', ('quack/',)),
371
 
             ('call', 'BzrDir.find_repository', ('quack/',))],
372
 
            client._calls)
 
691
        # A real repository instance will be created to determine the network
 
692
        # name.
 
693
        client.add_success_response_with_body(
 
694
            "Bazaar-NG meta directory, format 1\n", 'ok')
 
695
        client.add_success_response_with_body(
 
696
            reference_format.get_format_string(), 'ok')
 
697
        # PackRepository wants to do a stat
 
698
        client.add_success_response('stat', '0', '65535')
 
699
        remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
 
700
            _client=client)
 
701
        bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
 
702
            _client=client)
 
703
        repo = bzrdir.open_repository()
 
704
        self.assertEqual(
 
705
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
 
706
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
707
             ('call', 'BzrDir.find_repository', ('quack/',)),
 
708
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
 
709
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
 
710
             ('call', 'stat', ('/quack/.bzr/repository',)),
 
711
             ],
 
712
            client._calls)
 
713
        self.assertEqual(network_name, repo._format.network_name())
 
714
 
 
715
    def test_backwards_compat_2(self):
 
716
        # fallback to find_repositoryV2
 
717
        reference_format = self.get_repo_format()
 
718
        network_name = reference_format.network_name()
 
719
        client = FakeClient('bzr://example.com/')
 
720
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
 
721
        client.add_success_response('ok', '', 'no', 'no', 'no')
 
722
        # A real repository instance will be created to determine the network
 
723
        # name.
 
724
        client.add_success_response_with_body(
 
725
            "Bazaar-NG meta directory, format 1\n", 'ok')
 
726
        client.add_success_response_with_body(
 
727
            reference_format.get_format_string(), 'ok')
 
728
        # PackRepository wants to do a stat
 
729
        client.add_success_response('stat', '0', '65535')
 
730
        remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
 
731
            _client=client)
 
732
        bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
 
733
            _client=client)
 
734
        repo = bzrdir.open_repository()
 
735
        self.assertEqual(
 
736
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
 
737
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
738
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
 
739
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
 
740
             ('call', 'stat', ('/quack/.bzr/repository',)),
 
741
             ],
 
742
            client._calls)
 
743
        self.assertEqual(network_name, repo._format.network_name())
 
744
 
 
745
    def test_current_server(self):
 
746
        reference_format = self.get_repo_format()
 
747
        network_name = reference_format.network_name()
 
748
        transport = MemoryTransport()
 
749
        transport.mkdir('quack')
 
750
        transport = transport.clone('quack')
 
751
        client = FakeClient(transport.base)
 
752
        client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
 
753
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
754
            _client=client)
 
755
        repo = bzrdir.open_repository()
 
756
        self.assertEqual(
 
757
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
 
758
            client._calls)
 
759
        self.assertEqual(network_name, repo._format.network_name())
 
760
 
 
761
 
 
762
class TestBzrDirFormatInitializeEx(TestRemote):
 
763
 
 
764
    def test_success(self):
 
765
        """Simple test for typical successful call."""
 
766
        fmt = bzrdir.RemoteBzrDirFormat()
 
767
        default_format_name = BzrDirFormat.get_default_format().network_name()
 
768
        transport = self.get_transport()
 
769
        client = FakeClient(transport.base)
 
770
        client.add_expected_call(
 
771
            'BzrDirFormat.initialize_ex_1.16',
 
772
                (default_format_name, 'path', 'False', 'False', 'False', '',
 
773
                 '', '', '', 'False'),
 
774
            'success',
 
775
                ('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
 
776
                 'bzrdir fmt', 'False', '', '', 'repo lock token'))
 
777
        # XXX: It would be better to call fmt.initialize_on_transport_ex, but
 
778
        # it's currently hard to test that without supplying a real remote
 
779
        # transport connected to a real server.
 
780
        result = fmt._initialize_on_transport_ex_rpc(client, 'path',
 
781
            transport, False, False, False, None, None, None, None, False)
 
782
        client.finished_test()
 
783
 
 
784
    def test_error(self):
 
785
        """Error responses are translated, e.g. 'PermissionDenied' raises the
 
786
        corresponding error from the client.
 
787
        """
 
788
        fmt = bzrdir.RemoteBzrDirFormat()
 
789
        default_format_name = BzrDirFormat.get_default_format().network_name()
 
790
        transport = self.get_transport()
 
791
        client = FakeClient(transport.base)
 
792
        client.add_expected_call(
 
793
            'BzrDirFormat.initialize_ex_1.16',
 
794
                (default_format_name, 'path', 'False', 'False', 'False', '',
 
795
                 '', '', '', 'False'),
 
796
            'error',
 
797
                ('PermissionDenied', 'path', 'extra info'))
 
798
        # XXX: It would be better to call fmt.initialize_on_transport_ex, but
 
799
        # it's currently hard to test that without supplying a real remote
 
800
        # transport connected to a real server.
 
801
        err = self.assertRaises(errors.PermissionDenied,
 
802
            fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
 
803
            False, False, False, None, None, None, None, False)
 
804
        self.assertEqual('path', err.path)
 
805
        self.assertEqual(': extra info', err.extra)
 
806
        client.finished_test()
 
807
 
 
808
    def test_error_from_real_server(self):
 
809
        """Integration test for error translation."""
 
810
        transport = self.make_smart_server('foo')
 
811
        transport = transport.clone('no-such-path')
 
812
        fmt = bzrdir.RemoteBzrDirFormat()
 
813
        err = self.assertRaises(errors.NoSuchFile,
 
814
            fmt.initialize_on_transport_ex, transport, create_prefix=False)
373
815
 
374
816
 
375
817
class OldSmartClient(object):
400
842
        return OldSmartClient()
401
843
 
402
844
 
403
 
class TestBranchLastRevisionInfo(tests.TestCase):
 
845
class RemoteBzrDirTestCase(TestRemote):
 
846
 
 
847
    def make_remote_bzrdir(self, transport, client):
 
848
        """Make a RemotebzrDir using 'client' as the _client."""
 
849
        return RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
850
            _client=client)
 
851
 
 
852
 
 
853
class RemoteBranchTestCase(RemoteBzrDirTestCase):
 
854
 
 
855
    def make_remote_branch(self, transport, client):
 
856
        """Make a RemoteBranch using 'client' as its _SmartClient.
 
857
 
 
858
        A RemoteBzrDir and RemoteRepository will also be created to fill out
 
859
        the RemoteBranch, albeit with stub values for some of their attributes.
 
860
        """
 
861
        # we do not want bzrdir to make any remote calls, so use False as its
 
862
        # _client.  If it tries to make a remote call, this will fail
 
863
        # immediately.
 
864
        bzrdir = self.make_remote_bzrdir(transport, False)
 
865
        repo = RemoteRepository(bzrdir, None, _client=client)
 
866
        branch_format = self.get_branch_format()
 
867
        format = RemoteBranchFormat(network_name=branch_format.network_name())
 
868
        return RemoteBranch(bzrdir, repo, _client=client, format=format)
 
869
 
 
870
 
 
871
class TestBranchGetParent(RemoteBranchTestCase):
 
872
 
 
873
    def test_no_parent(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.get_parent', ('quack/',),
 
882
            'success', ('',))
 
883
        transport.mkdir('quack')
 
884
        transport = transport.clone('quack')
 
885
        branch = self.make_remote_branch(transport, client)
 
886
        result = branch.get_parent()
 
887
        client.finished_test()
 
888
        self.assertEqual(None, result)
 
889
 
 
890
    def test_parent_relative(self):
 
891
        transport = MemoryTransport()
 
892
        client = FakeClient(transport.base)
 
893
        client.add_expected_call(
 
894
            'Branch.get_stacked_on_url', ('kwaak/',),
 
895
            'error', ('NotStacked',))
 
896
        client.add_expected_call(
 
897
            'Branch.get_parent', ('kwaak/',),
 
898
            'success', ('../foo/',))
 
899
        transport.mkdir('kwaak')
 
900
        transport = transport.clone('kwaak')
 
901
        branch = self.make_remote_branch(transport, client)
 
902
        result = branch.get_parent()
 
903
        self.assertEqual(transport.clone('../foo').base, result)
 
904
 
 
905
    def test_parent_absolute(self):
 
906
        transport = MemoryTransport()
 
907
        client = FakeClient(transport.base)
 
908
        client.add_expected_call(
 
909
            'Branch.get_stacked_on_url', ('kwaak/',),
 
910
            'error', ('NotStacked',))
 
911
        client.add_expected_call(
 
912
            'Branch.get_parent', ('kwaak/',),
 
913
            'success', ('http://foo/',))
 
914
        transport.mkdir('kwaak')
 
915
        transport = transport.clone('kwaak')
 
916
        branch = self.make_remote_branch(transport, client)
 
917
        result = branch.get_parent()
 
918
        self.assertEqual('http://foo/', result)
 
919
        client.finished_test()
 
920
 
 
921
 
 
922
class TestBranchSetParentLocation(RemoteBranchTestCase):
 
923
 
 
924
    def test_no_parent(self):
 
925
        # We call the verb when setting parent to None
 
926
        transport = MemoryTransport()
 
927
        client = FakeClient(transport.base)
 
928
        client.add_expected_call(
 
929
            'Branch.get_stacked_on_url', ('quack/',),
 
930
            'error', ('NotStacked',))
 
931
        client.add_expected_call(
 
932
            'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
 
933
            'success', ())
 
934
        transport.mkdir('quack')
 
935
        transport = transport.clone('quack')
 
936
        branch = self.make_remote_branch(transport, client)
 
937
        branch._lock_token = 'b'
 
938
        branch._repo_lock_token = 'r'
 
939
        branch._set_parent_location(None)
 
940
        client.finished_test()
 
941
 
 
942
    def test_parent(self):
 
943
        transport = MemoryTransport()
 
944
        client = FakeClient(transport.base)
 
945
        client.add_expected_call(
 
946
            'Branch.get_stacked_on_url', ('kwaak/',),
 
947
            'error', ('NotStacked',))
 
948
        client.add_expected_call(
 
949
            'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
 
950
            'success', ())
 
951
        transport.mkdir('kwaak')
 
952
        transport = transport.clone('kwaak')
 
953
        branch = self.make_remote_branch(transport, client)
 
954
        branch._lock_token = 'b'
 
955
        branch._repo_lock_token = 'r'
 
956
        branch._set_parent_location('foo')
 
957
        client.finished_test()
 
958
 
 
959
    def test_backwards_compat(self):
 
960
        self.setup_smart_server_with_call_log()
 
961
        branch = self.make_branch('.')
 
962
        self.reset_smart_call_log()
 
963
        verb = 'Branch.set_parent_location'
 
964
        self.disable_verb(verb)
 
965
        branch.set_parent('http://foo/')
 
966
        self.assertLength(12, self.hpss_calls)
 
967
 
 
968
 
 
969
class TestBranchGetTagsBytes(RemoteBranchTestCase):
 
970
 
 
971
    def test_backwards_compat(self):
 
972
        self.setup_smart_server_with_call_log()
 
973
        branch = self.make_branch('.')
 
974
        self.reset_smart_call_log()
 
975
        verb = 'Branch.get_tags_bytes'
 
976
        self.disable_verb(verb)
 
977
        branch.tags.get_tag_dict()
 
978
        call_count = len([call for call in self.hpss_calls if
 
979
            call.call.method == verb])
 
980
        self.assertEqual(1, call_count)
 
981
 
 
982
    def test_trivial(self):
 
983
        transport = MemoryTransport()
 
984
        client = FakeClient(transport.base)
 
985
        client.add_expected_call(
 
986
            'Branch.get_stacked_on_url', ('quack/',),
 
987
            'error', ('NotStacked',))
 
988
        client.add_expected_call(
 
989
            'Branch.get_tags_bytes', ('quack/',),
 
990
            'success', ('',))
 
991
        transport.mkdir('quack')
 
992
        transport = transport.clone('quack')
 
993
        branch = self.make_remote_branch(transport, client)
 
994
        result = branch.tags.get_tag_dict()
 
995
        client.finished_test()
 
996
        self.assertEqual({}, result)
 
997
 
 
998
 
 
999
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
404
1000
 
405
1001
    def test_empty_branch(self):
406
1002
        # in an empty branch we decode the response properly
407
1003
        transport = MemoryTransport()
408
1004
        client = FakeClient(transport.base)
409
 
        client.add_success_response('ok', '0', 'null:')
 
1005
        client.add_expected_call(
 
1006
            'Branch.get_stacked_on_url', ('quack/',),
 
1007
            'error', ('NotStacked',))
 
1008
        client.add_expected_call(
 
1009
            'Branch.last_revision_info', ('quack/',),
 
1010
            'success', ('ok', '0', 'null:'))
410
1011
        transport.mkdir('quack')
411
1012
        transport = transport.clone('quack')
412
 
        # we do not want bzrdir to make any remote calls
413
 
        bzrdir = RemoteBzrDir(transport, _client=False)
414
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
1013
        branch = self.make_remote_branch(transport, client)
415
1014
        result = branch.last_revision_info()
416
 
 
417
 
        self.assertEqual(
418
 
            [('call', 'Branch.last_revision_info', ('quack/',))],
419
 
            client._calls)
 
1015
        client.finished_test()
420
1016
        self.assertEqual((0, NULL_REVISION), result)
421
1017
 
422
1018
    def test_non_empty_branch(self):
424
1020
        revid = u'\xc8'.encode('utf8')
425
1021
        transport = MemoryTransport()
426
1022
        client = FakeClient(transport.base)
427
 
        client.add_success_response('ok', '2', revid)
 
1023
        client.add_expected_call(
 
1024
            'Branch.get_stacked_on_url', ('kwaak/',),
 
1025
            'error', ('NotStacked',))
 
1026
        client.add_expected_call(
 
1027
            'Branch.last_revision_info', ('kwaak/',),
 
1028
            'success', ('ok', '2', revid))
428
1029
        transport.mkdir('kwaak')
429
1030
        transport = transport.clone('kwaak')
430
 
        # we do not want bzrdir to make any remote calls
431
 
        bzrdir = RemoteBzrDir(transport, _client=False)
432
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
1031
        branch = self.make_remote_branch(transport, client)
433
1032
        result = branch.last_revision_info()
434
 
 
435
 
        self.assertEqual(
436
 
            [('call', 'Branch.last_revision_info', ('kwaak/',))],
437
 
            client._calls)
438
1033
        self.assertEqual((2, revid), result)
439
1034
 
440
1035
 
441
 
class TestBranchSetLastRevision(tests.TestCase):
 
1036
class TestBranch_get_stacked_on_url(TestRemote):
 
1037
    """Test Branch._get_stacked_on_url rpc"""
 
1038
 
 
1039
    def test_get_stacked_on_invalid_url(self):
 
1040
        # test that asking for a stacked on url the server can't access works.
 
1041
        # This isn't perfect, but then as we're in the same process there
 
1042
        # really isn't anything we can do to be 100% sure that the server
 
1043
        # doesn't just open in - this test probably needs to be rewritten using
 
1044
        # a spawn()ed server.
 
1045
        stacked_branch = self.make_branch('stacked', format='1.9')
 
1046
        memory_branch = self.make_branch('base', format='1.9')
 
1047
        vfs_url = self.get_vfs_only_url('base')
 
1048
        stacked_branch.set_stacked_on_url(vfs_url)
 
1049
        transport = stacked_branch.bzrdir.root_transport
 
1050
        client = FakeClient(transport.base)
 
1051
        client.add_expected_call(
 
1052
            'Branch.get_stacked_on_url', ('stacked/',),
 
1053
            'success', ('ok', vfs_url))
 
1054
        # XXX: Multiple calls are bad, this second call documents what is
 
1055
        # today.
 
1056
        client.add_expected_call(
 
1057
            'Branch.get_stacked_on_url', ('stacked/',),
 
1058
            'success', ('ok', vfs_url))
 
1059
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
1060
            _client=client)
 
1061
        repo_fmt = remote.RemoteRepositoryFormat()
 
1062
        repo_fmt._custom_format = stacked_branch.repository._format
 
1063
        branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
 
1064
            _client=client)
 
1065
        result = branch.get_stacked_on_url()
 
1066
        self.assertEqual(vfs_url, result)
 
1067
 
 
1068
    def test_backwards_compatible(self):
 
1069
        # like with bzr1.6 with no Branch.get_stacked_on_url rpc
 
1070
        base_branch = self.make_branch('base', format='1.6')
 
1071
        stacked_branch = self.make_branch('stacked', format='1.6')
 
1072
        stacked_branch.set_stacked_on_url('../base')
 
1073
        client = FakeClient(self.get_url())
 
1074
        branch_network_name = self.get_branch_format().network_name()
 
1075
        client.add_expected_call(
 
1076
            'BzrDir.open_branchV2', ('stacked/',),
 
1077
            'success', ('branch', branch_network_name))
 
1078
        client.add_expected_call(
 
1079
            'BzrDir.find_repositoryV3', ('stacked/',),
 
1080
            'success', ('ok', '', 'no', 'no', 'yes',
 
1081
                stacked_branch.repository._format.network_name()))
 
1082
        # called twice, once from constructor and then again by us
 
1083
        client.add_expected_call(
 
1084
            'Branch.get_stacked_on_url', ('stacked/',),
 
1085
            'unknown', ('Branch.get_stacked_on_url',))
 
1086
        client.add_expected_call(
 
1087
            'Branch.get_stacked_on_url', ('stacked/',),
 
1088
            'unknown', ('Branch.get_stacked_on_url',))
 
1089
        # this will also do vfs access, but that goes direct to the transport
 
1090
        # and isn't seen by the FakeClient.
 
1091
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
1092
            remote.RemoteBzrDirFormat(), _client=client)
 
1093
        branch = bzrdir.open_branch()
 
1094
        result = branch.get_stacked_on_url()
 
1095
        self.assertEqual('../base', result)
 
1096
        client.finished_test()
 
1097
        # it's in the fallback list both for the RemoteRepository and its vfs
 
1098
        # repository
 
1099
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
1100
        self.assertEqual(1,
 
1101
            len(branch.repository._real_repository._fallback_repositories))
 
1102
 
 
1103
    def test_get_stacked_on_real_branch(self):
 
1104
        base_branch = self.make_branch('base', format='1.6')
 
1105
        stacked_branch = self.make_branch('stacked', format='1.6')
 
1106
        stacked_branch.set_stacked_on_url('../base')
 
1107
        reference_format = self.get_repo_format()
 
1108
        network_name = reference_format.network_name()
 
1109
        client = FakeClient(self.get_url())
 
1110
        branch_network_name = self.get_branch_format().network_name()
 
1111
        client.add_expected_call(
 
1112
            'BzrDir.open_branchV2', ('stacked/',),
 
1113
            'success', ('branch', branch_network_name))
 
1114
        client.add_expected_call(
 
1115
            'BzrDir.find_repositoryV3', ('stacked/',),
 
1116
            'success', ('ok', '', 'no', 'no', 'yes', network_name))
 
1117
        # called twice, once from constructor and then again by us
 
1118
        client.add_expected_call(
 
1119
            'Branch.get_stacked_on_url', ('stacked/',),
 
1120
            'success', ('ok', '../base'))
 
1121
        client.add_expected_call(
 
1122
            'Branch.get_stacked_on_url', ('stacked/',),
 
1123
            'success', ('ok', '../base'))
 
1124
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
1125
            remote.RemoteBzrDirFormat(), _client=client)
 
1126
        branch = bzrdir.open_branch()
 
1127
        result = branch.get_stacked_on_url()
 
1128
        self.assertEqual('../base', result)
 
1129
        client.finished_test()
 
1130
        # it's in the fallback list both for the RemoteRepository.
 
1131
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
1132
        # And we haven't had to construct a real repository.
 
1133
        self.assertEqual(None, branch.repository._real_repository)
 
1134
 
 
1135
 
 
1136
class TestBranchSetLastRevision(RemoteBranchTestCase):
442
1137
 
443
1138
    def test_set_empty(self):
444
1139
        # set_revision_history([]) is translated to calling
448
1143
        transport = transport.clone('branch')
449
1144
 
450
1145
        client = FakeClient(transport.base)
451
 
        # lock_write
452
 
        client.add_success_response('ok', 'branch token', 'repo token')
453
 
        # set_last_revision
454
 
        client.add_success_response('ok')
455
 
        # unlock
456
 
        client.add_success_response('ok')
457
 
        bzrdir = RemoteBzrDir(transport, _client=False)
458
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
1146
        client.add_expected_call(
 
1147
            'Branch.get_stacked_on_url', ('branch/',),
 
1148
            'error', ('NotStacked',))
 
1149
        client.add_expected_call(
 
1150
            'Branch.lock_write', ('branch/', '', ''),
 
1151
            'success', ('ok', 'branch token', 'repo token'))
 
1152
        client.add_expected_call(
 
1153
            'Branch.last_revision_info',
 
1154
            ('branch/',),
 
1155
            'success', ('ok', '0', 'null:'))
 
1156
        client.add_expected_call(
 
1157
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
 
1158
            'success', ('ok',))
 
1159
        client.add_expected_call(
 
1160
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1161
            'success', ('ok',))
 
1162
        branch = self.make_remote_branch(transport, client)
459
1163
        # This is a hack to work around the problem that RemoteBranch currently
460
1164
        # unnecessarily invokes _ensure_real upon a call to lock_write.
461
1165
        branch._ensure_real = lambda: None
462
1166
        branch.lock_write()
463
 
        client._calls = []
464
1167
        result = branch.set_revision_history([])
465
 
        self.assertEqual(
466
 
            [('call', 'Branch.set_last_revision',
467
 
                ('branch/', 'branch token', 'repo token', 'null:'))],
468
 
            client._calls)
469
1168
        branch.unlock()
470
1169
        self.assertEqual(None, result)
 
1170
        client.finished_test()
471
1171
 
472
1172
    def test_set_nonempty(self):
473
1173
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
477
1177
        transport = transport.clone('branch')
478
1178
 
479
1179
        client = FakeClient(transport.base)
480
 
        # lock_write
481
 
        client.add_success_response('ok', 'branch token', 'repo token')
482
 
        # set_last_revision
483
 
        client.add_success_response('ok')
484
 
        # unlock
485
 
        client.add_success_response('ok')
486
 
        bzrdir = RemoteBzrDir(transport, _client=False)
487
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
1180
        client.add_expected_call(
 
1181
            'Branch.get_stacked_on_url', ('branch/',),
 
1182
            'error', ('NotStacked',))
 
1183
        client.add_expected_call(
 
1184
            'Branch.lock_write', ('branch/', '', ''),
 
1185
            'success', ('ok', 'branch token', 'repo token'))
 
1186
        client.add_expected_call(
 
1187
            'Branch.last_revision_info',
 
1188
            ('branch/',),
 
1189
            'success', ('ok', '0', 'null:'))
 
1190
        lines = ['rev-id2']
 
1191
        encoded_body = bz2.compress('\n'.join(lines))
 
1192
        client.add_success_response_with_body(encoded_body, 'ok')
 
1193
        client.add_expected_call(
 
1194
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
 
1195
            'success', ('ok',))
 
1196
        client.add_expected_call(
 
1197
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1198
            'success', ('ok',))
 
1199
        branch = self.make_remote_branch(transport, client)
488
1200
        # This is a hack to work around the problem that RemoteBranch currently
489
1201
        # unnecessarily invokes _ensure_real upon a call to lock_write.
490
1202
        branch._ensure_real = lambda: None
491
1203
        # Lock the branch, reset the record of remote calls.
492
1204
        branch.lock_write()
493
 
        client._calls = []
494
 
 
495
1205
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
496
 
        self.assertEqual(
497
 
            [('call', 'Branch.set_last_revision',
498
 
                ('branch/', 'branch token', 'repo token', 'rev-id2'))],
499
 
            client._calls)
500
1206
        branch.unlock()
501
1207
        self.assertEqual(None, result)
 
1208
        client.finished_test()
502
1209
 
503
1210
    def test_no_such_revision(self):
504
1211
        transport = MemoryTransport()
506
1213
        transport = transport.clone('branch')
507
1214
        # A response of 'NoSuchRevision' is translated into an exception.
508
1215
        client = FakeClient(transport.base)
509
 
        # lock_write
510
 
        client.add_success_response('ok', 'branch token', 'repo token')
511
 
        # set_last_revision
512
 
        client.add_error_response('NoSuchRevision', 'rev-id')
513
 
        # unlock
514
 
        client.add_success_response('ok')
 
1216
        client.add_expected_call(
 
1217
            'Branch.get_stacked_on_url', ('branch/',),
 
1218
            'error', ('NotStacked',))
 
1219
        client.add_expected_call(
 
1220
            'Branch.lock_write', ('branch/', '', ''),
 
1221
            'success', ('ok', 'branch token', 'repo token'))
 
1222
        client.add_expected_call(
 
1223
            'Branch.last_revision_info',
 
1224
            ('branch/',),
 
1225
            'success', ('ok', '0', 'null:'))
 
1226
        # get_graph calls to construct the revision history, for the set_rh
 
1227
        # hook
 
1228
        lines = ['rev-id']
 
1229
        encoded_body = bz2.compress('\n'.join(lines))
 
1230
        client.add_success_response_with_body(encoded_body, 'ok')
 
1231
        client.add_expected_call(
 
1232
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
1233
            'error', ('NoSuchRevision', 'rev-id'))
 
1234
        client.add_expected_call(
 
1235
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1236
            'success', ('ok',))
515
1237
 
516
 
        bzrdir = RemoteBzrDir(transport, _client=False)
517
 
        branch = RemoteBranch(bzrdir, None, _client=client)
518
 
        branch._ensure_real = lambda: None
 
1238
        branch = self.make_remote_branch(transport, client)
519
1239
        branch.lock_write()
520
 
        client._calls = []
521
 
 
522
1240
        self.assertRaises(
523
1241
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
524
1242
        branch.unlock()
525
 
 
526
 
 
527
 
class TestBranchSetLastRevisionInfo(tests.TestCase):
 
1243
        client.finished_test()
 
1244
 
 
1245
    def test_tip_change_rejected(self):
 
1246
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
1247
        be raised.
 
1248
        """
 
1249
        transport = MemoryTransport()
 
1250
        transport.mkdir('branch')
 
1251
        transport = transport.clone('branch')
 
1252
        client = FakeClient(transport.base)
 
1253
        rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
 
1254
        rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
 
1255
        client.add_expected_call(
 
1256
            'Branch.get_stacked_on_url', ('branch/',),
 
1257
            'error', ('NotStacked',))
 
1258
        client.add_expected_call(
 
1259
            'Branch.lock_write', ('branch/', '', ''),
 
1260
            'success', ('ok', 'branch token', 'repo token'))
 
1261
        client.add_expected_call(
 
1262
            'Branch.last_revision_info',
 
1263
            ('branch/',),
 
1264
            'success', ('ok', '0', 'null:'))
 
1265
        lines = ['rev-id']
 
1266
        encoded_body = bz2.compress('\n'.join(lines))
 
1267
        client.add_success_response_with_body(encoded_body, 'ok')
 
1268
        client.add_expected_call(
 
1269
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
1270
            'error', ('TipChangeRejected', rejection_msg_utf8))
 
1271
        client.add_expected_call(
 
1272
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1273
            'success', ('ok',))
 
1274
        branch = self.make_remote_branch(transport, client)
 
1275
        branch._ensure_real = lambda: None
 
1276
        branch.lock_write()
 
1277
        # The 'TipChangeRejected' error response triggered by calling
 
1278
        # set_revision_history causes a TipChangeRejected exception.
 
1279
        err = self.assertRaises(
 
1280
            errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
 
1281
        # The UTF-8 message from the response has been decoded into a unicode
 
1282
        # object.
 
1283
        self.assertIsInstance(err.msg, unicode)
 
1284
        self.assertEqual(rejection_msg_unicode, err.msg)
 
1285
        branch.unlock()
 
1286
        client.finished_test()
 
1287
 
 
1288
 
 
1289
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
528
1290
 
529
1291
    def test_set_last_revision_info(self):
530
1292
        # set_last_revision_info(num, 'rev-id') is translated to calling
533
1295
        transport.mkdir('branch')
534
1296
        transport = transport.clone('branch')
535
1297
        client = FakeClient(transport.base)
 
1298
        # get_stacked_on_url
 
1299
        client.add_error_response('NotStacked')
536
1300
        # lock_write
537
1301
        client.add_success_response('ok', 'branch token', 'repo token')
 
1302
        # query the current revision
 
1303
        client.add_success_response('ok', '0', 'null:')
538
1304
        # set_last_revision
539
1305
        client.add_success_response('ok')
540
1306
        # unlock
541
1307
        client.add_success_response('ok')
542
1308
 
543
 
        bzrdir = RemoteBzrDir(transport, _client=False)
544
 
        branch = RemoteBranch(bzrdir, None, _client=client)
545
 
        # This is a hack to work around the problem that RemoteBranch currently
546
 
        # unnecessarily invokes _ensure_real upon a call to lock_write.
547
 
        branch._ensure_real = lambda: None
 
1309
        branch = self.make_remote_branch(transport, client)
548
1310
        # Lock the branch, reset the record of remote calls.
549
1311
        branch.lock_write()
550
1312
        client._calls = []
551
1313
        result = branch.set_last_revision_info(1234, 'a-revision-id')
552
1314
        self.assertEqual(
553
 
            [('call', 'Branch.set_last_revision_info',
 
1315
            [('call', 'Branch.last_revision_info', ('branch/',)),
 
1316
             ('call', 'Branch.set_last_revision_info',
554
1317
                ('branch/', 'branch token', 'repo token',
555
1318
                 '1234', 'a-revision-id'))],
556
1319
            client._calls)
562
1325
        transport.mkdir('branch')
563
1326
        transport = transport.clone('branch')
564
1327
        client = FakeClient(transport.base)
 
1328
        # get_stacked_on_url
 
1329
        client.add_error_response('NotStacked')
565
1330
        # lock_write
566
1331
        client.add_success_response('ok', 'branch token', 'repo token')
567
1332
        # set_last_revision
569
1334
        # unlock
570
1335
        client.add_success_response('ok')
571
1336
 
572
 
        bzrdir = RemoteBzrDir(transport, _client=False)
573
 
        branch = RemoteBranch(bzrdir, None, _client=client)
574
 
        # This is a hack to work around the problem that RemoteBranch currently
575
 
        # unnecessarily invokes _ensure_real upon a call to lock_write.
576
 
        branch._ensure_real = lambda: None
 
1337
        branch = self.make_remote_branch(transport, client)
577
1338
        # Lock the branch, reset the record of remote calls.
578
1339
        branch.lock_write()
579
1340
        client._calls = []
588
1349
        branch._lock_count = 2
589
1350
        branch._lock_token = 'branch token'
590
1351
        branch._repo_lock_token = 'repo token'
 
1352
        branch.repository._lock_mode = 'w'
 
1353
        branch.repository._lock_count = 2
 
1354
        branch.repository._lock_token = 'repo token'
591
1355
 
592
1356
    def test_backwards_compatibility(self):
593
1357
        """If the server does not support the Branch.set_last_revision_info
605
1369
        transport.mkdir('branch')
606
1370
        transport = transport.clone('branch')
607
1371
        client = FakeClient(transport.base)
608
 
        client.add_unknown_method_response('Branch.set_last_revision_info')
609
 
        bzrdir = RemoteBzrDir(transport, _client=False)
610
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
1372
        client.add_expected_call(
 
1373
            'Branch.get_stacked_on_url', ('branch/',),
 
1374
            'error', ('NotStacked',))
 
1375
        client.add_expected_call(
 
1376
            'Branch.last_revision_info',
 
1377
            ('branch/',),
 
1378
            'success', ('ok', '0', 'null:'))
 
1379
        client.add_expected_call(
 
1380
            'Branch.set_last_revision_info',
 
1381
            ('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
 
1382
            'unknown', 'Branch.set_last_revision_info')
 
1383
 
 
1384
        branch = self.make_remote_branch(transport, client)
611
1385
        class StubRealBranch(object):
612
1386
            def __init__(self):
613
1387
                self.calls = []
614
1388
            def set_last_revision_info(self, revno, revision_id):
615
1389
                self.calls.append(
616
1390
                    ('set_last_revision_info', revno, revision_id))
 
1391
            def _clear_cached_state(self):
 
1392
                pass
617
1393
        real_branch = StubRealBranch()
618
1394
        branch._real_branch = real_branch
619
1395
        self.lock_remote_branch(branch)
621
1397
        # Call set_last_revision_info, and verify it behaved as expected.
622
1398
        result = branch.set_last_revision_info(1234, 'a-revision-id')
623
1399
        self.assertEqual(
624
 
            [('call', 'Branch.set_last_revision_info',
625
 
                ('branch/', 'branch token', 'repo token',
626
 
                 '1234', 'a-revision-id')),],
627
 
            client._calls)
628
 
        self.assertEqual(
629
1400
            [('set_last_revision_info', 1234, 'a-revision-id')],
630
1401
            real_branch.calls)
 
1402
        client.finished_test()
631
1403
 
632
1404
    def test_unexpected_error(self):
633
 
        # A response of 'NoSuchRevision' is translated into an exception.
 
1405
        # If the server sends an error the client doesn't understand, it gets
 
1406
        # turned into an UnknownErrorFromSmartServer, which is presented as a
 
1407
        # non-internal error to the user.
634
1408
        transport = MemoryTransport()
635
1409
        transport.mkdir('branch')
636
1410
        transport = transport.clone('branch')
637
1411
        client = FakeClient(transport.base)
 
1412
        # get_stacked_on_url
 
1413
        client.add_error_response('NotStacked')
638
1414
        # lock_write
639
1415
        client.add_success_response('ok', 'branch token', 'repo token')
640
1416
        # set_last_revision
642
1418
        # unlock
643
1419
        client.add_success_response('ok')
644
1420
 
645
 
        bzrdir = RemoteBzrDir(transport, _client=False)
646
 
        branch = RemoteBranch(bzrdir, None, _client=client)
647
 
        # This is a hack to work around the problem that RemoteBranch currently
648
 
        # unnecessarily invokes _ensure_real upon a call to lock_write.
649
 
        branch._ensure_real = lambda: None
 
1421
        branch = self.make_remote_branch(transport, client)
650
1422
        # Lock the branch, reset the record of remote calls.
651
1423
        branch.lock_write()
652
1424
        client._calls = []
653
1425
 
654
1426
        err = self.assertRaises(
655
 
            errors.ErrorFromSmartServer,
 
1427
            errors.UnknownErrorFromSmartServer,
656
1428
            branch.set_last_revision_info, 123, 'revid')
657
1429
        self.assertEqual(('UnexpectedError',), err.error_tuple)
658
1430
        branch.unlock()
659
1431
 
660
 
 
661
 
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
662
 
    """Getting the branch configuration should use an abstract method not vfs.
663
 
    """
 
1432
    def test_tip_change_rejected(self):
 
1433
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
1434
        be raised.
 
1435
        """
 
1436
        transport = MemoryTransport()
 
1437
        transport.mkdir('branch')
 
1438
        transport = transport.clone('branch')
 
1439
        client = FakeClient(transport.base)
 
1440
        # get_stacked_on_url
 
1441
        client.add_error_response('NotStacked')
 
1442
        # lock_write
 
1443
        client.add_success_response('ok', 'branch token', 'repo token')
 
1444
        # set_last_revision
 
1445
        client.add_error_response('TipChangeRejected', 'rejection message')
 
1446
        # unlock
 
1447
        client.add_success_response('ok')
 
1448
 
 
1449
        branch = self.make_remote_branch(transport, client)
 
1450
        # Lock the branch, reset the record of remote calls.
 
1451
        branch.lock_write()
 
1452
        self.addCleanup(branch.unlock)
 
1453
        client._calls = []
 
1454
 
 
1455
        # The 'TipChangeRejected' error response triggered by calling
 
1456
        # set_last_revision_info causes a TipChangeRejected exception.
 
1457
        err = self.assertRaises(
 
1458
            errors.TipChangeRejected,
 
1459
            branch.set_last_revision_info, 123, 'revid')
 
1460
        self.assertEqual('rejection message', err.msg)
 
1461
 
 
1462
 
 
1463
class TestBranchGetSetConfig(RemoteBranchTestCase):
664
1464
 
665
1465
    def test_get_branch_conf(self):
666
 
        raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
667
 
        # We should see that branch.get_config() does a single rpc to get the
668
 
        # remote configuration file, abstracting away where that is stored on
669
 
        # the server.  However at the moment it always falls back to using the
670
 
        # vfs, and this would need some changes in config.py.
671
 
 
672
1466
        # in an empty branch we decode the response properly
673
 
        client = FakeClient(self.get_url())
 
1467
        client = FakeClient()
 
1468
        client.add_expected_call(
 
1469
            'Branch.get_stacked_on_url', ('memory:///',),
 
1470
            'error', ('NotStacked',),)
674
1471
        client.add_success_response_with_body('# config file body', 'ok')
675
 
        # we need to make a real branch because the remote_branch.control_files
676
 
        # will trigger _ensure_real.
677
 
        branch = self.make_branch('quack')
678
 
        transport = branch.bzrdir.root_transport
679
 
        # we do not want bzrdir to make any remote calls
680
 
        bzrdir = RemoteBzrDir(transport, _client=False)
681
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
1472
        transport = MemoryTransport()
 
1473
        branch = self.make_remote_branch(transport, client)
682
1474
        config = branch.get_config()
 
1475
        config.has_explicit_nickname()
683
1476
        self.assertEqual(
684
 
            [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
 
1477
            [('call', 'Branch.get_stacked_on_url', ('memory:///',)),
 
1478
             ('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
685
1479
            client._calls)
686
1480
 
687
 
 
688
 
class TestBranchLockWrite(tests.TestCase):
 
1481
    def test_get_multi_line_branch_conf(self):
 
1482
        # Make sure that multiple-line branch.conf files are supported
 
1483
        #
 
1484
        # https://bugs.edge.launchpad.net/bzr/+bug/354075
 
1485
        client = FakeClient()
 
1486
        client.add_expected_call(
 
1487
            'Branch.get_stacked_on_url', ('memory:///',),
 
1488
            'error', ('NotStacked',),)
 
1489
        client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
 
1490
        transport = MemoryTransport()
 
1491
        branch = self.make_remote_branch(transport, client)
 
1492
        config = branch.get_config()
 
1493
        self.assertEqual(u'2', config.get_user_option('b'))
 
1494
 
 
1495
    def test_set_option(self):
 
1496
        client = FakeClient()
 
1497
        client.add_expected_call(
 
1498
            'Branch.get_stacked_on_url', ('memory:///',),
 
1499
            'error', ('NotStacked',),)
 
1500
        client.add_expected_call(
 
1501
            'Branch.lock_write', ('memory:///', '', ''),
 
1502
            'success', ('ok', 'branch token', 'repo token'))
 
1503
        client.add_expected_call(
 
1504
            'Branch.set_config_option', ('memory:///', 'branch token',
 
1505
            'repo token', 'foo', 'bar', ''),
 
1506
            'success', ())
 
1507
        client.add_expected_call(
 
1508
            'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
 
1509
            'success', ('ok',))
 
1510
        transport = MemoryTransport()
 
1511
        branch = self.make_remote_branch(transport, client)
 
1512
        branch.lock_write()
 
1513
        config = branch._get_config()
 
1514
        config.set_option('foo', 'bar')
 
1515
        branch.unlock()
 
1516
        client.finished_test()
 
1517
 
 
1518
    def test_backwards_compat_set_option(self):
 
1519
        self.setup_smart_server_with_call_log()
 
1520
        branch = self.make_branch('.')
 
1521
        verb = 'Branch.set_config_option'
 
1522
        self.disable_verb(verb)
 
1523
        branch.lock_write()
 
1524
        self.addCleanup(branch.unlock)
 
1525
        self.reset_smart_call_log()
 
1526
        branch._get_config().set_option('value', 'name')
 
1527
        self.assertLength(10, self.hpss_calls)
 
1528
        self.assertEqual('value', branch._get_config().get_option('name'))
 
1529
 
 
1530
 
 
1531
class TestBranchLockWrite(RemoteBranchTestCase):
689
1532
 
690
1533
    def test_lock_write_unlockable(self):
691
1534
        transport = MemoryTransport()
692
1535
        client = FakeClient(transport.base)
693
 
        client.add_error_response('UnlockableTransport')
 
1536
        client.add_expected_call(
 
1537
            'Branch.get_stacked_on_url', ('quack/',),
 
1538
            'error', ('NotStacked',),)
 
1539
        client.add_expected_call(
 
1540
            'Branch.lock_write', ('quack/', '', ''),
 
1541
            'error', ('UnlockableTransport',))
694
1542
        transport.mkdir('quack')
695
1543
        transport = transport.clone('quack')
696
 
        # we do not want bzrdir to make any remote calls
697
 
        bzrdir = RemoteBzrDir(transport, _client=False)
698
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
1544
        branch = self.make_remote_branch(transport, client)
699
1545
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
 
1546
        client.finished_test()
 
1547
 
 
1548
 
 
1549
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
 
1550
 
 
1551
    def test__get_config(self):
 
1552
        client = FakeClient()
 
1553
        client.add_success_response_with_body('default_stack_on = /\n', 'ok')
 
1554
        transport = MemoryTransport()
 
1555
        bzrdir = self.make_remote_bzrdir(transport, client)
 
1556
        config = bzrdir.get_config()
 
1557
        self.assertEqual('/', config.get_default_stack_on())
700
1558
        self.assertEqual(
701
 
            [('call', 'Branch.lock_write', ('quack/', '', ''))],
 
1559
            [('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
702
1560
            client._calls)
703
1561
 
 
1562
    def test_set_option_uses_vfs(self):
 
1563
        self.setup_smart_server_with_call_log()
 
1564
        bzrdir = self.make_bzrdir('.')
 
1565
        self.reset_smart_call_log()
 
1566
        config = bzrdir.get_config()
 
1567
        config.set_default_stack_on('/')
 
1568
        self.assertLength(3, self.hpss_calls)
 
1569
 
 
1570
    def test_backwards_compat_get_option(self):
 
1571
        self.setup_smart_server_with_call_log()
 
1572
        bzrdir = self.make_bzrdir('.')
 
1573
        verb = 'BzrDir.get_config_file'
 
1574
        self.disable_verb(verb)
 
1575
        self.reset_smart_call_log()
 
1576
        self.assertEqual(None,
 
1577
            bzrdir._get_config().get_option('default_stack_on'))
 
1578
        self.assertLength(3, self.hpss_calls)
 
1579
 
704
1580
 
705
1581
class TestTransportIsReadonly(tests.TestCase):
706
1582
 
726
1602
 
727
1603
    def test_error_from_old_server(self):
728
1604
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
729
 
        
 
1605
 
730
1606
        Clients should treat it as a "no" response, because is_readonly is only
731
1607
        advisory anyway (a transport could be read-write, but then the
732
1608
        underlying filesystem could be readonly anyway).
741
1617
            client._calls)
742
1618
 
743
1619
 
744
 
class TestRemoteRepository(tests.TestCase):
 
1620
class TestTransportMkdir(tests.TestCase):
 
1621
 
 
1622
    def test_permissiondenied(self):
 
1623
        client = FakeClient()
 
1624
        client.add_error_response('PermissionDenied', 'remote path', 'extra')
 
1625
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1626
                                    _client=client)
 
1627
        exc = self.assertRaises(
 
1628
            errors.PermissionDenied, transport.mkdir, 'client path')
 
1629
        expected_error = errors.PermissionDenied('/client path', 'extra')
 
1630
        self.assertEqual(expected_error, exc)
 
1631
 
 
1632
 
 
1633
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
 
1634
 
 
1635
    def test_defaults_to_none(self):
 
1636
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1637
        self.assertIs(None, t._get_credentials()[0])
 
1638
 
 
1639
    def test_uses_authentication_config(self):
 
1640
        conf = config.AuthenticationConfig()
 
1641
        conf._get_config().update(
 
1642
            {'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
 
1643
            'example.com'}})
 
1644
        conf._save()
 
1645
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1646
        self.assertEqual('bar', t._get_credentials()[0])
 
1647
 
 
1648
 
 
1649
class TestRemoteRepository(TestRemote):
745
1650
    """Base for testing RemoteRepository protocol usage.
746
 
    
747
 
    These tests contain frozen requests and responses.  We want any changes to 
 
1651
 
 
1652
    These tests contain frozen requests and responses.  We want any changes to
748
1653
    what is sent or expected to be require a thoughtful update to these tests
749
1654
    because they might break compatibility with different-versioned servers.
750
1655
    """
751
1656
 
752
1657
    def setup_fake_client_and_repository(self, transport_path):
753
1658
        """Create the fake client and repository for testing with.
754
 
        
 
1659
 
755
1660
        There's no real server here; we just have canned responses sent
756
1661
        back one by one.
757
 
        
 
1662
 
758
1663
        :param transport_path: Path below the root of the MemoryTransport
759
1664
            where the repository will be created.
760
1665
        """
763
1668
        client = FakeClient(transport.base)
764
1669
        transport = transport.clone(transport_path)
765
1670
        # we do not want bzrdir to make any remote calls
766
 
        bzrdir = RemoteBzrDir(transport, _client=False)
 
1671
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
1672
            _client=False)
767
1673
        repo = RemoteRepository(bzrdir, None, _client=client)
768
1674
        return repo, client
769
1675
 
770
1676
 
 
1677
class TestRepositoryFormat(TestRemoteRepository):
 
1678
 
 
1679
    def test_fast_delta(self):
 
1680
        true_name = groupcompress_repo.RepositoryFormatCHK1().network_name()
 
1681
        true_format = RemoteRepositoryFormat()
 
1682
        true_format._network_name = true_name
 
1683
        self.assertEqual(True, true_format.fast_deltas)
 
1684
        false_name = pack_repo.RepositoryFormatKnitPack1().network_name()
 
1685
        false_format = RemoteRepositoryFormat()
 
1686
        false_format._network_name = false_name
 
1687
        self.assertEqual(False, false_format.fast_deltas)
 
1688
 
 
1689
 
771
1690
class TestRepositoryGatherStats(TestRemoteRepository):
772
1691
 
773
1692
    def test_revid_none(self):
829
1748
class TestRepositoryGetGraph(TestRemoteRepository):
830
1749
 
831
1750
    def test_get_graph(self):
832
 
        # get_graph returns a graph with the repository as the
833
 
        # parents_provider.
 
1751
        # get_graph returns a graph with a custom parents provider.
834
1752
        transport_path = 'quack'
835
1753
        repo, client = self.setup_fake_client_and_repository(transport_path)
836
1754
        graph = repo.get_graph()
837
 
        self.assertEqual(graph._parents_provider, repo)
 
1755
        self.assertNotEqual(graph._parents_provider, repo)
838
1756
 
839
1757
 
840
1758
class TestRepositoryGetParentMap(TestRemoteRepository):
862
1780
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
863
1781
        self.assertEqual(
864
1782
            [('call_with_body_bytes_expecting_body',
865
 
              'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
 
1783
              'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
 
1784
              '\n\n0')],
866
1785
            client._calls)
867
1786
        repo.unlock()
868
1787
        # now we call again, and it should use the second response.
872
1791
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
873
1792
        self.assertEqual(
874
1793
            [('call_with_body_bytes_expecting_body',
875
 
              'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
 
1794
              'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
 
1795
              '\n\n0'),
876
1796
             ('call_with_body_bytes_expecting_body',
877
 
              'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
 
1797
              'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
 
1798
              '\n\n0'),
878
1799
            ],
879
1800
            client._calls)
880
1801
        repo.unlock()
881
1802
 
882
1803
    def test_get_parent_map_reconnects_if_unknown_method(self):
883
1804
        transport_path = 'quack'
 
1805
        rev_id = 'revision-id'
884
1806
        repo, client = self.setup_fake_client_and_repository(transport_path)
885
 
        client.add_unknown_method_response('Repository,get_parent_map')
886
 
        client.add_success_response_with_body('', 'ok')
887
 
        self.assertTrue(client._medium._remote_is_at_least_1_2)
888
 
        rev_id = 'revision-id'
889
 
        expected_deprecations = [
890
 
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
891
 
            'in version 1.4.']
892
 
        parents = self.callDeprecated(
893
 
            expected_deprecations, repo.get_parent_map, [rev_id])
 
1807
        client.add_unknown_method_response('Repository.get_parent_map')
 
1808
        client.add_success_response_with_body(rev_id, 'ok')
 
1809
        self.assertFalse(client._medium._is_remote_before((1, 2)))
 
1810
        parents = repo.get_parent_map([rev_id])
894
1811
        self.assertEqual(
895
1812
            [('call_with_body_bytes_expecting_body',
896
 
              'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
 
1813
              'Repository.get_parent_map', ('quack/', 'include-missing:',
 
1814
              rev_id), '\n\n0'),
897
1815
             ('disconnect medium',),
898
1816
             ('call_expecting_body', 'Repository.get_revision_graph',
899
1817
              ('quack/', ''))],
900
1818
            client._calls)
901
1819
        # The medium is now marked as being connected to an older server
902
 
        self.assertFalse(client._medium._remote_is_at_least_1_2)
 
1820
        self.assertTrue(client._medium._is_remote_before((1, 2)))
 
1821
        self.assertEqual({rev_id: ('null:',)}, parents)
903
1822
 
904
1823
    def test_get_parent_map_fallback_parentless_node(self):
905
1824
        """get_parent_map falls back to get_revision_graph on old servers.  The
916
1835
        transport_path = 'quack'
917
1836
        repo, client = self.setup_fake_client_and_repository(transport_path)
918
1837
        client.add_success_response_with_body(rev_id, 'ok')
919
 
        client._medium._remote_is_at_least_1_2 = False
920
 
        expected_deprecations = [
921
 
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
922
 
            'in version 1.4.']
923
 
        parents = self.callDeprecated(
924
 
            expected_deprecations, repo.get_parent_map, [rev_id])
 
1838
        client._medium._remember_remote_is_before((1, 2))
 
1839
        parents = repo.get_parent_map([rev_id])
925
1840
        self.assertEqual(
926
1841
            [('call_expecting_body', 'Repository.get_revision_graph',
927
1842
             ('quack/', ''))],
935
1850
            errors.UnexpectedSmartServerResponse,
936
1851
            repo.get_parent_map, ['a-revision-id'])
937
1852
 
 
1853
    def test_get_parent_map_negative_caches_missing_keys(self):
 
1854
        self.setup_smart_server_with_call_log()
 
1855
        repo = self.make_repository('foo')
 
1856
        self.assertIsInstance(repo, RemoteRepository)
 
1857
        repo.lock_read()
 
1858
        self.addCleanup(repo.unlock)
 
1859
        self.reset_smart_call_log()
 
1860
        graph = repo.get_graph()
 
1861
        self.assertEqual({},
 
1862
            graph.get_parent_map(['some-missing', 'other-missing']))
 
1863
        self.assertLength(1, self.hpss_calls)
 
1864
        # No call if we repeat this
 
1865
        self.reset_smart_call_log()
 
1866
        graph = repo.get_graph()
 
1867
        self.assertEqual({},
 
1868
            graph.get_parent_map(['some-missing', 'other-missing']))
 
1869
        self.assertLength(0, self.hpss_calls)
 
1870
        # Asking for more unknown keys makes a request.
 
1871
        self.reset_smart_call_log()
 
1872
        graph = repo.get_graph()
 
1873
        self.assertEqual({},
 
1874
            graph.get_parent_map(['some-missing', 'other-missing',
 
1875
                'more-missing']))
 
1876
        self.assertLength(1, self.hpss_calls)
 
1877
 
 
1878
    def disableExtraResults(self):
 
1879
        old_flag = SmartServerRepositoryGetParentMap.no_extra_results
 
1880
        SmartServerRepositoryGetParentMap.no_extra_results = True
 
1881
        def reset_values():
 
1882
            SmartServerRepositoryGetParentMap.no_extra_results = old_flag
 
1883
        self.addCleanup(reset_values)
 
1884
 
 
1885
    def test_null_cached_missing_and_stop_key(self):
 
1886
        self.setup_smart_server_with_call_log()
 
1887
        # Make a branch with a single revision.
 
1888
        builder = self.make_branch_builder('foo')
 
1889
        builder.start_series()
 
1890
        builder.build_snapshot('first', None, [
 
1891
            ('add', ('', 'root-id', 'directory', ''))])
 
1892
        builder.finish_series()
 
1893
        branch = builder.get_branch()
 
1894
        repo = branch.repository
 
1895
        self.assertIsInstance(repo, RemoteRepository)
 
1896
        # Stop the server from sending extra results.
 
1897
        self.disableExtraResults()
 
1898
        repo.lock_read()
 
1899
        self.addCleanup(repo.unlock)
 
1900
        self.reset_smart_call_log()
 
1901
        graph = repo.get_graph()
 
1902
        # Query for 'first' and 'null:'.  Because 'null:' is a parent of
 
1903
        # 'first' it will be a candidate for the stop_keys of subsequent
 
1904
        # requests, and because 'null:' was queried but not returned it will be
 
1905
        # cached as missing.
 
1906
        self.assertEqual({'first': ('null:',)},
 
1907
            graph.get_parent_map(['first', 'null:']))
 
1908
        # Now query for another key.  This request will pass along a recipe of
 
1909
        # start and stop keys describing the already cached results, and this
 
1910
        # recipe's revision count must be correct (or else it will trigger an
 
1911
        # error from the server).
 
1912
        self.assertEqual({}, graph.get_parent_map(['another-key']))
 
1913
        # This assertion guards against disableExtraResults silently failing to
 
1914
        # work, thus invalidating the test.
 
1915
        self.assertLength(2, self.hpss_calls)
 
1916
 
 
1917
    def test_get_parent_map_gets_ghosts_from_result(self):
 
1918
        # asking for a revision should negatively cache close ghosts in its
 
1919
        # ancestry.
 
1920
        self.setup_smart_server_with_call_log()
 
1921
        tree = self.make_branch_and_memory_tree('foo')
 
1922
        tree.lock_write()
 
1923
        try:
 
1924
            builder = treebuilder.TreeBuilder()
 
1925
            builder.start_tree(tree)
 
1926
            builder.build([])
 
1927
            builder.finish_tree()
 
1928
            tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
 
1929
            rev_id = tree.commit('')
 
1930
        finally:
 
1931
            tree.unlock()
 
1932
        tree.lock_read()
 
1933
        self.addCleanup(tree.unlock)
 
1934
        repo = tree.branch.repository
 
1935
        self.assertIsInstance(repo, RemoteRepository)
 
1936
        # ask for rev_id
 
1937
        repo.get_parent_map([rev_id])
 
1938
        self.reset_smart_call_log()
 
1939
        # Now asking for rev_id's ghost parent should not make calls
 
1940
        self.assertEqual({}, repo.get_parent_map(['non-existant']))
 
1941
        self.assertLength(0, self.hpss_calls)
 
1942
 
 
1943
 
 
1944
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
 
1945
 
 
1946
    def test_allows_new_revisions(self):
 
1947
        """get_parent_map's results can be updated by commit."""
 
1948
        smart_server = server.SmartTCPServer_for_testing()
 
1949
        smart_server.setUp()
 
1950
        self.addCleanup(smart_server.tearDown)
 
1951
        self.make_branch('branch')
 
1952
        branch = Branch.open(smart_server.get_url() + '/branch')
 
1953
        tree = branch.create_checkout('tree', lightweight=True)
 
1954
        tree.lock_write()
 
1955
        self.addCleanup(tree.unlock)
 
1956
        graph = tree.branch.repository.get_graph()
 
1957
        # This provides an opportunity for the missing rev-id to be cached.
 
1958
        self.assertEqual({}, graph.get_parent_map(['rev1']))
 
1959
        tree.commit('message', rev_id='rev1')
 
1960
        graph = tree.branch.repository.get_graph()
 
1961
        self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
 
1962
 
938
1963
 
939
1964
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
940
 
    
 
1965
 
941
1966
    def test_null_revision(self):
942
1967
        # a null revision has the predictable result {}, we should have no wire
943
1968
        # traffic when calling it with this argument
944
1969
        transport_path = 'empty'
945
1970
        repo, client = self.setup_fake_client_and_repository(transport_path)
946
1971
        client.add_success_response('notused')
947
 
        result = self.applyDeprecated(one_four, repo.get_revision_graph,
948
 
            NULL_REVISION)
 
1972
        # actual RemoteRepository.get_revision_graph is gone, but there's an
 
1973
        # equivalent private method for testing
 
1974
        result = repo._get_revision_graph(NULL_REVISION)
949
1975
        self.assertEqual([], client._calls)
950
1976
        self.assertEqual({}, result)
951
1977
 
959
1985
        transport_path = 'sinhala'
960
1986
        repo, client = self.setup_fake_client_and_repository(transport_path)
961
1987
        client.add_success_response_with_body(encoded_body, 'ok')
962
 
        result = self.applyDeprecated(one_four, repo.get_revision_graph)
 
1988
        # actual RemoteRepository.get_revision_graph is gone, but there's an
 
1989
        # equivalent private method for testing
 
1990
        result = repo._get_revision_graph(None)
963
1991
        self.assertEqual(
964
1992
            [('call_expecting_body', 'Repository.get_revision_graph',
965
1993
             ('sinhala/', ''))],
978
2006
        transport_path = 'sinhala'
979
2007
        repo, client = self.setup_fake_client_and_repository(transport_path)
980
2008
        client.add_success_response_with_body(encoded_body, 'ok')
981
 
        result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
 
2009
        result = repo._get_revision_graph(r2)
982
2010
        self.assertEqual(
983
2011
            [('call_expecting_body', 'Repository.get_revision_graph',
984
2012
             ('sinhala/', r2))],
992
2020
        client.add_error_response('nosuchrevision', revid)
993
2021
        # also check that the right revision is reported in the error
994
2022
        self.assertRaises(errors.NoSuchRevision,
995
 
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
2023
            repo._get_revision_graph, revid)
996
2024
        self.assertEqual(
997
2025
            [('call_expecting_body', 'Repository.get_revision_graph',
998
2026
             ('sinhala/', revid))],
1003
2031
        transport_path = 'sinhala'
1004
2032
        repo, client = self.setup_fake_client_and_repository(transport_path)
1005
2033
        client.add_error_response('AnUnexpectedError')
1006
 
        e = self.assertRaises(errors.ErrorFromSmartServer,
1007
 
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
2034
        e = self.assertRaises(errors.UnknownErrorFromSmartServer,
 
2035
            repo._get_revision_graph, revid)
1008
2036
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1009
2037
 
1010
 
        
 
2038
 
 
2039
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
 
2040
 
 
2041
    def test_ok(self):
 
2042
        repo, client = self.setup_fake_client_and_repository('quack')
 
2043
        client.add_expected_call(
 
2044
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2045
            'success', ('ok', 'rev-five'))
 
2046
        result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
 
2047
        self.assertEqual((True, 'rev-five'), result)
 
2048
        client.finished_test()
 
2049
 
 
2050
    def test_history_incomplete(self):
 
2051
        repo, client = self.setup_fake_client_and_repository('quack')
 
2052
        client.add_expected_call(
 
2053
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2054
            'success', ('history-incomplete', 10, 'rev-ten'))
 
2055
        result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
 
2056
        self.assertEqual((False, (10, 'rev-ten')), result)
 
2057
        client.finished_test()
 
2058
 
 
2059
    def test_history_incomplete_with_fallback(self):
 
2060
        """A 'history-incomplete' response causes the fallback repository to be
 
2061
        queried too, if one is set.
 
2062
        """
 
2063
        # Make a repo with a fallback repo, both using a FakeClient.
 
2064
        format = remote.response_tuple_to_repo_format(
 
2065
            ('yes', 'no', 'yes', 'fake-network-name'))
 
2066
        repo, client = self.setup_fake_client_and_repository('quack')
 
2067
        repo._format = format
 
2068
        fallback_repo, ignored = self.setup_fake_client_and_repository(
 
2069
            'fallback')
 
2070
        fallback_repo._client = client
 
2071
        repo.add_fallback_repository(fallback_repo)
 
2072
        # First the client should ask the primary repo
 
2073
        client.add_expected_call(
 
2074
            'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
 
2075
            'success', ('history-incomplete', 2, 'rev-two'))
 
2076
        # Then it should ask the fallback, using revno/revid from the
 
2077
        # history-incomplete response as the known revno/revid.
 
2078
        client.add_expected_call(
 
2079
            'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
 
2080
            'success', ('ok', 'rev-one'))
 
2081
        result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
 
2082
        self.assertEqual((True, 'rev-one'), result)
 
2083
        client.finished_test()
 
2084
 
 
2085
    def test_nosuchrevision(self):
 
2086
        # 'nosuchrevision' is returned when the known-revid is not found in the
 
2087
        # remote repo.  The client translates that response to NoSuchRevision.
 
2088
        repo, client = self.setup_fake_client_and_repository('quack')
 
2089
        client.add_expected_call(
 
2090
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2091
            'error', ('nosuchrevision', 'rev-foo'))
 
2092
        self.assertRaises(
 
2093
            errors.NoSuchRevision,
 
2094
            repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
 
2095
        client.finished_test()
 
2096
 
 
2097
 
1011
2098
class TestRepositoryIsShared(TestRemoteRepository):
1012
2099
 
1013
2100
    def test_is_shared(self):
1064
2151
            client._calls)
1065
2152
 
1066
2153
 
 
2154
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
 
2155
 
 
2156
    def test_backwards_compat(self):
 
2157
        self.setup_smart_server_with_call_log()
 
2158
        repo = self.make_repository('.')
 
2159
        self.reset_smart_call_log()
 
2160
        verb = 'Repository.set_make_working_trees'
 
2161
        self.disable_verb(verb)
 
2162
        repo.set_make_working_trees(True)
 
2163
        call_count = len([call for call in self.hpss_calls if
 
2164
            call.call.method == verb])
 
2165
        self.assertEqual(1, call_count)
 
2166
 
 
2167
    def test_current(self):
 
2168
        transport_path = 'quack'
 
2169
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2170
        client.add_expected_call(
 
2171
            'Repository.set_make_working_trees', ('quack/', 'True'),
 
2172
            'success', ('ok',))
 
2173
        client.add_expected_call(
 
2174
            'Repository.set_make_working_trees', ('quack/', 'False'),
 
2175
            'success', ('ok',))
 
2176
        repo.set_make_working_trees(True)
 
2177
        repo.set_make_working_trees(False)
 
2178
 
 
2179
 
1067
2180
class TestRepositoryUnlock(TestRemoteRepository):
1068
2181
 
1069
2182
    def test_unlock(self):
1102
2215
        self.assertEqual([], client._calls)
1103
2216
 
1104
2217
 
 
2218
class TestRepositoryInsertStream(TestRemoteRepository):
 
2219
 
 
2220
    def test_unlocked_repo(self):
 
2221
        transport_path = 'quack'
 
2222
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2223
        client.add_expected_call(
 
2224
            'Repository.insert_stream', ('quack/', ''),
 
2225
            'success', ('ok',))
 
2226
        client.add_expected_call(
 
2227
            'Repository.insert_stream', ('quack/', ''),
 
2228
            'success', ('ok',))
 
2229
        sink = repo._get_sink()
 
2230
        fmt = repository.RepositoryFormat.get_default_format()
 
2231
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
 
2232
        self.assertEqual([], resume_tokens)
 
2233
        self.assertEqual(set(), missing_keys)
 
2234
        client.finished_test()
 
2235
 
 
2236
    def test_locked_repo_with_no_lock_token(self):
 
2237
        transport_path = 'quack'
 
2238
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2239
        client.add_expected_call(
 
2240
            'Repository.lock_write', ('quack/', ''),
 
2241
            'success', ('ok', ''))
 
2242
        client.add_expected_call(
 
2243
            'Repository.insert_stream', ('quack/', ''),
 
2244
            'success', ('ok',))
 
2245
        client.add_expected_call(
 
2246
            'Repository.insert_stream', ('quack/', ''),
 
2247
            'success', ('ok',))
 
2248
        repo.lock_write()
 
2249
        sink = repo._get_sink()
 
2250
        fmt = repository.RepositoryFormat.get_default_format()
 
2251
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
 
2252
        self.assertEqual([], resume_tokens)
 
2253
        self.assertEqual(set(), missing_keys)
 
2254
        client.finished_test()
 
2255
 
 
2256
    def test_locked_repo_with_lock_token(self):
 
2257
        transport_path = 'quack'
 
2258
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2259
        client.add_expected_call(
 
2260
            'Repository.lock_write', ('quack/', ''),
 
2261
            'success', ('ok', 'a token'))
 
2262
        client.add_expected_call(
 
2263
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
 
2264
            'success', ('ok',))
 
2265
        client.add_expected_call(
 
2266
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
 
2267
            'success', ('ok',))
 
2268
        repo.lock_write()
 
2269
        sink = repo._get_sink()
 
2270
        fmt = repository.RepositoryFormat.get_default_format()
 
2271
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
 
2272
        self.assertEqual([], resume_tokens)
 
2273
        self.assertEqual(set(), missing_keys)
 
2274
        client.finished_test()
 
2275
 
 
2276
 
1105
2277
class TestRepositoryTarball(TestRemoteRepository):
1106
2278
 
1107
2279
    # This is a canned tarball reponse we can validate against
1156
2328
        src_repo.copy_content_into(dest_repo)
1157
2329
 
1158
2330
 
1159
 
class TestRepositoryStreamKnitData(TestRemoteRepository):
1160
 
 
1161
 
    def make_pack_file(self, records):
1162
 
        pack_file = StringIO()
1163
 
        pack_writer = pack.ContainerWriter(pack_file.write)
1164
 
        pack_writer.begin()
1165
 
        for bytes, names in records:
1166
 
            pack_writer.add_bytes_record(bytes, names)
1167
 
        pack_writer.end()
1168
 
        pack_file.seek(0)
1169
 
        return pack_file
1170
 
 
1171
 
    def make_pack_stream(self, records):
1172
 
        pack_serialiser = pack.ContainerSerialiser()
1173
 
        yield pack_serialiser.begin()
1174
 
        for bytes, names in records:
1175
 
            yield pack_serialiser.bytes_record(bytes, names)
1176
 
        yield pack_serialiser.end()
1177
 
 
1178
 
    def test_bad_pack_from_server(self):
1179
 
        """A response with invalid data (e.g. it has a record with multiple
1180
 
        names) triggers an exception.
1181
 
        
1182
 
        Not all possible errors will be caught at this stage, but obviously
1183
 
        malformed data should be.
1184
 
        """
1185
 
        record = ('bytes', [('name1',), ('name2',)])
1186
 
        pack_stream = self.make_pack_stream([record])
1187
 
        transport_path = 'quack'
1188
 
        repo, client = self.setup_fake_client_and_repository(transport_path)
1189
 
        client.add_success_response_with_body(pack_stream, 'ok')
1190
 
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
1191
 
        stream = repo.get_data_stream_for_search(search)
1192
 
        self.assertRaises(errors.SmartProtocolError, list, stream)
1193
 
    
 
2331
class _StubRealPackRepository(object):
 
2332
 
 
2333
    def __init__(self, calls):
 
2334
        self.calls = calls
 
2335
        self._pack_collection = _StubPackCollection(calls)
 
2336
 
 
2337
    def is_in_write_group(self):
 
2338
        return False
 
2339
 
 
2340
    def refresh_data(self):
 
2341
        self.calls.append(('pack collection reload_pack_names',))
 
2342
 
 
2343
 
 
2344
class _StubPackCollection(object):
 
2345
 
 
2346
    def __init__(self, calls):
 
2347
        self.calls = calls
 
2348
 
 
2349
    def autopack(self):
 
2350
        self.calls.append(('pack collection autopack',))
 
2351
 
 
2352
 
 
2353
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
 
2354
    """Tests for RemoteRepository.autopack implementation."""
 
2355
 
 
2356
    def test_ok(self):
 
2357
        """When the server returns 'ok' and there's no _real_repository, then
 
2358
        nothing else happens: the autopack method is done.
 
2359
        """
 
2360
        transport_path = 'quack'
 
2361
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2362
        client.add_expected_call(
 
2363
            'PackRepository.autopack', ('quack/',), 'success', ('ok',))
 
2364
        repo.autopack()
 
2365
        client.finished_test()
 
2366
 
 
2367
    def test_ok_with_real_repo(self):
 
2368
        """When the server returns 'ok' and there is a _real_repository, then
 
2369
        the _real_repository's reload_pack_name's method will be called.
 
2370
        """
 
2371
        transport_path = 'quack'
 
2372
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2373
        client.add_expected_call(
 
2374
            'PackRepository.autopack', ('quack/',),
 
2375
            'success', ('ok',))
 
2376
        repo._real_repository = _StubRealPackRepository(client._calls)
 
2377
        repo.autopack()
 
2378
        self.assertEqual(
 
2379
            [('call', 'PackRepository.autopack', ('quack/',)),
 
2380
             ('pack collection reload_pack_names',)],
 
2381
            client._calls)
 
2382
 
1194
2383
    def test_backwards_compatibility(self):
1195
 
        """If the server doesn't recognise this request, fallback to VFS."""
1196
 
        repo, client = self.setup_fake_client_and_repository('path')
1197
 
        client.add_unknown_method_response(
1198
 
            'Repository.stream_revisions_chunked')
1199
 
        self.mock_called = False
1200
 
        repo._real_repository = MockRealRepository(self)
1201
 
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
1202
 
        repo.get_data_stream_for_search(search)
1203
 
        self.assertTrue(self.mock_called)
1204
 
        self.failIf(client.expecting_body,
1205
 
            "The protocol has been left in an unclean state that will cause "
1206
 
            "TooManyConcurrentRequests errors.")
1207
 
 
1208
 
 
1209
 
class MockRealRepository(object):
1210
 
    """Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
1211
 
 
1212
 
    def __init__(self, test):
1213
 
        self.test = test
1214
 
 
1215
 
    def get_data_stream_for_search(self, search):
1216
 
        self.test.assertEqual(set(['revid']), search.get_keys())
1217
 
        self.test.mock_called = True
1218
 
 
1219
 
 
 
2384
        """If the server does not recognise the PackRepository.autopack verb,
 
2385
        fallback to the real_repository's implementation.
 
2386
        """
 
2387
        transport_path = 'quack'
 
2388
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2389
        client.add_unknown_method_response('PackRepository.autopack')
 
2390
        def stub_ensure_real():
 
2391
            client._calls.append(('_ensure_real',))
 
2392
            repo._real_repository = _StubRealPackRepository(client._calls)
 
2393
        repo._ensure_real = stub_ensure_real
 
2394
        repo.autopack()
 
2395
        self.assertEqual(
 
2396
            [('call', 'PackRepository.autopack', ('quack/',)),
 
2397
             ('_ensure_real',),
 
2398
             ('pack collection autopack',)],
 
2399
            client._calls)
 
2400
 
 
2401
 
 
2402
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
 
2403
    """Base class for unit tests for bzrlib.remote._translate_error."""
 
2404
 
 
2405
    def translateTuple(self, error_tuple, **context):
 
2406
        """Call _translate_error with an ErrorFromSmartServer built from the
 
2407
        given error_tuple.
 
2408
 
 
2409
        :param error_tuple: A tuple of a smart server response, as would be
 
2410
            passed to an ErrorFromSmartServer.
 
2411
        :kwargs context: context items to call _translate_error with.
 
2412
 
 
2413
        :returns: The error raised by _translate_error.
 
2414
        """
 
2415
        # Raise the ErrorFromSmartServer before passing it as an argument,
 
2416
        # because _translate_error may need to re-raise it with a bare 'raise'
 
2417
        # statement.
 
2418
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2419
        translated_error = self.translateErrorFromSmartServer(
 
2420
            server_error, **context)
 
2421
        return translated_error
 
2422
 
 
2423
    def translateErrorFromSmartServer(self, error_object, **context):
 
2424
        """Like translateTuple, but takes an already constructed
 
2425
        ErrorFromSmartServer rather than a tuple.
 
2426
        """
 
2427
        try:
 
2428
            raise error_object
 
2429
        except errors.ErrorFromSmartServer, server_error:
 
2430
            translated_error = self.assertRaises(
 
2431
                errors.BzrError, remote._translate_error, server_error,
 
2432
                **context)
 
2433
        return translated_error
 
2434
 
 
2435
 
 
2436
class TestErrorTranslationSuccess(TestErrorTranslationBase):
 
2437
    """Unit tests for bzrlib.remote._translate_error.
 
2438
 
 
2439
    Given an ErrorFromSmartServer (which has an error tuple from a smart
 
2440
    server) and some context, _translate_error raises more specific errors from
 
2441
    bzrlib.errors.
 
2442
 
 
2443
    This test case covers the cases where _translate_error succeeds in
 
2444
    translating an ErrorFromSmartServer to something better.  See
 
2445
    TestErrorTranslationRobustness for other cases.
 
2446
    """
 
2447
 
 
2448
    def test_NoSuchRevision(self):
 
2449
        branch = self.make_branch('')
 
2450
        revid = 'revid'
 
2451
        translated_error = self.translateTuple(
 
2452
            ('NoSuchRevision', revid), branch=branch)
 
2453
        expected_error = errors.NoSuchRevision(branch, revid)
 
2454
        self.assertEqual(expected_error, translated_error)
 
2455
 
 
2456
    def test_nosuchrevision(self):
 
2457
        repository = self.make_repository('')
 
2458
        revid = 'revid'
 
2459
        translated_error = self.translateTuple(
 
2460
            ('nosuchrevision', revid), repository=repository)
 
2461
        expected_error = errors.NoSuchRevision(repository, revid)
 
2462
        self.assertEqual(expected_error, translated_error)
 
2463
 
 
2464
    def test_nobranch(self):
 
2465
        bzrdir = self.make_bzrdir('')
 
2466
        translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
 
2467
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
 
2468
        self.assertEqual(expected_error, translated_error)
 
2469
 
 
2470
    def test_LockContention(self):
 
2471
        translated_error = self.translateTuple(('LockContention',))
 
2472
        expected_error = errors.LockContention('(remote lock)')
 
2473
        self.assertEqual(expected_error, translated_error)
 
2474
 
 
2475
    def test_UnlockableTransport(self):
 
2476
        bzrdir = self.make_bzrdir('')
 
2477
        translated_error = self.translateTuple(
 
2478
            ('UnlockableTransport',), bzrdir=bzrdir)
 
2479
        expected_error = errors.UnlockableTransport(bzrdir.root_transport)
 
2480
        self.assertEqual(expected_error, translated_error)
 
2481
 
 
2482
    def test_LockFailed(self):
 
2483
        lock = 'str() of a server lock'
 
2484
        why = 'str() of why'
 
2485
        translated_error = self.translateTuple(('LockFailed', lock, why))
 
2486
        expected_error = errors.LockFailed(lock, why)
 
2487
        self.assertEqual(expected_error, translated_error)
 
2488
 
 
2489
    def test_TokenMismatch(self):
 
2490
        token = 'a lock token'
 
2491
        translated_error = self.translateTuple(('TokenMismatch',), token=token)
 
2492
        expected_error = errors.TokenMismatch(token, '(remote token)')
 
2493
        self.assertEqual(expected_error, translated_error)
 
2494
 
 
2495
    def test_Diverged(self):
 
2496
        branch = self.make_branch('a')
 
2497
        other_branch = self.make_branch('b')
 
2498
        translated_error = self.translateTuple(
 
2499
            ('Diverged',), branch=branch, other_branch=other_branch)
 
2500
        expected_error = errors.DivergedBranches(branch, other_branch)
 
2501
        self.assertEqual(expected_error, translated_error)
 
2502
 
 
2503
    def test_ReadError_no_args(self):
 
2504
        path = 'a path'
 
2505
        translated_error = self.translateTuple(('ReadError',), path=path)
 
2506
        expected_error = errors.ReadError(path)
 
2507
        self.assertEqual(expected_error, translated_error)
 
2508
 
 
2509
    def test_ReadError(self):
 
2510
        path = 'a path'
 
2511
        translated_error = self.translateTuple(('ReadError', path))
 
2512
        expected_error = errors.ReadError(path)
 
2513
        self.assertEqual(expected_error, translated_error)
 
2514
 
 
2515
    def test_PermissionDenied_no_args(self):
 
2516
        path = 'a path'
 
2517
        translated_error = self.translateTuple(('PermissionDenied',), path=path)
 
2518
        expected_error = errors.PermissionDenied(path)
 
2519
        self.assertEqual(expected_error, translated_error)
 
2520
 
 
2521
    def test_PermissionDenied_one_arg(self):
 
2522
        path = 'a path'
 
2523
        translated_error = self.translateTuple(('PermissionDenied', path))
 
2524
        expected_error = errors.PermissionDenied(path)
 
2525
        self.assertEqual(expected_error, translated_error)
 
2526
 
 
2527
    def test_PermissionDenied_one_arg_and_context(self):
 
2528
        """Given a choice between a path from the local context and a path on
 
2529
        the wire, _translate_error prefers the path from the local context.
 
2530
        """
 
2531
        local_path = 'local path'
 
2532
        remote_path = 'remote path'
 
2533
        translated_error = self.translateTuple(
 
2534
            ('PermissionDenied', remote_path), path=local_path)
 
2535
        expected_error = errors.PermissionDenied(local_path)
 
2536
        self.assertEqual(expected_error, translated_error)
 
2537
 
 
2538
    def test_PermissionDenied_two_args(self):
 
2539
        path = 'a path'
 
2540
        extra = 'a string with extra info'
 
2541
        translated_error = self.translateTuple(
 
2542
            ('PermissionDenied', path, extra))
 
2543
        expected_error = errors.PermissionDenied(path, extra)
 
2544
        self.assertEqual(expected_error, translated_error)
 
2545
 
 
2546
 
 
2547
class TestErrorTranslationRobustness(TestErrorTranslationBase):
 
2548
    """Unit tests for bzrlib.remote._translate_error's robustness.
 
2549
 
 
2550
    TestErrorTranslationSuccess is for cases where _translate_error can
 
2551
    translate successfully.  This class about how _translate_err behaves when
 
2552
    it fails to translate: it re-raises the original error.
 
2553
    """
 
2554
 
 
2555
    def test_unrecognised_server_error(self):
 
2556
        """If the error code from the server is not recognised, the original
 
2557
        ErrorFromSmartServer is propagated unmodified.
 
2558
        """
 
2559
        error_tuple = ('An unknown error tuple',)
 
2560
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2561
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2562
        expected_error = errors.UnknownErrorFromSmartServer(server_error)
 
2563
        self.assertEqual(expected_error, translated_error)
 
2564
 
 
2565
    def test_context_missing_a_key(self):
 
2566
        """In case of a bug in the client, or perhaps an unexpected response
 
2567
        from a server, _translate_error returns the original error tuple from
 
2568
        the server and mutters a warning.
 
2569
        """
 
2570
        # To translate a NoSuchRevision error _translate_error needs a 'branch'
 
2571
        # in the context dict.  So let's give it an empty context dict instead
 
2572
        # to exercise its error recovery.
 
2573
        empty_context = {}
 
2574
        error_tuple = ('NoSuchRevision', 'revid')
 
2575
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2576
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2577
        self.assertEqual(server_error, translated_error)
 
2578
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
2579
        # been muttered to the log file for developer to look at.
 
2580
        self.assertContainsRe(
 
2581
            self._get_log(keep_log_file=True),
 
2582
            "Missing key 'branch' in context")
 
2583
 
 
2584
    def test_path_missing(self):
 
2585
        """Some translations (PermissionDenied, ReadError) can determine the
 
2586
        'path' variable from either the wire or the local context.  If neither
 
2587
        has it, then an error is raised.
 
2588
        """
 
2589
        error_tuple = ('ReadError',)
 
2590
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2591
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2592
        self.assertEqual(server_error, translated_error)
 
2593
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
2594
        # been muttered to the log file for developer to look at.
 
2595
        self.assertContainsRe(
 
2596
            self._get_log(keep_log_file=True), "Missing key 'path' in context")
 
2597
 
 
2598
 
 
2599
class TestStacking(tests.TestCaseWithTransport):
 
2600
    """Tests for operations on stacked remote repositories.
 
2601
 
 
2602
    The underlying format type must support stacking.
 
2603
    """
 
2604
 
 
2605
    def test_access_stacked_remote(self):
 
2606
        # based on <http://launchpad.net/bugs/261315>
 
2607
        # make a branch stacked on another repository containing an empty
 
2608
        # revision, then open it over hpss - we should be able to see that
 
2609
        # revision.
 
2610
        base_transport = self.get_transport()
 
2611
        base_builder = self.make_branch_builder('base', format='1.9')
 
2612
        base_builder.start_series()
 
2613
        base_revid = base_builder.build_snapshot('rev-id', None,
 
2614
            [('add', ('', None, 'directory', None))],
 
2615
            'message')
 
2616
        base_builder.finish_series()
 
2617
        stacked_branch = self.make_branch('stacked', format='1.9')
 
2618
        stacked_branch.set_stacked_on_url('../base')
 
2619
        # start a server looking at this
 
2620
        smart_server = server.SmartTCPServer_for_testing()
 
2621
        smart_server.setUp()
 
2622
        self.addCleanup(smart_server.tearDown)
 
2623
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
 
2624
        # can get its branch and repository
 
2625
        remote_branch = remote_bzrdir.open_branch()
 
2626
        remote_repo = remote_branch.repository
 
2627
        remote_repo.lock_read()
 
2628
        try:
 
2629
            # it should have an appropriate fallback repository, which should also
 
2630
            # be a RemoteRepository
 
2631
            self.assertLength(1, remote_repo._fallback_repositories)
 
2632
            self.assertIsInstance(remote_repo._fallback_repositories[0],
 
2633
                RemoteRepository)
 
2634
            # and it has the revision committed to the underlying repository;
 
2635
            # these have varying implementations so we try several of them
 
2636
            self.assertTrue(remote_repo.has_revisions([base_revid]))
 
2637
            self.assertTrue(remote_repo.has_revision(base_revid))
 
2638
            self.assertEqual(remote_repo.get_revision(base_revid).message,
 
2639
                'message')
 
2640
        finally:
 
2641
            remote_repo.unlock()
 
2642
 
 
2643
    def prepare_stacked_remote_branch(self):
 
2644
        """Get stacked_upon and stacked branches with content in each."""
 
2645
        self.setup_smart_server_with_call_log()
 
2646
        tree1 = self.make_branch_and_tree('tree1', format='1.9')
 
2647
        tree1.commit('rev1', rev_id='rev1')
 
2648
        tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
 
2649
            ).open_workingtree()
 
2650
        tree2.commit('local changes make me feel good.')
 
2651
        branch2 = Branch.open(self.get_url('tree2'))
 
2652
        branch2.lock_read()
 
2653
        self.addCleanup(branch2.unlock)
 
2654
        return tree1.branch, branch2
 
2655
 
 
2656
    def test_stacked_get_parent_map(self):
 
2657
        # the public implementation of get_parent_map obeys stacking
 
2658
        _, branch = self.prepare_stacked_remote_branch()
 
2659
        repo = branch.repository
 
2660
        self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
 
2661
 
 
2662
    def test_unstacked_get_parent_map(self):
 
2663
        # _unstacked_provider.get_parent_map ignores stacking
 
2664
        _, branch = self.prepare_stacked_remote_branch()
 
2665
        provider = branch.repository._unstacked_provider
 
2666
        self.assertEqual([], provider.get_parent_map(['rev1']).keys())
 
2667
 
 
2668
    def fetch_stream_to_rev_order(self, stream):
 
2669
        result = []
 
2670
        for kind, substream in stream:
 
2671
            if not kind == 'revisions':
 
2672
                list(substream)
 
2673
            else:
 
2674
                for content in substream:
 
2675
                    result.append(content.key[-1])
 
2676
        return result
 
2677
 
 
2678
    def get_ordered_revs(self, format, order):
 
2679
        """Get a list of the revisions in a stream to format format.
 
2680
 
 
2681
        :param format: The format of the target.
 
2682
        :param order: the order that target should have requested.
 
2683
        :result: The revision ids in the stream, in the order seen,
 
2684
            the topological order of revisions in the source.
 
2685
        """
 
2686
        unordered_format = bzrdir.format_registry.get(format)()
 
2687
        target_repository_format = unordered_format.repository_format
 
2688
        # Cross check
 
2689
        self.assertEqual(order, target_repository_format._fetch_order)
 
2690
        trunk, stacked = self.prepare_stacked_remote_branch()
 
2691
        source = stacked.repository._get_source(target_repository_format)
 
2692
        tip = stacked.last_revision()
 
2693
        revs = stacked.repository.get_ancestry(tip)
 
2694
        search = graph.PendingAncestryResult([tip], stacked.repository)
 
2695
        self.reset_smart_call_log()
 
2696
        stream = source.get_stream(search)
 
2697
        if None in revs:
 
2698
            revs.remove(None)
 
2699
        # We trust that if a revision is in the stream the rest of the new
 
2700
        # content for it is too, as per our main fetch tests; here we are
 
2701
        # checking that the revisions are actually included at all, and their
 
2702
        # order.
 
2703
        return self.fetch_stream_to_rev_order(stream), revs
 
2704
 
 
2705
    def test_stacked_get_stream_unordered(self):
 
2706
        # Repository._get_source.get_stream() from a stacked repository with
 
2707
        # unordered yields the full data from both stacked and stacked upon
 
2708
        # sources.
 
2709
        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
 
2710
        self.assertEqual(set(expected_revs), set(rev_ord))
 
2711
        # Getting unordered results should have made a streaming data request
 
2712
        # from the server, then one from the backing branch.
 
2713
        self.assertLength(2, self.hpss_calls)
 
2714
 
 
2715
    def test_stacked_get_stream_topological(self):
 
2716
        # Repository._get_source.get_stream() from a stacked repository with
 
2717
        # topological sorting yields the full data from both stacked and
 
2718
        # stacked upon sources in topological order.
 
2719
        rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
 
2720
        self.assertEqual(expected_revs, rev_ord)
 
2721
        # Getting topological sort requires VFS calls still
 
2722
        self.assertLength(12, self.hpss_calls)
 
2723
 
 
2724
    def test_stacked_get_stream_groupcompress(self):
 
2725
        # Repository._get_source.get_stream() from a stacked repository with
 
2726
        # groupcompress sorting yields the full data from both stacked and
 
2727
        # stacked upon sources in groupcompress order.
 
2728
        raise tests.TestSkipped('No groupcompress ordered format available')
 
2729
        rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
 
2730
        self.assertEqual(expected_revs, reversed(rev_ord))
 
2731
        # Getting unordered results should have made a streaming data request
 
2732
        # from the backing branch, and one from the stacked on branch.
 
2733
        self.assertLength(2, self.hpss_calls)
 
2734
 
 
2735
    def test_stacked_pull_more_than_stacking_has_bug_360791(self):
 
2736
        # When pulling some fixed amount of content that is more than the
 
2737
        # source has (because some is coming from a fallback branch, no error
 
2738
        # should be received. This was reported as bug 360791.
 
2739
        # Need three branches: a trunk, a stacked branch, and a preexisting
 
2740
        # branch pulling content from stacked and trunk.
 
2741
        self.setup_smart_server_with_call_log()
 
2742
        trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
 
2743
        r1 = trunk.commit('start')
 
2744
        stacked_branch = trunk.branch.create_clone_on_transport(
 
2745
            self.get_transport('stacked'), stacked_on=trunk.branch.base)
 
2746
        local = self.make_branch('local', format='1.9-rich-root')
 
2747
        local.repository.fetch(stacked_branch.repository,
 
2748
            stacked_branch.last_revision())
 
2749
 
 
2750
 
 
2751
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
 
2752
 
 
2753
    def setUp(self):
 
2754
        super(TestRemoteBranchEffort, self).setUp()
 
2755
        # Create a smart server that publishes whatever the backing VFS server
 
2756
        # does.
 
2757
        self.smart_server = server.SmartTCPServer_for_testing()
 
2758
        self.smart_server.setUp(self.get_server())
 
2759
        self.addCleanup(self.smart_server.tearDown)
 
2760
        # Log all HPSS calls into self.hpss_calls.
 
2761
        _SmartClient.hooks.install_named_hook(
 
2762
            'call', self.capture_hpss_call, None)
 
2763
        self.hpss_calls = []
 
2764
 
 
2765
    def capture_hpss_call(self, params):
 
2766
        self.hpss_calls.append(params.method)
 
2767
 
 
2768
    def test_copy_content_into_avoids_revision_history(self):
 
2769
        local = self.make_branch('local')
 
2770
        remote_backing_tree = self.make_branch_and_tree('remote')
 
2771
        remote_backing_tree.commit("Commit.")
 
2772
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
2773
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
2774
        local.repository.fetch(remote_branch.repository)
 
2775
        self.hpss_calls = []
 
2776
        remote_branch.copy_content_into(local)
 
2777
        self.assertFalse('Branch.revision_history' in self.hpss_calls)