/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Andrew Bennetts
  • Date: 2009-03-06 02:21:00 UTC
  • mto: This revision was merged to the branch mainline in revision 4084.
  • Revision ID: andrew.bennetts@canonical.com-20090306022100-cb3sz5julsdhgrpa
Fix trivial bug in my trivial bug fix :)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007, 2008 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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
    pack,
 
35
    remote,
 
36
    repository,
 
37
    smart,
 
38
    tests,
 
39
    urlutils,
 
40
    )
 
41
from bzrlib.branch import Branch
 
42
from bzrlib.bzrdir import BzrDir, BzrDirFormat
 
43
from bzrlib.remote import (
 
44
    RemoteBranch,
 
45
    RemoteBzrDir,
 
46
    RemoteBzrDirFormat,
 
47
    RemoteRepository,
 
48
    )
 
49
from bzrlib.revision import NULL_REVISION
 
50
from bzrlib.smart import server, medium
 
51
from bzrlib.smart.client import _SmartClient
 
52
from bzrlib.symbol_versioning import one_four
 
53
from bzrlib.transport import get_transport, http
 
54
from bzrlib.transport.memory import MemoryTransport
 
55
from bzrlib.transport.remote import (
 
56
    RemoteTransport,
 
57
    RemoteSSHTransport,
 
58
    RemoteTCPTransport,
 
59
)
 
60
 
 
61
 
 
62
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
 
63
 
 
64
    def setUp(self):
 
65
        self.transport_server = server.SmartTCPServer_for_testing
 
66
        super(BasicRemoteObjectTests, self).setUp()
 
67
        self.transport = self.get_transport()
 
68
        # make a branch that can be opened over the smart transport
 
69
        self.local_wt = BzrDir.create_standalone_workingtree('.')
 
70
 
 
71
    def tearDown(self):
 
72
        self.transport.disconnect()
 
73
        tests.TestCaseWithTransport.tearDown(self)
 
74
 
 
75
    def test_create_remote_bzrdir(self):
 
76
        b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
 
77
        self.assertIsInstance(b, BzrDir)
 
78
 
 
79
    def test_open_remote_branch(self):
 
80
        # open a standalone branch in the working directory
 
81
        b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
 
82
        branch = b.open_branch()
 
83
        self.assertIsInstance(branch, Branch)
 
84
 
 
85
    def test_remote_repository(self):
 
86
        b = BzrDir.open_from_transport(self.transport)
 
87
        repo = b.open_repository()
 
88
        revid = u'\xc823123123'.encode('utf8')
 
89
        self.assertFalse(repo.has_revision(revid))
 
90
        self.local_wt.commit(message='test commit', rev_id=revid)
 
91
        self.assertTrue(repo.has_revision(revid))
 
92
 
 
93
    def test_remote_branch_revision_history(self):
 
94
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
95
        self.assertEqual([], b.revision_history())
 
96
        r1 = self.local_wt.commit('1st commit')
 
97
        r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
 
98
        self.assertEqual([r1, r2], b.revision_history())
 
99
 
 
100
    def test_find_correct_format(self):
 
101
        """Should open a RemoteBzrDir over a RemoteTransport"""
 
102
        fmt = BzrDirFormat.find_format(self.transport)
 
103
        self.assertTrue(RemoteBzrDirFormat
 
104
                        in BzrDirFormat._control_server_formats)
 
105
        self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
 
106
 
 
107
    def test_open_detected_smart_format(self):
 
108
        fmt = BzrDirFormat.find_format(self.transport)
 
109
        d = fmt.open(self.transport)
 
110
        self.assertIsInstance(d, BzrDir)
 
111
 
 
112
    def test_remote_branch_repr(self):
 
113
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
114
        self.assertStartsWith(str(b), 'RemoteBranch(')
 
115
 
 
116
 
 
117
class FakeProtocol(object):
 
118
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
 
119
 
 
120
    def __init__(self, body, fake_client):
 
121
        self.body = body
 
122
        self._body_buffer = None
 
123
        self._fake_client = fake_client
 
124
 
 
125
    def read_body_bytes(self, count=-1):
 
126
        if self._body_buffer is None:
 
127
            self._body_buffer = StringIO(self.body)
 
128
        bytes = self._body_buffer.read(count)
 
129
        if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
 
130
            self._fake_client.expecting_body = False
 
131
        return bytes
 
132
 
 
133
    def cancel_read_body(self):
 
134
        self._fake_client.expecting_body = False
 
135
 
 
136
    def read_streamed_body(self):
 
137
        return self.body
 
138
 
 
139
 
 
140
class FakeClient(_SmartClient):
 
141
    """Lookalike for _SmartClient allowing testing."""
 
142
 
 
143
    def __init__(self, fake_medium_base='fake base'):
 
144
        """Create a FakeClient."""
 
145
        self.responses = []
 
146
        self._calls = []
 
147
        self.expecting_body = False
 
148
        # if non-None, this is the list of expected calls, with only the
 
149
        # method name and arguments included.  the body might be hard to
 
150
        # compute so is not included. If a call is None, that call can
 
151
        # be anything.
 
152
        self._expected_calls = None
 
153
        _SmartClient.__init__(self, FakeMedium(self._calls, fake_medium_base))
 
154
 
 
155
    def add_expected_call(self, call_name, call_args, response_type,
 
156
        response_args, response_body=None):
 
157
        if self._expected_calls is None:
 
158
            self._expected_calls = []
 
159
        self._expected_calls.append((call_name, call_args))
 
160
        self.responses.append((response_type, response_args, response_body))
 
161
 
 
162
    def add_success_response(self, *args):
 
163
        self.responses.append(('success', args, None))
 
164
 
 
165
    def add_success_response_with_body(self, body, *args):
 
166
        self.responses.append(('success', args, body))
 
167
        if self._expected_calls is not None:
 
168
            self._expected_calls.append(None)
 
169
 
 
170
    def add_error_response(self, *args):
 
171
        self.responses.append(('error', args))
 
172
 
 
173
    def add_unknown_method_response(self, verb):
 
174
        self.responses.append(('unknown', verb))
 
175
 
 
176
    def finished_test(self):
 
177
        if self._expected_calls:
 
178
            raise AssertionError("%r finished but was still expecting %r"
 
179
                % (self, self._expected_calls[0]))
 
180
 
 
181
    def _get_next_response(self):
 
182
        try:
 
183
            response_tuple = self.responses.pop(0)
 
184
        except IndexError, e:
 
185
            raise AssertionError("%r didn't expect any more calls"
 
186
                % (self,))
 
187
        if response_tuple[0] == 'unknown':
 
188
            raise errors.UnknownSmartMethod(response_tuple[1])
 
189
        elif response_tuple[0] == 'error':
 
190
            raise errors.ErrorFromSmartServer(response_tuple[1])
 
191
        return response_tuple
 
192
 
 
193
    def _check_call(self, method, args):
 
194
        if self._expected_calls is None:
 
195
            # the test should be updated to say what it expects
 
196
            return
 
197
        try:
 
198
            next_call = self._expected_calls.pop(0)
 
199
        except IndexError:
 
200
            raise AssertionError("%r didn't expect any more calls "
 
201
                "but got %r%r"
 
202
                % (self, method, args,))
 
203
        if next_call is None:
 
204
            return
 
205
        if method != next_call[0] or args != next_call[1]:
 
206
            raise AssertionError("%r expected %r%r "
 
207
                "but got %r%r"
 
208
                % (self, next_call[0], next_call[1], method, args,))
 
209
 
 
210
    def call(self, method, *args):
 
211
        self._check_call(method, args)
 
212
        self._calls.append(('call', method, args))
 
213
        return self._get_next_response()[1]
 
214
 
 
215
    def call_expecting_body(self, method, *args):
 
216
        self._check_call(method, args)
 
217
        self._calls.append(('call_expecting_body', method, args))
 
218
        result = self._get_next_response()
 
219
        self.expecting_body = True
 
220
        return result[1], FakeProtocol(result[2], self)
 
221
 
 
222
    def call_with_body_bytes_expecting_body(self, method, args, body):
 
223
        self._check_call(method, args)
 
224
        self._calls.append(('call_with_body_bytes_expecting_body', method,
 
225
            args, body))
 
226
        result = self._get_next_response()
 
227
        self.expecting_body = True
 
228
        return result[1], FakeProtocol(result[2], self)
 
229
 
 
230
    def call_with_body_stream(self, args, stream):
 
231
        # Explicitly consume the stream before checking for an error, because
 
232
        # that's what happens a real medium.
 
233
        stream = list(stream)
 
234
        self._check_call(args[0], args[1:])
 
235
        self._calls.append(('call_with_body_stream', args[0], args[1:], stream))
 
236
        return self._get_next_response()[1]
 
237
 
 
238
 
 
239
class FakeMedium(medium.SmartClientMedium):
 
240
 
 
241
    def __init__(self, client_calls, base):
 
242
        medium.SmartClientMedium.__init__(self, base)
 
243
        self._client_calls = client_calls
 
244
 
 
245
    def disconnect(self):
 
246
        self._client_calls.append(('disconnect medium',))
 
247
 
 
248
 
 
249
class TestVfsHas(tests.TestCase):
 
250
 
 
251
    def test_unicode_path(self):
 
252
        client = FakeClient('/')
 
253
        client.add_success_response('yes',)
 
254
        transport = RemoteTransport('bzr://localhost/', _client=client)
 
255
        filename = u'/hell\u00d8'.encode('utf8')
 
256
        result = transport.has(filename)
 
257
        self.assertEqual(
 
258
            [('call', 'has', (filename,))],
 
259
            client._calls)
 
260
        self.assertTrue(result)
 
261
 
 
262
 
 
263
class TestRemote(tests.TestCaseWithMemoryTransport):
 
264
 
 
265
    def get_repo_format(self):
 
266
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
267
        return reference_bzrdir_format.repository_format
 
268
 
 
269
    def disable_verb(self, verb):
 
270
        """Disable a verb for one test."""
 
271
        request_handlers = smart.request.request_handlers
 
272
        orig_method = request_handlers.get(verb)
 
273
        request_handlers.remove(verb)
 
274
        def restoreVerb():
 
275
            request_handlers.register(verb, orig_method)
 
276
        self.addCleanup(restoreVerb)
 
277
 
 
278
 
 
279
class Test_ClientMedium_remote_path_from_transport(tests.TestCase):
 
280
    """Tests for the behaviour of client_medium.remote_path_from_transport."""
 
281
 
 
282
    def assertRemotePath(self, expected, client_base, transport_base):
 
283
        """Assert that the result of
 
284
        SmartClientMedium.remote_path_from_transport is the expected value for
 
285
        a given client_base and transport_base.
 
286
        """
 
287
        client_medium = medium.SmartClientMedium(client_base)
 
288
        transport = get_transport(transport_base)
 
289
        result = client_medium.remote_path_from_transport(transport)
 
290
        self.assertEqual(expected, result)
 
291
 
 
292
    def test_remote_path_from_transport(self):
 
293
        """SmartClientMedium.remote_path_from_transport calculates a URL for
 
294
        the given transport relative to the root of the client base URL.
 
295
        """
 
296
        self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
 
