~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

(jameinel) Allow 'bzr serve' to interpret SIGHUP as a graceful shutdown.
 (bug #795025) (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006-2011 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
    branch,
30
31
    bzrdir,
31
32
    config,
 
33
    controldir,
32
34
    errors,
33
 
    graph,
34
 
    pack,
 
35
    graph as _mod_graph,
 
36
    inventory,
 
37
    inventory_delta,
35
38
    remote,
36
39
    repository,
37
40
    tests,
38
 
    urlutils,
 
41
    transport,
 
42
    treebuilder,
 
43
    versionedfile,
39
44
    )
40
45
from bzrlib.branch import Branch
41
 
from bzrlib.bzrdir import BzrDir, BzrDirFormat
 
46
from bzrlib.bzrdir import (
 
47
    BzrDir,
 
48
    BzrDirFormat,
 
49
    RemoteBzrProber,
 
50
    )
42
51
from bzrlib.remote import (
43
52
    RemoteBranch,
 
53
    RemoteBranchFormat,
44
54
    RemoteBzrDir,
45
55
    RemoteBzrDirFormat,
46
56
    RemoteRepository,
 
57
    RemoteRepositoryFormat,
47
58
    )
 
59
from bzrlib.repofmt import groupcompress_repo, knitpack_repo
48
60
from bzrlib.revision import NULL_REVISION
49
 
from bzrlib.smart import server, medium
 
61
from bzrlib.smart import medium, request
50
62
from bzrlib.smart.client import _SmartClient
51
 
from bzrlib.symbol_versioning import one_four
52
 
from bzrlib.transport import get_transport, http
 
63
from bzrlib.smart.repository import (
 
64
    SmartServerRepositoryGetParentMap,
 
65
    SmartServerRepositoryGetStream_1_19,
 
66
    )
 
67
from bzrlib.tests import (
 
68
    test_server,
 
69
    )
 
70
from bzrlib.tests.scenarios import load_tests_apply_scenarios
53
71
from bzrlib.transport.memory import MemoryTransport
54
72
from bzrlib.transport.remote import (
55
73
    RemoteTransport,
56
74
    RemoteSSHTransport,
57
75
    RemoteTCPTransport,
58
 
)
 
76
    )
 
77
 
 
78
 
 
79
load_tests = load_tests_apply_scenarios
59
80
 
60
81
 
