/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: Canonical.com Patch Queue Manager
  • Date: 2008-03-28 06:42:20 UTC
  • mfrom: (3287.6.9 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080328064220-ongijg78bfqhvbay
Deprecate a number of VersionedFile method calls,
        and Repository.get_revision_graph. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 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
    errors,
 
31
    graph,
 
32
    pack,
 
33
    remote,
 
34
    repository,
 
35
    tests,
 
36
    )
 
37
from bzrlib.branch import Branch
 
38
from bzrlib.bzrdir import BzrDir, BzrDirFormat
 
39
from bzrlib.remote import (
 
40
    RemoteBranch,
 
41
    RemoteBzrDir,
 
42
    RemoteBzrDirFormat,
 
43
    RemoteRepository,
 
44
    )
 
45
from bzrlib.revision import NULL_REVISION
 
46
from bzrlib.smart import server, medium
 
47
from bzrlib.smart.client import _SmartClient
 
48
from bzrlib.symbol_versioning import one_four
 
49
from bzrlib.transport.memory import MemoryTransport
 
50
from bzrlib.transport.remote import RemoteTransport
 
51
 
 
52
 
 
53
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
 
54
 
 
55
    def setUp(self):
 
56
        self.transport_server = server.SmartTCPServer_for_testing
 
57
        super(BasicRemoteObjectTests, self).setUp()
 
58
        self.transport = self.get_transport()
 
59
        # make a branch that can be opened over the smart transport
 
60
        self.local_wt = BzrDir.create_standalone_workingtree('.')
 
61
 
 
62
    def tearDown(self):
 
63
        self.transport.disconnect()
 
64
        tests.TestCaseWithTransport.tearDown(self)
 
65
 
 
66
    def test_create_remote_bzrdir(self):
 
67
        b = remote.RemoteBzrDir(self.transport)
 
68
        self.assertIsInstance(b, BzrDir)
 
69
 
 
70
    def test_open_remote_branch(self):
 
71
        # open a standalone branch in the working directory
 
72
        b = remote.RemoteBzrDir(self.transport)
 
73
        branch = b.open_branch()
 
74
        self.assertIsInstance(branch, Branch)
 
75
 
 
76
    def test_remote_repository(self):
 
77
        b = BzrDir.open_from_transport(self.transport)
 
78
        repo = b.open_repository()
 
79
        revid = u'\xc823123123'.encode('utf8')
 
80
        self.assertFalse(repo.has_revision(revid))
 
81
        self.local_wt.commit(message='test commit', rev_id=revid)
 
82
        self.assertTrue(repo.has_revision(revid))
 
83
 
 
84
    def test_remote_branch_revision_history(self):
 
85
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
86
        self.assertEqual([], b.revision_history())
 
87
        r1 = self.local_wt.commit('1st commit')
 
88
        r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
 
89
        self.assertEqual([r1, r2], b.revision_history())
 
90
 
 
91
    def test_find_correct_format(self):
 
92
        """Should open a RemoteBzrDir over a RemoteTransport"""
 
93
        fmt = BzrDirFormat.find_format(self.transport)
 
94
        self.assertTrue(RemoteBzrDirFormat
 
95
                        in BzrDirFormat._control_server_formats)
 
96
        self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
 
97
 
 
98
    def test_open_detected_smart_format(self):
 
99
        fmt = BzrDirFormat.find_format(self.transport)
 
100
        d = fmt.open(self.transport)
 
101
        self.assertIsInstance(d, BzrDir)
 
102
 
 
103
    def test_remote_branch_repr(self):
 
104
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
105
        self.assertStartsWith(str(b), 'RemoteBranch(')
 
106
 
 
107
 
 
108
class FakeProtocol(object):
 
109
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
 
110
 
 
111
    def __init__(self, body, fake_client):
 
112
        self.body = body
 
113
        self._body_buffer = None
 
114
        self._fake_client = fake_client
 
115
 
 
116
    def read_body_bytes(self, count=-1):
 
117
        if self._body_buffer is None:
 
118
            self._body_buffer = StringIO(self.body)
 
119
        bytes = self._body_buffer.read(count)
 
120
        if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
 
121
            self._fake_client.expecting_body = False
 
122
        return bytes
 
123
 
 
124
    def cancel_read_body(self):
 
125
        self._fake_client.expecting_body = False
 
126
 
 
127
    def read_streamed_body(self):
 
128
        return self.body
 
129
 
 
130
 
 
131
class FakeClient(_SmartClient):
 
132
    """Lookalike for _SmartClient allowing testing."""
 
133
    
 
134
    def __init__(self, responses, fake_medium_base='fake base'):
 
135
        """Create a FakeClient.
 
136
 
 
137
        :param responses: A list of response-tuple, body-data pairs to be sent
 
138
            back to callers.
 
139
        """
 
140
        self.responses = responses
 
141
        self._calls = []
 
142
        self.expecting_body = False
 
143
        _SmartClient.__init__(self, FakeMedium(fake_medium_base, self._calls))
 
144
 
 
145
    def call(self, method, *args):
 
146
        self._calls.append(('call', method, args))
 
147
        return self.responses.pop(0)[0]
 
148
 
 
149
    def call_expecting_body(self, method, *args):
 