297
        self.assertRemotePath(
 
298
            'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
 
299
 
 
300
    def assertRemotePathHTTP(self, expected, transport_base, relpath):
 
301
        """Assert that the result of
 
302
        HttpTransportBase.remote_path_from_transport is the expected value for
 
303
        a given transport_base and relpath of that transport.  (Note that
 
304
        HttpTransportBase is a subclass of SmartClientMedium)
 
305
        """
 
306
        base_transport = get_transport(transport_base)
 
307
        client_medium = base_transport.get_smart_medium()
 
308
        cloned_transport = base_transport.clone(relpath)
 
309
        result = client_medium.remote_path_from_transport(cloned_transport)
 
310
        self.assertEqual(expected, result)
 
311
 
 
312
    def test_remote_path_from_transport_http(self):
 
313
        """Remote paths for HTTP transports are calculated differently to other
 
314
        transports.  They are just relative to the client base, not the root
 
315
        directory of the host.
 
316
        """
 
317
        for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
 
318
            self.assertRemotePathHTTP(
 
319
                '../xyz/', scheme + '//host/path', '../xyz/')
 
320
            self.assertRemotePathHTTP(
 
321
                'xyz/', scheme + '//host/path', 'xyz/')
 
322
 
 
323
 
 
324
class Test_ClientMedium_remote_is_at_least(tests.TestCase):
 
325
    """Tests for the behaviour of client_medium.remote_is_at_least."""
 
326
 
 
327
    def test_initially_unlimited(self):
 
328
        """A fresh medium assumes that the remote side supports all
 
329
        versions.
 
330
        """
 
331
        client_medium = medium.SmartClientMedium('dummy base')
 
332
        self.assertFalse(client_medium._is_remote_before((99, 99)))
 
333
 
 
334
    def test__remember_remote_is_before(self):
 
335
        """Calling _remember_remote_is_before ratchets down the known remote
 
336
        version.
 
337
        """
 
338
        client_medium = medium.SmartClientMedium('dummy base')
 
339
        # Mark the remote side as being less than 1.6.  The remote side may
 
340
        # still be 1.5.
 
341
        client_medium._remember_remote_is_before((1, 6))
 
342
        self.assertTrue(client_medium._is_remote_before((1, 6)))
 
343
        self.assertFalse(client_medium._is_remote_before((1, 5)))
 
344
        # Calling _remember_remote_is_before again with a lower value works.
 
345
        client_medium._remember_remote_is_before((1, 5))
 
346
        self.assertTrue(client_medium._is_remote_before((1, 5)))
 
347
        # You cannot call _remember_remote_is_before with a larger value.
 
348
        self.assertRaises(
 
349
            AssertionError, client_medium._remember_remote_is_before, (1, 9))
 
350
 
 
351
 
 
352
class TestBzrDirCloningMetaDir(TestRemote):
 
353
 
 
354
    def test_backwards_compat(self):
 
355
        self.setup_smart_server_with_call_log()
 
356
        a_dir = self.make_bzrdir('.')
 
357
        self.reset_smart_call_log()
 
358
        verb = 'BzrDir.cloning_metadir'
 
359
        self.disable_verb(verb)
 
360
        format = a_dir.cloning_metadir()
 
361
        call_count = len([call for call in self.hpss_calls if
 
362
            call.call.method == verb])
 
363
        self.assertEqual(1, call_count)
 
364
 
 
365
    def test_current_server(self):
 
366
        transport = self.get_transport('.')
 
367
        transport = transport.clone('quack')
 
368
        self.make_bzrdir('quack')
 
369
        client = FakeClient(transport.base)
 
370
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
371
        control_name = reference_bzrdir_format.network_name()
 
372
        client.add_expected_call(
 
373
            'BzrDir.cloning_metadir', ('quack/', 'False'),
 
374
            'success', (control_name, '', ('direct', ''))),
 
375
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
376
            _client=client)
 
377
        result = a_bzrdir.cloning_metadir()
 
378
        # We should have got a reference control dir with default branch and
 
379
        # repository formats.
 
380
        # This pokes a little, just to be sure.
 
381
        self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
 
382
        self.assertEqual(None, result._repository_format)
 
383
        self.assertEqual(None, result._branch_format)
 
384
        client.finished_test()
 
385
 
 
386
 
 
387
class TestBzrDirOpenBranch(TestRemote):
 
388
 
 
389
    def test_branch_present(self):
 
390
        reference_format = self.get_repo_format()
 
391
        network_name = reference_format.network_name()
 
392
        transport = MemoryTransport()
 
393
        transport.mkdir('quack')
 
394
        transport = transport.clone('quack')
 
395
        client = FakeClient(transport.base)
 
396
        client.add_expected_call(
 
397
            'BzrDir.open_branch', ('quack/',),
 
398
            'success', ('ok', ''))
 
399
        client.add_expected_call(
 
400
            'BzrDir.find_repositoryV3', ('quack/',),
 
401
            'success', ('ok', '', 'no', 'no', 'no', network_name))
 
402
        client.add_expected_call(
 
403
            'Branch.get_stacked_on_url', ('quack/',),
 
404
            'error', ('NotStacked',))
 
405
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
406
            _client=client)
 
407
        result = bzrdir.open_branch()
 
408
        self.assertIsInstance(result, RemoteBranch)
 
409
        self.assertEqual(bzrdir, result.bzrdir)
 
410
        client.finished_test()
 
411
 
 
412
    def test_branch_missing(self):
 
413
        transport = MemoryTransport()
 
414
        transport.mkdir('quack')
 
415
        transport = transport.clone('quack')
 
416
        client = FakeClient(transport.base)
 
417
        client.add_error_response('nobranch')
 
418
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
419
            _client=client)
 
420
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
 
421
        self.assertEqual(
 
422
            [('call', 'BzrDir.open_branch', ('quack/',))],
 
423
            client._calls)
 
424
 
 
425
    def test__get_tree_branch(self):
 
426
        # _get_tree_branch is a form of open_branch, but it should only ask for
 
427
        # branch opening, not any other network requests.
 
428
        calls = []
 
429
        def open_branch():
 
430
            calls.append("Called")
 
431
            return "a-branch"
 
432
        transport = MemoryTransport()
 
433
        # no requests on the network - catches other api calls being made.
 
434
        client = FakeClient(transport.base)
 
435
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
436
            _client=client)
 
437
        # patch the open_branch call to record that it was called.
 
438
        bzrdir.open_branch = open_branch
 
439
        self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
 
440
        self.assertEqual(["Called"], calls)
 
441
        self.assertEqual([], client._calls)
 
442
 
 
443
    def test_url_quoting_of_path(self):
 
444
        # Relpaths on the wire should not be URL-escaped.  So "~" should be
 
445
        # transmitted as "~", not "%7E".
 
446
        transport = RemoteTCPTransport('bzr://localhost/~hello/')
 
447
        client = FakeClient(transport.base)
 
448
        reference_format = self.get_repo_format()
 
449
        network_name = reference_format.network_name()
 
450
        client.add_expected_call(
 
451
            'BzrDir.open_branch', ('~hello/',),
 
452
            'success', ('ok', ''))
 
453
        client.add_expected_call(
 
454
            'BzrDir.find_repositoryV3', ('~hello/',),
 
455
            'success', ('ok', '', 'no', 'no', 'no', network_name))
 
456
        client.add_expected_call(
 
457
            'Branch.get_stacked_on_url', ('~hello/',),
 
458
            'error', ('NotStacked',))
 
459
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
460
            _client=client)
 
461
        result = bzrdir.open_branch()
 
462
        client.finished_test()
 
463
 
 
464
    def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
 
465
        reference_format = self.get_repo_format()
 
466
        network_name = reference_format.network_name()
 
467
        transport = MemoryTransport()
 
468
        transport.mkdir('quack')
 
469
        transport = transport.clone('quack')
 
470
        if rich_root:
 
471
            rich_response = 'yes'
 
472
        else:
 
473
            rich_response = 'no'
 
474
        if subtrees:
 
475
            subtree_response = 'yes'
 
476
        else:
 
477
            subtree_response = 'no'
 
478
        client = FakeClient(transport.base)
 
479
        client.add_success_response(
 
480
            'ok', '', rich_response, subtree_response, external_lookup,
 
481
            network_name)
 
482
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
483
            _client=client)
 
484
        result = bzrdir.open_repository()
 
485
        self.assertEqual(
 
486
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
 
487
            client._calls)
 
488
        self.assertIsInstance(result, RemoteRepository)
 
489
        self.assertEqual(bzrdir, result.bzrdir)
 
490
        self.assertEqual(rich_root, result._format.rich_root_data)
 
491
        self.assertEqual(subtrees, result._format.supports_tree_reference)
 
492
 
 
493
    def test_open_repository_sets_format_attributes(self):
 
494
        self.check_open_repository(True, True)
 
495
        self.check_open_repository(False, True)
 
496
        self.check_open_repository(True, False)
 
497
        self.check_open_repository(False, False)
 
498
        self.check_open_repository(False, False, 'yes')
 
499
 
 
500
    def test_old_server(self):
 
501
        """RemoteBzrDirFormat should fail to probe if the server version is too
 
502
        old.
 
503
        """
 
504
        self.assertRaises(errors.NotBranchError,
 
505
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
 
506
 
 
507
 
 
508
class TestBzrDirCreateBranch(TestRemote):
 
509
 
 
510
    def test_backwards_compat(self):
 
511
        self.setup_smart_server_with_call_log()
 
512
        repo = self.make_repository('.')
 
513
        self.reset_smart_call_log()
 
514
        self.disable_verb('BzrDir.create_branch')
 
515
        branch = repo.bzrdir.create_branch()
 
516
        create_branch_call_count = len([call for call in self.hpss_calls if
 
517
            call.call.method == 'BzrDir.create_branch'])
 
518
        self.assertEqual(1, create_branch_call_count)
 
519
 
 
520
    def test_current_server(self):
 
521
        transport = self.get_transport('.')
 
522
        transport = transport.clone('quack')
 
523
        self.make_repository('quack')
 
524
        client = FakeClient(transport.base)
 
525
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
526
        reference_format = reference_bzrdir_format.get_branch_format()
 
527
        network_name = reference_format.network_name()
 
528
        reference_repo_fmt = reference_bzrdir_format.repository_format
 
529
        reference_repo_name = reference_repo_fmt.network_name()
 
530
        client.add_expected_call(
 
531
            'BzrDir.create_branch', ('quack/', network_name),
 
532
            'success', ('ok', network_name, '', 'no', 'no', 'yes',
 
533
            reference_repo_name))
 
534
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
535
            _client=client)
 
536
        branch = a_bzrdir.create_branch()
 
537
        # We should have got a remote branch
 
538
        self.assertIsInstance(branch, remote.RemoteBranch)
 
539
        # its format should have the settings from the response
 
540
        format = branch._format
 
541
        self.assertEqual(network_name, format.network_name())
 
542
 
 
543
 
 
544
class TestBzrDirCreateRepository(TestRemote):
 
545
 
 
546
    def test_backwards_compat(self):
 
547
        self.setup_smart_server_with_call_log()
 
548
        bzrdir = self.make_bzrdir('.')
 
549
        self.reset_smart_call_log()
 
550
        self.disable_verb('BzrDir.create_repository')
 
551
        repo = bzrdir.create_repository()
 
552
        create_repo_call_count = len([call for call in self.hpss_calls if
 
553
            call.call.method == 'BzrDir.create_repository'])
 
554
        self.assertEqual(1, create_repo_call_count)
 
555
 
 
556
    def test_current_server(self):
 
557
        transport = self.get_transport('.')
 
558
        transport = transport.clone('quack')
 
559
        self.make_bzrdir('quack')
 
560
        client = FakeClient(transport.base)
 
561
        reference_bzrdir_format = bzrdir.format_registry.get('default')()
 
562
        reference_format = reference_bzrdir_format.repository_format
 
563
        network_name = reference_format.network_name()
 
564
        client.add_expected_call(
 
565
            'BzrDir.create_repository', ('quack/',
 
566
                'Bazaar pack repository format 1 (needs bzr 0.92)\n', 'False'),
 
567
            'success', ('ok', 'no', 'no', 'no', network_name))
 
568
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
569
            _client=client)
 
570
        repo = a_bzrdir.create_repository()
 
571
        # We should have got a remote repository
 
572
        self.assertIsInstance(repo, remote.RemoteRepository)
 
573
        # its format should have the settings from the response
 
574
        format = repo._format
 
575
        self.assertFalse(format.rich_root_data)
 
576
        self.assertFalse(format.supports_tree_reference)
 
577
        self.assertFalse(format.supports_external_lookups)
 
578
        self.assertEqual(network_name, format.network_name())
 
579
 
 
580
 
 
581
class TestBzrDirOpenRepository(TestRemote):
 
582
 
 
583
    def test_backwards_compat_1_2_3(self):
 
584
        # fallback all the way to the first version.
 