61
82
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
62
83
 
 
84
    scenarios = [
 
85
        ('HPSS-v2',
 
86
            {'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
 
87
        ('HPSS-v3',
 
88
            {'transport_server': test_server.SmartTCPServer_for_testing})]
 
89
 
 
90
 
63
91
    def setUp(self):
64
 
        self.transport_server = server.SmartTCPServer_for_testing
65
92
        super(BasicRemoteObjectTests, self).setUp()
66
93
        self.transport = self.get_transport()
67
94
        # make a branch that can be opened over the smart transport
68
95
        self.local_wt = BzrDir.create_standalone_workingtree('.')
69
 
 
70
 
    def tearDown(self):
71
 
        self.transport.disconnect()
72
 
        tests.TestCaseWithTransport.tearDown(self)
 
96
        self.addCleanup(self.transport.disconnect)
73
97
 
74
98
    def test_create_remote_bzrdir(self):
75
 
        b = remote.RemoteBzrDir(self.transport)
 
99
        b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
76
100
        self.assertIsInstance(b, BzrDir)
77
101
 
78
102
    def test_open_remote_branch(self):
79
103
        # open a standalone branch in the working directory
80
 
        b = remote.RemoteBzrDir(self.transport)
 
104
        b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
81
105
        branch = b.open_branch()
82
106
        self.assertIsInstance(branch, Branch)
83
107
 
99
123
    def test_find_correct_format(self):
100
124
        """Should open a RemoteBzrDir over a RemoteTransport"""
101
125
        fmt = BzrDirFormat.find_format(self.transport)
102
 
        self.assertTrue(RemoteBzrDirFormat
103
 
                        in BzrDirFormat._control_server_formats)
104
 
        self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
 
126
        self.assertTrue(bzrdir.RemoteBzrProber
 
127
                        in controldir.ControlDirFormat._server_probers)
 
128
        self.assertIsInstance(fmt, RemoteBzrDirFormat)
105
129
 
106
130
    def test_open_detected_smart_format(self):
107
131
        fmt = BzrDirFormat.find_format(self.transport)
112
136
        b = BzrDir.open_from_transport(self.transport).open_branch()
113
137
        self.assertStartsWith(str(b), 'RemoteBranch(')
114
138
 
115
 
 
116
 
class FakeRemoteTransport(object):
117
 
    """This class provides the minimum support for use in place of a RemoteTransport.
118
 
    
119
 
    It doesn't actually transmit requests, but rather expects them to be
120
 
    handled by a FakeClient which holds canned responses.  It does not allow
121
 
    any vfs access, therefore is not suitable for testing any operation that
122
 
    will fallback to vfs access.  Backing the test by an instance of this
123
 
    class guarantees that it's - done using non-vfs operations.
124
 
    """
125
 
 
126
 
    _default_url = 'fakeremotetransport://host/path/'
127
 
 
128
 
    def __init__(self, url=None):
129
 
        if url is None:
130
 
            url = self._default_url
131
 
        self.base = url
132
 
 
133
 
    def __repr__(self):
134
 
        return "%r(%r)" % (self.__class__.__name__,
135
 
            self.base)
136
 
 
137
 
    def clone(self, relpath):
138
 
        return FakeRemoteTransport(urlutils.join(self.base, relpath))
139
 
 
140
 
    def get(self, relpath):
141
 
        # only get is specifically stubbed out, because it's usually the first
142
 
        # thing we do.  anything else will fail with an AttributeError.
143
 
        raise AssertionError("%r doesn't support file access to %r"
144
 
            % (self, relpath))
145
 
 
 
139
    def test_remote_bzrdir_repr(self):
 
140
        b = BzrDir.open_from_transport(self.transport)
 
141
        self.assertStartsWith(str(b), 'RemoteBzrDir(')
 
142
 
 
143
    def test_remote_branch_format_supports_stacking(self):
 
144
        t = self.transport
 
145
        self.make_branch('unstackable', format='pack-0.92')
 
146
        b = BzrDir.open_from_transport(t.clone('unstackable')).open_branch()
 
147
        self.assertFalse(b._format.supports_stacking())
 
148
        self.make_branch('stackable', format='1.9')
 
149
        b = BzrDir.open_from_transport(t.clone('stackable')).open_branch()
 
150
        self.assertTrue(b._format.supports_stacking())
 
151
 
 
152
    def test_remote_repo_format_supports_external_references(self):
 
153
        t = self.transport
 
154
        bd = self.make_bzrdir('unstackable', format='pack-0.92')
 
155
        r = bd.create_repository()
 
156
        self.assertFalse(r._format.supports_external_lookups)
 
157
        r = BzrDir.open_from_transport(t.clone('unstackable')).open_repository()
 
158
        self.assertFalse(r._format.supports_external_lookups)
 
159
        bd = self.make_bzrdir('stackable', format='1.9')
 
160
        r = bd.create_repository()
 
161
        self.assertTrue(r._format.supports_external_lookups)
 
162
        r = BzrDir.open_from_transport(t.clone('stackable')).open_repository()
 
163
        self.assertTrue(r._format.supports_external_lookups)
 
164
 
 
165
    def test_remote_branch_set_append_revisions_only(self):
 
166
        # Make a format 1.9 branch, which supports append_revisions_only
 
167
        branch = self.make_branch('branch', format='1.9')
 
168
        config = branch.get_config()
 
169
        branch.set_append_revisions_only(True)
 
170
        self.assertEqual(
 
171
            'True', config.get_user_option('append_revisions_only'))
 
172
        branch.set_append_revisions_only(False)
 
173
        self.assertEqual(
 
174
            'False', config.get_user_option('append_revisions_only'))
 
175
 
 
176
    def test_remote_branch_set_append_revisions_only_upgrade_reqd(self):
 
177
        branch = self.make_branch('branch', format='knit')
 
178
        config = branch.get_config()
 
179
        self.assertRaises(
 
180
            errors.UpgradeRequired, branch.set_append_revisions_only, True)
146
181
 
147
182
 
148
183
class FakeProtocol(object):
170
205
 
171
206
class FakeClient(_SmartClient):
172
207
    """Lookalike for _SmartClient allowing testing."""
173
 
    
 
208
 
174
209
    def __init__(self, fake_medium_base='fake base'):
175
210
        """Create a FakeClient."""
176
211
        self.responses = []
178
213
        self.expecting_body = False
179
214
        # if non-None, this is the list of expected calls, with only the
180
215
        # method name and arguments included.  the body might be hard to
181
 
        # compute so is not included
 
216
        # compute so is not included. If a call is None, that call can
 
217
        # be anything.
182
218
        self._expected_calls = None
183
219
        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
184
220
 
194
230
 
195
231
    def add_success_response_with_body(self, body, *args):
196
232
        self.responses.append(('success', args, body))
 
233
        if self._expected_calls is not None:
 
234
            self._expected_calls.append(None)
197
235
 
198
236
    def add_error_response(self, *args):
199
237
        self.responses.append(('error', args))
228
266
            raise AssertionError("%r didn't expect any more calls "
229
267
                "but got %r%r"
230
268
                % (self, method, args,))
 
269
        if next_call is None:
 
270
            return
231
271
        if method != next_call[0] or args != next_call[1]:
232
272
            raise AssertionError("%r expected %r%r "
233
273
                "but got %r%r"
245
285
        self.expecting_body = True
246
286
        return result[1], FakeProtocol(result[2], self)
247
287
 
 
288
    def call_with_body_bytes(self, method, args, body):
 
289
        self._check_call(method, args)
 
290
        self._calls.append(('call_with_body_bytes', method, args, body))
 
291
        result = self._get_next_response()
 
292
        return result[1], FakeProtocol(result[2], self)
 
293
 
248
294
    def call_with_body_bytes_expecting_body(self, method, args, body):
249
295
        self._check_call(method, args)
250
296
        self._calls.append(('call_with_body_bytes_expecting_body', method,
259
305
        stream = list(stream)
260
306
        self._check_call(args[0], args[1:])
261
307
        self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
262
 
        return self._get_next_response()[1]
 
308
        result = self._get_next_response()
 
309
        # The second value returned from call_with_body_stream is supposed to
 
310
        # be a response_handler object, but so far no tests depend on that.
 
311
        response_handler = None 
 
312
        return result[1], response_handler
263
313
 
264
314
 
265
315
class FakeMedium(medium.SmartClientMedium):
286
336
        self.assertTrue(result)
287
337
 
288
338
 
 
339
class TestRemote(tests.TestCaseWithMemoryTransport):
 
340
 
 
341
    def get_branch_format(self):
 
342
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
343
        return reference_bzrdir_format.get_branch_format()
 
344
 
 
345
    def get_repo_format(self):
 
346
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
347
        return reference_bzrdir_format.repository_format
 
348
 
 
349
    def assertFinished(self, fake_client):
 
350
        """Assert that all of a FakeClient's expected calls have occurred."""
 
351
        fake_client.finished_test()
 
352
 
 
353
 
289
354
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
290
355
    """Tests for the behaviour of client_medium.remote_path_from_transport."""
291
356
 
295
360
        a given client_base and transport_base.
296
361
        """
297
362
        client_medium = medium.SmartClientMedium(client_base)
298
 
        transport = get_transport(transport_base)
299
 
        result = client_medium.remote_path_from_transport(transport)
 
363
        t = transport.get_transport(transport_base)
 
364
        result = client_medium.remote_path_from_transport(t)
300
365
        self.assertEqual(expected, result)
301
366
 
302
367
    def test_remote_path_from_transport(self):
313
378
        a given transport_base and relpath of that transport.  (Note that
314
379
        HttpTransportBase is a subclass of SmartClientMedium)
315
380
        """
316
 
        base_transport = get_transport(transport_base)
 
381
        base_transport = transport.get_transport(transport_base)
317
382
        client_medium = base_transport.get_smart_medium()
318
383
        cloned_transport = base_transport.clone(relpath)
319
384
        result = client_medium.remote_path_from_transport(cloned_transport)
320
385
        self.assertEqual(expected, result)
321
 
        
 
386
 
322
387
    def test_remote_path_from_transport_http(self):
323
388
        """Remote paths for HTTP transports are calculated differently to other
324
389
        transports.  They are just relative to the client base, not the root
340
405
        """
341
406
        client_medium = medium.SmartClientMedium('dummy base')
342
407
        self.assertFalse(client_medium._is_remote_before((99, 99)))
343
 
    
 
408
 
344
409
    def test__remember_remote_is_before(self):
345
410
        """Calling _remember_remote_is_before ratchets down the known remote
346
411
        version.
354
419
        # Calling _remember_remote_is_before again with a lower value works.
355
420
        client_medium._remember_remote_is_before((1, 5))
356
421
        self.assertTrue(client_medium._is_remote_before((1, 5)))
357
 
        # You cannot call _remember_remote_is_before with a larger value.
358
 
        self.assertRaises(
359
 
            AssertionError, client_medium._remember_remote_is_before, (1, 9))
360
 
 
361
 
 
362
 
class TestBzrDirOpenBranch(tests.TestCase):
 
422
        # If you call _remember_remote_is_before with a higher value it logs a
 
423
        # warning, and continues to remember the lower value.
 
424
        self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
 
425
        client_medium._remember_remote_is_before((1, 9))
 
426
        self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
 
427
        self.assertTrue(client_medium._is_remote_before((1, 5)))
 
428
 
 
429
 
 
430
class TestBzrDirCloningMetaDir(TestRemote):
 
431
 
 
432
    def test_backwards_compat(self):
 
433
        self.setup_smart_server_with_call_log()
 
434
        a_dir = self.make_bzrdir('.')
 
435
        self.reset_smart_call_log()
 
436
        verb = 'BzrDir.cloning_metadir'
 
437
        self.disable_verb(verb)
 
438
        format = a_dir.cloning_metadir()
 
439
        call_count = len([call for call in self.hpss_calls if
 
440
            call.call.method == verb])
 
441
        self.assertEqual(1, call_count)
 
442
 
 
443
    def test_branch_reference(self):
 
444
        transport = self.get_transport('quack')
 
445
        referenced = self.make_branch('referenced')
 
446
        expected = referenced.bzrdir.cloning_metadir()
 
447
        client = FakeClient(transport.base)
 
448
        client.add_expected_call(
 
449
            'BzrDir.cloning_metadir', ('quack/', 'False'),
 
450
            'error', ('BranchReference',)),
 
451
        client.add_expected_call(
 
452
            'BzrDir.open_branchV3', ('quack/',),
 
453
            'success', ('ref', self.get_url('referenced'))),
 
454
        a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
455
            _client=client)
 
456
        result = a_bzrdir.cloning_metadir()
 
457
        # We should have got a control dir matching the referenced branch.
 
458
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
 
459
        self.assertEqual(expected._repository_format, result._repository_format)
 
460
        self.assertEqual(expected._branch_format, result._branch_format)
 
461
        self.assertFinished(client)
 
462
 
 
463
    def test_current_server(self):
 
464
        transport = self.get_transport('.')
 
465
        transport = transport.clone('quack')
 
466
        self.make_bzrdir('quack')
 
467
        client = FakeClient(transport.base)
 
468
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
469
        control_name = reference_bzrdir_format.network_name()
 
470
        client.add_expected_call(
 
471
            'BzrDir.cloning_metadir', ('quack/', 'False'),
 
472
            'success', (control_name, '', ('branch', ''))),
 
473
        a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
474
            _client=client)
 
475
        result = a_bzrdir.cloning_metadir()
 
476
        # We should have got a reference control dir with default branch and
 
477
        # repository formats.
 
478
        # This pokes a little, just to be sure.
 
479
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
 
480
        self.assertEqual(None, result._repository_format)
 
481
        self.assertEqual(None, result._branch_format)
 
482
        self.assertFinished(client)
 
483
 
 
484
 
 
485
class TestBzrDirOpen(TestRemote):
 
486
 
 
487
    def make_fake_client_and_transport(self, path='quack'):
 
488
        transport = MemoryTransport()
 
489
        transport.mkdir(path)
 
490
        transport = transport.clone(path)
 
491
        client = FakeClient(transport.base)
 
492
        return client, transport
 
493
 
 
494
    def test_absent(self):
 
495
        client, transport = self.make_fake_client_and_transport()
 
496
        client.add_expected_call(
 
497
            'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
 
498
        self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
 
499
                RemoteBzrDirFormat(), _client=client, _force_probe=True)
 
500
        self.assertFinished(client)
 
501
 
 
502
    def test_present_without_workingtree(self):
 
503
        client, transport = self.make_fake_client_and_transport()
 
504
        client.add_expected_call(
 
505
            'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
 
506
        bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
507
            _client=client, _force_probe=True)
 
508
        self.assertIsInstance(bd, RemoteBzrDir)
 
509
        self.assertFalse(bd.has_workingtree())
 
510
        self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
 
511
        self.assertFinished(client)
 
512
 
 
513
    def test_present_with_workingtree(self):
 
514
        client, transport = self.make_fake_client_and_transport()
 
515
        client.add_expected_call(
 
516
            'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
 
517
        bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
518
            _client=client, _force_probe=True)
 
519
        self.assertIsInstance(bd, RemoteBzrDir)
 
520
        self.assertTrue(bd.has_workingtree())
 
521
        self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
 
522
        self.assertFinished(client)
 
523
 
 
524
    def test_backwards_compat(self):
 
525
        client, transport = self.make_fake_client_and_transport()
 
526
        client.add_expected_call(
 
527
            'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
 
528
        client.add_expected_call(
 
529
            'BzrDir.open', ('quack/',), 'success', ('yes',))
 
530
        bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
531
            _client=client, _force_probe=True)
 
532
        self.assertIsInstance(bd, RemoteBzrDir)
 
533
        self.assertFinished(client)
 
534
 
 
535
    def test_backwards_compat_hpss_v2(self):
 
536
        client, transport = self.make_fake_client_and_transport()
 
537
        # Monkey-patch fake client to simulate real-world behaviour with v2
 
538
        # server: upon first RPC call detect the protocol version, and because
 
539
        # the version is 2 also do _remember_remote_is_before((1, 6)) before
 
540
        # continuing with the RPC.
 
541
        orig_check_call = client._check_call
 
542
        def check_call(method, args):
 
543
            client._medium._protocol_version = 2
 
544
            client._medium._remember_remote_is_before((1, 6))
 
545
            client._check_call = orig_check_call
 
546
            client._check_call(method, args)
 
547
        client._check_call = check_call
 
548
        client.add_expected_call(
 
549
            'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
 
550
        client.add_expected_call(
 
551
            'BzrDir.open', ('quack/',), 'success', ('yes',))
 
552
        bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
553
            _client=client, _force_probe=True)
 
554
        self.assertIsInstance(bd, RemoteBzrDir)
 
555
        self.assertFinished(client)
 
556
 
 
557
 
 
558
class TestBzrDirOpenBranch(TestRemote):
 
559
 
 
560
    def test_backwards_compat(self):
 
561
        self.setup_smart_server_with_call_log()
 
562
        self.make_branch('.')
 
563
        a_dir = BzrDir.open(self.get_url('.'))
 
564
        self.reset_smart_call_log()
 
565
        verb = 'BzrDir.open_branchV3'
 
566
        self.disable_verb(verb)
 
567
        format = a_dir.open_branch()
 
568
        call_count = len([call for call in self.hpss_calls if
 
569
            call.call.method == verb])
 
570
        self.assertEqual(1, call_count)
363
571
 
364
572
    def test_branch_present(self):
 
573
        reference_format = self.get_repo_format()
 
574
        network_name = reference_format.network_name()
 
575
        branch_network_name = self.get_branch_format().network_name()
365
576
        transport = MemoryTransport()
366
577
        transport.mkdir('quack')
367
578
        transport = transport.clone('quack')
368
579
        client = FakeClient(transport.base)
369
580
        client.add_expected_call(
370
 
            'BzrDir.open_branch', ('quack/',),
371
 
            'success', ('ok', ''))
 
581
            'BzrDir.open_branchV3', ('quack/',),
 
582
            'success', ('branch', branch_network_name))
372
583
        client.add_expected_call(
373
 
            'BzrDir.find_repositoryV2', ('quack/',),
374
 
            'success', ('ok', '', 'no', 'no', 'no'))
 
584
            'BzrDir.find_repositoryV3', ('quack/',),
 
585
            'success', ('ok', '', 'no', 'no', 'no', network_name))
375
586
        client.add_expected_call(
376
587
            'Branch.get_stacked_on_url', ('quack/',),
377
588
            'error', ('NotStacked',))
378
 
        bzrdir = RemoteBzrDir(transport, _client=client)
 
589
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
590
            _client=client)
379
591
        result = bzrdir.open_branch()
380
592
        self.assertIsInstance(result, RemoteBranch)
381
593
        self.assertEqual(bzrdir, result.bzrdir)
382
 
        client.finished_test()
 
594
        self.assertFinished(client)
383
595
 
384
596
    def test_branch_missing(self):
385
597
        transport = MemoryTransport()
387
599
        transport = transport.clone('quack')
388
600
        client = FakeClient(transport.base)
389
601
        client.add_error_response('nobranch')
390
 
        bzrdir = RemoteBzrDir(transport, _client=client)
 
602
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
603
            _client=client)
391
604
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
392
605
        self.assertEqual(
393
 
            [('call', 'BzrDir.open_branch', ('quack/',))],
 
606
            [('call', 'BzrDir.open_branchV3', ('quack/',))],
394
607
            client._calls)
395
608
 
396
609
    def test__get_tree_branch(self):
397
610
        # _get_tree_branch is a form of open_branch, but it should only ask for
398
611
        # branch opening, not any other network requests.
399
612
        calls = []
400
 
        def open_branch():
 
613
        def open_branch(name=None):
401
614
            calls.append("Called")
402
615
            return "a-branch"
403
616
        transport = MemoryTransport()
404
617
        # no requests on the network - catches other api calls being made.
405
618
        client = FakeClient(transport.base)
406
 
        bzrdir = RemoteBzrDir(transport, _client=client)
 
619
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
620
            _client=client)
407
621
        # patch the open_branch call to record that it was called.
408
622
        bzrdir.open_branch = open_branch
409
623
        self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
415
629
        # transmitted as "~", not "%7E".
416
630
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
417
631
        client = FakeClient(transport.base)
418
 
        client.add_expected_call(
419
 
            'BzrDir.open_branch', ('~hello/',),
420
 
            'success', ('ok', ''))
421
 
        client.add_expected_call(
422
 
            'BzrDir.find_repositoryV2', ('~hello/',),
423
 
            'success', ('ok', '', 'no', 'no', 'no'))
 
632
        reference_format = self.get_repo_format()
 
633
        network_name = reference_format.network_name()
 
634
        branch_network_name = self.get_branch_format().network_name()
 
635
        client.add_expected_call(
 
636
            'BzrDir.open_branchV3', ('~hello/',),
 
637
            'success', ('branch', branch_network_name))
 
638
        client.add_expected_call(
 
639
            'BzrDir.find_repositoryV3', ('~hello/',),
 
640
            'success', ('ok', '', 'no', 'no', 'no', network_name))
424
641
        client.add_expected_call(
425
642
            'Branch.get_stacked_on_url', ('~hello/',),
426
643
            'error', ('NotStacked',))
427
 
        bzrdir = RemoteBzrDir(transport, _client=client)
 
644
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
645
            _client=client)
428
646
        result = bzrdir.open_branch()
429
 
        client.finished_test()
 
647
        self.assertFinished(client)
430
648
 
431
649
    def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
 
650
        reference_format = self.get_repo_format()
 
651
        network_name = reference_format.network_name()
432
652
        transport = MemoryTransport()
433
653
        transport.mkdir('quack')
434
654
        transport = transport.clone('quack')
442
662
            subtree_response = 'no'
443
663
        client = FakeClient(transport.base)
444
664
        client.add_success_response(
445
 
            'ok', '', rich_response, subtree_response, external_lookup)
446
 
        bzrdir = RemoteBzrDir(transport, _client=client)
 
665
            'ok', '', rich_response, subtree_response, external_lookup,
 
666
            network_name)
 
667
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
668
            _client=client)
447
669
        result = bzrdir.open_repository()
448
670
        self.assertEqual(
449
 
            [('call', 'BzrDir.find_repositoryV2', ('quack/',))],
 
671
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
450
672
            client._calls)
451
673
        self.assertIsInstance(result, RemoteRepository)
452
674
        self.assertEqual(bzrdir, result.bzrdir)
465
687
        old.
466
688
        """
467
689
        self.assertRaises(errors.NotBranchError,
468
 
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
469
 
 
470
 
 
471
 
class TestBzrDirOpenRepository(tests.TestCase):
472
 
 
473
 
    def test_backwards_compat_1_2(self):
474
 
        transport = MemoryTransport()
475
 
        transport.mkdir('quack')
476
 
        transport = transport.clone('quack')
477
 
        client = FakeClient(transport.base)
478
 
        client.add_unknown_method_response('RemoteRepository.find_repositoryV2')
 
690
            RemoteBzrProber.probe_transport, OldServerTransport())
 
691
 
 
692
 
 
693
class TestBzrDirCreateBranch(TestRemote):
 
694
 
 
695
    def test_backwards_compat(self):
 
696
        self.setup_smart_server_with_call_log()
 
697
        repo = self.make_repository('.')
 
698
        self.reset_smart_call_log()
 
699
        self.disable_verb('BzrDir.create_branch')
 
700
        branch = repo.bzrdir.create_branch()
 
701
        create_branch_call_count = len([call for call in self.hpss_calls if
 
702
            call.call.method == 'BzrDir.create_branch'])
 
703
        self.assertEqual(1, create_branch_call_count)
 
704
 
 
705
    def test_current_server(self):
 
706
        transport = self.get_transport('.')
 
707
        transport = transport.clone('quack')
 
708
        self.make_repository('quack')
 
709
        client = FakeClient(transport.base)
 
710
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
711
        reference_format = reference_bzrdir_format.get_branch_format()
 
712
        network_name = reference_format.network_name()
 
713
        reference_repo_fmt = reference_bzrdir_format.repository_format
 
714
        reference_repo_name = reference_repo_fmt.network_name()
 
715
        client.add_expected_call(
 
716
            'BzrDir.create_branch', ('quack/', network_name),
 
717
            'success', ('ok', network_name, '', 'no', 'no', 'yes',
 
718
            reference_repo_name))
 
719
        a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
720
            _client=client)
 
721
        branch = a_bzrdir.create_branch()
 
722
        # We should have got a remote branch
 
723
        self.assertIsInstance(branch, remote.RemoteBranch)
 
724
        # its format should have the settings from the response
 
725
        format = branch._format
 
726
        self.assertEqual(network_name, format.network_name())
 
727
 
 
728
    def test_already_open_repo_and_reused_medium(self):
 
729
        """Bug 726584: create_branch(..., repository=repo) should work
 
730
        regardless of what the smart medium's base URL is.
 
731
        """
 
732
        self.transport_server = test_server.SmartTCPServer_for_testing
 
733
        transport = self.get_transport('.')
 
734
        repo = self.make_repository('quack')
 
735
        # Client's medium rooted a transport root (not at the bzrdir)
 
736
        client = FakeClient(transport.base)
 
737
        transport = transport.clone('quack')
 
738
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
739
        reference_format = reference_bzrdir_format.get_branch_format()
 
740
        network_name = reference_format.network_name()
 
741
        reference_repo_fmt = reference_bzrdir_format.repository_format
 
742
        reference_repo_name = reference_repo_fmt.network_name()
 
743
        client.add_expected_call(
 
744
            'BzrDir.create_branch', ('extra/quack/', network_name),
 
745
            'success', ('ok', network_name, '', 'no', 'no', 'yes',
 
746
            reference_repo_name))
 
747
        a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
748
            _client=client)
 
749
        branch = a_bzrdir.create_branch(repository=repo)
 
750
        # We should have got a remote branch
 
751
        self.assertIsInstance(branch, remote.RemoteBranch)
 
752
        # its format should have the settings from the response
 
753
        format = branch._format
 
754
        self.assertEqual(network_name, format.network_name())
 
755
 
 
756
 
 
757
class TestBzrDirCreateRepository(TestRemote):
 
758
 
 
759
    def test_backwards_compat(self):
 
760
        self.setup_smart_server_with_call_log()
 
761
        bzrdir = self.make_bzrdir('.')
 
762
        self.reset_smart_call_log()
 
763
        self.disable_verb('BzrDir.create_repository')
 
764
        repo = bzrdir.create_repository()
 
765
        create_repo_call_count = len([call for call in self.hpss_calls if
 
766
            call.call.method == 'BzrDir.create_repository'])
 
767
        self.assertEqual(1, create_repo_call_count)
 
768
 
 
769
    def test_current_server(self):
 
770
        transport = self.get_transport('.')
 
771
        transport = transport.clone('quack')
 
772
        self.make_bzrdir('quack')
 
773
        client = FakeClient(transport.base)
 
774
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
775
        reference_format = reference_bzrdir_format.repository_format
 
776
        network_name = reference_format.network_name()
 
777
        client.add_expected_call(
 
778
            'BzrDir.create_repository', ('quack/',
 
779
                'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
780
                'False'),
 
781
            'success', ('ok', 'yes', 'yes', 'yes', network_name))
 
782
        a_bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
783
            _client=client)
 
784
        repo = a_bzrdir.create_repository()
 
785
        # We should have got a remote repository
 
786
        self.assertIsInstance(repo, remote.RemoteRepository)
 
787
        # its format should have the settings from the response
 
788
        format = repo._format
 
789
        self.assertTrue(format.rich_root_data)
 
790
        self.assertTrue(format.supports_tree_reference)
 
791
        self.assertTrue(format.supports_external_lookups)
 
792
        self.assertEqual(network_name, format.network_name())
 
793
 
 
794
 
 
795
class TestBzrDirOpenRepository(TestRemote):
 
796
 
 
797
    def test_backwards_compat_1_2_3(self):
 
798
        # fallback all the way to the first version.
 
799
        reference_format = self.get_repo_format()
 
800
        network_name = reference_format.network_name()
 
801
        server_url = 'bzr://example.com/'
 
802
        self.permit_url(server_url)
 
803
        client = FakeClient(server_url)
 
804
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
 
805
        client.add_unknown_method_response('BzrDir.find_repositoryV2')
479
806
        client.add_success_response('ok', '', 'no', 'no')
480
 
        bzrdir = RemoteBzrDir(transport, _client=client)
481
 
        repo = bzrdir.open_repository()
482
 
        self.assertEqual(
483
 
            [('call', 'BzrDir.find_repositoryV2', ('quack/',)),
484
 
             ('call', 'BzrDir.find_repository', ('quack/',))],
485
 
            client._calls)
 
807
        # A real repository instance will be created to determine the network
 
808
        # name.
 
809
        client.add_success_response_with_body(
 
810
            "Bazaar-NG meta directory, format 1\n", 'ok')
 
811
        client.add_success_response_with_body(
 
812
            reference_format.get_format_string(), 'ok')
 
813
        # PackRepository wants to do a stat
 
814
        client.add_success_response('stat', '0', '65535')
 
815
        remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
 
816
            _client=client)
 
817
        bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
 
818
            _client=client)
 
819
        repo = bzrdir.open_repository()
 
820
        self.assertEqual(
 
821
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
 
822
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
823
             ('call', 'BzrDir.find_repository', ('quack/',)),
 
824
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
 
825
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
 
826
             ('call', 'stat', ('/quack/.bzr/repository',)),
 
827
             ],
 
828
            client._calls)
 
829
        self.assertEqual(network_name, repo._format.network_name())
 
830
 
 
831
    def test_backwards_compat_2(self):
 
832
        # fallback to find_repositoryV2
 
833
        reference_format = self.get_repo_format()
 
834
        network_name = reference_format.network_name()
 
835
        server_url = 'bzr://example.com/'
 
836
        self.permit_url(server_url)
 
837
        client = FakeClient(server_url)
 
838
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
 
839
        client.add_success_response('ok', '', 'no', 'no', 'no')
 
840
        # A real repository instance will be created to determine the network
 
841
        # name.
 
842
        client.add_success_response_with_body(
 
843
            "Bazaar-NG meta directory, format 1\n", 'ok')
 
844
        client.add_success_response_with_body(
 
845
            reference_format.get_format_string(), 'ok')
 
846
        # PackRepository wants to do a stat
 
847
        client.add_success_response('stat', '0', '65535')
 
848
        remote_transport = RemoteTransport(server_url + 'quack/', medium=False,
 
849
            _client=client)
 
850
        bzrdir = RemoteBzrDir(remote_transport, RemoteBzrDirFormat(),
 
851
            _client=client)
 
852
        repo = bzrdir.open_repository()
 
853
        self.assertEqual(
 
854
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
 
855
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
856
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
 
857
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
 
858
             ('call', 'stat', ('/quack/.bzr/repository',)),
 
859
             ],
 
860
            client._calls)
 
861
        self.assertEqual(network_name, repo._format.network_name())
 
862
 
 
863
    def test_current_server(self):
 
864
        reference_format = self.get_repo_format()
 
865
        network_name = reference_format.network_name()
 
866
        transport = MemoryTransport()
 
867
        transport.mkdir('quack')
 