150
        self._calls.append(('call_expecting_body', method, args))
 
151
        result = self.responses.pop(0)
 
152
        self.expecting_body = True
 
153
        return result[0], FakeProtocol(result[1], self)
 
154
 
 
155
    def call_with_body_bytes_expecting_body(self, method, args, body):
 
156
        self._calls.append(('call_with_body_bytes_expecting_body', method,
 
157
            args, body))
 
158
        result = self.responses.pop(0)
 
159
        self.expecting_body = True
 
160
        return result[0], FakeProtocol(result[1], self)
 
161
 
 
162
 
 
163
class FakeMedium(object):
 
164
 
 
165
    def __init__(self, base, client_calls):
 
166
        self.base = base
 
167
        self.connection = FakeConnection(client_calls)
 
168
        self._client_calls = client_calls
 
169
 
 
170
 
 
171
class FakeConnection(object):
 
172
 
 
173
    def __init__(self, client_calls):
 
174
        self._remote_is_at_least_1_2 = True
 
175
        self._client_calls = client_calls
 
176
 
 
177
    def disconnect(self):
 
178
        self._client_calls.append(('disconnect medium',))
 
179
 
 
180
 
 
181
class TestVfsHas(tests.TestCase):
 
182
 
 
183
    def test_unicode_path(self):
 
184
        client = FakeClient([(('yes',), )], '/')
 
185
        transport = RemoteTransport('bzr://localhost/', _client=client)
 
186
        filename = u'/hell\u00d8'.encode('utf8')
 
187
        result = transport.has(filename)
 
188
        self.assertEqual(
 
189
            [('call', 'has', (filename,))],
 
190
            client._calls)
 
191
        self.assertTrue(result)
 
192
 
 
193
 
 
194
class TestBzrDirOpenBranch(tests.TestCase):
 
195
 
 
196
    def test_branch_present(self):
 
197
        transport = MemoryTransport()
 
198
        transport.mkdir('quack')
 
199
        transport = transport.clone('quack')
 
200
        client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no', 'no'), )],
 
201
                            transport.base)
 
202
        bzrdir = RemoteBzrDir(transport, _client=client)
 
203
        result = bzrdir.open_branch()
 
204
        self.assertEqual(
 
205
            [('call', 'BzrDir.open_branch', ('quack/',)),
 
206
             ('call', 'BzrDir.find_repositoryV2', ('quack/',))],
 
207
            client._calls)
 
208
        self.assertIsInstance(result, RemoteBranch)
 
209
        self.assertEqual(bzrdir, result.bzrdir)
 
210
 
 
211
    def test_branch_missing(self):
 
212
        transport = MemoryTransport()
 
213
        transport.mkdir('quack')
 
214
        transport = transport.clone('quack')
 
215
        client = FakeClient([(('nobranch',), )], transport.base)
 
216
        bzrdir = RemoteBzrDir(transport, _client=client)
 
217
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
 
218
        self.assertEqual(
 
219
            [('call', 'BzrDir.open_branch', ('quack/',))],
 
220
            client._calls)
 
221
 
 
222
    def test__get_tree_branch(self):
 
223
        # _get_tree_branch is a form of open_branch, but it should only ask for
 
224
        # branch opening, not any other network requests.
 
225
        calls = []
 
226
        def open_branch():
 
227
            calls.append("Called")
 
228
            return "a-branch"
 
229
        transport = MemoryTransport()
 
230
        # no requests on the network - catches other api calls being made.
 
231
        client = FakeClient([], transport.base)
 
232
        bzrdir = RemoteBzrDir(transport, _client=client)
 
233
        # patch the open_branch call to record that it was called.
 
234
        bzrdir.open_branch = open_branch
 
235
        self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
 
236
        self.assertEqual(["Called"], calls)
 
237
        self.assertEqual([], client._calls)
 
238
 
 
239
    def test_url_quoting_of_path(self):
 
240
        # Relpaths on the wire should not be URL-escaped.  So "~" should be
 
241
        # transmitted as "~", not "%7E".
 
242
        transport = RemoteTransport('bzr://localhost/~hello/')
 
243
        client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no', 'no'), )],
 
244
                            transport.base)
 
245
        bzrdir = RemoteBzrDir(transport, _client=client)
 
246
        result = bzrdir.open_branch()
 
247
        self.assertEqual(
 
248
            [('call', 'BzrDir.open_branch', ('~hello/',)),
 
249
             ('call', 'BzrDir.find_repositoryV2', ('~hello/',))],
 
250
            client._calls)
 
251
 
 
252
    def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
 
253
        transport = MemoryTransport()
 
254
        transport.mkdir('quack')
 
255
        transport = transport.clone('quack')
 
256
        if rich_root:
 
257
            rich_response = 'yes'
 
258
        else:
 
259
            rich_response = 'no'
 
260
        if subtrees:
 
261
            subtree_response = 'yes'
 
262
        else:
 
263
            subtree_response = 'no'
 
264
        client = FakeClient(
 
265
            [(('ok', '', rich_response, subtree_response, external_lookup), ),],
 
266
            transport.base)
 
267
        bzrdir = RemoteBzrDir(transport, _client=client)
 
268
        result = bzrdir.open_repository()
 