585
        reference_format = self.get_repo_format()
 
586
        network_name = reference_format.network_name()
 
587
        client = FakeClient('bzr://example.com/')
 
588
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
 
589
        client.add_unknown_method_response('BzrDir.find_repositoryV2')
 
590
        client.add_success_response('ok', '', 'no', 'no')
 
591
        # A real repository instance will be created to determine the network
 
592
        # name.
 
593
        client.add_success_response_with_body(
 
594
            "Bazaar-NG meta directory, format 1\n", 'ok')
 
595
        client.add_success_response_with_body(
 
596
            reference_format.get_format_string(), 'ok')
 
597
        # PackRepository wants to do a stat
 
598
        client.add_success_response('stat', '0', '65535')
 
599
        remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
 
600
            _client=client)
 
601
        bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
 
602
            _client=client)
 
603
        repo = bzrdir.open_repository()
 
604
        self.assertEqual(
 
605
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
 
606
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
607
             ('call', 'BzrDir.find_repository', ('quack/',)),
 
608
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
 
609
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
 
610
             ('call', 'stat', ('/quack/.bzr/repository',)),
 
611
             ],
 
612
            client._calls)
 
613
        self.assertEqual(network_name, repo._format.network_name())
 
614
 
 
615
    def test_backwards_compat_2(self):
 
616
        # fallback to find_repositoryV2
 
617
        reference_format = self.get_repo_format()
 
618
        network_name = reference_format.network_name()
 
619
        client = FakeClient('bzr://example.com/')
 
620
        client.add_unknown_method_response('BzrDir.find_repositoryV3')
 
621
        client.add_success_response('ok', '', 'no', 'no', 'no')
 
622
        # A real repository instance will be created to determine the network
 
623
        # name.
 
624
        client.add_success_response_with_body(
 
625
            "Bazaar-NG meta directory, format 1\n", 'ok')
 
626
        client.add_success_response_with_body(
 
627
            reference_format.get_format_string(), 'ok')
 
628
        # PackRepository wants to do a stat
 
629
        client.add_success_response('stat', '0', '65535')
 
630
        remote_transport = RemoteTransport('bzr://example.com/quack/', medium=False,
 
631
            _client=client)
 
632
        bzrdir = RemoteBzrDir(remote_transport, remote.RemoteBzrDirFormat(),
 
633
            _client=client)
 
634
        repo = bzrdir.open_repository()
 
635
        self.assertEqual(
 
636
            [('call', 'BzrDir.find_repositoryV3', ('quack/',)),
 
637
             ('call', 'BzrDir.find_repositoryV2', ('quack/',)),
 
638
             ('call_expecting_body', 'get', ('/quack/.bzr/branch-format',)),
 
639
             ('call_expecting_body', 'get', ('/quack/.bzr/repository/format',)),
 
640
             ('call', 'stat', ('/quack/.bzr/repository',)),
 
641
             ],
 
642
            client._calls)
 
643
        self.assertEqual(network_name, repo._format.network_name())
 
644
 
 
645
    def test_current_server(self):
 
646
        reference_format = self.get_repo_format()
 
647
        network_name = reference_format.network_name()
 
648
        transport = MemoryTransport()
 
649
        transport.mkdir('quack')
 
650
        transport = transport.clone('quack')
 
651
        client = FakeClient(transport.base)
 
652
        client.add_success_response('ok', '', 'no', 'no', 'no', network_name)
 
653
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
654
            _client=client)
 
655
        repo = bzrdir.open_repository()
 
656
        self.assertEqual(
 
657
            [('call', 'BzrDir.find_repositoryV3', ('quack/',))],
 
658
            client._calls)
 
659
        self.assertEqual(network_name, repo._format.network_name())
 
660
 
 
661
 
 
662
class OldSmartClient(object):
 
663
    """A fake smart client for test_old_version that just returns a version one
 
664
    response to the 'hello' (query version) command.
 
665
    """
 
666
 
 
667
    def get_request(self):
 
668
        input_file = StringIO('ok\x011\n')
 
669
        output_file = StringIO()
 
670
        client_medium = medium.SmartSimplePipesClientMedium(
 
671
            input_file, output_file)
 
672
        return medium.SmartClientStreamMediumRequest(client_medium)
 
673
 
 
674
    def protocol_version(self):
 
675
        return 1
 
676
 
 
677
 
 
678
class OldServerTransport(object):
 
679
    """A fake transport for test_old_server that reports it's smart server
 
680
    protocol version as version one.
 
681
    """
 
682
 
 
683
    def __init__(self):
 
684
        self.base = 'fake:'
 
685
 
 
686
    def get_smart_client(self):
 
687
        return OldSmartClient()
 
688
 
 
689
 
 
690
class RemoteBranchTestCase(tests.TestCase):
 
691
 
 
692
    def make_remote_branch(self, transport, client):
 
693
        """Make a RemoteBranch using 'client' as its _SmartClient.
 
694
 
 
695
        A RemoteBzrDir and RemoteRepository will also be created to fill out
 
696
        the RemoteBranch, albeit with stub values for some of their attributes.
 
697
        """
 
698
        # we do not want bzrdir to make any remote calls, so use False as its
 
699
        # _client.  If it tries to make a remote call, this will fail
 
700
        # immediately.
 
701
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
702
            _client=False)
 
703
        repo = RemoteRepository(bzrdir, None, _client=client)
 
704
        return RemoteBranch(bzrdir, repo, _client=client)
 
705
 
 
706
 
 
707
class TestBranchGetParent(RemoteBranchTestCase):
 
708
 
 
709
    def test_no_parent(self):
 
710
        # in an empty branch we decode the response properly
 
711
        transport = MemoryTransport()
 
712
        client = FakeClient(transport.base)
 
713
        client.add_expected_call(
 
714
            'Branch.get_stacked_on_url', ('quack/',),
 
715
            'error', ('NotStacked',))
 
716
        client.add_expected_call(
 
717
            'Branch.get_parent', ('quack/',),
 
718
            'success', (''))
 
719
        transport.mkdir('quack')
 
720
        transport = transport.clone('quack')
 
721
        branch = self.make_remote_branch(transport, client)
 
722
        result = branch.get_parent()
 
723
        client.finished_test()
 
724
        self.assertEqual(None, result)
 
725
 
 
726
    def test_parent_relative(self):
 
727
        transport = MemoryTransport()
 
728
        client = FakeClient(transport.base)
 
729
        client.add_expected_call(
 
730
            'Branch.get_stacked_on_url', ('kwaak/',),
 
731
            'error', ('NotStacked',))
 
732
        client.add_expected_call(
 
733
            'Branch.get_parent', ('kwaak/',),
 
734
            'success', ('../foo/'))
 
735
        transport.mkdir('kwaak')
 
736
        transport = transport.clone('kwaak')
 
737
        branch = self.make_remote_branch(transport, client)
 
738
        result = branch.get_parent()
 
739
        self.assertEqual(transport.clone('../foo').base, result)
 
740
 
 
741
    def test_parent_absolute(self):
 
742
        transport = MemoryTransport()
 
743
        client = FakeClient(transport.base)
 
744
        client.add_expected_call(
 
745
            'Branch.get_stacked_on_url', ('kwaak/',),
 
746
            'error', ('NotStacked',))
 
747
        client.add_expected_call(
 
748
            'Branch.get_parent', ('kwaak/',),
 
749
            'success', ('http://foo/'))
 
750
        transport.mkdir('kwaak')
 
751
        transport = transport.clone('kwaak')
 
752
        branch = self.make_remote_branch(transport, client)
 
753
        result = branch.get_parent()
 
754
        self.assertEqual('http://foo/', result)
 
755
 
 
756
 
 
757
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
 
758
 
 
759
    def test_empty_branch(self):
 
760
        # in an empty branch we decode the response properly
 
761
        transport = MemoryTransport()
 
762
        client = FakeClient(transport.base)
 
763
        client.add_expected_call(
 
764
            'Branch.get_stacked_on_url', ('quack/',),
 
765
            'error', ('NotStacked',))
 
766
        client.add_expected_call(
 
767
            'Branch.last_revision_info', ('quack/',),
 
768
            'success', ('ok', '0', 'null:'))
 
769
        transport.mkdir('quack')
 
770
        transport = transport.clone('quack')
 
771
        branch = self.make_remote_branch(transport, client)
 
772
        result = branch.last_revision_info()
 
773
        client.finished_test()
 
774
        self.assertEqual((0, NULL_REVISION), result)
 
775
 
 
776
    def test_non_empty_branch(self):
 
777
        # in a non-empty branch we also decode the response properly
 
778
        revid = u'\xc8'.encode('utf8')
 
779
        transport = MemoryTransport()
 
780
        client = FakeClient(transport.base)
 
781
        client.add_expected_call(
 
782
            'Branch.get_stacked_on_url', ('kwaak/',),
 
783
            'error', ('NotStacked',))
 
784
        client.add_expected_call(
 
785
            'Branch.last_revision_info', ('kwaak/',),
 
786
            'success', ('ok', '2', revid))
 
787
        transport.mkdir('kwaak')
 
788
        transport = transport.clone('kwaak')
 
789
        branch = self.make_remote_branch(transport, client)
 
790
        result = branch.last_revision_info()
 
791
        self.assertEqual((2, revid), result)
 
792
 
 
793
 
 
794
class TestBranch_get_stacked_on_url(TestRemote):
 
795
    """Test Branch._get_stacked_on_url rpc"""
 
796
 
 
797
    def test_get_stacked_on_invalid_url(self):
 
798
        # test that asking for a stacked on url the server can't access works.
 
799
        # This isn't perfect, but then as we're in the same process there
 
800
        # really isn't anything we can do to be 100% sure that the server
 
801
        # doesn't just open in - this test probably needs to be rewritten using
 
802
        # a spawn()ed server.
 
803
        stacked_branch = self.make_branch('stacked', format='1.9')
 
804
        memory_branch = self.make_branch('base', format='1.9')
 
805
        vfs_url = self.get_vfs_only_url('base')
 
806
        stacked_branch.set_stacked_on_url(vfs_url)
 
807
        transport = stacked_branch.bzrdir.root_transport
 
808
        client = FakeClient(transport.base)
 
809
        client.add_expected_call(
 
810
            'Branch.get_stacked_on_url', ('stacked/',),
 
811
            'success', ('ok', vfs_url))
 
812
        # XXX: Multiple calls are bad, this second call documents what is
 
813
        # today.
 
814
        client.add_expected_call(
 
815
            'Branch.get_stacked_on_url', ('stacked/',),
 
816
            'success', ('ok', vfs_url))
 
817
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
818
            _client=client)
 
819
        branch = RemoteBranch(bzrdir, RemoteRepository(bzrdir, None),
 
820
            _client=client)
 
821
        result = branch.get_stacked_on_url()
 
822
        self.assertEqual(vfs_url, result)
 
823
 
 
824
    def test_backwards_compatible(self):
 
825
        # like with bzr1.6 with no Branch.get_stacked_on_url rpc
 
826
        base_branch = self.make_branch('base', format='1.6')
 
827
        stacked_branch = self.make_branch('stacked', format='1.6')
 
828
        stacked_branch.set_stacked_on_url('../base')
 
829
        client = FakeClient(self.get_url())
 
830
        client.add_expected_call(
 
831
            'BzrDir.open_branch', ('stacked/',),
 
832
            'success', ('ok', ''))
 
833
        client.add_expected_call(
 
834
            'BzrDir.find_repositoryV3', ('stacked/',),
 
835
            'success', ('ok', '', 'no', 'no', 'no',
 
836
                stacked_branch.repository._format.network_name()))
 
837
        # called twice, once from constructor and then again by us
 
838
        client.add_expected_call(
 
839
            'Branch.get_stacked_on_url', ('stacked/',),
 
840
            'unknown', ('Branch.get_stacked_on_url',))
 
841
        client.add_expected_call(
 
842
            'Branch.get_stacked_on_url', ('stacked/',),
 
843
            'unknown', ('Branch.get_stacked_on_url',))
 