868
        transport = transport.clone('quack')
 
869
        client = FakeClient(transport.base)
 
870
        client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
 
871
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
872
            _client=client)
 
873
        repo = bzrdir.open_repository()
 
874
        self.assertEqual(
 
875
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
 
876
            client._calls)
 
877
        self.assertEqual(network_name, repo._format.network_name())
 
878
 
 
879
 
 
880
class TestBzrDirFormatInitializeEx(TestRemote):
 
881
 
 
882
    def test_success(self):
 
883
        """Simple test for typical successful call."""
 
884
        fmt = RemoteBzrDirFormat()
 
885
        default_format_name = BzrDirFormat.get_default_format().network_name()
 
886
        transport = self.get_transport()
 
887
        client = FakeClient(transport.base)
 
888
        client.add_expected_call(
 
889
            'BzrDirFormat.initialize_ex_1.16',
 
890
                (default_format_name, 'path', 'False', 'False', 'False', '',
 
891
                 '', '', '', 'False'),
 
892
            'success',
 
893
                ('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
 
894
                 'bzrdir fmt', 'False', '', '', 'repo lock token'))
 
895
        # XXX: It would be better to call fmt.initialize_on_transport_ex, but
 
896
        # it's currently hard to test that without supplying a real remote
 
897
        # transport connected to a real server.
 
898
        result = fmt._initialize_on_transport_ex_rpc(client, 'path',
 
899
            transport, False, False, False, None, None, None, None, False)
 
900
        self.assertFinished(client)
 
901
 
 
902
    def test_error(self):
 
903
        """Error responses are translated, e.g. 'PermissionDenied' raises the
 
904
        corresponding error from the client.
 
905
        """
 
906
        fmt = RemoteBzrDirFormat()
 
907
        default_format_name = BzrDirFormat.get_default_format().network_name()
 
908
        transport = self.get_transport()
 
909
        client = FakeClient(transport.base)
 
910
        client.add_expected_call(
 
911
            'BzrDirFormat.initialize_ex_1.16',
 
912
                (default_format_name, 'path', 'False', 'False', 'False', '',
 
913
                 '', '', '', 'False'),
 
914
            'error',
 
915
                ('PermissionDenied', 'path', 'extra info'))
 
916
        # XXX: It would be better to call fmt.initialize_on_transport_ex, but
 
917
        # it's currently hard to test that without supplying a real remote
 
918
        # transport connected to a real server.
 
919
        err = self.assertRaises(errors.PermissionDenied,
 
920
            fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
 
921
            False, False, False, None, None, None, None, False)
 
922
        self.assertEqual('path', err.path)
 
923
        self.assertEqual(': extra info', err.extra)
 
924
        self.assertFinished(client)
 
925
 
 
926
    def test_error_from_real_server(self):
 
927
        """Integration test for error translation."""
 
928
        transport = self.make_smart_server('foo')
 
929
        transport = transport.clone('no-such-path')
 
930
        fmt = RemoteBzrDirFormat()
 
931
        err = self.assertRaises(errors.NoSuchFile,
 
932
            fmt.initialize_on_transport_ex, transport, create_prefix=False)
486
933
 
487
934
 
488
935
class OldSmartClient(object):
513
960
        return OldSmartClient()
514
961
 
515
962
 
516
 
class RemoteBranchTestCase(tests.TestCase):
 
963
class RemoteBzrDirTestCase(TestRemote):
 
964
 
 
965
    def make_remote_bzrdir(self, transport, client):
 
966
        """Make a RemotebzrDir using 'client' as the _client."""
 
967
        return RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
968
            _client=client)
 
969
 
 
970
 
 
971
class RemoteBranchTestCase(RemoteBzrDirTestCase):
 
972
 
 
973
    def lock_remote_branch(self, branch):
 
974
        """Trick a RemoteBranch into thinking it is locked."""
 
975
        branch._lock_mode = 'w'
 
976
        branch._lock_count = 2
 
977
        branch._lock_token = 'branch token'
 
978
        branch._repo_lock_token = 'repo token'
 
979
        branch.repository._lock_mode = 'w'
 
980
        branch.repository._lock_count = 2
 
981
        branch.repository._lock_token = 'repo token'
517
982
 
518
983
    def make_remote_branch(self, transport, client):
519
984
        """Make a RemoteBranch using 'client' as its _SmartClient.
520
 
        
 
985
 
521
986
        A RemoteBzrDir and RemoteRepository will also be created to fill out
522
987
        the RemoteBranch, albeit with stub values for some of their attributes.
523
988
        """
524
989
        # we do not want bzrdir to make any remote calls, so use False as its
525
990
        # _client.  If it tries to make a remote call, this will fail
526
991
        # immediately.
527
 
        bzrdir = RemoteBzrDir(transport, _client=False)
 
992
        bzrdir = self.make_remote_bzrdir(transport, False)
528
993
        repo = RemoteRepository(bzrdir, None, _client=client)
529
 
        return RemoteBranch(bzrdir, repo, _client=client)
 
994
        branch_format = self.get_branch_format()
 
995
        format = RemoteBranchFormat(network_name=branch_format.network_name())
 
996
        return RemoteBranch(bzrdir, repo, _client=client, format=format)
 
997
 
 
998
 
 
999
class TestBranchGetParent(RemoteBranchTestCase):
 
1000
 
 
1001
    def test_no_parent(self):
 
1002
        # in an empty branch we decode the response properly
 
1003
        transport = MemoryTransport()
 
1004
        client = FakeClient(transport.base)
 
1005
        client.add_expected_call(
 
1006
            'Branch.get_stacked_on_url', ('quack/',),
 
1007
            'error', ('NotStacked',))
 
1008
        client.add_expected_call(
 
1009
            'Branch.get_parent', ('quack/',),
 
1010
            'success', ('',))
 
1011
        transport.mkdir('quack')
 
1012
        transport = transport.clone('quack')
 
1013
        branch = self.make_remote_branch(transport, client)
 
1014
        result = branch.get_parent()
 
1015
        self.assertFinished(client)
 
1016
        self.assertEqual(None, result)
 
1017
 
 
1018
    def test_parent_relative(self):
 
1019
        transport = MemoryTransport()
 
1020
        client = FakeClient(transport.base)
 
1021
        client.add_expected_call(
 
1022
            'Branch.get_stacked_on_url', ('kwaak/',),
 
1023
            'error', ('NotStacked',))
 
1024
        client.add_expected_call(
 
1025
            'Branch.get_parent', ('kwaak/',),
 
1026
            'success', ('../foo/',))
 
1027
        transport.mkdir('kwaak')
 
1028
        transport = transport.clone('kwaak')
 
1029
        branch = self.make_remote_branch(transport, client)
 
1030
        result = branch.get_parent()
 
1031
        self.assertEqual(transport.clone('../foo').base, result)
 
1032
 
 
1033
    def test_parent_absolute(self):
 
1034
        transport = MemoryTransport()
 
1035
        client = FakeClient(transport.base)
 
1036
        client.add_expected_call(
 
1037
            'Branch.get_stacked_on_url', ('kwaak/',),
 
1038
            'error', ('NotStacked',))
 
1039
        client.add_expected_call(
 
1040
            'Branch.get_parent', ('kwaak/',),
 
1041
            'success', ('http://foo/',))
 
1042
        transport.mkdir('kwaak')
 
1043
        transport = transport.clone('kwaak')
 
1044
        branch = self.make_remote_branch(transport, client)
 
1045
        result = branch.get_parent()
 
1046
        self.assertEqual('http://foo/', result)
 
1047
        self.assertFinished(client)
 
1048
 
 
1049
 
 
1050
class TestBranchSetParentLocation(RemoteBranchTestCase):
 
1051
 
 
1052
    def test_no_parent(self):
 
1053
        # We call the verb when setting parent to None
 
1054
        transport = MemoryTransport()
 
1055
        client = FakeClient(transport.base)
 
1056
        client.add_expected_call(
 
1057
            'Branch.get_stacked_on_url', ('quack/',),
 
1058
            'error', ('NotStacked',))
 
1059
        client.add_expected_call(
 
1060
            'Branch.set_parent_location', ('quack/', 'b', 'r', ''),
 
1061
            'success', ())
 
1062
        transport.mkdir('quack')
 
1063
        transport = transport.clone('quack')
 
1064
        branch = self.make_remote_branch(transport, client)
 
1065
        branch._lock_token = 'b'
 
1066
        branch._repo_lock_token = 'r'
 
1067
        branch._set_parent_location(None)
 
1068
        self.assertFinished(client)
 
1069
 
 
1070
    def test_parent(self):
 
1071
        transport = MemoryTransport()
 
1072
        client = FakeClient(transport.base)
 
1073
        client.add_expected_call(
 
1074
            'Branch.get_stacked_on_url', ('kwaak/',),
 
1075
            'error', ('NotStacked',))
 
1076
        client.add_expected_call(
 
1077
            'Branch.set_parent_location', ('kwaak/', 'b', 'r', 'foo'),
 
1078
            'success', ())
 
1079
        transport.mkdir('kwaak')
 
1080
        transport = transport.clone('kwaak')
 
1081
        branch = self.make_remote_branch(transport, client)
 
1082
        branch._lock_token = 'b'
 
1083
        branch._repo_lock_token = 'r'
 
1084
        branch._set_parent_location('foo')
 
1085
        self.assertFinished(client)
 
1086
 
 
1087
    def test_backwards_compat(self):
 
1088
        self.setup_smart_server_with_call_log()
 
1089
        branch = self.make_branch('.')
 
1090
        self.reset_smart_call_log()
 
1091
        verb = 'Branch.set_parent_location'
 
1092
        self.disable_verb(verb)
 
1093
        branch.set_parent('http://foo/')
 
1094
        self.assertLength(12, self.hpss_calls)
 
1095
 
 
1096
 
 
1097
class TestBranchGetTagsBytes(RemoteBranchTestCase):
 
1098
 
 
1099
    def test_backwards_compat(self):
 
1100
        self.setup_smart_server_with_call_log()
 
1101
        branch = self.make_branch('.')
 
1102
        self.reset_smart_call_log()
 
1103
        verb = 'Branch.get_tags_bytes'
 
1104
        self.disable_verb(verb)
 
1105
        branch.tags.get_tag_dict()
 
1106
        call_count = len([call for call in self.hpss_calls if
 
1107
            call.call.method == verb])
 
1108
        self.assertEqual(1, call_count)
 
1109
 
 
1110
    def test_trivial(self):
 
1111
        transport = MemoryTransport()
 
1112
        client = FakeClient(transport.base)
 
1113
        client.add_expected_call(
 
1114
            'Branch.get_stacked_on_url', ('quack/',),
 
1115
            'error', ('NotStacked',))
 
1116
        client.add_expected_call(
 
1117
            'Branch.get_tags_bytes', ('quack/',),
 
1118
            'success', ('',))
 
1119
        transport.mkdir('quack')
 
1120
        transport = transport.clone('quack')
 
1121
        branch = self.make_remote_branch(transport, client)
 
1122
        result = branch.tags.get_tag_dict()
 
1123
        self.assertFinished(client)
 
1124
        self.assertEqual({}, result)
 
1125
 
 
1126
 
 
1127
class TestBranchSetTagsBytes(RemoteBranchTestCase):
 
1128
 
 
1129
    def test_trivial(self):
 
1130
        transport = MemoryTransport()
 
1131
        client = FakeClient(transport.base)
 
1132
        client.add_expected_call(
 
1133
            'Branch.get_stacked_on_url', ('quack/',),
 
1134
            'error', ('NotStacked',))
 
1135
        client.add_expected_call(
 
1136
            'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
 
1137
            'success', ('',))
 
1138
        transport.mkdir('quack')
 
1139
        transport = transport.clone('quack')
 
1140
        branch = self.make_remote_branch(transport, client)
 
1141
        self.lock_remote_branch(branch)
 
1142
        branch._set_tags_bytes('tags bytes')
 
1143
        self.assertFinished(client)
 
1144
        self.assertEqual('tags bytes', client._calls[-1][-1])
 
1145
 
 
1146
    def test_backwards_compatible(self):
 
1147
        transport = MemoryTransport()
 
1148
        client = FakeClient(transport.base)
 
1149
        client.add_expected_call(
 
1150
            'Branch.get_stacked_on_url', ('quack/',),
 
1151
            'error', ('NotStacked',))
 
1152
        client.add_expected_call(
 
1153
            'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
 
1154
            'unknown', ('Branch.set_tags_bytes',))
 
1155
        transport.mkdir('quack')
 
1156
        transport = transport.clone('quack')
 
1157
        branch = self.make_remote_branch(transport, client)
 
1158
        self.lock_remote_branch(branch)
 
1159
        class StubRealBranch(object):
 
1160
            def __init__(self):
 
1161
                self.calls = []
 
1162
            def _set_tags_bytes(self, bytes):
 
1163
                self.calls.append(('set_tags_bytes', bytes))
 
1164
        real_branch = StubRealBranch()
 
1165
        branch._real_branch = real_branch
 
1166
        branch._set_tags_bytes('tags bytes')
 
1167
        # Call a second time, to exercise the 'remote version already inferred'
 
1168
        # code path.
 
1169
        branch._set_tags_bytes('tags bytes')
 
1170
        self.assertFinished(client)
 
