/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: Martin Pool
  • Date: 2008-02-06 00:41:04 UTC
  • mfrom: (3215 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3219.
  • Revision ID: mbp@sourcefrog.net-20080206004104-mxtn32habuhjq6b8
Merge trunk

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