844
        # this will also do vfs access, but that goes direct to the transport
 
845
        # and isn't seen by the FakeClient.
 
846
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
847
            remote.RemoteBzrDirFormat(), _client=client)
 
848
        branch = bzrdir.open_branch()
 
849
        result = branch.get_stacked_on_url()
 
850
        self.assertEqual('../base', result)
 
851
        client.finished_test()
 
852
        # it's in the fallback list both for the RemoteRepository and its vfs
 
853
        # repository
 
854
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
855
        self.assertEqual(1,
 
856
            len(branch.repository._real_repository._fallback_repositories))
 
857
 
 
858
    def test_get_stacked_on_real_branch(self):
 
859
        base_branch = self.make_branch('base', format='1.6')
 
860
        stacked_branch = self.make_branch('stacked', format='1.6')
 
861
        stacked_branch.set_stacked_on_url('../base')
 
862
        reference_format = self.get_repo_format()
 
863
        network_name = reference_format.network_name()
 
864
        client = FakeClient(self.get_url())
 
865
        client.add_expected_call(
 
866
            'BzrDir.open_branch', ('stacked/',),
 
867
            'success', ('ok', ''))
 
868
        client.add_expected_call(
 
869
            'BzrDir.find_repositoryV3', ('stacked/',),
 
870
            'success', ('ok', '', 'no', 'no', 'no', network_name))
 
871
        # called twice, once from constructor and then again by us
 
872
        client.add_expected_call(
 
873
            'Branch.get_stacked_on_url', ('stacked/',),
 
874
            'success', ('ok', '../base'))
 
875
        client.add_expected_call(
 
876
            'Branch.get_stacked_on_url', ('stacked/',),
 
877
            'success', ('ok', '../base'))
 
878
        bzrdir = RemoteBzrDir(self.get_transport('stacked'),
 
879
            remote.RemoteBzrDirFormat(), _client=client)
 
880
        branch = bzrdir.open_branch()
 
881
        result = branch.get_stacked_on_url()
 
882
        self.assertEqual('../base', result)
 
883
        client.finished_test()
 
884
        # it's in the fallback list both for the RemoteRepository and its vfs
 
885
        # repository
 
886
        self.assertEqual(1, len(branch.repository._fallback_repositories))
 
887
        self.assertEqual(1,
 
888
            len(branch.repository._real_repository._fallback_repositories))
 
889
 
 
890
 
 
891
class TestBranchSetLastRevision(RemoteBranchTestCase):
 
892
 
 
893
    def test_set_empty(self):
 
894
        # set_revision_history([]) is translated to calling
 
895
        # Branch.set_last_revision(path, '') on the wire.
 
896
        transport = MemoryTransport()
 
897
        transport.mkdir('branch')
 
898
        transport = transport.clone('branch')
 
899
 
 
900
        client = FakeClient(transport.base)
 
901
        client.add_expected_call(
 
902
            'Branch.get_stacked_on_url', ('branch/',),
 
903
            'error', ('NotStacked',))
 
904
        client.add_expected_call(
 
905
            'Branch.lock_write', ('branch/', '', ''),
 
906
            'success', ('ok', 'branch token', 'repo token'))
 
907
        client.add_expected_call(
 
908
            'Branch.last_revision_info',
 
909
            ('branch/',),
 
910
            'success', ('ok', '0', 'null:'))
 
911
        client.add_expected_call(
 
912
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'null:',),
 
913
            'success', ('ok',))
 
914
        client.add_expected_call(
 
915
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
916
            'success', ('ok',))
 
917
        branch = self.make_remote_branch(transport, client)
 
918
        # This is a hack to work around the problem that RemoteBranch currently
 
919
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
920
        branch._ensure_real = lambda: None
 
921
        branch.lock_write()
 
922
        result = branch.set_revision_history([])
 
923
        branch.unlock()
 
924
        self.assertEqual(None, result)
 
925
        client.finished_test()
 
926
 
 
927
    def test_set_nonempty(self):
 
928
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
 
929
        # Branch.set_last_revision(path, rev-idN) on the wire.
 
930
        transport = MemoryTransport()
 
931
        transport.mkdir('branch')
 
932
        transport = transport.clone('branch')
 
933
 
 
934
        client = FakeClient(transport.base)
 
935
        client.add_expected_call(
 
936
            'Branch.get_stacked_on_url', ('branch/',),
 
937
            'error', ('NotStacked',))
 
938
        client.add_expected_call(
 
939
            'Branch.lock_write', ('branch/', '', ''),
 
940
            'success', ('ok', 'branch token', 'repo token'))
 
941
        client.add_expected_call(
 
942
            'Branch.last_revision_info',
 
943
            ('branch/',),
 
944
            'success', ('ok', '0', 'null:'))
 
945
        lines = ['rev-id2']
 
946
        encoded_body = bz2.compress('\n'.join(lines))
 
947
        client.add_success_response_with_body(encoded_body, 'ok')
 
948
        client.add_expected_call(
 
949
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id2',),
 
950
            'success', ('ok',))
 
951
        client.add_expected_call(
 
952
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
953
            'success', ('ok',))
 
954
        branch = self.make_remote_branch(transport, client)
 
955
        # This is a hack to work around the problem that RemoteBranch currently
 
956
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
957
        branch._ensure_real = lambda: None
 
958
        # Lock the branch, reset the record of remote calls.
 
959
        branch.lock_write()
 
960
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
 
961
        branch.unlock()
 
962
        self.assertEqual(None, result)
 
963
        client.finished_test()
 
964
 
 
965
    def test_no_such_revision(self):
 
966
        transport = MemoryTransport()
 
967
        transport.mkdir('branch')
 
968
        transport = transport.clone('branch')
 
969
        # A response of 'NoSuchRevision' is translated into an exception.
 
970
        client = FakeClient(transport.base)
 
971
        client.add_expected_call(
 
972
            'Branch.get_stacked_on_url', ('branch/',),
 
973
            'error', ('NotStacked',))
 
974
        client.add_expected_call(
 
975
            'Branch.lock_write', ('branch/', '', ''),
 
976
            'success', ('ok', 'branch token', 'repo token'))
 
977
        client.add_expected_call(
 
978
            'Branch.last_revision_info',
 
979
            ('branch/',),
 
980
            'success', ('ok', '0', 'null:'))
 
981
        # get_graph calls to construct the revision history, for the set_rh
 
982
        # hook
 
983
        lines = ['rev-id']
 
984
        encoded_body = bz2.compress('\n'.join(lines))
 
985
        client.add_success_response_with_body(encoded_body, 'ok')
 
986
        client.add_expected_call(
 
987
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
988
            'error', ('NoSuchRevision', 'rev-id'))
 
989
        client.add_expected_call(
 
990
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
991
            'success', ('ok',))
 
992
 
 
993
        branch = self.make_remote_branch(transport, client)
 
994
        branch.lock_write()
 