1171
        self.assertEqual(
 
1172
            [('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
 
1173
 
 
1174
 
 
1175
class TestBranchHeadsToFetch(RemoteBranchTestCase):
 
1176
 
 
1177
    def test_uses_last_revision_info_and_tags_by_default(self):
 
1178
        transport = MemoryTransport()
 
1179
        client = FakeClient(transport.base)
 
1180
        client.add_expected_call(
 
1181
            'Branch.get_stacked_on_url', ('quack/',),
 
1182
            'error', ('NotStacked',))
 
1183
        client.add_expected_call(
 
1184
            'Branch.last_revision_info', ('quack/',),
 
1185
            'success', ('ok', '1', 'rev-tip'))
 
1186
        client.add_expected_call(
 
1187
            'Branch.get_config_file', ('quack/',),
 
1188
            'success', ('ok',), '')
 
1189
        transport.mkdir('quack')
 
1190
        transport = transport.clone('quack')
 
1191
        branch = self.make_remote_branch(transport, client)
 
1192
        result = branch.heads_to_fetch()
 
1193
        self.assertFinished(client)
 
1194
        self.assertEqual((set(['rev-tip']), set()), result)
 
1195
 
 
1196
    def test_uses_last_revision_info_and_tags_when_set(self):
 
1197
        transport = MemoryTransport()
 
1198
        client = FakeClient(transport.base)
 
1199
        client.add_expected_call(
 
1200
            'Branch.get_stacked_on_url', ('quack/',),
 
1201
            'error', ('NotStacked',))
 
1202
        client.add_expected_call(
 
1203
            'Branch.last_revision_info', ('quack/',),
 
1204
            'success', ('ok', '1', 'rev-tip'))
 
1205
        client.add_expected_call(
 
1206
            'Branch.get_config_file', ('quack/',),
 
1207
            'success', ('ok',), 'branch.fetch_tags = True')
 
1208
        # XXX: this will break if the default format's serialization of tags
 
1209
        # changes, or if the RPC for fetching tags changes from get_tags_bytes.
 
1210
        client.add_expected_call(
 
1211
            'Branch.get_tags_bytes', ('quack/',),
 
1212
            'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
 
1213
        transport.mkdir('quack')
 
1214
        transport = transport.clone('quack')
 
1215
        branch = self.make_remote_branch(transport, client)
 
1216
        result = branch.heads_to_fetch()
 
1217
        self.assertFinished(client)
 
1218
        self.assertEqual(
 
1219
            (set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
 
1220
 
 
1221
    def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
 
1222
        transport = MemoryTransport()
 
1223
        client = FakeClient(transport.base)
 
1224
        client.add_expected_call(
 
1225
            'Branch.get_stacked_on_url', ('quack/',),
 
1226
            'error', ('NotStacked',))
 
1227
        client.add_expected_call(
 
1228
            'Branch.heads_to_fetch', ('quack/',),
 
1229
            'success', (['tip'], ['tagged-1', 'tagged-2']))
 
1230
        transport.mkdir('quack')
 
1231
        transport = transport.clone('quack')
 
1232
        branch = self.make_remote_branch(transport, client)
 
1233
        branch._format._use_default_local_heads_to_fetch = lambda: False
 
1234
        result = branch.heads_to_fetch()
 
1235
        self.assertFinished(client)
 
1236
        self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
 
1237
 
 
1238
    def make_branch_with_tags(self):
 
1239
        self.setup_smart_server_with_call_log()
 
1240
        # Make a branch with a single revision.
 
1241
        builder = self.make_branch_builder('foo')
 
1242
        builder.start_series()
 
1243
        builder.build_snapshot('tip', None, [
 
1244
            ('add', ('', 'root-id', 'directory', ''))])
 
1245
        builder.finish_series()
 
1246
        branch = builder.get_branch()
 
1247
        # Add two tags to that branch
 
1248
        branch.tags.set_tag('tag-1', 'rev-1')
 
1249
        branch.tags.set_tag('tag-2', 'rev-2')
 
1250
        return branch
 
1251
 
 
1252
    def test_backwards_compatible(self):
 
1253
        branch = self.make_branch_with_tags()
 
1254
        c = branch.get_config()
 
1255
        c.set_user_option('branch.fetch_tags', 'True')
 
1256
        self.addCleanup(branch.lock_read().unlock)
 
1257
        # Disable the heads_to_fetch verb
 
1258
        verb = 'Branch.heads_to_fetch'
 
1259
        self.disable_verb(verb)
 
1260
        self.reset_smart_call_log()
 
1261
        result = branch.heads_to_fetch()
 
1262
        self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
 
1263
        self.assertEqual(
 
1264
            ['Branch.last_revision_info', 'Branch.get_config_file',
 
1265
             'Branch.get_tags_bytes'],
 
1266
            [call.call.method for call in self.hpss_calls])
 
1267
 
 
1268
    def test_backwards_compatible_no_tags(self):
 
1269
        branch = self.make_branch_with_tags()
 
1270
        c = branch.get_config()
 
1271
        c.set_user_option('branch.fetch_tags', 'False')
 
1272
        self.addCleanup(branch.lock_read().unlock)
 
1273
        # Disable the heads_to_fetch verb
 
1274
        verb = 'Branch.heads_to_fetch'
 
1275
        self.disable_verb(verb)
 
1276
        self.reset_smart_call_log()
 
1277
        result = branch.heads_to_fetch()
 
1278
        self.assertEqual((set(['tip']), set()), result)
 
1279
        self.assertEqual(
 
1280
            ['Branch.last_revision_info', 'Branch.get_config_file'],
 
1281
            [call.call.method for call in self.hpss_calls])
530
1282
 
531
1283
 
532
1284
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
545
1297
        transport = transport.clone('quack')
546
1298
        branch = self.make_remote_branch(transport, client)
547
1299
        result = branch.last_revision_info()
548
 
        client.finished_test()
 
1300
        self.assertFinished(client)
549
1301
        self.assertEqual((0, NULL_REVISION), result)
550
1302
 
551
1303
    def test_non_empty_branch(self):
566
1318
        self.assertEqual((2, revid), result)
567
1319
 
568
1320
 
569
 
class TestBranch_get_stacked_on_url(tests.TestCaseWithMemoryTransport):
 
1321
class TestBranch_get_stacked_on_url(TestRemote):
570
1322
    """Test Branch._get_stacked_on_url rpc"""
571
1323
 
572
1324
    def test_get_stacked_on_invalid_url(self):
573
 
        raise tests.KnownFailure('opening a branch requires the server to open the fallback repository')
574
 
        transport = FakeRemoteTransport('fakeremotetransport:///')
 
1325
        # test that asking for a stacked on url the server can't access works.
 
1326
        # This isn't perfect, but then as we're in the same process there
 
1327
        # really isn't anything we can do to be 100% sure that the server
 
1328
        # doesn't just open in - this test probably needs to be rewritten using
 
1329
        # a spawn()ed server.
 
1330
        stacked_branch = self.make_branch('stacked', format='1.9')
 
1331
        memory_branch = self.make_branch('base', format='1.9')
 
1332
        vfs_url = self.get_vfs_only_url('base')
 
1333
        stacked_branch.set_stacked_on_url(vfs_url)
 
1334
        transport = stacked_branch.bzrdir.root_transport
575
1335
        client = FakeClient(transport.base)
576
1336
        client.add_expected_call(
577
 
            'Branch.get_stacked_on_url', ('.',),
578
 
            'success', ('ok', 'file:///stacked/on'))
579
 
        bzrdir = RemoteBzrDir(transport, _client=client)
580
 
        branch = RemoteBranch(bzrdir, None, _client=client)
 
1337
            'Branch.get_stacked_on_url', ('stacked/',),
 
1338
            'success', ('ok', vfs_url))
 
1339
        # XXX: Multiple calls are bad, this second call documents what is
 
1340
        # today.
 
1341
        client.add_expected_call(
 
1342
            'Branch.get_stacked_on_url', ('stacked/',),
 
1343
            'success', ('ok', vfs_url))
 
1344
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
1345
            _client=client)
 
1346
        repo_fmt = remote.RemoteRepositoryFormat()
 
1347
        repo_fmt._custom_format = stacked_branch.repository._format
 
1348
        branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, repo_fmt),
 
1349
            _client=client)
581
1350
        result = branch.get_stacked_on_url()
582
 
        self.assertEqual(
583
 
            'file:///stacked/on', result)
 
1351
        self.assertEqual(vfs_url, result)
584
1352
 
585
1353
    def test_backwards_compatible(self):
586
1354
        # like with bzr1.6 with no Branch.get_stacked_on_url rpc
588
1356
        stacked_branch = self.make_branch('stacked', format='1.6')
589
1357
        stacked_branch.set_stacked_on_url('../base')
590
1358
        client = FakeClient(self.get_url())
591
 
        client.add_expected_call(
592
 
            'BzrDir.open_branch', ('stacked/',),
593
 
            'success', ('ok', ''))
594
 
        client.add_expected_call(
595
 
            'BzrDir.find_repositoryV2', ('stacked/',),
596
 
            'success', ('ok', '', 'no', 'no', 'no'))
 
1359
        branch_network_name = self.get_branch_format().network_name()
 
1360
        client.add_expected_call(
 
1361
            'BzrDir.open_branchV3', ('stacked/',),
 
1362
            'success', ('branch', branch_network_name))
 
1363
        client.add_expected_call(
 
1364
            'BzrDir.find_repositoryV3', ('stacked/',),
 
1365
            'success', ('ok', '', 'no', 'no', 'yes',
 
1366
                stacked_branch.repository._format.network_name()))
597
1367
        # called twice, once from constructor and then again by us
598
1368
        client.add_expected_call(
599
1369
            'Branch.get_stacked_on_url', ('stacked/',),
603
1373
            'unknown', ('Branch.get_stacked_on_url',))
604
1374
        # this will also do vfs access, but that goes direct to the transport
605
1375
        # and isn't seen by the FakeClient.
606
 
        bzrdir = RemoteBzrDir(self.get_transport('stacked'), _client=client)
 
1376
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
1377
            RemoteBzrDirFormat(), _client=client)
607
1378
        branch = bzrdir.open_branch()
608
1379
        result = branch.get_stacked_on_url()
609
1380
        self.assertEqual('../base', result)
610
 
        client.finished_test()
 
1381
        self.assertFinished(client)
611
1382
        # it's in the fallback list both for the RemoteRepository and its vfs
612
1383
        # repository
613
1384
        self.assertEqual(1, len(branch.repository._fallback_repositories))
615
1386
            len(branch.repository._real_repository._fallback_repositories))
616
1387
 
617
1388
    def test_get_stacked_on_real_branch(self):
618
 
        base_branch = self.make_branch('base', format='1.6')
619
 
        stacked_branch = self.make_branch('stacked', format='1.6')
 
1389
        base_branch = self.make_branch('base')
 
1390
        stacked_branch = self.make_branch('stacked')
620
1391
        stacked_branch.set_stacked_on_url('../base')
 
1392
        reference_format = self.get_repo_format()
 
1393
        network_name = reference_format.network_name()
621
1394
        client = FakeClient(self.get_url())
622
 
        client.add_expected_call(
623
 
            'BzrDir.open_branch', ('stacked/',),
624
 
            'success', ('ok', ''))
625
 
        client.add_expected_call(
626
 
            'BzrDir.find_repositoryV2', ('stacked/',),
627
 
            'success', ('ok', '', 'no', 'no', 'no'))
 
1395
        branch_network_name = self.get_branch_format().network_name()
 
1396
        client.add_expected_call(
 
1397
            'BzrDir.open_branchV3', ('stacked/',),
 
1398
            'success', ('branch', branch_network_name))
 
1399
        client.add_expected_call(
 
1400
            'BzrDir.find_repositoryV3', ('stacked/',),
 
1401
            'success', ('ok', '', 'yes', 'no', 'yes', network_name))
628
1402
        # called twice, once from constructor and then again by us
629
1403
        client.add_expected_call(
630
1404
            'Branch.get_stacked_on_url', ('stacked/',),
632
1406
        client.add_expected_call(
633
1407
            'Branch.get_stacked_on_url', ('stacked/',),
634
1408
            'success', ('ok', '../base'))
635
 
        bzrdir = RemoteBzrDir(self.get_transport('stacked'), _client=client)
 
1409
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
1410
            RemoteBzrDirFormat(), _client=client)
636
1411
        branch = bzrdir.open_branch()
637
1412
        result = branch.get_stacked_on_url()
638
1413
        self.assertEqual('../base', result)
639
 
        client.finished_test()
640
 
        # it's in the fallback list both for the RemoteRepository and its vfs
641
 
        # repository
 
1414
        self.assertFinished(client)
 
1415
        # it's in the fallback list both for the RemoteRepository.
642
1416
        self.assertEqual(1, len(branch.repository._fallback_repositories))
643
 
        self.assertEqual(1,
644
 
            len(branch.repository._real_repository._fallback_repositories))
 
1417
        # And we haven't had to construct a real repository.
 
1418
        self.assertEqual(None, branch.repository._real_repository)
645
1419
 
646
1420
 
647
1421
class TestBranchSetLastRevision(RemoteBranchTestCase):
648
1422
 
649
1423
    def test_set_empty(self):
650
 
        # set_revision_history([]) is translated to calling
 
1424
        # _set_last_revision_info('null:') is translated to calling
651
1425
        # Branch.set_last_revision(path, '') on the wire.
652
1426
        transport = MemoryTransport()
653
1427
        transport.mkdir('branch')
661
1435
            'Branch.lock_write', ('branch/', '', ''),
662
1436
            'success', ('ok', 'branch token', 'repo token'))
663
1437
        client.add_expected_call(
 
1438
            'Branch.last_revision_info',
 
1439
            ('branch/',),
 
1440
            'success', ('ok', '0', 'null:'))
 
1441
        client.add_expected_call(
664
1442
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
665
1443
            'success', ('ok',))
666
1444
        client.add_expected_call(
671
1449
        # unnecessarily invokes _ensure_real upon a call to lock_write.
672
1450
        branch._ensure_real = lambda: None
673
1451
        branch.lock_write()
674
 
        result = branch.set_revision_history([])
 
1452
        result = branch._set_last_revision(NULL_REVISION)
675
1453
        branch.unlock()
676
1454
        self.assertEqual(None, result)
677
 
        client.finished_test()
 
1455
        self.assertFinished(client)
678
1456
 
679
1457
    def test_set_nonempty(self):
680
 
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
 
1458
        # set_last_revision_info(N, rev-idN) is translated to calling
681
1459
        # Branch.set_last_revision(path, rev-idN) on the wire.
682
1460
        transport = MemoryTransport()
683
1461
        transport.mkdir('branch')
691
1469
            'Branch.lock_write', ('branch/', '', ''),
692
1470
            'success', ('ok', 'branch token', 'repo token'))
693
1471
        client.add_expected_call(
 
1472
            'Branch.last_revision_info',
 
1473
            ('branch/',),
 
1474
            'success', ('ok', '0', 'null:'))
 
1475
        lines = ['rev-id2']
 
1476
        encoded_body = bz2.compress('\n'.join(lines))
 
1477
        client.add_success_response_with_body(encoded_body, 'ok')
 
1478
        client.add_expected_call(
694
1479
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
695
1480
            'success', ('ok',))
696
1481
        client.add_expected_call(
702
1487
        branch._ensure_real = lambda: None
703
1488
        # Lock the branch, reset the record of remote calls.
704
1489
        branch.lock_write()
705
 
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
 
1490
        result = branch._set_last_revision('rev-id2')
706
1491
        branch.unlock()
707
1492
        self.assertEqual(None, result)
708
 
        client.finished_test()
 
1493
        self.assertFinished(client)
709
1494
 
710
1495
    def test_no_such_revision(self):
711
1496
        transport = MemoryTransport()
720
1505
            'Branch.lock_write', ('branch/', '', ''),
721
1506
            'success', ('ok', 'branch token', 'repo token'))
722
1507
        client.add_expected_call(
 
1508
            'Branch.last_revision_info',
 
1509
            ('branch/',),
 
1510
            'success', ('ok', '0', 'null:'))
 
1511
        # get_graph calls to construct the revision history, for the set_rh
 
1512
        # hook
 
1513
        lines = ['rev-id']
 
1514
        encoded_body = bz2.compress('\n'.join(lines))
 
1515
        client.add_success_response_with_body(encoded_body, 'ok')
 
1516
        client.add_expected_call(
723
1517
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
724
1518
            'error', ('NoSuchRevision', 'rev-id'))
725
1519
        client.add_expected_call(
729
1523
        branch = self.make_remote_branch(transport, client)
730
1524
        branch.lock_write()
731
1525
        self.assertRaises(
732
 
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
 
1526
            errors.NoSuchRevision, branch._set_last_revision, 'rev-id')
733
1527
        branch.unlock()
734
 
        client.finished_test()
 
1528
        self.assertFinished(client)
735
1529
 
736
1530
    def test_tip_change_rejected(self):
737
1531
        """TipChangeRejected responses cause a TipChangeRejected exception to
750
1544
            'Branch.lock_write', ('branch/', '', ''),
751
1545
            'success', ('ok', 'branch token', 'repo token'))
752
1546
        client.add_expected_call(
 
1547
            'Branch.last_revision_info',
 
1548
            ('branch/',),
 
1549
            'success', ('ok', '0', 'null:'))
 
1550
        lines = ['rev-id']
 
1551
        encoded_body = bz2.compress('\n'.join(lines))
 
1552
        client.add_success_response_with_body(encoded_body, 'ok')
 
1553
        client.add_expected_call(
753
1554
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
754
1555
            'error', ('TipChangeRejected', rejection_msg_utf8))
755
1556
        client.add_expected_call(
758
1559
        branch = self.make_remote_branch(transport, client)
759
1560
        branch._ensure_real = lambda: None
760
1561
        branch.lock_write()
761
 
        self.addCleanup(branch.unlock)
762
1562
        # The 'TipChangeRejected' error response triggered by calling
763
 
        # set_revision_history causes a TipChangeRejected exception.
 
1563
        # set_last_revision_info causes a TipChangeRejected exception.
764
1564
        err = self.assertRaises(
765
 
            errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
 
1565
            errors.TipChangeRejected,
 
1566
            branch._set_last_revision, 'rev-id')
766
1567
        # The UTF-8 message from the response has been decoded into a unicode
767
1568
        # object.
768
1569
        self.assertIsInstance(err.msg, unicode)
769
1570
        self.assertEqual(rejection_msg_unicode, err.msg)
770
1571
        branch.unlock()
771
 
        client.finished_test()
 
1572
        self.assertFinished(client)
772
1573
 
773
1574
 
774
1575
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
784
1585
        client.add_error_response('NotStacked')
785
1586
        # lock_write
786
1587
        client.add_success_response('ok', 'branch token', 'repo token')
 
1588
        # query the current revision
 
1589
        client.add_success_response('ok', '0', 'null:')
787
1590
        # set_last_revision
788
1591
        client.add_success_response('ok')
789
1592
        # unlock
795
1598
        client._calls = []
796
1599
        result = branch.set_last_revision_info(1234, 'a-revision-id')
797
1600
        self.assertEqual(
798
 
            [('call', 'Branch.set_last_revision_info',
 
1601
            [('call', 'Branch.last_revision_info', ('branch/',)),
 
1602
             ('call', 'Branch.set_last_revision_info',
799
1603
                ('branch/', 'branch token', 'repo token',
800
1604
                 '1234', 'a-revision-id'))],
801
1605
            client._calls)
825
1629
            errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
826
1630
        branch.unlock()
827
1631
 
828
 
    def lock_remote_branch(self, branch):
829
 
        """Trick a RemoteBranch into thinking it is locked."""
830
 
        branch._lock_mode = 'w'
831
 
        branch._lock_count = 2
832
 
        branch._lock_token = 'branch token'
833
 
        branch._repo_lock_token = 'repo token'
834
 
        branch.repository._lock_mode = 'w'
835
 
        branch.repository._lock_count = 2
836
 
        branch.repository._lock_token = 'repo token'
837
 
 
838
1632
    def test_backwards_compatibility(self):
839
1633
        """If the server does not support the Branch.set_last_revision_info
840
1634
        verb (which is new in 1.4), then the client falls back to VFS methods.
855
1649
            'Branch.get_stacked_on_url', ('branch/',),
856
1650
            'error', ('NotStacked',))