269
        self.assertEqual(
 
270
            [('call', 'BzrDir.find_repositoryV2', ('quack/',))],
 
271
            client._calls)
 
272
        self.assertIsInstance(result, RemoteRepository)
 
273
        self.assertEqual(bzrdir, result.bzrdir)
 
274
        self.assertEqual(rich_root, result._format.rich_root_data)
 
275
        self.assertEqual(subtrees, result._format.supports_tree_reference)
 
276
 
 
277
    def test_open_repository_sets_format_attributes(self):
 
278
        self.check_open_repository(True, True)
 
279
        self.check_open_repository(False, True)
 
280
        self.check_open_repository(True, False)
 
281
        self.check_open_repository(False, False)
 
282
        self.check_open_repository(False, False, 'yes')
 
283
 
 
284
    def test_old_server(self):
 
285
        """RemoteBzrDirFormat should fail to probe if the server version is too
 
286
        old.
 
287
        """
 
288
        self.assertRaises(errors.NotBranchError,
 
289
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
 
290
 
 
291
 
 
292
class OldSmartClient(object):
 
293
    """A fake smart client for test_old_version that just returns a version one
 
294
    response to the 'hello' (query version) command.
 
295
    """
 
296
 
 
297
    def get_request(self):
 
298
        input_file = StringIO('ok\x011\n')
 
299
        output_file = StringIO()
 
300
        client_medium = medium.SmartSimplePipesClientMedium(
 
301
            input_file, output_file)
 
302
        return medium.SmartClientStreamMediumRequest(client_medium)
 
303
 
 
304
    def protocol_version(self):
 
305
        return 1
 
306
 
 
307
 
 
308
class OldServerTransport(object):
 
309
    """A fake transport for test_old_server that reports it's smart server
 
310
    protocol version as version one.
 
311
    """
 
312
 
 
313
    def __init__(self):
 
314
        self.base = 'fake:'
 
315
 
 
316
    def get_smart_client(self):
 
317
        return OldSmartClient()
 
318
 
 
319
 
 
320
class TestBranchLastRevisionInfo(tests.TestCase):
 
321
 
 
322
    def test_empty_branch(self):
 
323
        # in an empty branch we decode the response properly
 
324
        transport = MemoryTransport()
 
325
        client = FakeClient([(('ok', '0', 'null:'), )], transport.base)
 
326
        transport.mkdir('quack')
 
327
        transport = transport.clone('quack')
 
328
        # we do not want bzrdir to make any remote calls
 
329
        bzrdir = RemoteBzrDir(transport, _client=False)
 
330
        branch = RemoteBranch(bzrdir, None, _client=client)
 
331
        result = branch.last_revision_info()
 
332
 
 
333
        self.assertEqual(
 
334
            [('call', 'Branch.last_revision_info', ('quack/',))],
 
335
            client._calls)
 
336
        self.assertEqual((0, NULL_REVISION), result)
 
337
 
 
338
    def test_non_empty_branch(self):
 
339
        # in a non-empty branch we also decode the response properly
 
340
        revid = u'\xc8'.encode('utf8')
 
341
        transport = MemoryTransport()
 
342
        client = FakeClient([(('ok', '2', revid), )], transport.base)
 
343
        transport.mkdir('kwaak')
 
344
        transport = transport.clone('kwaak')
 
345
        # we do not want bzrdir to make any remote calls
 
346
        bzrdir = RemoteBzrDir(transport, _client=False)
 
347
        branch = RemoteBranch(bzrdir, None, _client=client)
 
348
        result = branch.last_revision_info()
 
349
 
 
350
        self.assertEqual(
 
351
            [('call', 'Branch.last_revision_info', ('kwaak/',))],
 
352
            client._calls)
 
353
        self.assertEqual((2, revid), result)
 
354
 
 
355
 
 
356
class TestBranchSetLastRevision(tests.TestCase):
 
357
 
 
358
    def test_set_empty(self):
 
359
        # set_revision_history([]) is translated to calling
 
360
        # Branch.set_last_revision(path, '') on the wire.
 
361
        transport = MemoryTransport()
 
362
        transport.mkdir('branch')
 
363
        transport = transport.clone('branch')
 
364
 
 
365
        client = FakeClient([
 
366
            # lock_write
 
367
            (('ok', 'branch token', 'repo token'), ),
 
368
            # set_last_revision
 
369
            (('ok',), ),
 
370
            # unlock
 
371
            (('ok',), )],
 
372
            transport.base)
 
373
        bzrdir = RemoteBzrDir(transport, _client=False)
 
374
        branch = RemoteBranch(bzrdir, None, _client=client)
 
375
        # This is a hack to work around the problem that RemoteBranch currently
 
376
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
377
        branch._ensure_real = lambda: None
 
378
        branch.lock_write()
 
379
        client._calls = []
 
380
        result = branch.set_revision_history([])
 
381
        self.assertEqual(
 
382
            [('call', 'Branch.set_last_revision',
 
383
                ('branch/', 'branch token', 'repo token', 'null:'))],
 
384
            client._calls)
 
385
        branch.unlock()
 
386
        self.assertEqual(None, result)
 
387
 
 
388
    def test_set_nonempty(self):
 
389
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
 
390
        # Branch.set_last_revision(path, rev-idN) on the wire.
 
391
        transport = MemoryTransport()
 
392
        transport.mkdir('branch')
 
393
        transport = transport.clone('branch')
 
394
 
 
395
        client = FakeClient([
 
396
            # lock_write
 
397
            (('ok', 'branch token', 'repo token'), ),
 
398
            # set_last_revision
 
399
            (('ok',), ),
 
400
            # unlock
 
401
            (('ok',), )],
 
402
            transport.base)
 
403
        bzrdir = RemoteBzrDir(transport, _client=False)
 
404
        branch = RemoteBranch(bzrdir, None, _client=client)
 
405
        # This is a hack to work around the problem that RemoteBranch currently
 
406
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
407
        branch._ensure_real = lambda: None
 
408
        # Lock the branch, reset the record of remote calls.
 
409
        branch.lock_write()
 
410
        client._calls = []
 
411
 
 
412
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
 
413
        self.assertEqual(
 
414
            [('call', 'Branch.set_last_revision',
 
415
                ('branch/', 'branch token', 'repo token', 'rev-id2'))],
 
416
            client._calls)
 
417
        branch.unlock()
 
418
        self.assertEqual(None, result)
 
419
 
 
420
    def test_no_such_revision(self):
 
421
        # A response of 'NoSuchRevision' is translated into an exception.
 
422
        client = FakeClient([
 
423
            # lock_write
 
424
            (('ok', 'branch token', 'repo token'), ),
 
425
            # set_last_revision
 
426
            (('NoSuchRevision', 'rev-id'), ),
 
427
            # unlock
 
428
            (('ok',), )])
 
429
        transport = MemoryTransport()
 
430
        transport.mkdir('branch')
 
431
        transport = transport.clone('branch')
 
432
 
 
433
        bzrdir = RemoteBzrDir(transport, _client=False)
 
434
        branch = RemoteBranch(bzrdir, None, _client=client)
 
435
        branch._ensure_real = lambda: None
 
436
        branch.lock_write()
 
437
        client._calls = []
 
438
 
 
439
        self.assertRaises(
 
440
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
 
441
        branch.unlock()
 
442
 
 
443
 
 
444
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
 
445
    """Test branch.control_files api munging...
 
446
 
 
447
    We special case RemoteBranch.control_files.get('branch.conf') to
 
448
    call a specific API so that RemoteBranch's can intercept configuration
 
449
    file reading, allowing them to signal to the client about things like
 
450
    'email is configured for commits'.
 
451
    """
 
452
 
 
453
    def test_get_branch_conf(self):
 
454
        # in an empty branch we decode the response properly
 
455
        client = FakeClient([(('ok', ), 'config file body')], self.get_url())
 
456
        # we need to make a real branch because the remote_branch.control_files
 
457
        # will trigger _ensure_real.
 
458
        branch = self.make_branch('quack')
 
459
        transport = branch.bzrdir.root_transport
 
460
        # we do not want bzrdir to make any remote calls
 
461
        bzrdir = RemoteBzrDir(transport, _client=False)
 
462
        branch = RemoteBranch(bzrdir, None, _client=client)
 
463
        result = branch.control_files.get('branch.conf')
 
464
        self.assertEqual(
 
465
            [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
 
466
            client._calls)
 
467
        self.assertEqual('config file body', result.read())
 
468
 
 
469
 
 
470
class TestBranchLockWrite(tests.TestCase):
 
471
 
 
472
    def test_lock_write_unlockable(self):
 
473
        transport = MemoryTransport()
 
474
        client = FakeClient([(('UnlockableTransport', ), '')], transport.base)
 
475
        transport.mkdir('quack')
 
476
        transport = transport.clone('quack')
 
477
        # we do not want bzrdir to make any remote calls
 
478
        bzrdir = RemoteBzrDir(transport, _client=False)
 
479
        branch = RemoteBranch(bzrdir, None, _client=client)
 
480
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
 
481
        self.assertEqual(
 
482
            [('call', 'Branch.lock_write', ('quack/', '', ''))],
 
483
            client._calls)
 
484
 
 
485
 
 
486
class TestTransportIsReadonly(tests.TestCase):
 
487
 
 
488
    def test_true(self):
 
489
        client = FakeClient([(('yes',), '')])
 
490
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
491
                                    _client=client)
 
492
        self.assertEqual(True, transport.is_readonly())
 
493
        self.assertEqual(
 
494
            [('call', 'Transport.is_readonly', ())],
 
495
            client._calls)
 
496
 
 
497
    def test_false(self):
 
498
        client = FakeClient([(('no',), '')])
 
499
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
500
                                    _client=client)
 
501
        self.assertEqual(False, transport.is_readonly())
 
502
        self.assertEqual(
 
503
            [('call', 'Transport.is_readonly', ())],
 
504
            client._calls)
 
505
 
 
506
    def test_error_from_old_server(self):
 
507
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
 
508
        
 
509
        Clients should treat it as a "no" response, because is_readonly is only
 
510
        advisory anyway (a transport could be read-write, but then the
 
511
        underlying filesystem could be readonly anyway).
 
512
        """
 
513
        client = FakeClient([(
 
514
            ('error', "Generic bzr smart protocol error: "
 
515
                      "bad request 'Transport.is_readonly'"), '')])
 
516
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
517
                                    _client=client)
 
518
        self.assertEqual(False, transport.is_readonly())
 
519
        self.assertEqual(
 
520
            [('call', 'Transport.is_readonly', ())],
 
521
            client._calls)
 
522
 
 
523
    def test_error_from_old_0_11_server(self):
 
524
        """Same as test_error_from_old_server, but with the slightly different
 
525
        error message from bzr 0.11 servers.
 
526
        """
 
527
        client = FakeClient([(
 
528
            ('error', "Generic bzr smart protocol error: "
 
529
                      "bad request u'Transport.is_readonly'"), '')])
 
530
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
531
                                    _client=client)
 
532
        self.assertEqual(False, transport.is_readonly())
 
533
        self.assertEqual(
 
534
            [('call', 'Transport.is_readonly', ())],
 
535
            client._calls)
 
536
 
 
537
 
 
538
class TestRemoteRepository(tests.TestCase):
 
539
    """Base for testing RemoteRepository protocol usage.
 
540
    
 
541
    These tests contain frozen requests and responses.  We want any changes to 
 
542
    what is sent or expected to be require a thoughtful update to these tests
 
543
    because they might break compatibility with different-versioned servers.
 
544
    """
 
545
 
 
546
    def setup_fake_client_and_repository(self, responses, transport_path):
 
547
        """Create the fake client and repository for testing with.
 
548
        
 
549
        There's no real server here; we just have canned responses sent
 
550
        back one by one.
 
551
        
 
552
        :param transport_path: Path below the root of the MemoryTransport
 
553
            where the repository will be created.
 
554
        """
 
555
        transport = MemoryTransport()
 
556
        transport.mkdir(transport_path)
 
557
        client = FakeClient(responses, transport.base)
 
558
        transport = transport.clone(transport_path)
 
559
        # we do not want bzrdir to make any remote calls
 
560
        bzrdir = RemoteBzrDir(transport, _client=False)
 
561
        repo = RemoteRepository(bzrdir, None, _client=client)
 
562
        return repo, client
 
563
 
 
564
 
 
565
class TestRepositoryGatherStats(TestRemoteRepository):
 
566
 
 
567
    def test_revid_none(self):
 
568
        # ('ok',), body with revisions and size
 
569
        responses = [(('ok', ), 'revisions: 2\nsize: 18\n')]
 
570
        transport_path = 'quack'
 
571
        repo, client = self.setup_fake_client_and_repository(
 
572
            responses, transport_path)
 
573
        result = repo.gather_stats(None)
 
574
        self.assertEqual(
 
575
            [('call_expecting_body', 'Repository.gather_stats',
 
576
             ('quack/','','no'))],
 
577
            client._calls)
 
578
        self.assertEqual({'revisions': 2, 'size': 18}, result)
 
579
 
 
580
    def test_revid_no_committers(self):
 
581
        # ('ok',), body without committers
 
582
        responses = [(('ok', ),
 
583
                      'firstrev: 123456.300 3600\n'
 
584
                      'latestrev: 654231.400 0\n'
 
585
                      'revisions: 2\n'
 
586
                      'size: 18\n')]
 
587
        transport_path = 'quick'
 
588
        revid = u'\xc8'.encode('utf8')
 
589
        repo, client = self.setup_fake_client_and_repository(
 
590
            responses, transport_path)
 
591
        result = repo.gather_stats(revid)
 
592
        self.assertEqual(
 
593
            [('call_expecting_body', 'Repository.gather_stats',
 
594
              ('quick/', revid, 'no'))],
 
595
            client._calls)
 
596
        self.assertEqual({'revisions': 2, 'size': 18,
 
597
                          'firstrev': (123456.300, 3600),
 
598
                          'latestrev': (654231.400, 0),},
 
599
                         result)
 
600
 
 
601
    def test_revid_with_committers(self):
 
602
        # ('ok',), body with committers
 
603
        responses = [(('ok', ),
 
604
                      'committers: 128\n'
 
605
                      'firstrev: 123456.300 3600\n'
 
606
                      'latestrev: 654231.400 0\n'
 
607
                      'revisions: 2\n'
 
608
                      'size: 18\n')]
 
609
        transport_path = 'buick'
 
610
        revid = u'\xc8'.encode('utf8')
 
611
        repo, client = self.setup_fake_client_and_repository(
 
612
            responses, transport_path)
 
613
        result = repo.gather_stats(revid, True)
 
614
        self.assertEqual(
 
615
            [('call_expecting_body', 'Repository.gather_stats',
 
616
              ('buick/', revid, 'yes'))],
 
617
            client._calls)
 
618
        self.assertEqual({'revisions': 2, 'size': 18,
 
619
                          'committers': 128,
 
620
                          'firstrev': (123456.300, 3600),
 
621
                          'latestrev': (654231.400, 0),},
 
622
                         result)
 
623
 
 
624
 
 
625
class TestRepositoryGetGraph(TestRemoteRepository):
 
626
 
 
627
    def test_get_graph(self):
 
628
        # get_graph returns a graph with the repository as the
 
629
        # parents_provider.
 
630
        responses = []
 
631
        transport_path = 'quack'
 
632
        repo, client = self.setup_fake_client_and_repository(
 
633
            responses, transport_path)
 
634
        graph = repo.get_graph()
 
635
        self.assertEqual(graph._parents_provider, repo)
 
636
 
 
637
 
 
638
class TestRepositoryGetParentMap(TestRemoteRepository):
 
639
 
 
640
    def test_get_parent_map_caching(self):
 
641
        # get_parent_map returns from cache until unlock()
 
642
        # setup a reponse with two revisions
 
643
        r1 = u'\u0e33'.encode('utf8')
 
644
        r2 = u'\u0dab'.encode('utf8')
 
645
        lines = [' '.join([r2, r1]), r1]
 
646
        encoded_body = bz2.compress('\n'.join(lines))
 
647
        responses = [(('ok', ), encoded_body), (('ok', ), encoded_body)]
 
648
 
 
649
        transport_path = 'quack'
 
650
        repo, client = self.setup_fake_client_and_repository(
 
651
            responses, transport_path)
 
652
        repo.lock_read()
 
653
        graph = repo.get_graph()
 
654
        parents = graph.get_parent_map([r2])
 
655
        self.assertEqual({r2: (r1,)}, parents)
 
656
        # locking and unlocking deeper should not reset
 
657
        repo.lock_read()
 
658
        repo.unlock()
 
659
        parents = graph.get_parent_map([r1])
 
660
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
661
        self.assertEqual(
 
662
            [('call_with_body_bytes_expecting_body',
 
663
              'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
 
664
            client._calls)
 
665
        repo.unlock()
 
666
        # now we call again, and it should use the second response.
 
667
        repo.lock_read()
 
668
        graph = repo.get_graph()
 
669
        parents = graph.get_parent_map([r1])
 
670
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
671
        self.assertEqual(
 
672
            [('call_with_body_bytes_expecting_body',
 
673
              'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
 
674
             ('call_with_body_bytes_expecting_body',
 
675
              'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
 
676
            ],
 
677
            client._calls)
 
678
        repo.unlock()
 
679
 
 
680
    def test_get_parent_map_reconnects_if_unknown_method(self):
 
681
        error_msg = (
 
682
            "Generic bzr smart protocol error: "
 
683
            "bad request 'Repository.get_parent_map'")
 
684
        responses = [
 
685
            (('error', error_msg), ''),
 
686
            (('ok',), '')]
 
687
        transport_path = 'quack'
 
688
        repo, client = self.setup_fake_client_and_repository(
 
689
            responses, transport_path)
 
690
        rev_id = 'revision-id'
 
691
        parents = repo.get_parent_map([rev_id])
 
692
        self.assertEqual(
 
693
            [('call_with_body_bytes_expecting_body',
 
694
              'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
 
695
             ('disconnect medium',),
 
696
             ('call_expecting_body', 'Repository.get_revision_graph',
 
697
              ('quack/', ''))],
 
698
            client._calls)
 
699
 
 
700
 
 
701
 
 
702
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
 
703
    
 
704
    def test_null_revision(self):
 
705
        # a null revision has the predictable result {}, we should have no wire
 
706
        # traffic when calling it with this argument
 
707
        responses = [(('notused', ), '')]
 
708
        transport_path = 'empty'
 
709
        repo, client = self.setup_fake_client_and_repository(
 
710
            responses, transport_path)
 
711
        result = self.applyDeprecated(one_four, repo.get_revision_graph,
 
712
            NULL_REVISION)
 
713
        self.assertEqual([], client._calls)
 
714
        self.assertEqual({}, result)
 
715
 
 
716
    def test_none_revision(self):
 
717
        # with none we want the entire graph
 
718
        r1 = u'\u0e33'.encode('utf8')
 
719
        r2 = u'\u0dab'.encode('utf8')
 
720
        lines = [' '.join([r2, r1]), r1]
 
721
        encoded_body = '\n'.join(lines)
 
722
 
 
723
        responses = [(('ok', ), encoded_body)]
 
724
        transport_path = 'sinhala'
 
725
        repo, client = self.setup_fake_client_and_repository(
 
726
            responses, transport_path)
 
727
        result = self.applyDeprecated(one_four, repo.get_revision_graph)
 
728
        self.assertEqual(
 
729
            [('call_expecting_body', 'Repository.get_revision_graph',
 
730
             ('sinhala/', ''))],
 
731
            client._calls)
 
732
        self.assertEqual({r1: (), r2: (r1, )}, result)
 
733
 
 
734
    def test_specific_revision(self):
 
735
        # with a specific revision we want the graph for that
 
736
        # with none we want the entire graph
 
737
        r11 = u'\u0e33'.encode('utf8')
 
738
        r12 = u'\xc9'.encode('utf8')
 
739
        r2 = u'\u0dab'.encode('utf8')
 
740
        lines = [' '.join([r2, r11, r12]), r11, r12]
 
741
        encoded_body = '\n'.join(lines)
 
742
 
 
743
        responses = [(('ok', ), encoded_body)]
 
744
        transport_path = 'sinhala'
 
745
        repo, client = self.setup_fake_client_and_repository(
 
746
            responses, transport_path)
 
747
        result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
 
748
        self.assertEqual(
 
749
            [('call_expecting_body', 'Repository.get_revision_graph',
 
750
             ('sinhala/', r2))],
 
751
            client._calls)
 
752
        self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
 
753
 
 
754
    def test_no_such_revision(self):
 
755
        revid = '123'
 
756
        responses = [(('nosuchrevision', revid), '')]
 
757
        transport_path = 'sinhala'
 
758
        repo, client = self.setup_fake_client_and_repository(
 
759
            responses, transport_path)
 
760
        # also check that the right revision is reported in the error
 
761
        self.assertRaises(errors.NoSuchRevision,
 
762
            self.applyDeprecated, one_four, repo.get_revision_graph, revid)
 
763
        self.assertEqual(
 
764
            [('call_expecting_body', 'Repository.get_revision_graph',
 
765
             ('sinhala/', revid))],
 
766
            client._calls)
 
767
 
 
768
        
 
769
class TestRepositoryIsShared(TestRemoteRepository):
 
770
 
 
771
    def test_is_shared(self):
 
772
        # ('yes', ) for Repository.is_shared -> 'True'.
 
773
        responses = [(('yes', ), )]
 
774
        transport_path = 'quack'
 
775
        repo, client = self.setup_fake_client_and_repository(
 
776
            responses, transport_path)
 
777
        result = repo.is_shared()
 
778
        self.assertEqual(
 
779
            [('call', 'Repository.is_shared', ('quack/',))],
 
780
            client._calls)
 
781
        self.assertEqual(True, result)
 
782
 
 
783
    def test_is_not_shared(self):
 
784
        # ('no', ) for Repository.is_shared -> 'False'.
 
785
        responses = [(('no', ), )]
 
786
        transport_path = 'qwack'
 
787
        repo, client = self.setup_fake_client_and_repository(
 
788
            responses, transport_path)
 
789
        result = repo.is_shared()
 
790
        self.assertEqual(
 
791
            [('call', 'Repository.is_shared', ('qwack/',))],
 
792
            client._calls)
 
793
        self.assertEqual(False, result)
 
794
 
 
795
 
 
796
class TestRepositoryLockWrite(TestRemoteRepository):
 
797
 
 
798
    def test_lock_write(self):
 
799
        responses = [(('ok', 'a token'), '')]
 
800
        transport_path = 'quack'
 
801
        repo, client = self.setup_fake_client_and_repository(
 
802
            responses, transport_path)
 
803
        result = repo.lock_write()
 
804
        self.assertEqual(
 
805
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
806
            client._calls)
 
807
        self.assertEqual('a token', result)
 
808
 
 
809
    def test_lock_write_already_locked(self):
 
810
        responses = [(('LockContention', ), '')]
 
811
        transport_path = 'quack'
 
812
        repo, client = self.setup_fake_client_and_repository(
 
813
            responses, transport_path)
 
814
        self.assertRaises(errors.LockContention, repo.lock_write)
 
815
        self.assertEqual(
 
816
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
817
            client._calls)
 
818
 
 
819
    def test_lock_write_unlockable(self):
 
820
        responses = [(('UnlockableTransport', ), '')]
 
821
        transport_path = 'quack'
 
822
        repo, client = self.setup_fake_client_and_repository(
 
823
            responses, transport_path)
 
824
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
 
825
        self.assertEqual(
 
826
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
827
            client._calls)
 
828
 
 
829
 
 
830
class TestRepositoryUnlock(TestRemoteRepository):
 
831
 
 
832
    def test_unlock(self):
 
833
        responses = [(('ok', 'a token'), ''),
 
834
                     (('ok',), '')]
 
835
        transport_path = 'quack'
 
836
        repo, client = self.setup_fake_client_and_repository(
 
837
            responses, transport_path)
 
838
        repo.lock_write()
 
839
        repo.unlock()
 
840
        self.assertEqual(
 
841
            [('call', 'Repository.lock_write', ('quack/', '')),
 
842
             ('call', 'Repository.unlock', ('quack/', 'a token'))],
 
843
            client._calls)
 
844
 
 
845
    def test_unlock_wrong_token(self):
 
846
        # If somehow the token is wrong, unlock will raise TokenMismatch.
 
847
        responses = [(('ok', 'a token'), ''),
 
848
                     (('TokenMismatch',), '')]
 
849
        transport_path = 'quack'
 
850
        repo, client = self.setup_fake_client_and_repository(
 
851
            responses, transport_path)
 
852
        repo.lock_write()
 
853
        self.assertRaises(errors.TokenMismatch, repo.unlock)
 
854
 
 
855
 
 
856
class TestRepositoryHasRevision(TestRemoteRepository):
 
857
 
 
858
    def test_none(self):
 
859
        # repo.has_revision(None) should not cause any traffic.
 
860
        transport_path = 'quack'
 
861
        responses = None
 
862
        repo, client = self.setup_fake_client_and_repository(
 
863
            responses, transport_path)
 
864
 
 
865
        # The null revision is always there, so has_revision(None) == True.
 
866
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
 
867
 
 
868
        # The remote repo shouldn't be accessed.
 
869
        self.assertEqual([], client._calls)
 
870
 
 
871
 
 
872
class TestRepositoryTarball(TestRemoteRepository):
 
873
 
 
874
    # This is a canned tarball reponse we can validate against
 
875
    tarball_content = (
 
876
        'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
 
877
        'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
 
878
        'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
 
879
        'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
 
880
        'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
 
881
        'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
 
882
        'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
 
883
        'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
 
884
        '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
 
885
        'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
 
886
        'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
 
887
        'nWQ7QH/F3JFOFCQ0aSPfA='
 
888
        ).decode('base64')
 
889
 
 
890
    def test_repository_tarball(self):
 
891
        # Test that Repository.tarball generates the right operations
 
892
        transport_path = 'repo'
 
893
        expected_responses = [(('ok',), self.tarball_content),
 
894
            ]
 
895
        expected_calls = [('call_expecting_body', 'Repository.tarball',
 
896
                           ('repo/', 'bz2',),),
 
897
            ]
 
898
        remote_repo, client = self.setup_fake_client_and_repository(
 
899
            expected_responses, transport_path)
 
900
        # Now actually ask for the tarball
 
901
        tarball_file = remote_repo._get_tarball('bz2')
 
902
        try:
 
903
            self.assertEqual(expected_calls, client._calls)
 
904
            self.assertEqual(self.tarball_content, tarball_file.read())
 
905
        finally:
 
906
            tarball_file.close()
 
907
 
 
908
 
 
909
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
 
910
    """RemoteRepository.copy_content_into optimizations"""
 
911
 
 
912
    def test_copy_content_remote_to_local(self):
 
913
        self.transport_server = server.SmartTCPServer_for_testing
 
914
        src_repo = self.make_repository('repo1')
 
915
        src_repo = repository.Repository.open(self.get_url('repo1'))
 
916
        # At the moment the tarball-based copy_content_into can't write back
 
917
        # into a smart server.  It would be good if it could upload the
 
918
        # tarball; once that works we'd have to create repositories of
 
919
        # different formats. -- mbp 20070410
 
920
        dest_url = self.get_vfs_only_url('repo2')
 
921
        dest_bzrdir = BzrDir.create(dest_url)
 
922
        dest_repo = dest_bzrdir.create_repository()
 
923
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
 
924
        self.assertTrue(isinstance(src_repo, RemoteRepository))
 
925
        src_repo.copy_content_into(dest_repo)
 
926
 
 
927
 
 
928
class TestRepositoryStreamKnitData(TestRemoteRepository):
 
929
 
 
930
    def make_pack_file(self, records):
 
931
        pack_file = StringIO()
 
932
        pack_writer = pack.ContainerWriter(pack_file.write)
 
933
        pack_writer.begin()
 
934
        for bytes, names in records:
 
935
            pack_writer.add_bytes_record(bytes, names)
 
936
        pack_writer.end()
 
937
        pack_file.seek(0)
 
938
        return pack_file
 
939
 
 
940
    def make_pack_stream(self, records):
 
941
        pack_serialiser = pack.ContainerSerialiser()
 
942
        yield pack_serialiser.begin()
 
943
        for bytes, names in records:
 
944
            yield pack_serialiser.bytes_record(bytes, names)
 
945
        yield pack_serialiser.end()
 
946
 
 
947
    def test_bad_pack_from_server(self):
 
948
        """A response with invalid data (e.g. it has a record with multiple
 
949
        names) triggers an exception.
 
950
        
 
951
        Not all possible errors will be caught at this stage, but obviously
 
952
        malformed data should be.
 
953
        """
 
954
        record = ('bytes', [('name1',), ('name2',)])
 
955
        pack_stream = self.make_pack_stream([record])
 
956
        responses = [(('ok',), pack_stream), ]
 
957
        transport_path = 'quack'
 
958
        repo, client = self.setup_fake_client_and_repository(
 
959
            responses, transport_path)
 
960
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
 
961
        stream = repo.get_data_stream_for_search(search)
 
962
        self.assertRaises(errors.SmartProtocolError, list, stream)
 
963
    
 
964
    def test_backwards_compatibility(self):
 
965
        """If the server doesn't recognise this request, fallback to VFS."""
 
966
        error_msg = (
 
967
            "Generic bzr smart protocol error: "
 
968
            "bad request 'Repository.stream_revisions_chunked'")
 
969
        responses = [
 
970
            (('error', error_msg), '')]
 
971
        repo, client = self.setup_fake_client_and_repository(
 
972
            responses, 'path')
 
973
        self.mock_called = False
 
974
        repo._real_repository = MockRealRepository(self)
 
975
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
 
976
        repo.get_data_stream_for_search(search)
 
977
        self.assertTrue(self.mock_called)
 
978
        self.failIf(client.expecting_body,
 
979
            "The protocol has been left in an unclean state that will cause "
 
980
            "TooManyConcurrentRequests errors.")
 
981
 
 
982
 
 
983
class MockRealRepository(object):
 
984
    """Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
 
985
 
 
986
    def __init__(self, test):
 
987
        self.test = test
 
988
 
 
989
    def get_data_stream_for_search(self, search):
 
990
        self.test.assertEqual(set(['revid']), search.get_keys())
 
991
        self.test.mock_called = True
 
992
 
 
993