995
        self.assertRaises(
 
996
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
 
997
        branch.unlock()
 
998
        client.finished_test()
 
999
 
 
1000
    def test_tip_change_rejected(self):
 
1001
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
1002
        be raised.
 
1003
        """
 
1004
        transport = MemoryTransport()
 
1005
        transport.mkdir('branch')
 
1006
        transport = transport.clone('branch')
 
1007
        client = FakeClient(transport.base)
 
1008
        rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
 
1009
        rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
 
1010
        client.add_expected_call(
 
1011
            'Branch.get_stacked_on_url', ('branch/',),
 
1012
            'error', ('NotStacked',))
 
1013
        client.add_expected_call(
 
1014
            'Branch.lock_write', ('branch/', '', ''),
 
1015
            'success', ('ok', 'branch token', 'repo token'))
 
1016
        client.add_expected_call(
 
1017
            'Branch.last_revision_info',
 
1018
            ('branch/',),
 
1019
            'success', ('ok', '0', 'null:'))
 
1020
        lines = ['rev-id']
 
1021
        encoded_body = bz2.compress('\n'.join(lines))
 
1022
        client.add_success_response_with_body(encoded_body, 'ok')
 
1023
        client.add_expected_call(
 
1024
            'Branch.set_last_revision', ('branch/', 'branch token', 'repo token', 'rev-id',),
 
1025
            'error', ('TipChangeRejected', rejection_msg_utf8))
 
1026
        client.add_expected_call(
 
1027
            'Branch.unlock', ('branch/', 'branch token', 'repo token'),
 
1028
            'success', ('ok',))
 
1029
        branch = self.make_remote_branch(transport, client)
 
1030
        branch._ensure_real = lambda: None
 
1031
        branch.lock_write()
 
1032
        # The 'TipChangeRejected' error response triggered by calling
 
1033
        # set_revision_history causes a TipChangeRejected exception.
 
1034
        err = self.assertRaises(
 
1035
            errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
 
1036
        # The UTF-8 message from the response has been decoded into a unicode
 
1037
        # object.
 
1038
        self.assertIsInstance(err.msg, unicode)
 
1039
        self.assertEqual(rejection_msg_unicode, err.msg)
 
1040
        branch.unlock()
 
1041
        client.finished_test()
 
1042
 
 
1043
 
 
1044
class TestBranchSetLastRevisionInfo(RemoteBranchTestCase):
 
1045
 
 
1046
    def test_set_last_revision_info(self):
 
1047
        # set_last_revision_info(num, 'rev-id') is translated to calling
 
1048
        # Branch.set_last_revision_info(num, 'rev-id') on the wire.
 
1049
        transport = MemoryTransport()
 
1050
        transport.mkdir('branch')
 
1051
        transport = transport.clone('branch')
 
1052
        client = FakeClient(transport.base)
 
1053
        # get_stacked_on_url
 
1054
        client.add_error_response('NotStacked')
 
1055
        # lock_write
 
1056
        client.add_success_response('ok', 'branch token', 'repo token')
 
1057
        # query the current revision
 
1058
        client.add_success_response('ok', '0', 'null:')
 
1059
        # set_last_revision
 
1060
        client.add_success_response('ok')
 
1061
        # unlock
 
1062
        client.add_success_response('ok')
 
1063
 
 
1064
        branch = self.make_remote_branch(transport, client)
 
1065
        # Lock the branch, reset the record of remote calls.
 
1066
        branch.lock_write()
 
1067
        client._calls = []
 
1068
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
1069
        self.assertEqual(
 
1070
            [('call', 'Branch.last_revision_info', ('branch/',)),
 
1071
             ('call', 'Branch.set_last_revision_info',
 
1072
                ('branch/', 'branch token', 'repo token',
 
1073
                 '1234', 'a-revision-id'))],
 
1074
            client._calls)
 
1075
        self.assertEqual(None, result)
 
1076
 
 
1077
    def test_no_such_revision(self):
 
1078
        # A response of 'NoSuchRevision' is translated into an exception.
 
1079
        transport = MemoryTransport()
 
1080
        transport.mkdir('branch')
 
1081
        transport = transport.clone('branch')
 
1082
        client = FakeClient(transport.base)
 
1083
        # get_stacked_on_url
 
1084
        client.add_error_response('NotStacked')
 
1085
        # lock_write
 
1086
        client.add_success_response('ok', 'branch token', 'repo token')
 
1087
        # set_last_revision
 
1088
        client.add_error_response('NoSuchRevision', 'revid')
 
1089
        # unlock
 
1090
        client.add_success_response('ok')
 
1091
 
 
1092
        branch = self.make_remote_branch(transport, client)
 
1093
        # Lock the branch, reset the record of remote calls.
 
1094
        branch.lock_write()
 
1095
        client._calls = []
 
1096
 
 
1097
        self.assertRaises(
 
1098
            errors.NoSuchRevision, branch.set_last_revision_info, 123, 'revid')
 
1099
        branch.unlock()
 
1100
 
 
1101
    def lock_remote_branch(self, branch):
 
1102
        """Trick a RemoteBranch into thinking it is locked."""
 
1103
        branch._lock_mode = 'w'
 
1104
        branch._lock_count = 2
 
1105
        branch._lock_token = 'branch token'
 
1106
        branch._repo_lock_token = 'repo token'
 
1107
        branch.repository._lock_mode = 'w'
 
1108
        branch.repository._lock_count = 2
 
1109
        branch.repository._lock_token = 'repo token'
 
1110
 
 
1111
    def test_backwards_compatibility(self):
 
1112
        """If the server does not support the Branch.set_last_revision_info
 
1113
        verb (which is new in 1.4), then the client falls back to VFS methods.
 
1114
        """
 
1115
        # This test is a little messy.  Unlike most tests in this file, it
 
1116
        # doesn't purely test what a Remote* object sends over the wire, and
 
1117
        # how it reacts to responses from the wire.  It instead relies partly
 
1118
        # on asserting that the RemoteBranch will call
 
1119
        # self._real_branch.set_last_revision_info(...).
 
1120
 
 
1121
        # First, set up our RemoteBranch with a FakeClient that raises
 
1122
        # UnknownSmartMethod, and a StubRealBranch that logs how it is called.
 
1123
        transport = MemoryTransport()
 
1124
        transport.mkdir('branch')
 
1125
        transport = transport.clone('branch')
 
1126
        client = FakeClient(transport.base)
 
1127
        client.add_expected_call(
 
1128
            'Branch.get_stacked_on_url', ('branch/',),
 
1129
            'error', ('NotStacked',))
 
1130
        client.add_expected_call(
 
1131
            'Branch.last_revision_info',
 
1132
            ('branch/',),
 
1133
            'success', ('ok', '0', 'null:'))
 
1134
        client.add_expected_call(
 
1135
            'Branch.set_last_revision_info',
 
1136
            ('branch/', 'branch token', 'repo token', '1234', 'a-revision-id',),
 
1137
            'unknown', 'Branch.set_last_revision_info')
 
1138
 
 
1139
        branch = self.make_remote_branch(transport, client)
 
1140
        class StubRealBranch(object):
 
1141
            def __init__(self):
 
1142
                self.calls = []
 
1143
            def set_last_revision_info(self, revno, revision_id):
 
1144
                self.calls.append(
 
1145
                    ('set_last_revision_info', revno, revision_id))
 
1146
            def _clear_cached_state(self):
 
1147
                pass
 
1148
        real_branch = StubRealBranch()
 
1149
        branch._real_branch = real_branch
 
1150
        self.lock_remote_branch(branch)
 
1151
 
 
1152
        # Call set_last_revision_info, and verify it behaved as expected.
 
1153
        result = branch.set_last_revision_info(1234, 'a-revision-id')
 
1154
        self.assertEqual(
 
1155
            [('set_last_revision_info', 1234, 'a-revision-id')],
 
1156
            real_branch.calls)
 
1157
        client.finished_test()
 
1158
 
 
1159
    def test_unexpected_error(self):
 
1160
        # If the server sends an error the client doesn't understand, it gets
 
1161
        # turned into an UnknownErrorFromSmartServer, which is presented as a
 
1162
        # non-internal error to the user.
 
1163
        transport = MemoryTransport()
 
1164
        transport.mkdir('branch')
 
1165
        transport = transport.clone('branch')
 
1166
        client = FakeClient(transport.base)
 
1167
        # get_stacked_on_url
 
1168
        client.add_error_response('NotStacked')
 
1169
        # lock_write
 
1170
        client.add_success_response('ok', 'branch token', 'repo token')
 
1171
        # set_last_revision
 
1172
        client.add_error_response('UnexpectedError')
 
1173
        # unlock
 
1174
        client.add_success_response('ok')
 
1175
 
 
1176
        branch = self.make_remote_branch(transport, client)
 
1177
        # Lock the branch, reset the record of remote calls.
 
1178
        branch.lock_write()
 
1179
        client._calls = []
 
1180
 
 
1181
        err = self.assertRaises(
 
1182
            errors.UnknownErrorFromSmartServer,
 
1183
            branch.set_last_revision_info, 123, 'revid')
 
1184
        self.assertEqual(('UnexpectedError',), err.error_tuple)
 
1185
        branch.unlock()
 
1186
 
 
1187
    def test_tip_change_rejected(self):
 
1188
        """TipChangeRejected responses cause a TipChangeRejected exception to
 
1189
        be raised.
 
1190
        """
 
1191
        transport = MemoryTransport()
 
1192
        transport.mkdir('branch')
 
1193
        transport = transport.clone('branch')
 
1194
        client = FakeClient(transport.base)
 
1195
        # get_stacked_on_url
 
1196
        client.add_error_response('NotStacked')
 
1197
        # lock_write
 
1198
        client.add_success_response('ok', 'branch token', 'repo token')
 
1199
        # set_last_revision
 
1200
        client.add_error_response('TipChangeRejected', 'rejection message')
 
1201
        # unlock
 
1202
        client.add_success_response('ok')
 
1203
 
 
1204
        branch = self.make_remote_branch(transport, client)
 
1205
        # Lock the branch, reset the record of remote calls.
 
1206
        branch.lock_write()
 
1207
        self.addCleanup(branch.unlock)
 
1208
        client._calls = []
 
1209
 
 
1210
        # The 'TipChangeRejected' error response triggered by calling
 
1211
        # set_last_revision_info causes a TipChangeRejected exception.
 
1212
        err = self.assertRaises(
 
1213
            errors.TipChangeRejected,
 
1214
            branch.set_last_revision_info, 123, 'revid')
 
1215
        self.assertEqual('rejection message', err.msg)
 
1216
 
 
1217
 
 
1218
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
 
1219
    """Getting the branch configuration should use an abstract method not vfs.
 
1220
    """
 
1221
 
 
1222
    def test_get_branch_conf(self):
 
1223
        raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
 
1224
        ## # We should see that branch.get_config() does a single rpc to get the
 
1225
        ## # remote configuration file, abstracting away where that is stored on
 
1226
        ## # the server.  However at the moment it always falls back to using the
 
1227
        ## # vfs, and this would need some changes in config.py.
 
1228
 
 
1229
        ## # in an empty branch we decode the response properly
 
1230
        ## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
 
1231
        ## # we need to make a real branch because the remote_branch.control_files
 
1232
        ## # will trigger _ensure_real.
 
1233
        ## branch = self.make_branch('quack')
 
1234
        ## transport = branch.bzrdir.root_transport
 
1235
        ## # we do not want bzrdir to make any remote calls
 
1236
        ## bzrdir = RemoteBzrDir(transport, _client=False)
 
1237
        ## branch = RemoteBranch(bzrdir, None, _client=client)
 
1238
        ## config = branch.get_config()
 
1239
        ## self.assertEqual(
 
1240
        ##     [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
 
1241
        ##     client._calls)
 
1242
 
 
1243
 
 
1244
class TestBranchLockWrite(RemoteBranchTestCase):
 
1245
 
 
1246
    def test_lock_write_unlockable(self):
 
1247
        transport = MemoryTransport()
 
1248
        client = FakeClient(transport.base)
 
1249
        client.add_expected_call(
 
1250
            'Branch.get_stacked_on_url', ('quack/',),
 
1251
            'error', ('NotStacked',),)
 
1252
        client.add_expected_call(
 
1253
            'Branch.lock_write', ('quack/', '', ''),
 
1254
            'error', ('UnlockableTransport',))
 
1255
        transport.mkdir('quack')
 
1256
        transport = transport.clone('quack')
 
1257
        branch = self.make_remote_branch(transport, client)
 
1258
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
 
1259
        client.finished_test()
 
1260
 
 
1261
 
 
1262
class TestTransportIsReadonly(tests.TestCase):
 
1263
 
 
1264
    def test_true(self):
 
1265
        client = FakeClient()
 
1266
        client.add_success_response('yes')
 
1267
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1268
                                    _client=client)
 
1269
        self.assertEqual(True, transport.is_readonly())
 
1270
        self.assertEqual(
 
1271
            [('call', 'Transport.is_readonly', ())],
 
1272
            client._calls)
 
1273
 
 
1274
    def test_false(self):
 
1275
        client = FakeClient()
 
1276
        client.add_success_response('no')
 
1277
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1278
                                    _client=client)
 
1279
        self.assertEqual(False, transport.is_readonly())
 
1280
        self.assertEqual(
 
1281
            [('call', 'Transport.is_readonly', ())],
 
1282
            client._calls)
 
1283
 
 
1284
    def test_error_from_old_server(self):
 
1285
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
 
1286
 
 
1287
        Clients should treat it as a "no" response, because is_readonly is only
 
1288
        advisory anyway (a transport could be read-write, but then the
 
1289
        underlying filesystem could be readonly anyway).
 
1290
        """
 
1291
        client = FakeClient()
 
1292
        client.add_unknown_method_response('Transport.is_readonly')
 
1293
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1294
                                    _client=client)
 
1295
        self.assertEqual(False, transport.is_readonly())
 
1296
        self.assertEqual(
 
1297
            [('call', 'Transport.is_readonly', ())],
 
1298
            client._calls)
 
1299
 
 
1300
 
 
1301
class TestTransportMkdir(tests.TestCase):
 
1302
 
 
1303
    def test_permissiondenied(self):
 
1304
        client = FakeClient()
 
1305
        client.add_error_response('PermissionDenied', 'remote path', 'extra')
 
1306
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
1307
                                    _client=client)
 
1308
        exc = self.assertRaises(
 
1309
            errors.PermissionDenied, transport.mkdir, 'client path')
 
1310
        expected_error = errors.PermissionDenied('/client path', 'extra')
 
1311
        self.assertEqual(expected_error, exc)
 
1312
 
 
1313
 
 
1314
class TestRemoteSSHTransportAuthentication(tests.TestCaseInTempDir):
 
1315
 
 
1316
    def test_defaults_to_none(self):
 
1317
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1318
        self.assertIs(None, t._get_credentials()[0])
 
1319
 
 
1320
    def test_uses_authentication_config(self):
 
1321
        conf = config.AuthenticationConfig()
 
1322
        conf._get_config().update(
 
1323
            {'bzr+sshtest': {'scheme': 'ssh', 'user': 'bar', 'host':
 
1324
            'example.com'}})
 
1325
        conf._save()
 
1326
        t = RemoteSSHTransport('bzr+ssh://example.com')
 
1327
        self.assertEqual('bar', t._get_credentials()[0])
 
1328
 
 
1329
 
 
1330
class TestRemoteRepository(TestRemote):
 
1331
    """Base for testing RemoteRepository protocol usage.
 
1332
 
 
1333
    These tests contain frozen requests and responses.  We want any changes to
 
1334
    what is sent or expected to be require a thoughtful update to these tests
 
1335
    because they might break compatibility with different-versioned servers.
 
1336
    """
 
1337
 
 
1338
    def setup_fake_client_and_repository(self, transport_path):
 
1339
        """Create the fake client and repository for testing with.
 
1340
 
 
1341
        There's no real server here; we just have canned responses sent
 
1342
        back one by one.
 
1343
 
 
1344
        :param transport_path: Path below the root of the MemoryTransport
 
1345
            where the repository will be created.
 
1346
        """
 
1347
        transport = MemoryTransport()
 
1348
        transport.mkdir(transport_path)
 
1349
        client = FakeClient(transport.base)
 
1350
        transport = transport.clone(transport_path)
 
1351
        # we do not want bzrdir to make any remote calls
 
1352
        bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
1353
            _client=False)
 
1354
        repo = RemoteRepository(bzrdir, None, _client=client)
 
1355
        return repo, client
 
1356
 
 
1357
 
 
1358
class TestRepositoryGatherStats(TestRemoteRepository):
 
1359
 
 
1360
    def test_revid_none(self):
 
1361
        # ('ok',), body with revisions and size
 
1362
        transport_path = 'quack'
 
1363
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1364
        client.add_success_response_with_body(
 
1365
            'revisions: 2\nsize: 18\n', 'ok')
 
1366
        result = repo.gather_stats(None)
 
1367
        self.assertEqual(
 
1368
            [('call_expecting_body', 'Repository.gather_stats',
 
1369
             ('quack/','','no'))],
 
1370
            client._calls)
 
1371
        self.assertEqual({'revisions': 2, 'size': 18}, result)
 