857
1651
        client.add_expected_call(
 
1652
            'Branch.last_revision_info',
 
1653
            ('branch/',),
 
1654
            'success', ('ok', '0', 'null:'))
 
1655
        client.add_expected_call(
858
1656
            'Branch.set_last_revision_info',
859
1657
            ('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
860
1658
            'unknown', 'Branch.set_last_revision_info')
877
1675
        self.assertEqual(
878
1676
            [('set_last_revision_info', 1234, 'a-revision-id')],
879
1677
            real_branch.calls)
880
 
        client.finished_test()
 
1678
        self.assertFinished(client)
881
1679
 
882
1680
    def test_unexpected_error(self):
883
1681
        # If the server sends an error the client doesn't understand, it gets
938
1736
        self.assertEqual('rejection message', err.msg)
939
1737
 
940
1738
 
941
 
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
942
 
    """Getting the branch configuration should use an abstract method not vfs.
943
 
    """
 
1739
class TestBranchGetSetConfig(RemoteBranchTestCase):
944
1740
 
945
1741
    def test_get_branch_conf(self):
946
 
        raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
947
 
        ## # We should see that branch.get_config() does a single rpc to get the
948
 
        ## # remote configuration file, abstracting away where that is stored on
949
 
        ## # the server.  However at the moment it always falls back to using the
950
 
        ## # vfs, and this would need some changes in config.py.
951
 
 
952
 
        ## # in an empty branch we decode the response properly
953
 
        ## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
954
 
        ## # we need to make a real branch because the remote_branch.control_files
955
 
        ## # will trigger _ensure_real.
956
 
        ## branch = self.make_branch('quack')
957
 
        ## transport = branch.bzrdir.root_transport
958
 
        ## # we do not want bzrdir to make any remote calls
959
 
        ## bzrdir = RemoteBzrDir(transport, _client=False)
960
 
        ## branch = RemoteBranch(bzrdir, None, _client=client)
961
 
        ## config = branch.get_config()
962
 
        ## self.assertEqual(
963
 
        ##     [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
964
 
        ##     client._calls)
 
1742
        # in an empty branch we decode the response properly
 
1743
        client = FakeClient()
 
1744
        client.add_expected_call(
 
1745
            'Branch.get_stacked_on_url', ('memory:///',),
 
1746
            'error', ('NotStacked',),)
 
1747
        client.add_success_response_with_body('# config file body', 'ok')
 
1748
        transport = MemoryTransport()
 
1749
        branch = self.make_remote_branch(transport, client)
 
1750
        config = branch.get_config()
 
1751
        config.has_explicit_nickname()
 
1752
        self.assertEqual(
 
1753
            [('call', 'Branch.get_stacked_on_url', ('memory:///',)),
 
1754
             ('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
 
1755
            client._calls)
 
1756
 
 
1757
    def test_get_multi_line_branch_conf(self):
 
1758
        # Make sure that multiple-line branch.conf files are supported
 
1759
        #
 
1760
        # https://bugs.launchpad.net/bzr/+bug/354075
 
1761
        client = FakeClient()
 
1762
        client.add_expected_call(
 
1763
            'Branch.get_stacked_on_url', ('memory:///',),
 
1764
            'error', ('NotStacked',),)
 
1765
        client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
 
1766
        transport = MemoryTransport()
 
1767
        branch = self.make_remote_branch(transport, client)
 
1768
        config = branch.get_config()
 
1769
        self.assertEqual(u'2', config.get_user_option('b'))
 
1770
 
 
1771
    def test_set_option(self):
 
1772
        client = FakeClient()
 
1773
        client.add_expected_call(
 
1774
            'Branch.get_stacked_on_url', ('memory:///',),
 
1775
            'error', ('NotStacked',),)
 
1776
        client.add_expected_call(
 
1777
            'Branch.lock_write', ('memory:///', '', ''),
 
1778
            'success', ('ok', 'branch token', 'repo token'))
 
1779
        client.add_expected_call(
 
1780
            'Branch.set_config_option', ('memory:///', 'branch token',
 
1781
            'repo token', 'foo', 'bar', ''),
 
1782
            'success', ())
 
1783
        client.add_expected_call(
 
1784
            'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
 
1785
            'success', ('ok',))
 
1786
        transport = MemoryTransport()
 
1787
        branch = self.make_remote_branch(transport, client)
 
1788
        branch.lock_write()
 
1789
        config = branch._get_config()
 
1790
        config.set_option('foo', 'bar')
 
1791
        branch.unlock()
 
1792
        self.assertFinished(client)
 
1793
 
 
1794
    def test_set_option_with_dict(self):
 
1795
        client = FakeClient()
 
1796
        client.add_expected_call(
 
1797
            'Branch.get_stacked_on_url', ('memory:///',),
 
1798
            'error', ('NotStacked',),)
 
1799
        client.add_expected_call(
 
1800
            'Branch.lock_write', ('memory:///', '', ''),
 
1801
            'success', ('ok', 'branch token', 'repo token'))
 
1802
        encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
 
1803
        client.add_expected_call(
 
1804
            'Branch.set_config_option_dict', ('memory:///', 'branch token',
 
1805
            'repo token', encoded_dict_value, 'foo', ''),
 
1806
            'success', ())
 
1807
        client.add_expected_call(
 
1808
            'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
 
1809
            'success', ('ok',))
 
1810
        transport = MemoryTransport()
 
1811
        branch = self.make_remote_branch(transport, client)
 
1812
        branch.lock_write()
 
1813
        config = branch._get_config()
 
1814
        config.set_option(
 
1815
            {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
 
1816
            'foo')
 
1817
        branch.unlock()
 
1818
        self.assertFinished(client)
 
1819
 
 
1820
    def test_backwards_compat_set_option(self):
 
1821
        self.setup_smart_server_with_call_log()
 
1822
        branch = self.make_branch('.')
 
1823
        verb = 'Branch.set_config_option'
 
1824
        self.disable_verb(verb)
 
1825
        branch.lock_write()
 
1826
        self.addCleanup(branch.unlock)
 
1827
        self.reset_smart_call_log()
 
1828
        branch._get_config().set_option('value', 'name')
 
1829
        self.assertLength(10, self.hpss_calls)
 
1830
        self.assertEqual('value', branch._get_config().get_option('name'))
 
1831
 
 
1832
    def test_backwards_compat_set_option_with_dict(self):
 
1833
        self.setup_smart_server_with_call_log()
 
1834
        branch = self.make_branch('.')
 
1835
        verb = 'Branch.set_config_option_dict'
 
1836
        self.disable_verb(verb)
 
1837
        branch.lock_write()
 
1838
        self.addCleanup(branch.unlock)
 
1839
        self.reset_smart_call_log()
 
1840
        config = branch._get_config()
 
1841
        value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
 
1842
        config.set_option(value_dict, 'name')
 
1843
        self.assertLength(10, self.hpss_calls)
 
1844
        self.assertEqual(value_dict, branch._get_config().get_option('name'))
965
1845
 
966
1846
 
967
1847
class TestBranchLockWrite(RemoteBranchTestCase):
979
1859
        transport = transport.clone('quack')
980
1860
        branch = self.make_remote_branch(transport, client)
981
1861
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
982
 
        client.finished_test()
 
1862
        self.assertFinished(client)
 
1863
 
 
1864
 
 
1865
class TestBzrDirGetSetConfig(RemoteBzrDirTestCase):
 
1866
 
 
1867
    def test__get_config(self):
 
1868
        client = FakeClient()
 
1869
        client.add_success_response_with_body('default_stack_on = /\n', 'ok')
 
1870
        transport = MemoryTransport()
 
1871
        bzrdir = self.make_remote_bzrdir(transport, client)
 
1872
        config = bzrdir.get_config()
 
1873
        self.assertEqual('/', config.get_default_stack_on())
 
1874
        self.assertEqual(
 
1875
            [('call_expecting_body', 'BzrDir.get_config_file', ('memory:///',))],
 
1876
            client._calls)
 
1877
 
 
1878
    def test_set_option_uses_vfs(self):
 
1879
        self.setup_smart_server_with_call_log()
 
1880
        bzrdir = self.make_bzrdir('.')
 
1881
        self.reset_smart_call_log()
 
1882
        config = bzrdir.get_config()
 
1883
        config.set_default_stack_on('/')
 
1884
        self.assertLength(3, self.hpss_calls)
 
1885
 
 
1886
    def test_backwards_compat_get_option(self):
 
1887
        self.setup_smart_server_with_call_log()
 
1888
        bzrdir = self.make_bzrdir('.')
 
1889
        verb = 'BzrDir.get_config_file'
 
1890
        self.disable_verb(verb)
 
1891
        self.reset_smart_call_log()
 
1892
        self.assertEqual(None,
 
1893
            bzrdir._get_config().get_option('default_stack_on'))
 
1894
        self.assertLength(3, self.hpss_calls)
983
1895
 
984
1896
 
985
1897
class TestTransportIsReadonly(tests.TestCase):
1006
1918
 
1007
1919
    def test_error_from_old_server(self):
1008
1920
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
1009
 
        
 
1921
 
1010
1922
        Clients should treat it as a "no" response, because is_readonly is only
1011
1923
        advisory anyway (a transport could be read-write, but then the
1012
1924
        underlying filesystem could be readonly anyway).
1050
1962
        self.assertEqual('bar', t._get_credentials()[0])
1051
1963
 
1052
1964
 
1053
 
class TestRemoteRepository(tests.TestCase):
 
1965
class TestRemoteRepository(TestRemote):
1054
1966
    """Base for testing RemoteRepository protocol usage.
1055
 
    
1056
 
    These tests contain frozen requests and responses.  We want any changes to 
 
1967
 
 
1968
    These tests contain frozen requests and responses.  We want any changes to
1057
1969
    what is sent or expected to be require a thoughtful update to these tests
1058
1970
    because they might break compatibility with different-versioned servers.
1059
1971
    """
1060
1972
 
1061
1973
    def setup_fake_client_and_repository(self, transport_path):
1062
1974
        """Create the fake client and repository for testing with.
1063
 
        
 
1975
 
1064
1976
        There's no real server here; we just have canned responses sent
1065
1977
        back one by one.
1066
 
        
 
1978
 
1067
1979
        :param transport_path: Path below the root of the MemoryTransport
1068
1980
            where the repository will be created.
1069
1981
        """
1072
1984
        client = FakeClient(transport.base)
1073
1985
        transport = transport.clone(transport_path)
1074
1986
        # we do not want bzrdir to make any remote calls
1075
 
        bzrdir = RemoteBzrDir(transport, _client=False)
 
1987
        bzrdir = RemoteBzrDir(transport, RemoteBzrDirFormat(),
 
1988
            _client=False)
1076
1989
        repo = RemoteRepository(bzrdir, None, _client=client)
1077
1990
        return repo, client
1078
1991
 
1079
1992
 
 
1993
def remoted_description(format):
 
1994
    return 'Remote: ' + format.get_format_description()
 
1995
 
 
1996
 
 
1997
class TestBranchFormat(tests.TestCase):
 
1998
 
 
1999
    def test_get_format_description(self):
 
2000
        remote_format = RemoteBranchFormat()
 
2001
        real_format = branch.format_registry.get_default()
 
2002
        remote_format._network_name = real_format.network_name()
 
2003
        self.assertEqual(remoted_description(real_format),
 
2004
            remote_format.get_format_description())
 
2005
 
 
2006
 
 
2007
class TestRepositoryFormat(TestRemoteRepository):
 
2008
 
 
2009
    def test_fast_delta(self):
 
2010
        true_name = groupcompress_repo.RepositoryFormat2a().network_name()
 
2011
        true_format = RemoteRepositoryFormat()
 
2012
        true_format._network_name = true_name
 
2013
        self.assertEqual(True, true_format.fast_deltas)
 
2014
        false_name = knitpack_repo.RepositoryFormatKnitPack1().network_name()
 
2015
        false_format = RemoteRepositoryFormat()
 
2016
        false_format._network_name = false_name
 
2017
        self.assertEqual(False, false_format.fast_deltas)
 
2018
 
 
2019
    def test_get_format_description(self):
 
2020
        remote_repo_format = RemoteRepositoryFormat()
 
2021
        real_format = repository.format_registry.get_default()
 
2022
        remote_repo_format._network_name = real_format.network_name()
 
2023
        self.assertEqual(remoted_description(real_format),
 
2024
            remote_repo_format.get_format_description())
 
2025
 
 
2026
 
1080
2027
class TestRepositoryGatherStats(TestRemoteRepository):
1081
2028
 
1082
2029
    def test_revid_none(self):
1170
2117
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
1171
2118
        self.assertEqual(
1172
2119
            [('call_with_body_bytes_expecting_body',
1173
 
              'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
 
2120
              'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
 
2121
              '\n\n0')],
1174
2122
            client._calls)
1175
2123
        repo.unlock()
1176
2124
        # now we call again, and it should use the second response.
1180
2128
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
1181
2129
        self.assertEqual(
1182
2130
            [('call_with_body_bytes_expecting_body',
1183
 
              'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
 
2131
              'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
 
2132
              '\n\n0'),
1184
2133
             ('call_with_body_bytes_expecting_body',
1185
 
              'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
 
2134
              'Repository.get_parent_map', ('quack/', 'include-missing:', r1),
 
2135
              '\n\n0'),
1186
2136
            ],
1187
2137
            client._calls)
1188
2138
        repo.unlock()
1189
2139
 
1190
2140
    def test_get_parent_map_reconnects_if_unknown_method(self):
1191
2141
        transport_path = 'quack'
 
2142
        rev_id = 'revision-id'
1192
2143
        repo, client = self.setup_fake_client_and_repository(transport_path)
1193
 
        client.add_unknown_method_response('Repository,get_parent_map')
1194
 
        client.add_success_response_with_body('', 'ok')
 
2144
        client.add_unknown_method_response('Repository.get_parent_map')
 
2145
        client.add_success_response_with_body(rev_id, 'ok')
1195
2146
        self.assertFalse(client._medium._is_remote_before((1, 2)))
1196
 
        rev_id = 'revision-id'
1197
 
        expected_deprecations = [
1198
 
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1199
 
            'in version 1.4.']
1200
 
        parents = self.callDeprecated(
1201
 
            expected_deprecations, repo.get_parent_map, [rev_id])
 
2147
        parents = repo.get_parent_map([rev_id])
1202
2148
        self.assertEqual(
1203
2149
            [('call_with_body_bytes_expecting_body',
1204
 
              'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
 
2150
              'Repository.get_parent_map',
 
2151
              ('quack/', 'include-missing:', rev_id), '\n\n0'),
1205
2152
             ('disconnect medium',),
1206
2153
             ('call_expecting_body', 'Repository.get_revision_graph',
1207
2154
              ('quack/', ''))],
1208
2155
            client._calls)
1209
2156
        # The medium is now marked as being connected to an older server
1210
2157
        self.assertTrue(client._medium._is_remote_before((1, 2)))
 
2158
        self.assertEqual({rev_id: ('null:',)}, parents)
1211
2159
 
1212
2160
    def test_get_parent_map_fallback_parentless_node(self):
1213
2161
        """get_parent_map falls back to get_revision_graph on old servers.  The
1225
2173
        repo, client = self.setup_fake_client_and_repository(transport_path)
1226
2174
        client.add_success_response_with_body(rev_id, 'ok')
1227
2175
        client._medium._remember_remote_is_before((1, 2))
1228
 
        expected_deprecations = [
1229
 
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
1230
 
            'in version 1.4.']
1231
 
        parents = self.callDeprecated(
1232
 
            expected_deprecations, repo.get_parent_map, [rev_id])
 
2176
        parents = repo.get_parent_map([rev_id])
1233
2177
        self.assertEqual(
1234
2178
            [('call_expecting_body', 'Repository.get_revision_graph',
1235
2179
             ('quack/', ''))],
1243
2187
            errors.UnexpectedSmartServerResponse,
1244
2188
            repo.get_parent_map, ['a-revision-id'])
1245
2189
 
 
2190
    def test_get_parent_map_negative_caches_missing_keys(self):
 
2191
        self.setup_smart_server_with_call_log()
 
2192
        repo = self.make_repository('foo')
 
2193
        self.assertIsInstance(repo, RemoteRepository)
 
2194
        repo.lock_read()
 
2195
        self.addCleanup(repo.unlock)
 
2196
        self.reset_smart_call_log()
 
2197
        graph = repo.get_graph()
 
2198
        self.assertEqual({},
 
2199
            graph.get_parent_map(['some-missing', 'other-missing']))
 
2200
        self.assertLength(1, self.hpss_calls)
 
2201
        # No call if we repeat this
 
2202
        self.reset_smart_call_log()
 
2203
        graph = repo.get_graph()
 
2204
        self.assertEqual({},
 
2205
            graph.get_parent_map(['some-missing', 'other-missing']))
 
2206
        self.assertLength(0, self.hpss_calls)
 
2207
        # Asking for more unknown keys makes a request.
 
2208
        self.reset_smart_call_log()
 
2209
        graph = repo.get_graph()
 
2210
        self.assertEqual({},
 
2211
            graph.get_parent_map(['some-missing', 'other-missing',
 
2212
                'more-missing']))
 
2213
        self.assertLength(1, self.hpss_calls)
 
2214
 
 
2215
    def disableExtraResults(self):
 
2216
        self.overrideAttr(SmartServerRepositoryGetParentMap,
 
2217
                          'no_extra_results', True)
 
2218
 
 
2219
    def test_null_cached_missing_and_stop_key(self):
 
2220
        self.setup_smart_server_with_call_log()
 
2221
        # Make a branch with a single revision.
 
2222
        builder = self.make_branch_builder('foo')
 
2223
        builder.start_series()
 
2224
        builder.build_snapshot('first', None, [
 
2225
            ('add', ('', 'root-id', 'directory', ''))])
 
2226
        builder.finish_series()
 
2227
        branch = builder.get_branch()
 
2228
        repo = branch.repository
 
2229
        self.assertIsInstance(repo, RemoteRepository)
 
2230
        # Stop the server from sending extra results.
 
2231
        self.disableExtraResults()
 
2232
        repo.lock_read()
 
2233
        self.addCleanup(repo.unlock)
 
2234
        self.reset_smart_call_log()
 
2235
        graph = repo.get_graph()
 
2236
        # Query for 'first' and 'null:'.  Because 'null:' is a parent of
 
2237
        # 'first' it will be a candidate for the stop_keys of subsequent
 
2238
        # requests, and because 'null:' was queried but not returned it will be
 
2239
        # cached as missing.
 
2240
        self.assertEqual({'first': ('null:',)},
 
2241
            graph.get_parent_map(['first', 'null:']))
 
2242
        # Now query for another key.  This request will pass along a recipe of
 
2243
        # start and stop keys describing the already cached results, and this
 
2244
        # recipe's revision count must be correct (or else it will trigger an
 
2245
        # error from the server).
 
2246
        self.assertEqual({}, graph.get_parent_map(['another-key']))
 
2247
        # This assertion guards against disableExtraResults silently failing to
 
2248
        # work, thus invalidating the test.
 
2249
        self.assertLength(2, self.hpss_calls)
 
2250
 
 
2251
    def test_get_parent_map_gets_ghosts_from_result(self):
 
2252
        # asking for a revision should negatively cache close ghosts in its
 
2253
        # ancestry.
 
2254
        self.setup_smart_server_with_call_log()
 
2255
        tree = self.make_branch_and_memory_tree('foo')
 
2256
        tree.lock_write()
 
2257
        try:
 
2258
            builder = treebuilder.TreeBuilder()
 
2259
            builder.start_tree(tree)
 
2260
            builder.build([])
 
2261
            builder.finish_tree()
 
2262
            tree.set_parent_ids(['non-existant'], allow_leftmost_as_ghost=True)
 
2263
            rev_id = tree.commit('')
 
2264
        finally:
 
2265
            tree.unlock()
 
2266
        tree.lock_read()
 
2267
        self.addCleanup(tree.unlock)
 
2268
        repo = tree.branch.repository
 
2269
        self.assertIsInstance(repo, RemoteRepository)
 
2270
        # ask for rev_id
 
2271
        repo.get_parent_map([rev_id])
 
2272
        self.reset_smart_call_log()
 
2273
        # Now asking for rev_id's ghost parent should not make calls
 
2274
        self.assertEqual({}, repo.get_parent_map(['non-existant']))
 
2275
        self.assertLength(0, self.hpss_calls)
 
2276
 
 
2277
    def test_exposes_get_cached_parent_map(self):
 
2278
        """RemoteRepository exposes get_cached_parent_map from
 
2279
        _unstacked_provider
 
2280
        """
 
2281
        r1 = u'\u0e33'.encode('utf8')
 
2282
        r2 = u'\u0dab'.encode('utf8')
 
2283
        lines = [' '.join([r2, r1]), r1]
 
2284
        encoded_body = bz2.compress('\n'.join(lines))
 
2285
 
 
2286
        transport_path = 'quack'
 
2287
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2288
        client.add_success_response_with_body(encoded_body, 'ok')
 
2289
        repo.lock_read()
 
2290
        # get_cached_parent_map should *not* trigger an RPC
 
2291
        self.assertEqual({}, repo.get_cached_parent_map([r1]))
 
2292
        self.assertEqual([], client._calls)
 
2293
        self.assertEqual({r2: (r1,)}, repo.get_parent_map([r2]))
 
2294
        self.assertEqual({r1: (NULL_REVISION,)},
 
2295
            repo.get_cached_parent_map([r1]))
 
2296
        self.assertEqual(
 
2297
            [('call_with_body_bytes_expecting_body',
 
2298
              'Repository.get_parent_map', ('quack/', 'include-missing:', r2),
 
2299
              '\n\n0')],
 
2300
            client._calls)
 
2301
        repo.unlock()
 
2302
 
1246
2303
 
1247
2304
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
1248
2305
 
1249
2306
    def test_allows_new_revisions(self):
1250
2307
        """get_parent_map's results can be updated by commit."""
1251
 
        smart_server = server.SmartTCPServer_for_testing()
1252
 
        smart_server.setUp()
1253
 
        self.addCleanup(smart_server.tearDown)
 
2308
        smart_server = test_server.SmartTCPServer_for_testing()
 
2309
        self.start_server(smart_server)
1254
2310
        self.make_branch('branch')
1255
2311
        branch = Branch.open(smart_server.get_url() + '/branch')
1256
2312
        tree = branch.create_checkout('tree', lightweight=True)
1265
2321
 
1266
2322
 
1267
2323
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
1268
 
    
 
2324
 
1269
2325
    def test_null_revision(self):
1270
2326
        # a null revision has the predictable result {}, we should have no wire
1271
2327
        # traffic when calling it with this argument
1272
2328
        transport_path = 'empty'
1273
2329
        repo, client = self.setup_fake_client_and_repository(transport_path)
1274
2330
        client.add_success_response('notused')
1275
 
        result = self.applyDeprecated(one_four, repo.get_revision_graph,
1276
 
            NULL_REVISION)
 
2331
        # actual RemoteRepository.get_revision_graph is gone, but there's an
 
2332
        # equivalent private method for testing
 
2333
        result = repo._get_revision_graph(NULL_REVISION)
1277
2334
        self.assertEqual([], client._calls)
1278
2335
        self.assertEqual({}, result)
1279
2336
 
1287
2344
        transport_path = 'sinhala'
1288
2345
        repo, client = self.setup_fake_client_and_repository(transport_path)
1289
2346
        client.add_success_response_with_body(encoded_body, 'ok')
1290
 
        result = self.applyDeprecated(one_four, repo.get_revision_graph)
 
2347
        # actual RemoteRepository.get_revision_graph is gone, but there's an
 
2348
        # equivalent private method for testing
 
2349
        result = repo._get_revision_graph(None)
1291
2350
        self.assertEqual(
1292
2351
            [('call_expecting_body', 'Repository.get_revision_graph',
1293
2352
             ('sinhala/', ''))],
1306
2365
        transport_path = 'sinhala'
1307
2366
        repo, client = self.setup_fake_client_and_repository(transport_path)
1308
2367
        client.add_success_response_with_body(encoded_body, 'ok')
1309
 
        result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
 
2368
        result = repo._get_revision_graph(r2)
1310
2369
        self.assertEqual(
1311
2370
            [('call_expecting_body', 'Repository.get_revision_graph',
1312
2371
             ('sinhala/', r2))],
1320
2379
        client.add_error_response('nosuchrevision', revid)
1321
2380
        # also check that the right revision is reported in the error
1322
2381
        self.assertRaises(errors.NoSuchRevision,
1323
 
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
2382
            repo._get_revision_graph, revid)
1324
2383
        self.assertEqual(
1325
2384
            [('call_expecting_body', 'Repository.get_revision_graph',
1326
2385
             ('sinhala/', revid))],
1332
2391
        repo, client = self.setup_fake_client_and_repository(transport_path)
1333
2392
        client.add_error_response('AnUnexpectedError')
1334
2393
        e = self.assertRaises(errors.UnknownErrorFromSmartServer,
1335
 
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
2394
            repo._get_revision_graph, revid)
1336
2395
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
1337
2396
 
1338
 
        
 
2397
 
 
2398
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
 
2399
 
 
2400
    def test_ok(self):
 
2401
        repo, client = self.setup_fake_client_and_repository('quack')
 
2402
        client.add_expected_call(
 
2403
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2404
            'success', ('ok', 'rev-five'))
 
2405
        result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
 
2406
        self.assertEqual((True, 'rev-five'), result)
 
2407
        self.assertFinished(client)
 
2408
 
 
2409
    def test_history_incomplete(self):
 
2410
        repo, client = self.setup_fake_client_and_repository('quack')
 
2411
        client.add_expected_call(
 
2412
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2413
            'success', ('history-incomplete', 10, 'rev-ten'))
 
2414
        result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
 
2415
        self.assertEqual((False, (10, 'rev-ten')), result)
 
2416
        self.assertFinished(client)
 
2417
 
 
2418
    def test_history_incomplete_with_fallback(self):
 
2419
        """A 'history-incomplete' response causes the fallback repository to be
 
2420
        queried too, if one is set.
 
2421
        """
 
2422
        # Make a repo with a fallback repo, both using a FakeClient.
 
2423
        format = remote.response_tuple_to_repo_format(
 
2424
            ('yes', 'no', 'yes', self.get_repo_format().network_name()))
 
2425
        repo, client = self.setup_fake_client_and_repository('quack')
 
2426
        repo._format = format
 
2427
        fallback_repo, ignored = self.setup_fake_client_and_repository(
 
2428
            'fallback')
 
2429
        fallback_repo._client = client
 
2430
        fallback_repo._format = format
 
2431
        repo.add_fallback_repository(fallback_repo)
 
2432
        # First the client should ask the primary repo
 
2433
        client.add_expected_call(
 
2434
            'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
 
2435
            'success', ('history-incomplete', 2, 'rev-two'))
 
2436
        # Then it should ask the fallback, using revno/revid from the
 
2437
        # history-incomplete response as the known revno/revid.
 
2438
        client.add_expected_call(
 
2439
            'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
 
2440
            'success', ('ok', 'rev-one'))
 
2441
        result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
 
2442
        self.assertEqual((True, 'rev-one'), result)
 
2443
        self.assertFinished(client)
 
2444
 
 
2445
    def test_nosuchrevision(self):
 
2446
        # 'nosuchrevision' is returned when the known-revid is not found in the
 
2447
        # remote repo.  The client translates that response to NoSuchRevision.
 
2448
        repo, client = self.setup_fake_client_and_repository('quack')
 
2449
        client.add_expected_call(
 
2450
            'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
 
2451
            'error', ('nosuchrevision', 'rev-foo'))
 
2452
        self.assertRaises(
 
2453
            errors.NoSuchRevision,
 
2454
            repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
 
2455
        self.assertFinished(client)
 
2456
 
 
2457
    def test_branch_fallback_locking(self):
 
2458
        """RemoteBranch.get_rev_id takes a read lock, and tries to call the
 
2459
        get_rev_id_for_revno verb.  If the verb is unknown the VFS fallback
 
2460
        will be invoked, which will fail if the repo is unlocked.
 
2461
        """
 
2462
        self.setup_smart_server_with_call_log()
 
2463
        tree = self.make_branch_and_memory_tree('.')
 
2464
        tree.lock_write()
 
2465
        tree.add('')
 
2466
        rev1 = tree.commit('First')
 
2467
        rev2 = tree.commit('Second')
 
2468
        tree.unlock()
 
2469
        branch = tree.branch
 
2470
        self.assertFalse(branch.is_locked())
 
2471
        self.reset_smart_call_log()
 
2472
        verb = 'Repository.get_rev_id_for_revno'
 
2473
        self.disable_verb(verb)
 
2474
        self.assertEqual(rev1, branch.get_rev_id(1))
 
2475
        self.assertLength(1, [call for call in self.hpss_calls if
 
2476
                              call.call.method == verb])
 
2477
 
 
2478
 
1339
2479
class TestRepositoryIsShared(TestRemoteRepository):
1340
2480
 
1341
2481
    def test_is_shared(self):
1367
2507
        transport_path = 'quack'
1368
2508
        repo, client = self.setup_fake_client_and_repository(transport_path)
1369
2509
        client.add_success_response('ok', 'a token')
1370
 
        result = repo.lock_write()
 
2510
        token = repo.lock_write().repository_token
1371
2511
        self.assertEqual(
1372
2512
            [('call', 'Repository.lock_write', ('quack/', ''))],
1373
2513
            client._calls)
1374
 
        self.assertEqual('a token', result)
 
2514
        self.assertEqual('a token', token)
1375
2515
 
1376
2516
    def test_lock_write_already_locked(self):
1377
2517
        transport_path = 'quack'
1392
2532
            client._calls)
1393
2533
 
1394
2534
 
 
2535
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
 
2536
 
 
2537
    def test_backwards_compat(self):
 
2538
        self.setup_smart_server_with_call_log()
 
2539
        repo = self.make_repository('.')
 
2540
        self.reset_smart_call_log()
 
2541
        verb = 'Repository.set_make_working_trees'
 
2542
        self.disable_verb(verb)
 
2543
        repo.set_make_working_trees(True)
 
2544
        call_count = len([call for call in self.hpss_calls if
 
2545
            call.call.method == verb])
 
2546
        self.assertEqual(1, call_count)
 
2547
 
 
2548
    def test_current(self):
 
2549
        transport_path = 'quack'
 
2550
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2551
        client.add_expected_call(
 
2552
            'Repository.set_make_working_trees', ('quack/', 'True'),
 
2553
            'success', ('ok',))
 
2554
        client.add_expected_call(
 
2555
            'Repository.set_make_working_trees', ('quack/', 'False'),
 
2556
            'success', ('ok',))
 
2557
        repo.set_make_working_trees(True)
 
2558
        repo.set_make_working_trees(False)
 
2559
 
 
2560
 
1395
2561
class TestRepositoryUnlock(TestRemoteRepository):
1396
2562
 
1397
2563
    def test_unlock(self):
1430
2596
        self.assertEqual([], client._calls)
1431
2597
 
1432
2598
 
 
2599
class TestRepositoryInsertStreamBase(TestRemoteRepository):
 
2600
    """Base class for Repository.insert_stream and .insert_stream_1.19
 
2601
    tests.
 
2602
    """
 
2603
    
 
2604
    def checkInsertEmptyStream(self, repo, client):
 
2605
        """Insert an empty stream, checking the result.
 
2606
 
 
2607
        This checks that there are no resume_tokens or missing_keys, and that
 
2608
        the client is finished.
 
2609
        """
 
2610
        sink = repo._get_sink()
 
2611
        fmt = repository.format_registry.get_default()
 
2612
        resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
 
2613
        self.assertEqual([], resume_tokens)
 
2614
        self.assertEqual(set(), missing_keys)
 
2615
        self.assertFinished(client)
 
2616
 
 
2617
 
 
2618
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
 
2619
    """Tests for using Repository.insert_stream verb when the _1.19 variant is
 
2620
    not available.
 
2621
 
 
2622
    This test case is very similar to TestRepositoryInsertStream_1_19.
 
2623
    """
 
2624
 
 
2625
    def setUp(self):
 
2626
        TestRemoteRepository.setUp(self)
 
2627
        self.disable_verb('Repository.insert_stream_1.19')
 
2628
 
 
2629
    def test_unlocked_repo(self):
 
2630
        transport_path = 'quack'
 
2631
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2632
        client.add_expected_call(
 
2633
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2634
            'unknown', ('Repository.insert_stream_1.19',))
 
2635
        client.add_expected_call(
 
2636
            'Repository.insert_stream', ('quack/', ''),
 
2637
            'success', ('ok',))
 
2638
        client.add_expected_call(
 
2639
            'Repository.insert_stream', ('quack/', ''),
 
2640
            'success', ('ok',))
 
2641
        self.checkInsertEmptyStream(repo, client)
 
2642
 
 
2643
    def test_locked_repo_with_no_lock_token(self):
 
2644
        transport_path = 'quack'
 
2645
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2646
        client.add_expected_call(
 
2647
            'Repository.lock_write', ('quack/', ''),
 
2648
            'success', ('ok', ''))
 
2649
        client.add_expected_call(
 
2650
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2651
            'unknown', ('Repository.insert_stream_1.19',))
 
2652
        client.add_expected_call(
 
2653
            'Repository.insert_stream', ('quack/', ''),
 
2654
            'success', ('ok',))
 
2655
        client.add_expected_call(
 
2656
            'Repository.insert_stream', ('quack/', ''),
 
2657
            'success', ('ok',))
 
2658
        repo.lock_write()
 
2659
        self.checkInsertEmptyStream(repo, client)
 
2660
 
 
2661
    def test_locked_repo_with_lock_token(self):
 
2662
        transport_path = 'quack'
 
2663
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2664
        client.add_expected_call(
 
2665
            'Repository.lock_write', ('quack/', ''),
 
2666
            'success', ('ok', 'a token'))
 
2667
        client.add_expected_call(
 
2668
            'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
 
2669
            'unknown', ('Repository.insert_stream_1.19',))
 
2670
        client.add_expected_call(
 
2671
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
 
2672
            'success', ('ok',))
 
2673
        client.add_expected_call(
 
2674
            'Repository.insert_stream_locked', ('quack/', '', 'a token'),
 
2675
            'success', ('ok',))
 
2676
        repo.lock_write()
 
2677
        self.checkInsertEmptyStream(repo, client)
 
2678
 
 
2679
    def test_stream_with_inventory_deltas(self):
 
2680
        """'inventory-deltas' substreams cannot be sent to the
 
2681
        Repository.insert_stream verb, because not all servers that implement
 
2682
        that verb will accept them.  So when one is encountered the RemoteSink
 
2683
        immediately stops using that verb and falls back to VFS insert_stream.
 
2684
        """
 
2685
        transport_path = 'quack'
 
2686
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2687
        client.add_expected_call(
 
2688
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2689
            'unknown', ('Repository.insert_stream_1.19',))
 
2690
        client.add_expected_call(
 
2691
            'Repository.insert_stream', ('quack/', ''),
 
2692
            'success', ('ok',))
 
2693
        client.add_expected_call(
 
2694
            'Repository.insert_stream', ('quack/', ''),
 
2695
            'success', ('ok',))
 
2696
        # Create a fake real repository for insert_stream to fall back on, so
 
2697
        # that we can directly see the records the RemoteSink passes to the
 
2698
        # real sink.
 
2699
        class FakeRealSink:
 
2700
            def __init__(self):
 
2701
                self.records = []
 
2702
            def insert_stream(self, stream, src_format, resume_tokens):
 
2703
                for substream_kind, substream in stream:
 
2704
                    self.records.append(
 
2705
                        (substream_kind, [record.key for record in substream]))
 
2706
                return ['fake tokens'], ['fake missing keys']
 
2707
        fake_real_sink = FakeRealSink()
 
2708
        class FakeRealRepository:
 
2709
            def _get_sink(self):
 
2710
                return fake_real_sink
 
2711
            def is_in_write_group(self):
 
2712
                return False
 
2713
            def refresh_data(self):
 
2714
                return True
 
2715
        repo._real_repository = FakeRealRepository()
 
2716
        sink = repo._get_sink()
 
2717
        fmt = repository.format_registry.get_default()
 
2718
        stream = self.make_stream_with_inv_deltas(fmt)
 
2719
        resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
 
2720
        # Every record from the first inventory delta should have been sent to
 
2721
        # the VFS sink.
 
2722
        expected_records = [
 
2723
            ('inventory-deltas', [('rev2',), ('rev3',)]),
 
2724
            ('texts', [('some-rev', 'some-file')])]
 
2725
        self.assertEqual(expected_records, fake_real_sink.records)
 
2726
        # The return values from the real sink's insert_stream are propagated
 
2727
        # back to the original caller.
 
2728
        self.assertEqual(['fake tokens'], resume_tokens)
 
2729
        self.assertEqual(['fake missing keys'], missing_keys)
 
2730
        self.assertFinished(client)
 
2731
 
 
2732
    def make_stream_with_inv_deltas(self, fmt):
 
2733
        """Make a simple stream with an inventory delta followed by more
 
2734
        records and more substreams to test that all records and substreams
 
2735
        from that point on are used.
 
2736
 
 
2737
        This sends, in order:
 
2738
           * inventories substream: rev1, rev2, rev3.  rev2 and rev3 are
 
2739
             inventory-deltas.
 
2740
           * texts substream: (some-rev, some-file)
 
2741
        """
 
2742
        # Define a stream using generators so that it isn't rewindable.
 
2743
        inv = inventory.Inventory(revision_id='rev1')
 
2744
        inv.root.revision = 'rev1'
 
2745
        def stream_with_inv_delta():
 
2746
            yield ('inventories', inventories_substream())
 
2747
            yield ('inventory-deltas', inventory_delta_substream())
 
2748
            yield ('texts', [
 
2749
                versionedfile.FulltextContentFactory(
 
2750
                    ('some-rev', 'some-file'), (), None, 'content')])
 
2751
        def inventories_substream():
 
2752
            # An empty inventory fulltext.  This will be streamed normally.
 
2753
            text = fmt._serializer.write_inventory_to_string(inv)
 
2754
            yield versionedfile.FulltextContentFactory(
 
2755
                ('rev1',), (), None, text)
 
2756
        def inventory_delta_substream():
 
2757
            # An inventory delta.  This can't be streamed via this verb, so it
 
2758
            # will trigger a fallback to VFS insert_stream.
 
2759
            entry = inv.make_entry(
 
2760
                'directory', 'newdir', inv.root.file_id, 'newdir-id')
 
2761
            entry.revision = 'ghost'
 
2762
            delta = [(None, 'newdir', 'newdir-id', entry)]
 
2763
            serializer = inventory_delta.InventoryDeltaSerializer(
 
2764
                versioned_root=True, tree_references=False)
 
2765
            lines = serializer.delta_to_lines('rev1', 'rev2', delta)
 
2766
            yield versionedfile.ChunkedContentFactory(
 
2767
                ('rev2',), (('rev1',)), None, lines)
 
2768
            # Another delta.
 
2769
            lines = serializer.delta_to_lines('rev1', 'rev3', delta)
 
2770
            yield versionedfile.ChunkedContentFactory(
 
2771
                ('rev3',), (('rev1',)), None, lines)
 
2772
        return stream_with_inv_delta()
 
2773
 
 
2774
 
 
2775
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
 
2776
 
 
2777
    def test_unlocked_repo(self):
 
2778
        transport_path = 'quack'
 
2779
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2780
        client.add_expected_call(
 
2781
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2782
            'success', ('ok',))
 
2783
        client.add_expected_call(
 
2784
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2785
            'success', ('ok',))
 
2786
        self.checkInsertEmptyStream(repo, client)
 
2787
 
 
2788
    def test_locked_repo_with_no_lock_token(self):
 
2789
        transport_path = 'quack'
 
2790
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2791
        client.add_expected_call(
 
2792
            'Repository.lock_write', ('quack/', ''),
 
2793
            'success', ('ok', ''))
 
2794
        client.add_expected_call(
 
2795
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2796
            'success', ('ok',))
 
2797
        client.add_expected_call(
 
2798
            'Repository.insert_stream_1.19', ('quack/', ''),
 
2799
            'success', ('ok',))
 
2800
        repo.lock_write()
 
2801
        self.checkInsertEmptyStream(repo, client)
 
2802
 
 
2803
    def test_locked_repo_with_lock_token(self):
 
2804
        transport_path = 'quack'
 
2805
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2806
        client.add_expected_call(
 
2807
            'Repository.lock_write', ('quack/', ''),
 
2808
            'success', ('ok', 'a token'))
 
2809
        client.add_expected_call(
 
2810
            'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
 
2811
            'success', ('ok',))
 
2812
        client.add_expected_call(
 
2813
            'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
 
2814
            'success', ('ok',))
 
2815
        repo.lock_write()
 
2816
        self.checkInsertEmptyStream(repo, client)
 
2817
 
 
2818
 
1433
2819
class TestRepositoryTarball(TestRemoteRepository):
1434
2820
 
1435
2821
    # This is a canned tarball reponse we can validate against
1469
2855
    """RemoteRepository.copy_content_into optimizations"""
1470
2856
 
1471
2857
    def test_copy_content_remote_to_local(self):
1472
 
        self.transport_server = server.SmartTCPServer_for_testing
 
2858
        self.transport_server = test_server.SmartTCPServer_for_testing
1473
2859
        src_repo = self.make_repository('repo1')
1474
2860
        src_repo = repository.Repository.open(self.get_url('repo1'))
1475
2861
        # At the moment the tarball-based copy_content_into can't write back
1487
2873
class _StubRealPackRepository(object):
1488
2874
 
1489
2875
    def __init__(self, calls):
 
2876
        self.calls = calls
1490
2877
        self._pack_collection = _StubPackCollection(calls)
1491
2878
 
 
2879
    def is_in_write_group(self):
 
2880
        return False
 
2881
 
 
2882
    def refresh_data(self):
 
2883
        self.calls.append(('pack collection reload_pack_names',))
 
2884
 
1492
2885
 
1493
2886
class _StubPackCollection(object):
1494
2887
 
1498
2891
    def autopack(self):
1499
2892
        self.calls.append(('pack collection autopack',))
1500
2893
 
1501
 
    def reload_pack_names(self):
1502
 
        self.calls.append(('pack collection reload_pack_names',))
1503
2894
 
1504
 
    
1505
2895
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
1506
2896
    """Tests for RemoteRepository.autopack implementation."""
1507
2897
 
1514
2904
        client.add_expected_call(
1515
2905
            'PackRepository.autopack', ('quack/',), 'success', ('ok',))
1516
2906
        repo.autopack()
1517
 
        client.finished_test()
 
2907
        self.assertFinished(client)
1518
2908
 
1519
2909
    def test_ok_with_real_repo(self):
1520
2910
        """When the server returns 'ok' and there is a _real_repository, then
1531
2921
            [('call', 'PackRepository.autopack', ('quack/',)),
1532
2922
             ('pack collection reload_pack_names',)],
1533
2923
            client._calls)
1534
 
        
 
2924
 
1535
2925
    def test_backwards_compatibility(self):
1536
2926
        """If the server does not recognise the PackRepository.autopack verb,
1537
2927
        fallback to the real_repository's implementation.
1550
2940
             ('pack collection autopack',)],
1551
2941
            client._calls)
1552
2942
 
 
2943
    def test_oom_error_reporting(self):
 
2944
        """An out-of-memory condition on the server is reported clearly"""
 
2945
        transport_path = 'quack'
 
2946
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
2947
        client.add_expected_call(
 
2948
            'PackRepository.autopack', ('quack/',),
 
2949
            'error', ('MemoryError',))
 
2950
        err = self.assertRaises(errors.BzrError, repo.autopack)
 
2951
        self.assertContainsRe(str(err), "^remote server out of mem")
 
2952
 
1553
2953
 
1554
2954
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
1555
2955
    """Base class for unit tests for bzrlib.remote._translate_error."""
1587
2987
 
1588
2988
class TestErrorTranslationSuccess(TestErrorTranslationBase):
1589
2989
    """Unit tests for bzrlib.remote._translate_error.
1590
 
    
 
2990
 
1591
2991
    Given an ErrorFromSmartServer (which has an error tuple from a smart
1592
2992
    server) and some context, _translate_error raises more specific errors from
1593
2993
    bzrlib.errors.
1619
3019
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
1620
3020
        self.assertEqual(expected_error, translated_error)
1621
3021
 
 
3022
    def test_nobranch_one_arg(self):
 
3023
        bzrdir = self.make_bzrdir('')
 
3024
        translated_error = self.translateTuple(
 
3025
            ('nobranch', 'extra detail'), bzrdir=bzrdir)
 
3026
        expected_error = errors.NotBranchError(
 
3027
            path=bzrdir.root_transport.base,
 
3028
            detail='extra detail')
 
3029
        self.assertEqual(expected_error, translated_error)
 
3030
 
 
3031
    def test_norepository(self):
 
3032
        bzrdir = self.make_bzrdir('')
 
3033
        translated_error = self.translateTuple(('norepository',),
 
3034
            bzrdir=bzrdir)
 
3035
        expected_error = errors.NoRepositoryPresent(bzrdir)
 
3036
        self.assertEqual(expected_error, translated_error)
 
3037
 
1622
3038
    def test_LockContention(self):
1623
3039
        translated_error = self.translateTuple(('LockContention',))
1624
3040
        expected_error = errors.LockContention('(remote lock)')
1652
3068
        expected_error = errors.DivergedBranches(branch, other_branch)
1653
3069
        self.assertEqual(expected_error, translated_error)
1654
3070
 
 
3071
    def test_NotStacked(self):
 
3072
        branch = self.make_branch('')
 
3073
        translated_error = self.translateTuple(('NotStacked',), branch=branch)
 
3074
        expected_error = errors.NotStacked(branch)
 
3075
        self.assertEqual(expected_error, translated_error)
 
3076
 
1655
3077
    def test_ReadError_no_args(self):
1656
3078
        path = 'a path'
1657
3079
        translated_error = self.translateTuple(('ReadError',), path=path)
1664
3086
        expected_error = errors.ReadError(path)
1665
3087
        self.assertEqual(expected_error, translated_error)
1666
3088
 
 
3089
    def test_IncompatibleRepositories(self):
 
3090
        translated_error = self.translateTuple(('IncompatibleRepositories',
 
3091
            "repo1", "repo2", "details here"))
 
3092
        expected_error = errors.IncompatibleRepositories("repo1", "repo2",
 
3093
            "details here")
 
3094
        self.assertEqual(expected_error, translated_error)
 
3095
 
1667
3096
    def test_PermissionDenied_no_args(self):
1668
3097
        path = 'a path'
1669
 
        translated_error = self.translateTuple(('PermissionDenied',), path=path)
 
3098
        translated_error = self.translateTuple(('PermissionDenied',),
 
3099
            path=path)
1670
3100
        expected_error = errors.PermissionDenied(path)
1671
3101
        self.assertEqual(expected_error, translated_error)
1672
3102
 
1695
3125
        expected_error = errors.PermissionDenied(path, extra)
1696
3126
        self.assertEqual(expected_error, translated_error)
1697
3127
 
 
3128
    # GZ 2011-03-02: TODO test for PermissionDenied with non-ascii 'extra'
 
3129
 
 
3130
    def test_NoSuchFile_context_path(self):
 
3131
        local_path = "local path"
 
3132
        translated_error = self.translateTuple(('ReadError', "remote path"),
 
3133
            path=local_path)
 
3134
        expected_error = errors.ReadError(local_path)
 
3135
        self.assertEqual(expected_error, translated_error)
 
3136
 
 
3137
    def test_NoSuchFile_without_context(self):
 
3138
        remote_path = "remote path"
 
3139
        translated_error = self.translateTuple(('ReadError', remote_path))
 
3140
        expected_error = errors.ReadError(remote_path)
 
3141
        self.assertEqual(expected_error, translated_error)
 
3142
 
 
3143
    def test_ReadOnlyError(self):
 
3144
        translated_error = self.translateTuple(('ReadOnlyError',))
 
3145
        expected_error = errors.TransportNotPossible("readonly transport")
 
3146
        self.assertEqual(expected_error, translated_error)
 
3147
 
 
3148
    def test_MemoryError(self):
 
3149
        translated_error = self.translateTuple(('MemoryError',))
 
3150
        self.assertStartsWith(str(translated_error),
 
3151
            "remote server out of memory")
 
3152
 
 
3153
    def test_generic_IndexError_no_classname(self):
 
3154
        err = errors.ErrorFromSmartServer(('error', "list index out of range"))
 
3155
        translated_error = self.translateErrorFromSmartServer(err)
 
3156
        expected_error = errors.UnknownErrorFromSmartServer(err)
 
3157
        self.assertEqual(expected_error, translated_error)
 
3158
 
 
3159
    # GZ 2011-03-02: TODO test generic non-ascii error string
 
3160
 
 
3161
    def test_generic_KeyError(self):
 
3162
        err = errors.ErrorFromSmartServer(('error', 'KeyError', "1"))
 
3163
        translated_error = self.translateErrorFromSmartServer(err)
 
3164
        expected_error = errors.UnknownErrorFromSmartServer(err)
 
3165
        self.assertEqual(expected_error, translated_error)
 
3166
 
1698
3167
 
1699
3168
class TestErrorTranslationRobustness(TestErrorTranslationBase):
1700
3169
    """Unit tests for bzrlib.remote._translate_error's robustness.
1701
 
    
 
3170
 
1702
3171
    TestErrorTranslationSuccess is for cases where _translate_error can
1703
3172
    translate successfully.  This class about how _translate_err behaves when
1704
3173
    it fails to translate: it re-raises the original error.
1730
3199
        # In addition to re-raising ErrorFromSmartServer, some debug info has
1731
3200
        # been muttered to the log file for developer to look at.
1732
3201
        self.assertContainsRe(
1733
 
            self._get_log(keep_log_file=True),
 
3202
            self.get_log(),
1734
3203
            "Missing key 'branch' in context")
1735
 
        
 
3204
 
1736
3205
    def test_path_missing(self):
1737
3206
        """Some translations (PermissionDenied, ReadError) can determine the
1738
3207
        'path' variable from either the wire or the local context.  If neither
1744
3213
        self.assertEqual(server_error, translated_error)
1745
3214
        # In addition to re-raising ErrorFromSmartServer, some debug info has
1746
3215
        # been muttered to the log file for developer to look at.
1747
 
        self.assertContainsRe(
1748
 
            self._get_log(keep_log_file=True), "Missing key 'path' in context")
 
3216
        self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
1749
3217
 
1750
3218
 
1751
3219
class TestStacking(tests.TestCaseWithTransport):
1752
3220
    """Tests for operations on stacked remote repositories.
1753
 
    
 
3221
 
1754
3222
    The underlying format type must support stacking.
1755
3223
    """
1756
3224
 
1760
3228
        # revision, then open it over hpss - we should be able to see that
1761
3229
        # revision.
1762
3230
        base_transport = self.get_transport()
1763
 
        base_builder = self.make_branch_builder('base', format='1.6')
 
3231
        base_builder = self.make_branch_builder('base', format='1.9')
1764
3232
        base_builder.start_series()
1765
3233
        base_revid = base_builder.build_snapshot('rev-id', None,
1766
3234
            [('add', ('', None, 'directory', None))],
1767
3235
            'message')
1768
3236
        base_builder.finish_series()
1769
 
        stacked_branch = self.make_branch('stacked', format='1.6')
 
3237
        stacked_branch = self.make_branch('stacked', format='1.9')
1770
3238
        stacked_branch.set_stacked_on_url('../base')
1771
3239
        # start a server looking at this
1772
 
        smart_server = server.SmartTCPServer_for_testing()
1773
 
        smart_server.setUp()
1774
 
        self.addCleanup(smart_server.tearDown)
 
3240
        smart_server = test_server.SmartTCPServer_for_testing()
 
3241
        self.start_server(smart_server)
1775
3242
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
1776
3243
        # can get its branch and repository
1777
3244
        remote_branch = remote_bzrdir.open_branch()
1780
3247
        try:
1781
3248
            # it should have an appropriate fallback repository, which should also
1782
3249
            # be a RemoteRepository
1783
 
            self.assertEquals(len(remote_repo._fallback_repositories), 1)
 
3250
            self.assertLength(1, remote_repo._fallback_repositories)
1784
3251
            self.assertIsInstance(remote_repo._fallback_repositories[0],
1785
3252
                RemoteRepository)
1786
3253
            # and it has the revision committed to the underlying repository;
1793
3260
            remote_repo.unlock()
1794
3261
 
1795
3262
    def prepare_stacked_remote_branch(self):
1796
 
        smart_server = server.SmartTCPServer_for_testing()
1797
 
        smart_server.setUp()
1798
 
        self.addCleanup(smart_server.tearDown)
1799
 
        tree1 = self.make_branch_and_tree('tree1')
 
3263
        """Get stacked_upon and stacked branches with content in each."""
 
3264
        self.setup_smart_server_with_call_log()
 
3265
        tree1 = self.make_branch_and_tree('tree1', format='1.9')
1800
3266
        tree1.commit('rev1', rev_id='rev1')
1801
 
        tree2 = self.make_branch_and_tree('tree2', format='1.6')
1802
 
        tree2.branch.set_stacked_on_url(tree1.branch.base)
1803
 
        branch2 = Branch.open(smart_server.get_url() + '/tree2')
 
3267
        tree2 = tree1.branch.bzrdir.sprout('tree2', stacked=True
 
3268
            ).open_workingtree()
 
3269
        local_tree = tree2.branch.create_checkout('local')
 
3270
        local_tree.commit('local changes make me feel good.')
 
3271
        branch2 = Branch.open(self.get_url('tree2'))
1804
3272
        branch2.lock_read()
1805
3273
        self.addCleanup(branch2.unlock)
1806
 
        return branch2
 
3274
        return tree1.branch, branch2
1807
3275
 
1808
3276
    def test_stacked_get_parent_map(self):
1809
3277
        # the public implementation of get_parent_map obeys stacking
1810
 
        branch = self.prepare_stacked_remote_branch()
 
3278
        _, branch = self.prepare_stacked_remote_branch()
1811
3279
        repo = branch.repository
1812
3280
        self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
1813
3281
 
1814
3282
    def test_unstacked_get_parent_map(self):
1815
3283
        # _unstacked_provider.get_parent_map ignores stacking
1816
 
        branch = self.prepare_stacked_remote_branch()
 
3284
        _, branch = self.prepare_stacked_remote_branch()
1817
3285
        provider = branch.repository._unstacked_provider
1818
3286
        self.assertEqual([], provider.get_parent_map(['rev1']).keys())
1819
3287
 
 
3288
    def fetch_stream_to_rev_order(self, stream):
 
3289
        result = []
 
3290
        for kind, substream in stream:
 
3291
            if not kind == 'revisions':
 
3292
                list(substream)
 
3293
            else:
 
3294
                for content in substream:
 
3295
                    result.append(content.key[-1])
 
3296
        return result
 
3297
 
 
3298
    def get_ordered_revs(self, format, order, branch_factory=None):
 
3299
        """Get a list of the revisions in a stream to format format.
 
3300
 
 
3301
        :param format: The format of the target.
 
3302
        :param order: the order that target should have requested.
 
3303
        :param branch_factory: A callable to create a trunk and stacked branch
 
3304
            to fetch from. If none, self.prepare_stacked_remote_branch is used.
 
3305
        :result: The revision ids in the stream, in the order seen,
 
3306
            the topological order of revisions in the source.
 
3307
        """
 
3308
        unordered_format = bzrdir.format_registry.get(format)()
 
3309
        target_repository_format = unordered_format.repository_format
 
3310
        # Cross check
 
3311
        self.assertEqual(order, target_repository_format._fetch_order)
 
3312
        if branch_factory is None:
 
3313
            branch_factory = self.prepare_stacked_remote_branch
 
3314
        _, stacked = branch_factory()
 
3315
        source = stacked.repository._get_source(target_repository_format)
 
3316
        tip = stacked.last_revision()
 
3317
        stacked.repository._ensure_real()
 
3318
        graph = stacked.repository.get_graph()
 
3319
        revs = [r for (r,ps) in graph.iter_ancestry([tip])
 
3320
                if r != NULL_REVISION]
 
3321
        revs.reverse()
 
3322
        search = _mod_graph.PendingAncestryResult([tip], stacked.repository)
 
3323
        self.reset_smart_call_log()
 
3324
        stream = source.get_stream(search)
 
3325
        # We trust that if a revision is in the stream the rest of the new
 
3326
        # content for it is too, as per our main fetch tests; here we are
 
3327
        # checking that the revisions are actually included at all, and their
 
3328
        # order.
 
3329
        return self.fetch_stream_to_rev_order(stream), revs
 
3330
 
 
3331
    def test_stacked_get_stream_unordered(self):
 
3332
        # Repository._get_source.get_stream() from a stacked repository with
 
3333
        # unordered yields the full data from both stacked and stacked upon
 
3334
        # sources.
 
3335
        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered')
 
3336
        self.assertEqual(set(expected_revs), set(rev_ord))
 
3337
        # Getting unordered results should have made a streaming data request
 
3338
        # from the server, then one from the backing branch.
 
3339
        self.assertLength(2, self.hpss_calls)
 
3340
 
 
3341
    def test_stacked_on_stacked_get_stream_unordered(self):
 
3342
        # Repository._get_source.get_stream() from a stacked repository which
 
3343
        # is itself stacked yields the full data from all three sources.
 
3344
        def make_stacked_stacked():
 
3345
            _, stacked = self.prepare_stacked_remote_branch()
 
3346
            tree = stacked.bzrdir.sprout('tree3', stacked=True
 
3347
                ).open_workingtree()
 
3348
            local_tree = tree.branch.create_checkout('local-tree3')
 
3349
            local_tree.commit('more local changes are better')
 
3350
            branch = Branch.open(self.get_url('tree3'))
 
3351
            branch.lock_read()
 
3352
            self.addCleanup(branch.unlock)
 
3353
            return None, branch
 
3354
        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
 
3355
            branch_factory=make_stacked_stacked)
 
3356
        self.assertEqual(set(expected_revs), set(rev_ord))
 
3357
        # Getting unordered results should have made a streaming data request
 
3358
        # from the server, and one from each backing repo
 
3359
        self.assertLength(3, self.hpss_calls)
 
3360
 
 
3361
    def test_stacked_get_stream_topological(self):
 
3362
        # Repository._get_source.get_stream() from a stacked repository with
 
3363
        # topological sorting yields the full data from both stacked and
 
3364
        # stacked upon sources in topological order.
 
3365
        rev_ord, expected_revs = self.get_ordered_revs('knit', 'topological')
 
3366
        self.assertEqual(expected_revs, rev_ord)
 
3367
        # Getting topological sort requires VFS calls still - one of which is
 
3368
        # pushing up from the bound branch.
 
3369
        self.assertLength(14, self.hpss_calls)
 
3370
 
 
3371
    def test_stacked_get_stream_groupcompress(self):
 
3372
        # Repository._get_source.get_stream() from a stacked repository with
 
3373
        # groupcompress sorting yields the full data from both stacked and
 
3374
        # stacked upon sources in groupcompress order.
 
3375
        raise tests.TestSkipped('No groupcompress ordered format available')
 
3376
        rev_ord, expected_revs = self.get_ordered_revs('dev5', 'groupcompress')
 
3377
        self.assertEqual(expected_revs, reversed(rev_ord))
 
3378
        # Getting unordered results should have made a streaming data request
 
3379
        # from the backing branch, and one from the stacked on branch.
 
3380
        self.assertLength(2, self.hpss_calls)
 
3381
 
 
3382
    def test_stacked_pull_more_than_stacking_has_bug_360791(self):
 
3383
        # When pulling some fixed amount of content that is more than the
 
3384
        # source has (because some is coming from a fallback branch, no error
 
3385
        # should be received. This was reported as bug 360791.
 
3386
        # Need three branches: a trunk, a stacked branch, and a preexisting
 
3387
        # branch pulling content from stacked and trunk.
 
3388
        self.setup_smart_server_with_call_log()
 
3389
        trunk = self.make_branch_and_tree('trunk', format="1.9-rich-root")
 
3390
        r1 = trunk.commit('start')
 
3391
        stacked_branch = trunk.branch.create_clone_on_transport(
 
3392
            self.get_transport('stacked'), stacked_on=trunk.branch.base)
 
3393
        local = self.make_branch('local', format='1.9-rich-root')
 
3394
        local.repository.fetch(stacked_branch.repository,
 
3395
            stacked_branch.last_revision())
 
3396
 
1820
3397
 
1821
3398
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
1822
3399
 
1824
3401
        super(TestRemoteBranchEffort, self).setUp()
1825
3402
        # Create a smart server that publishes whatever the backing VFS server
1826
3403
        # does.
1827
 
        self.smart_server = server.SmartTCPServer_for_testing()
1828
 
        self.smart_server.setUp(self.get_server())
1829
 
        self.addCleanup(self.smart_server.tearDown)
 
3404
        self.smart_server = test_server.SmartTCPServer_for_testing()
 
3405
        self.start_server(self.smart_server, self.get_server())
1830
3406
        # Log all HPSS calls into self.hpss_calls.
1831
3407
        _SmartClient.hooks.install_named_hook(
1832
3408
            'call', self.capture_hpss_call, None)
1837
3413
 
1838
3414
    def test_copy_content_into_avoids_revision_history(self):
1839
3415
        local = self.make_branch('local')
1840
 
        remote_backing_tree = self.make_branch_and_tree('remote')
1841
 
        remote_backing_tree.commit("Commit.")
 
3416
        builder = self.make_branch_builder('remote')
 
3417
        builder.build_commit(message="Commit.")
1842
3418
        remote_branch_url = self.smart_server.get_url() + 'remote'
1843
3419
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
1844
3420
        local.repository.fetch(remote_branch.repository)
1845
3421
        self.hpss_calls = []
1846
3422
        remote_branch.copy_content_into(local)
1847
3423
        self.assertFalse('Branch.revision_history' in self.hpss_calls)
 
3424
 
 
3425
    def test_fetch_everything_needs_just_one_call(self):
 
3426
        local = self.make_branch('local')
 
3427
        builder = self.make_branch_builder('remote')
 
3428
        builder.build_commit(message="Commit.")
 
3429
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
3430
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
3431
        self.hpss_calls = []
 
3432
        local.repository.fetch(
 
3433
            remote_branch.repository,
 
3434
            fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
 
3435
        self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
 
3436
 
 
3437
    def override_verb(self, verb_name, verb):
 
3438
        request_handlers = request.request_handlers
 
3439
        orig_verb = request_handlers.get(verb_name)
 
3440
        request_handlers.register(verb_name, verb, override_existing=True)
 
3441
        self.addCleanup(request_handlers.register, verb_name, orig_verb,
 
3442
                override_existing=True)
 
3443
 
 
3444
    def test_fetch_everything_backwards_compat(self):
 
3445
        """Can fetch with EverythingResult even with pre 2.4 servers.
 
3446
        
 
3447
        Pre-2.4 do not support 'everything' searches with the
 
3448
        Repository.get_stream_1.19 verb.
 
3449
        """
 
3450
        verb_log = []
 
3451
        class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
 
3452
            """A version of the Repository.get_stream_1.19 verb patched to
 
3453
            reject 'everything' searches the way 2.3 and earlier do.
 
3454
            """
 
3455
            def recreate_search(self, repository, search_bytes,
 
3456
                                discard_excess=False):
 
3457
                verb_log.append(search_bytes.split('\n', 1)[0])
 
3458
                if search_bytes == 'everything':
 
3459
                    return (None,
 
3460
                            request.FailedSmartServerResponse(('BadSearch',)))
 
3461
                return super(OldGetStreamVerb,
 
3462
                        self).recreate_search(repository, search_bytes,
 
3463
                            discard_excess=discard_excess)
 
3464
        self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
 
3465
        local = self.make_branch('local')
 
3466
        builder = self.make_branch_builder('remote')
 
3467
        builder.build_commit(message="Commit.")
 
3468
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
3469
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
3470
        self.hpss_calls = []
 
3471
        local.repository.fetch(
 
3472
            remote_branch.repository,
 
3473
            fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
 
3474
        # make sure the overridden verb was used
 
3475
        self.assertLength(1, verb_log)
 
3476
        # more than one HPSS call is needed, but because it's a VFS callback
 
3477
        # its hard to predict exactly how many.
 
3478
        self.assertTrue(len(self.hpss_calls) > 1)
 
3479
 
 
3480
 
 
3481
class TestUpdateBoundBranchWithModifiedBoundLocation(
 
3482
    tests.TestCaseWithTransport):
 
3483
    """Ensure correct handling of bound_location modifications.
 
3484
 
 
3485
    This is tested against a smart server as http://pad.lv/786980 was about a
 
3486
    ReadOnlyError (write attempt during a read-only transaction) which can only
 
3487
    happen in this context.
 
3488
    """
 
3489
 
 
3490
    def setUp(self):
 
3491
        super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
 
3492
        self.transport_server = test_server.SmartTCPServer_for_testing
 
3493
 
 
3494
    def make_master_and_checkout(self, master_name, checkout_name):
 
3495
        # Create the master branch and its associated checkout
 
3496
        self.master = self.make_branch_and_tree(master_name)
 
3497
        self.checkout = self.master.branch.create_checkout(checkout_name)
 
3498
        # Modify the master branch so there is something to update
 
3499
        self.master.commit('add stuff')
 
3500
        self.last_revid = self.master.commit('even more stuff')
 
3501
        self.bound_location = self.checkout.branch.get_bound_location()
 
3502
 
 
3503
    def assertUpdateSucceeds(self, new_location):
 
3504
        self.checkout.branch.set_bound_location(new_location)
 
3505
        self.checkout.update()
 
3506
        self.assertEquals(self.last_revid, self.checkout.last_revision())
 
3507
 
 
3508
    def test_without_final_slash(self):
 
3509
        self.make_master_and_checkout('master', 'checkout')
 
3510
        # For unclear reasons some users have a bound_location without a final
 
3511
        # '/', simulate that by forcing such a value
 
3512
        self.assertEndsWith(self.bound_location, '/')
 
3513
        self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
 
3514
 
 
3515
    def test_plus_sign(self):
 
3516
        self.make_master_and_checkout('+master', 'checkout')
 
3517
        self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
 
3518
 
 
3519
    def test_tilda(self):
 
3520
        # Embed ~ in the middle of the path just to avoid any $HOME
 
3521
        # interpretation
 
3522
        self.make_master_and_checkout('mas~ter', 'checkout')
 
3523
        self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))