~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: John Arbash Meinel
  • Date: 2009-10-12 21:44:27 UTC
  • mto: This revision was merged to the branch mainline in revision 4737.
  • Revision ID: john@arbash-meinel.com-20091012214427-zddi1kmc2jlf7v31
Py_ssize_t and its associated function typedefs are not available w/ python 2.4

So we define them in python-compat.h
Even further, gcc issued a warning for:
static int
_workaround_pyrex_096()
So we changed it to:
_workaround_pyrex_096(void)

Also, some python api funcs were incorrectly defined as 'char *' when they meant
'const char *'. Work around that with a (char *) cast, to avoid compiler warnings.

Show diffs side-by-side

added added

removed removed

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