1372
 
 
1373
    def test_revid_no_committers(self):
 
1374
        # ('ok',), body without committers
 
1375
        body = ('firstrev: 123456.300 3600\n'
 
1376
                'latestrev: 654231.400 0\n'
 
1377
                'revisions: 2\n'
 
1378
                'size: 18\n')
 
1379
        transport_path = 'quick'
 
1380
        revid = u'\xc8'.encode('utf8')
 
1381
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1382
        client.add_success_response_with_body(body, 'ok')
 
1383
        result = repo.gather_stats(revid)
 
1384
        self.assertEqual(
 
1385
            [('call_expecting_body', 'Repository.gather_stats',
 
1386
              ('quick/', revid, 'no'))],
 
1387
            client._calls)
 
1388
        self.assertEqual({'revisions': 2, 'size': 18,
 
1389
                          'firstrev': (123456.300, 3600),
 
1390
                          'latestrev': (654231.400, 0),},
 
1391
                         result)
 
1392
 
 
1393
    def test_revid_with_committers(self):
 
1394
        # ('ok',), body with committers
 
1395
        body = ('committers: 128\n'
 
1396
                'firstrev: 123456.300 3600\n'
 
1397
                'latestrev: 654231.400 0\n'
 
1398
                'revisions: 2\n'
 
1399
                'size: 18\n')
 
1400
        transport_path = 'buick'
 
1401
        revid = u'\xc8'.encode('utf8')
 
1402
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1403
        client.add_success_response_with_body(body, 'ok')
 
1404
        result = repo.gather_stats(revid, True)
 
1405
        self.assertEqual(
 
1406
            [('call_expecting_body', 'Repository.gather_stats',
 
1407
              ('buick/', revid, 'yes'))],
 
1408
            client._calls)
 
1409
        self.assertEqual({'revisions': 2, 'size': 18,
 
1410
                          'committers': 128,
 
1411
                          'firstrev': (123456.300, 3600),
 
1412
                          'latestrev': (654231.400, 0),},
 
1413
                         result)
 
1414
 
 
1415
 
 
1416
class TestRepositoryGetGraph(TestRemoteRepository):
 
1417
 
 
1418
    def test_get_graph(self):
 
1419
        # get_graph returns a graph with a custom parents provider.
 
1420
        transport_path = 'quack'
 
1421
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1422
        graph = repo.get_graph()
 
1423
        self.assertNotEqual(graph._parents_provider, repo)
 
1424
 
 
1425
 
 
1426
class TestRepositoryGetParentMap(TestRemoteRepository):
 
1427
 
 
1428
    def test_get_parent_map_caching(self):
 
1429
        # get_parent_map returns from cache until unlock()
 
1430
        # setup a reponse with two revisions
 
1431
        r1 = u'\u0e33'.encode('utf8')
 
1432
        r2 = u'\u0dab'.encode('utf8')
 
1433
        lines = [' '.join([r2, r1]), r1]
 
1434
        encoded_body = bz2.compress('\n'.join(lines))
 
1435
 
 
1436
        transport_path = 'quack'
 
1437
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1438
        client.add_success_response_with_body(encoded_body, 'ok')
 
1439
        client.add_success_response_with_body(encoded_body, 'ok')
 
1440
        repo.lock_read()
 
1441
        graph = repo.get_graph()
 
1442
        parents = graph.get_parent_map([r2])
 
1443
        self.assertEqual({r2: (r1,)}, parents)
 
1444
        # locking and unlocking deeper should not reset
 
1445
        repo.lock_read()
 
1446
        repo.unlock()
 
1447
        parents = graph.get_parent_map([r1])
 
1448
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
1449
        self.assertEqual(
 
1450
            [('call_with_body_bytes_expecting_body',
 
1451
              'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
 
1452
            client._calls)
 
1453
        repo.unlock()
 
1454
        # now we call again, and it should use the second response.
 
1455
        repo.lock_read()
 
1456
        graph = repo.get_graph()
 
1457
        parents = graph.get_parent_map([r1])
 
1458
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
1459
        self.assertEqual(
 
1460
            [('call_with_body_bytes_expecting_body',
 
1461
              'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
 
1462
             ('call_with_body_bytes_expecting_body',
 
1463
              'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
 
1464
            ],
 
1465
            client._calls)
 
1466
        repo.unlock()
 
1467
 
 
1468
    def test_get_parent_map_reconnects_if_unknown_method(self):
 
1469
        transport_path = 'quack'
 
1470
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1471
        client.add_unknown_method_response('Repository,get_parent_map')
 
1472
        client.add_success_response_with_body('', 'ok')
 
1473
        self.assertFalse(client._medium._is_remote_before((1, 2)))
 
1474
        rev_id = 'revision-id'
 
1475
        expected_deprecations = [
 
1476
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
 
1477
            'in version 1.4.']
 
1478
        parents = self.callDeprecated(
 
1479
            expected_deprecations, repo.get_parent_map, [rev_id])
 
1480
        self.assertEqual(
 
1481
            [('call_with_body_bytes_expecting_body',
 
1482
              'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
 
1483
             ('disconnect medium',),
 
1484
             ('call_expecting_body', 'Repository.get_revision_graph',
 
1485
              ('quack/', ''))],
 
1486
            client._calls)
 
1487
        # The medium is now marked as being connected to an older server
 
1488
        self.assertTrue(client._medium._is_remote_before((1, 2)))
 
1489
 
 
1490
    def test_get_parent_map_fallback_parentless_node(self):
 
1491
        """get_parent_map falls back to get_revision_graph on old servers.  The
 
1492
        results from get_revision_graph are tweaked to match the get_parent_map
 
1493
        API.
 
1494
 
 
1495
        Specifically, a {key: ()} result from get_revision_graph means "no
 
1496
        parents" for that key, which in get_parent_map results should be
 
1497
        represented as {key: ('null:',)}.
 
1498
 
 
1499
        This is the test for https://bugs.launchpad.net/bzr/+bug/214894
 
1500
        """
 
1501
        rev_id = 'revision-id'
 
1502
        transport_path = 'quack'
 
1503
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1504
        client.add_success_response_with_body(rev_id, 'ok')
 
1505
        client._medium._remember_remote_is_before((1, 2))
 
1506
        expected_deprecations = [
 
1507
            'bzrlib.remote.RemoteRepository.get_revision_graph was deprecated '
 
1508
            'in version 1.4.']
 
1509
        parents = self.callDeprecated(
 
1510
            expected_deprecations, repo.get_parent_map, [rev_id])
 
1511
        self.assertEqual(
 
1512
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1513
             ('quack/', ''))],
 
1514
            client._calls)
 
1515
        self.assertEqual({rev_id: ('null:',)}, parents)
 
1516
 
 
1517
    def test_get_parent_map_unexpected_response(self):
 
1518
        repo, client = self.setup_fake_client_and_repository('path')
 
1519
        client.add_success_response('something unexpected!')
 
1520
        self.assertRaises(
 
1521
            errors.UnexpectedSmartServerResponse,
 
1522
            repo.get_parent_map, ['a-revision-id'])
 
1523
 
 
1524
 
 
1525
class TestGetParentMapAllowsNew(tests.TestCaseWithTransport):
 
1526
 
 
1527
    def test_allows_new_revisions(self):
 
1528
        """get_parent_map's results can be updated by commit."""
 
1529
        smart_server = server.SmartTCPServer_for_testing()
 
1530
        smart_server.setUp()
 
1531
        self.addCleanup(smart_server.tearDown)
 
1532
        self.make_branch('branch')
 
1533
        branch = Branch.open(smart_server.get_url() + '/branch')
 
1534
        tree = branch.create_checkout('tree', lightweight=True)
 
1535
        tree.lock_write()
 
1536
        self.addCleanup(tree.unlock)
 
1537
        graph = tree.branch.repository.get_graph()
 
1538
        # This provides an opportunity for the missing rev-id to be cached.
 
1539
        self.assertEqual({}, graph.get_parent_map(['rev1']))
 
1540
        tree.commit('message', rev_id='rev1')
 
1541
        graph = tree.branch.repository.get_graph()
 
1542
        self.assertEqual({'rev1': ('null:',)}, graph.get_parent_map(['rev1']))
 
1543
 
 
1544
 
 
1545
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
 
1546
 
 
1547
    def test_null_revision(self):
 
1548
        # a null revision has the predictable result {}, we should have no wire
 
1549
        # traffic when calling it with this argument
 
1550
        transport_path = 'empty'
 
1551
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1552
        client.add_success_response('notused')
 
1553
        result = self.applyDeprecated(one_four, repo.get_revision_graph,
 
1554
            NULL_REVISION)
 
1555
        self.assertEqual([], client._calls)
 
1556
        self.assertEqual({}, result)
 
1557
 
 
1558
    def test_none_revision(self):
 
1559
        # with none we want the entire graph
 
1560
        r1 = u'\u0e33'.encode('utf8')
 
1561
        r2 = u'\u0dab'.encode('utf8')
 
1562
        lines = [' '.join([r2, r1]), r1]
 
1563
        encoded_body = '\n'.join(lines)
 
1564
 
 
1565
        transport_path = 'sinhala'
 
1566
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1567
        client.add_success_response_with_body(encoded_body, 'ok')
 
1568
        result = self.applyDeprecated(one_four, repo.get_revision_graph)
 
1569
        self.assertEqual(
 
1570
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1571
             ('sinhala/', ''))],
 
1572
            client._calls)
 
1573
        self.assertEqual({r1: (), r2: (r1, )}, result)
 
1574
 
 
1575
    def test_specific_revision(self):
 
1576
        # with a specific revision we want the graph for that
 
1577
        # with none we want the entire graph
 
1578
        r11 = u'\u0e33'.encode('utf8')
 
1579
        r12 = u'\xc9'.encode('utf8')
 
1580
        r2 = u'\u0dab'.encode('utf8')
 
1581
        lines = [' '.join([r2, r11, r12]), r11, r12]
 
1582
        encoded_body = '\n'.join(lines)
 
1583
 
 
1584
        transport_path = 'sinhala'
 
1585
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1586
        client.add_success_response_with_body(encoded_body, 'ok')
 
1587
        result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
 
1588
        self.assertEqual(
 
1589
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1590
             ('sinhala/', r2))],
 
1591
            client._calls)
 
1592
        self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
 
1593
 
 
1594
    def test_no_such_revision(self):
 
1595
        revid = '123'
 
1596
        transport_path = 'sinhala'
 
1597
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1598
        client.add_error_response('nosuchrevision', revid)
 
1599
        # also check that the right revision is reported in the error
 
