~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: John Ferlito
  • Date: 2009-09-02 04:31:45 UTC
  • mto: (4665.7.1 serve-init)
  • mto: This revision was merged to the branch mainline in revision 4913.
  • Revision ID: johnf@inodes.org-20090902043145-gxdsfw03ilcwbyn5
Add a debian init script for bzr --serve

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