~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Robert Collins
  • Date: 2009-07-07 04:32:13 UTC
  • mto: This revision was merged to the branch mainline in revision 4524.
  • Revision ID: robertc@robertcollins.net-20090707043213-4hjjhgr40iq7gk2d
More informative assertions in xml serialisation.

Show diffs side-by-side

added added

removed removed

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