1600
        self.assertRaises(errors.NoSuchRevision,
 
1601
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
1602
        self.assertEqual(
 
1603
            [('call_expecting_body', 'Repository.get_revision_graph',
 
1604
             ('sinhala/', revid))],
 
1605
            client._calls)
 
1606
 
 
1607
    def test_unexpected_error(self):
 
1608
        revid = '123'
 
1609
        transport_path = 'sinhala'
 
1610
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1611
        client.add_error_response('AnUnexpectedError')
 
1612
        e = self.assertRaises(errors.UnknownErrorFromSmartServer,
 
1613
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
1614
        self.assertEqual(('AnUnexpectedError',), e.error_tuple)
 
1615
 
 
1616
 
 
1617
class TestRepositoryIsShared(TestRemoteRepository):
 
1618
 
 
1619
    def test_is_shared(self):
 
1620
        # ('yes', ) for Repository.is_shared -> 'True'.
 
1621
        transport_path = 'quack'
 
1622
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1623
        client.add_success_response('yes')
 
1624
        result = repo.is_shared()
 
1625
        self.assertEqual(
 
1626
            [('call', 'Repository.is_shared', ('quack/',))],
 
1627
            client._calls)
 
1628
        self.assertEqual(True, result)
 
1629
 
 
1630
    def test_is_not_shared(self):
 
1631
        # ('no', ) for Repository.is_shared -> 'False'.
 
1632
        transport_path = 'qwack'
 
1633
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1634
        client.add_success_response('no')
 
1635
        result = repo.is_shared()
 
1636
        self.assertEqual(
 
1637
            [('call', 'Repository.is_shared', ('qwack/',))],
 
1638
            client._calls)
 
1639
        self.assertEqual(False, result)
 
1640
 
 
1641
 
 
1642
class TestRepositoryLockWrite(TestRemoteRepository):
 
1643
 
 
1644
    def test_lock_write(self):
 
1645
        transport_path = 'quack'
 
1646
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1647
        client.add_success_response('ok', 'a token')
 
1648
        result = repo.lock_write()
 
1649
        self.assertEqual(
 
1650
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
1651
            client._calls)
 
1652
        self.assertEqual('a token', result)
 
1653
 
 
1654
    def test_lock_write_already_locked(self):
 
1655
        transport_path = 'quack'
 
1656
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1657
        client.add_error_response('LockContention')
 
1658
        self.assertRaises(errors.LockContention, repo.lock_write)
 
1659
        self.assertEqual(
 
1660
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
1661
            client._calls)
 
1662
 
 
1663
    def test_lock_write_unlockable(self):
 
1664
        transport_path = 'quack'
 
1665
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1666
        client.add_error_response('UnlockableTransport')
 
1667
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
 
1668
        self.assertEqual(
 
1669
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
1670
            client._calls)
 
1671
 
 
1672
 
 
1673
class TestRepositorySetMakeWorkingTrees(TestRemoteRepository):
 
1674
 
 
1675
    def test_backwards_compat(self):
 
1676
        self.setup_smart_server_with_call_log()
 
1677
        repo = self.make_repository('.')
 
1678
        self.reset_smart_call_log()
 
1679
        verb = 'Repository.set_make_working_trees'
 
1680
        self.disable_verb(verb)
 
1681
        repo.set_make_working_trees(True)
 
1682
        call_count = len([call for call in self.hpss_calls if
 
1683
            call.call.method == verb])
 
1684
        self.assertEqual(1, call_count)
 
1685
 
 
1686
    def test_current(self):
 
1687
        transport_path = 'quack'
 
1688
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1689
        client.add_expected_call(
 
1690
            'Repository.set_make_working_trees', ('quack/', 'True'),
 
1691
            'success', ('ok',))
 
1692
        client.add_expected_call(
 
1693
            'Repository.set_make_working_trees', ('quack/', 'False'),
 
1694
            'success', ('ok',))
 
1695
        repo.set_make_working_trees(True)
 
1696
        repo.set_make_working_trees(False)
 
1697
 
 
1698
 
 
1699
class TestRepositoryUnlock(TestRemoteRepository):
 
1700
 
 
1701
    def test_unlock(self):
 
1702
        transport_path = 'quack'
 
1703
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1704
        client.add_success_response('ok', 'a token')
 
1705
        client.add_success_response('ok')
 
1706
        repo.lock_write()
 
1707
        repo.unlock()
 
1708
        self.assertEqual(
 
1709
            [('call', 'Repository.lock_write', ('quack/', '')),
 
1710
             ('call', 'Repository.unlock', ('quack/', 'a token'))],
 
1711
            client._calls)
 
1712
 
 
1713
    def test_unlock_wrong_token(self):
 
1714
        # If somehow the token is wrong, unlock will raise TokenMismatch.
 
1715
        transport_path = 'quack'
 
1716
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1717
        client.add_success_response('ok', 'a token')
 
1718
        client.add_error_response('TokenMismatch')
 
1719
        repo.lock_write()
 
1720
        self.assertRaises(errors.TokenMismatch, repo.unlock)
 
1721
 
 
1722
 
 
1723
class TestRepositoryHasRevision(TestRemoteRepository):
 
1724
 
 
1725
    def test_none(self):
 
1726
        # repo.has_revision(None) should not cause any traffic.
 
1727
        transport_path = 'quack'
 
1728
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1729
 
 
1730
        # The null revision is always there, so has_revision(None) == True.
 
1731
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
 
1732
 
 
1733
        # The remote repo shouldn't be accessed.
 
1734
        self.assertEqual([], client._calls)
 
1735
 
 
1736
 
 
1737
class TestRepositoryTarball(TestRemoteRepository):
 
1738
 
 
1739
    # This is a canned tarball reponse we can validate against
 
1740
    tarball_content = (
 
1741
        'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
 
1742
        'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
 
1743
        'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
 
1744
        'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
 
1745
        'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
 
1746
        'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
 
1747
        'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
 
1748
        'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
 
1749
        '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
 
1750
        'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
 
1751
        'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
 
1752
        'nWQ7QH/F3JFOFCQ0aSPfA='
 
1753
        ).decode('base64')
 
1754
 
 
1755
    def test_repository_tarball(self):
 
1756
        # Test that Repository.tarball generates the right operations
 
1757
        transport_path = 'repo'
 
1758
        expected_calls = [('call_expecting_body', 'Repository.tarball',
 
1759
                           ('repo/', 'bz2',),),
 
1760
            ]
 
1761
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1762
        client.add_success_response_with_body(self.tarball_content, 'ok')
 
1763
        # Now actually ask for the tarball
 
1764
        tarball_file = repo._get_tarball('bz2')
 
1765
        try:
 
1766
            self.assertEqual(expected_calls, client._calls)
 
1767
            self.assertEqual(self.tarball_content, tarball_file.read())
 
1768
        finally:
 
1769
            tarball_file.close()
 
1770
 
 
1771
 
 
1772
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
 
1773
    """RemoteRepository.copy_content_into optimizations"""
 
1774
 
 
1775
    def test_copy_content_remote_to_local(self):
 
1776
        self.transport_server = server.SmartTCPServer_for_testing
 
1777
        src_repo = self.make_repository('repo1')
 
1778
        src_repo = repository.Repository.open(self.get_url('repo1'))
 
1779
        # At the moment the tarball-based copy_content_into can't write back
 
1780
        # into a smart server.  It would be good if it could upload the
 
1781
        # tarball; once that works we'd have to create repositories of
 
1782
        # different formats. -- mbp 20070410
 
1783
        dest_url = self.get_vfs_only_url('repo2')
 
1784
        dest_bzrdir = BzrDir.create(dest_url)
 
1785
        dest_repo = dest_bzrdir.create_repository()
 
1786
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
 
1787
        self.assertTrue(isinstance(src_repo, RemoteRepository))
 
1788
        src_repo.copy_content_into(dest_repo)
 
1789
 
 
1790
 
 
1791
class _StubRealPackRepository(object):
 
1792
 
 
1793
    def __init__(self, calls):
 
1794
        self._pack_collection = _StubPackCollection(calls)
 
1795
 
 
1796
 
 
1797
class _StubPackCollection(object):
 
1798
 
 
1799
    def __init__(self, calls):
 
1800
        self.calls = calls
 
1801
 
 
1802
    def autopack(self):
 
1803
        self.calls.append(('pack collection autopack',))
 
1804
 
 
1805
    def reload_pack_names(self):
 
1806
        self.calls.append(('pack collection reload_pack_names',))
 
1807
 
 
1808
 
 
1809
class TestRemotePackRepositoryAutoPack(TestRemoteRepository):
 
1810
    """Tests for RemoteRepository.autopack implementation."""
 
1811
 
 
1812
    def test_ok(self):
 
1813
        """When the server returns 'ok' and there's no _real_repository, then
 
1814
        nothing else happens: the autopack method is done.
 
1815
        """
 
1816
        transport_path = 'quack'
 
1817
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1818
        client.add_expected_call(
 
1819
            'PackRepository.autopack', ('quack/',), 'success', ('ok',))
 
1820
        repo.autopack()
 
1821
        client.finished_test()
 
1822
 
 
1823
    def test_ok_with_real_repo(self):
 
1824
        """When the server returns 'ok' and there is a _real_repository, then
 
1825
        the _real_repository's reload_pack_name's method will be called.
 
1826
        """
 
1827
        transport_path = 'quack'
 
1828
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1829
        client.add_expected_call(
 
1830
            'PackRepository.autopack', ('quack/',),
 
1831
            'success', ('ok',))
 
1832
        repo._real_repository = _StubRealPackRepository(client._calls)
 
1833
        repo.autopack()
 
1834
        self.assertEqual(
 
1835
            [('call', 'PackRepository.autopack', ('quack/',)),
 
1836
             ('pack collection reload_pack_names',)],
 
1837
            client._calls)
 
1838
 
 
1839
    def test_backwards_compatibility(self):
 
1840
        """If the server does not recognise the PackRepository.autopack verb,
 
1841
        fallback to the real_repository's implementation.
 
1842
        """
 
1843
        transport_path = 'quack'
 
1844
        repo, client = self.setup_fake_client_and_repository(transport_path)
 
1845
        client.add_unknown_method_response('PackRepository.autopack')
 
1846
        def stub_ensure_real():
 
1847
            client._calls.append(('_ensure_real',))
 
1848
            repo._real_repository = _StubRealPackRepository(client._calls)
 
1849
        repo._ensure_real = stub_ensure_real
 
1850
        repo.autopack()
 
1851
        self.assertEqual(
 
1852
            [('call', 'PackRepository.autopack', ('quack/',)),
 
1853
             ('_ensure_real',),
 
1854
             ('pack collection autopack',)],
 
1855
            client._calls)
 
1856
 
 
1857
 
 
1858
class TestErrorTranslationBase(tests.TestCaseWithMemoryTransport):
 
1859
    """Base class for unit tests for bzrlib.remote._translate_error."""
 
1860
 
 
1861
    def translateTuple(self, error_tuple, **context):
 
1862
        """Call _translate_error with an ErrorFromSmartServer built from the
 
1863
        given error_tuple.
 
1864
 
 
1865
        :param error_tuple: A tuple of a smart server response, as would be
 
1866
            passed to an ErrorFromSmartServer.
 
1867
        :kwargs context: context items to call _translate_error with.
 
1868
 
 
1869
        :returns: The error raised by _translate_error.
 
1870
        """
 
1871
        # Raise the ErrorFromSmartServer before passing it as an argument,
 
1872
        # because _translate_error may need to re-raise it with a bare 'raise'
 
1873
        # statement.
 
1874
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
1875
        translated_error = self.translateErrorFromSmartServer(
 
1876
            server_error, **context)
 
1877
        return translated_error
 
1878
 
 
1879
    def translateErrorFromSmartServer(self, error_object, **context):
 
1880
        """Like translateTuple, but takes an already constructed
 
1881
        ErrorFromSmartServer rather than a tuple.
 
1882
        """
 
1883
        try:
 
1884
            raise error_object
 
1885
        except errors.ErrorFromSmartServer, server_error:
 
1886
            translated_error = self.assertRaises(
 
1887
                errors.BzrError, remote._translate_error, server_error,
 
1888
                **context)
 
1889
        return translated_error
 
1890
 
 
1891
 
 
1892
class TestErrorTranslationSuccess(TestErrorTranslationBase):
 
1893
    """Unit tests for bzrlib.remote._translate_error.
 
1894
 
 
1895
    Given an ErrorFromSmartServer (which has an error tuple from a smart
 
1896
    server) and some context, _translate_error raises more specific errors from
 
1897
    bzrlib.errors.
 
1898
 
 
1899
    This test case covers the cases where _translate_error succeeds in
 
1900
    translating an ErrorFromSmartServer to something better.  See
 
1901
    TestErrorTranslationRobustness for other cases.
 
1902
    """
 
1903
 
 
1904
    def test_NoSuchRevision(self):
 
1905
        branch = self.make_branch('')
 
1906
        revid = 'revid'
 
1907
        translated_error = self.translateTuple(
 
1908
            ('NoSuchRevision', revid), branch=branch)
 
1909
        expected_error = errors.NoSuchRevision(branch, revid)
 
1910
        self.assertEqual(expected_error, translated_error)
 
1911
 
 
1912
    def test_nosuchrevision(self):
 
1913
        repository = self.make_repository('')
 
1914
        revid = 'revid'
 
1915
        translated_error = self.translateTuple(
 
1916
            ('nosuchrevision', revid), repository=repository)
 
1917
        expected_error = errors.NoSuchRevision(repository, revid)
 
1918
        self.assertEqual(expected_error, translated_error)
 
1919
 
 
1920
    def test_nobranch(self):
 
1921
        bzrdir = self.make_bzrdir('')
 
1922
        translated_error = self.translateTuple(('nobranch',), bzrdir=bzrdir)
 
1923
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
 
1924
        self.assertEqual(expected_error, translated_error)
 
1925
 
 
1926
    def test_LockContention(self):
 
1927
        translated_error = self.translateTuple(('LockContention',))
 
1928
        expected_error = errors.LockContention('(remote lock)')
 
1929
        self.assertEqual(expected_error, translated_error)
 
1930
 
 
1931
    def test_UnlockableTransport(self):
 
1932
        bzrdir = self.make_bzrdir('')
 
1933
        translated_error = self.translateTuple(
 
1934
            ('UnlockableTransport',), bzrdir=bzrdir)
 
1935
        expected_error = errors.UnlockableTransport(bzrdir.root_transport)
 
1936
        self.assertEqual(expected_error, translated_error)
 
1937
 
 
1938
    def test_LockFailed(self):
 
1939
        lock = 'str() of a server lock'
 
1940
        why = 'str() of why'
 
1941
        translated_error = self.translateTuple(('LockFailed', lock, why))
 
1942
        expected_error = errors.LockFailed(lock, why)
 
1943
        self.assertEqual(expected_error, translated_error)
 
1944
 
 
1945
    def test_TokenMismatch(self):
 
1946
        token = 'a lock token'
 
1947
        translated_error = self.translateTuple(('TokenMismatch',), token=token)
 
1948
        expected_error = errors.TokenMismatch(token, '(remote token)')
 
1949
        self.assertEqual(expected_error, translated_error)
 
1950
 
 
1951
    def test_Diverged(self):
 
1952
        branch = self.make_branch('a')
 
1953
        other_branch = self.make_branch('b')
 
1954
        translated_error = self.translateTuple(
 
1955
            ('Diverged',), branch=branch, other_branch=other_branch)
 
1956
        expected_error = errors.DivergedBranches(branch, other_branch)
 
1957
        self.assertEqual(expected_error, translated_error)
 
1958
 
 
1959
    def test_ReadError_no_args(self):
 
1960
        path = 'a path'
 
1961
        translated_error = self.translateTuple(('ReadError',), path=path)
 
1962
        expected_error = errors.ReadError(path)
 
1963
        self.assertEqual(expected_error, translated_error)
 
1964
 
 
1965
    def test_ReadError(self):
 
1966
        path = 'a path'
 
1967
        translated_error = self.translateTuple(('ReadError', path))
 
1968
        expected_error = errors.ReadError(path)
 
1969
        self.assertEqual(expected_error, translated_error)
 
1970
 
 
1971
    def test_PermissionDenied_no_args(self):
 
1972
        path = 'a path'
 
1973
        translated_error = self.translateTuple(('PermissionDenied',), path=path)
 
1974
        expected_error = errors.PermissionDenied(path)
 
1975
        self.assertEqual(expected_error, translated_error)
 
1976
 
 
1977
    def test_PermissionDenied_one_arg(self):
 
1978
        path = 'a path'
 
1979
        translated_error = self.translateTuple(('PermissionDenied', path))
 
1980
        expected_error = errors.PermissionDenied(path)
 
1981
        self.assertEqual(expected_error, translated_error)
 
1982
 
 
1983
    def test_PermissionDenied_one_arg_and_context(self):
 
1984
        """Given a choice between a path from the local context and a path on
 
1985
        the wire, _translate_error prefers the path from the local context.
 
1986
        """
 
1987
        local_path = 'local path'
 
1988
        remote_path = 'remote path'
 
1989
        translated_error = self.translateTuple(
 
1990
            ('PermissionDenied', remote_path), path=local_path)
 
1991
        expected_error = errors.PermissionDenied(local_path)
 
1992
        self.assertEqual(expected_error, translated_error)
 
1993
 
 
1994
    def test_PermissionDenied_two_args(self):
 
1995
        path = 'a path'
 
1996
        extra = 'a string with extra info'
 
1997
        translated_error = self.translateTuple(
 
1998
            ('PermissionDenied', path, extra))
 
1999
        expected_error = errors.PermissionDenied(path, extra)
 
2000
        self.assertEqual(expected_error, translated_error)
 
2001
 
 
2002
 
 
2003
class TestErrorTranslationRobustness(TestErrorTranslationBase):
 
2004
    """Unit tests for bzrlib.remote._translate_error's robustness.
 
2005
 
 
2006
    TestErrorTranslationSuccess is for cases where _translate_error can
 
2007
    translate successfully.  This class about how _translate_err behaves when
 
2008
    it fails to translate: it re-raises the original error.
 
2009
    """
 
2010
 
 
2011
    def test_unrecognised_server_error(self):
 
2012
        """If the error code from the server is not recognised, the original
 
2013
        ErrorFromSmartServer is propagated unmodified.
 
2014
        """
 
2015
        error_tuple = ('An unknown error tuple',)
 
2016
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2017
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2018
        expected_error = errors.UnknownErrorFromSmartServer(server_error)
 
2019
        self.assertEqual(expected_error, translated_error)
 
2020
 
 
2021
    def test_context_missing_a_key(self):
 
2022
        """In case of a bug in the client, or perhaps an unexpected response
 
2023
        from a server, _translate_error returns the original error tuple from
 
2024
        the server and mutters a warning.
 
2025
        """
 
2026
        # To translate a NoSuchRevision error _translate_error needs a 'branch'
 
2027
        # in the context dict.  So let's give it an empty context dict instead
 
2028
        # to exercise its error recovery.
 
2029
        empty_context = {}
 
2030
        error_tuple = ('NoSuchRevision', 'revid')
 
2031
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2032
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2033
        self.assertEqual(server_error, translated_error)
 
2034
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
2035
        # been muttered to the log file for developer to look at.
 
2036
        self.assertContainsRe(
 
2037
            self._get_log(keep_log_file=True),
 
2038
            "Missing key 'branch' in context")
 
2039
 
 
2040
    def test_path_missing(self):
 
2041
        """Some translations (PermissionDenied, ReadError) can determine the
 
2042
        'path' variable from either the wire or the local context.  If neither
 
2043
        has it, then an error is raised.
 
2044
        """
 
2045
        error_tuple = ('ReadError',)
 
2046
        server_error = errors.ErrorFromSmartServer(error_tuple)
 
2047
        translated_error = self.translateErrorFromSmartServer(server_error)
 
2048
        self.assertEqual(server_error, translated_error)
 
2049
        # In addition to re-raising ErrorFromSmartServer, some debug info has
 
2050
        # been muttered to the log file for developer to look at.
 
2051
        self.assertContainsRe(
 
2052
            self._get_log(keep_log_file=True), "Missing key 'path' in context")
 
2053
 
 
2054
 
 
2055
class TestStacking(tests.TestCaseWithTransport):
 
2056
    """Tests for operations on stacked remote repositories.
 
2057
 
 
2058
    The underlying format type must support stacking.
 
2059
    """
 
2060
 
 
2061
    def test_access_stacked_remote(self):
 
2062
        # based on <http://launchpad.net/bugs/261315>
 
2063
        # make a branch stacked on another repository containing an empty
 
2064
        # revision, then open it over hpss - we should be able to see that
 
2065
        # revision.
 
2066
        base_transport = self.get_transport()
 
2067
        base_builder = self.make_branch_builder('base', format='1.6')
 
2068
        base_builder.start_series()
 
2069
        base_revid = base_builder.build_snapshot('rev-id', None,
 
2070
            [('add', ('', None, 'directory', None))],
 
2071
            'message')
 
2072
        base_builder.finish_series()
 
2073
        stacked_branch = self.make_branch('stacked', format='1.6')
 
2074
        stacked_branch.set_stacked_on_url('../base')
 
2075
        # start a server looking at this
 
2076
        smart_server = server.SmartTCPServer_for_testing()
 
2077
        smart_server.setUp()
 
2078
        self.addCleanup(smart_server.tearDown)
 
2079
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
 
2080
        # can get its branch and repository
 
2081
        remote_branch = remote_bzrdir.open_branch()
 
2082
        remote_repo = remote_branch.repository
 
2083
        remote_repo.lock_read()
 
2084
        try:
 
2085
            # it should have an appropriate fallback repository, which should also
 
2086
            # be a RemoteRepository
 
2087
            self.assertEquals(len(remote_repo._fallback_repositories), 1)
 
2088
            self.assertIsInstance(remote_repo._fallback_repositories[0],
 
2089
                RemoteRepository)
 
2090
            # and it has the revision committed to the underlying repository;
 
2091
            # these have varying implementations so we try several of them
 
2092
            self.assertTrue(remote_repo.has_revisions([base_revid]))
 
2093
            self.assertTrue(remote_repo.has_revision(base_revid))
 
2094
            self.assertEqual(remote_repo.get_revision(base_revid).message,
 
2095
                'message')
 
2096
        finally:
 
2097
            remote_repo.unlock()
 
2098
 
 
2099
    def prepare_stacked_remote_branch(self):
 
2100
        smart_server = server.SmartTCPServer_for_testing()
 
2101
        smart_server.setUp()
 
2102
        self.addCleanup(smart_server.tearDown)
 
2103
        tree1 = self.make_branch_and_tree('tree1')
 
2104
        tree1.commit('rev1', rev_id='rev1')
 
2105
        tree2 = self.make_branch_and_tree('tree2', format='1.6')
 
2106
        tree2.branch.set_stacked_on_url(tree1.branch.base)
 
2107
        branch2 = Branch.open(smart_server.get_url() + '/tree2')
 
2108
        branch2.lock_read()
 
2109
        self.addCleanup(branch2.unlock)
 
2110
        return branch2
 
2111
 
 
2112
    def test_stacked_get_parent_map(self):
 
2113
        # the public implementation of get_parent_map obeys stacking
 
2114
        branch = self.prepare_stacked_remote_branch()
 
2115
        repo = branch.repository
 
2116
        self.assertEqual(['rev1'], repo.get_parent_map(['rev1']).keys())
 
2117
 
 
2118
    def test_unstacked_get_parent_map(self):
 
2119
        # _unstacked_provider.get_parent_map ignores stacking
 
2120
        branch = self.prepare_stacked_remote_branch()
 
2121
        provider = branch.repository._unstacked_provider
 
2122
        self.assertEqual([], provider.get_parent_map(['rev1']).keys())
 
2123
 
 
2124
 
 
2125
class TestRemoteBranchEffort(tests.TestCaseWithTransport):
 
2126
 
 
2127
    def setUp(self):
 
2128
        super(TestRemoteBranchEffort, self).setUp()
 
2129
        # Create a smart server that publishes whatever the backing VFS server
 
2130
        # does.
 
2131
        self.smart_server = server.SmartTCPServer_for_testing()
 
2132
        self.smart_server.setUp(self.get_server())
 
2133
        self.addCleanup(self.smart_server.tearDown)
 
2134
        # Log all HPSS calls into self.hpss_calls.
 
2135
        _SmartClient.hooks.install_named_hook(
 
2136
            'call', self.capture_hpss_call, None)
 
2137
        self.hpss_calls = []
 
2138
 
 
2139
    def capture_hpss_call(self, params):
 
2140
        self.hpss_calls.append(params.method)
 
2141
 
 
2142
    def test_copy_content_into_avoids_revision_history(self):
 
2143
        local = self.make_branch('local')
 
2144
        remote_backing_tree = self.make_branch_and_tree('remote')
 
2145
        remote_backing_tree.commit("Commit.")
 
2146
        remote_branch_url = self.smart_server.get_url() + 'remote'
 
2147
        remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
 
2148
        local.repository.fetch(remote_branch.repository)
 
2149
        self.hpss_calls = []
 
2150
        remote_branch.copy_content_into(local)
 
2151
        self.assertFalse('Branch.revision_history' in self.hpss_calls)