/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-13 01:42:47 UTC
  • mto: (3221.6.1 1.2)
  • mto: This revision was merged to the branch mainline in revision 3222.
  • Revision ID: mbp@sourcefrog.net-20080213014247-buajf9y91w5xuaa8
Update error format in test_inconsistent_delta

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.transport.memory import MemoryTransport
 
49
from bzrlib.transport.remote import RemoteTransport
 
50
 
 
51
 
 
52
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
 
53
 
 
54
    def setUp(self):
 
55
        self.transport_server = server.SmartTCPServer_for_testing
 
56
        super(BasicRemoteObjectTests, self).setUp()
 
57
        self.transport = self.get_transport()
 
58
        self.client = self.transport.get_smart_client()
 
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'), )],
 
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_repository', ('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'), )],
 
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_repository', ('~hello/',))],
 
250
            client._calls)
 
251
 
 
252
    def check_open_repository(self, rich_root, subtrees):
 
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([(('ok', '', rich_response, subtree_response), ),],
 
265
                            transport.base)
 
266
        bzrdir = RemoteBzrDir(transport, _client=client)
 
267
        result = bzrdir.open_repository()
 
268
        self.assertEqual(
 
269
            [('call', 'BzrDir.find_repository', ('quack/',))],
 
270
            client._calls)
 
271
        self.assertIsInstance(result, RemoteRepository)
 
272
        self.assertEqual(bzrdir, result.bzrdir)
 
273
        self.assertEqual(rich_root, result._format.rich_root_data)
 
274
        self.assertEqual(subtrees, result._format.supports_tree_reference)
 
275
 
 
276
    def test_open_repository_sets_format_attributes(self):
 
277
        self.check_open_repository(True, True)
 
278
        self.check_open_repository(False, True)
 
279
        self.check_open_repository(True, False)
 
280
        self.check_open_repository(False, False)
 
281
 
 
282
    def test_old_server(self):
 
283
        """RemoteBzrDirFormat should fail to probe if the server version is too
 
284
        old.
 
285
        """
 
286
        self.assertRaises(errors.NotBranchError,
 
287
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
 
288
 
 
289
 
 
290
class OldSmartClient(object):
 
291
    """A fake smart client for test_old_version that just returns a version one
 
292
    response to the 'hello' (query version) command.
 
293
    """
 
294
 
 
295
    def get_request(self):
 
296
        input_file = StringIO('ok\x011\n')
 
297
        output_file = StringIO()
 
298
        client_medium = medium.SmartSimplePipesClientMedium(
 
299
            input_file, output_file)
 
300
        return medium.SmartClientStreamMediumRequest(client_medium)
 
301
 
 
302
 
 
303
class OldServerTransport(object):
 
304
    """A fake transport for test_old_server that reports it's smart server
 
305
    protocol version as version one.
 
306
    """
 
307
 
 
308
    def __init__(self):
 
309
        self.base = 'fake:'
 
310
 
 
311
    def get_smart_client(self):
 
312
        return OldSmartClient()
 
313
 
 
314
 
 
315
class TestBranchLastRevisionInfo(tests.TestCase):
 
316
 
 
317
    def test_empty_branch(self):
 
318
        # in an empty branch we decode the response properly
 
319
        transport = MemoryTransport()
 
320
        client = FakeClient([(('ok', '0', 'null:'), )], transport.base)
 
321
        transport.mkdir('quack')
 
322
        transport = transport.clone('quack')
 
323
        # we do not want bzrdir to make any remote calls
 
324
        bzrdir = RemoteBzrDir(transport, _client=False)
 
325
        branch = RemoteBranch(bzrdir, None, _client=client)
 
326
        result = branch.last_revision_info()
 
327
 
 
328
        self.assertEqual(
 
329
            [('call', 'Branch.last_revision_info', ('quack/',))],
 
330
            client._calls)
 
331
        self.assertEqual((0, NULL_REVISION), result)
 
332
 
 
333
    def test_non_empty_branch(self):
 
334
        # in a non-empty branch we also decode the response properly
 
335
        revid = u'\xc8'.encode('utf8')
 
336
        transport = MemoryTransport()
 
337
        client = FakeClient([(('ok', '2', revid), )], transport.base)
 
338
        transport.mkdir('kwaak')
 
339
        transport = transport.clone('kwaak')
 
340
        # we do not want bzrdir to make any remote calls
 
341
        bzrdir = RemoteBzrDir(transport, _client=False)
 
342
        branch = RemoteBranch(bzrdir, None, _client=client)
 
343
        result = branch.last_revision_info()
 
344
 
 
345
        self.assertEqual(
 
346
            [('call', 'Branch.last_revision_info', ('kwaak/',))],
 
347
            client._calls)
 
348
        self.assertEqual((2, revid), result)
 
349
 
 
350
 
 
351
class TestBranchSetLastRevision(tests.TestCase):
 
352
 
 
353
    def test_set_empty(self):
 
354
        # set_revision_history([]) is translated to calling
 
355
        # Branch.set_last_revision(path, '') on the wire.
 
356
        transport = MemoryTransport()
 
357
        transport.mkdir('branch')
 
358
        transport = transport.clone('branch')
 
359
 
 
360
        client = FakeClient([
 
361
            # lock_write
 
362
            (('ok', 'branch token', 'repo token'), ),
 
363
            # set_last_revision
 
364
            (('ok',), ),
 
365
            # unlock
 
366
            (('ok',), )],
 
367
            transport.base)
 
368
        bzrdir = RemoteBzrDir(transport, _client=False)
 
369
        branch = RemoteBranch(bzrdir, None, _client=client)
 
370
        # This is a hack to work around the problem that RemoteBranch currently
 
371
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
372
        branch._ensure_real = lambda: None
 
373
        branch.lock_write()
 
374
        client._calls = []
 
375
        result = branch.set_revision_history([])
 
376
        self.assertEqual(
 
377
            [('call', 'Branch.set_last_revision',
 
378
                ('branch/', 'branch token', 'repo token', 'null:'))],
 
379
            client._calls)
 
380
        branch.unlock()
 
381
        self.assertEqual(None, result)
 
382
 
 
383
    def test_set_nonempty(self):
 
384
        # set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
 
385
        # Branch.set_last_revision(path, rev-idN) on the wire.
 
386
        transport = MemoryTransport()
 
387
        transport.mkdir('branch')
 
388
        transport = transport.clone('branch')
 
389
 
 
390
        client = FakeClient([
 
391
            # lock_write
 
392
            (('ok', 'branch token', 'repo token'), ),
 
393
            # set_last_revision
 
394
            (('ok',), ),
 
395
            # unlock
 
396
            (('ok',), )],
 
397
            transport.base)
 
398
        bzrdir = RemoteBzrDir(transport, _client=False)
 
399
        branch = RemoteBranch(bzrdir, None, _client=client)
 
400
        # This is a hack to work around the problem that RemoteBranch currently
 
401
        # unnecessarily invokes _ensure_real upon a call to lock_write.
 
402
        branch._ensure_real = lambda: None
 
403
        # Lock the branch, reset the record of remote calls.
 
404
        branch.lock_write()
 
405
        client._calls = []
 
406
 
 
407
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
 
408
        self.assertEqual(
 
409
            [('call', 'Branch.set_last_revision',
 
410
                ('branch/', 'branch token', 'repo token', 'rev-id2'))],
 
411
            client._calls)
 
412
        branch.unlock()
 
413
        self.assertEqual(None, result)
 
414
 
 
415
    def test_no_such_revision(self):
 
416
        # A response of 'NoSuchRevision' is translated into an exception.
 
417
        client = FakeClient([
 
418
            # lock_write
 
419
            (('ok', 'branch token', 'repo token'), ),
 
420
            # set_last_revision
 
421
            (('NoSuchRevision', 'rev-id'), ),
 
422
            # unlock
 
423
            (('ok',), )])
 
424
        transport = MemoryTransport()
 
425
        transport.mkdir('branch')
 
426
        transport = transport.clone('branch')
 
427
 
 
428
        bzrdir = RemoteBzrDir(transport, _client=False)
 
429
        branch = RemoteBranch(bzrdir, None, _client=client)
 
430
        branch._ensure_real = lambda: None
 
431
        branch.lock_write()
 
432
        client._calls = []
 
433
 
 
434
        self.assertRaises(
 
435
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
 
436
        branch.unlock()
 
437
 
 
438
 
 
439
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
 
440
    """Test branch.control_files api munging...
 
441
 
 
442
    We special case RemoteBranch.control_files.get('branch.conf') to
 
443
    call a specific API so that RemoteBranch's can intercept configuration
 
444
    file reading, allowing them to signal to the client about things like
 
445
    'email is configured for commits'.
 
446
    """
 
447
 
 
448
    def test_get_branch_conf(self):
 
449
        # in an empty branch we decode the response properly
 
450
        client = FakeClient([(('ok', ), 'config file body')], self.get_url())
 
451
        # we need to make a real branch because the remote_branch.control_files
 
452
        # will trigger _ensure_real.
 
453
        branch = self.make_branch('quack')
 
454
        transport = branch.bzrdir.root_transport
 
455
        # we do not want bzrdir to make any remote calls
 
456
        bzrdir = RemoteBzrDir(transport, _client=False)
 
457
        branch = RemoteBranch(bzrdir, None, _client=client)
 
458
        result = branch.control_files.get('branch.conf')
 
459
        self.assertEqual(
 
460
            [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
 
461
            client._calls)
 
462
        self.assertEqual('config file body', result.read())
 
463
 
 
464
 
 
465
class TestBranchLockWrite(tests.TestCase):
 
466
 
 
467
    def test_lock_write_unlockable(self):
 
468
        transport = MemoryTransport()
 
469
        client = FakeClient([(('UnlockableTransport', ), '')], transport.base)
 
470
        transport.mkdir('quack')
 
471
        transport = transport.clone('quack')
 
472
        # we do not want bzrdir to make any remote calls
 
473
        bzrdir = RemoteBzrDir(transport, _client=False)
 
474
        branch = RemoteBranch(bzrdir, None, _client=client)
 
475
        self.assertRaises(errors.UnlockableTransport, branch.lock_write)
 
476
        self.assertEqual(
 
477
            [('call', 'Branch.lock_write', ('quack/', '', ''))],
 
478
            client._calls)
 
479
 
 
480
 
 
481
class TestTransportIsReadonly(tests.TestCase):
 
482
 
 
483
    def test_true(self):
 
484
        client = FakeClient([(('yes',), '')])
 
485
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
486
                                    _client=client)
 
487
        self.assertEqual(True, transport.is_readonly())
 
488
        self.assertEqual(
 
489
            [('call', 'Transport.is_readonly', ())],
 
490
            client._calls)
 
491
 
 
492
    def test_false(self):
 
493
        client = FakeClient([(('no',), '')])
 
494
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
495
                                    _client=client)
 
496
        self.assertEqual(False, transport.is_readonly())
 
497
        self.assertEqual(
 
498
            [('call', 'Transport.is_readonly', ())],
 
499
            client._calls)
 
500
 
 
501
    def test_error_from_old_server(self):
 
502
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
 
503
        
 
504
        Clients should treat it as a "no" response, because is_readonly is only
 
505
        advisory anyway (a transport could be read-write, but then the
 
506
        underlying filesystem could be readonly anyway).
 
507
        """
 
508
        client = FakeClient([(
 
509
            ('error', "Generic bzr smart protocol error: "
 
510
                      "bad request 'Transport.is_readonly'"), '')])
 
511
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
512
                                    _client=client)
 
513
        self.assertEqual(False, transport.is_readonly())
 
514
        self.assertEqual(
 
515
            [('call', 'Transport.is_readonly', ())],
 
516
            client._calls)
 
517
 
 
518
    def test_error_from_old_0_11_server(self):
 
519
        """Same as test_error_from_old_server, but with the slightly different
 
520
        error message from bzr 0.11 servers.
 
521
        """
 
522
        client = FakeClient([(
 
523
            ('error', "Generic bzr smart protocol error: "
 
524
                      "bad request u'Transport.is_readonly'"), '')])
 
525
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
526
                                    _client=client)
 
527
        self.assertEqual(False, transport.is_readonly())
 
528
        self.assertEqual(
 
529
            [('call', 'Transport.is_readonly', ())],
 
530
            client._calls)
 
531
 
 
532
 
 
533
class TestRemoteRepository(tests.TestCase):
 
534
    """Base for testing RemoteRepository protocol usage.
 
535
    
 
536
    These tests contain frozen requests and responses.  We want any changes to 
 
537
    what is sent or expected to be require a thoughtful update to these tests
 
538
    because they might break compatibility with different-versioned servers.
 
539
    """
 
540
 
 
541
    def setup_fake_client_and_repository(self, responses, transport_path):
 
542
        """Create the fake client and repository for testing with.
 
543
        
 
544
        There's no real server here; we just have canned responses sent
 
545
        back one by one.
 
546
        
 
547
        :param transport_path: Path below the root of the MemoryTransport
 
548
            where the repository will be created.
 
549
        """
 
550
        transport = MemoryTransport()
 
551
        transport.mkdir(transport_path)
 
552
        client = FakeClient(responses, transport.base)
 
553
        transport = transport.clone(transport_path)
 
554
        # we do not want bzrdir to make any remote calls
 
555
        bzrdir = RemoteBzrDir(transport, _client=False)
 
556
        repo = RemoteRepository(bzrdir, None, _client=client)
 
557
        return repo, client
 
558
 
 
559
 
 
560
class TestRepositoryGatherStats(TestRemoteRepository):
 
561
 
 
562
    def test_revid_none(self):
 
563
        # ('ok',), body with revisions and size
 
564
        responses = [(('ok', ), 'revisions: 2\nsize: 18\n')]
 
565
        transport_path = 'quack'
 
566
        repo, client = self.setup_fake_client_and_repository(
 
567
            responses, transport_path)
 
568
        result = repo.gather_stats(None)
 
569
        self.assertEqual(
 
570
            [('call_expecting_body', 'Repository.gather_stats',
 
571
             ('quack/','','no'))],
 
572
            client._calls)
 
573
        self.assertEqual({'revisions': 2, 'size': 18}, result)
 
574
 
 
575
    def test_revid_no_committers(self):
 
576
        # ('ok',), body without committers
 
577
        responses = [(('ok', ),
 
578
                      'firstrev: 123456.300 3600\n'
 
579
                      'latestrev: 654231.400 0\n'
 
580
                      'revisions: 2\n'
 
581
                      'size: 18\n')]
 
582
        transport_path = 'quick'
 
583
        revid = u'\xc8'.encode('utf8')
 
584
        repo, client = self.setup_fake_client_and_repository(
 
585
            responses, transport_path)
 
586
        result = repo.gather_stats(revid)
 
587
        self.assertEqual(
 
588
            [('call_expecting_body', 'Repository.gather_stats',
 
589
              ('quick/', revid, 'no'))],
 
590
            client._calls)
 
591
        self.assertEqual({'revisions': 2, 'size': 18,
 
592
                          'firstrev': (123456.300, 3600),
 
593
                          'latestrev': (654231.400, 0),},
 
594
                         result)
 
595
 
 
596
    def test_revid_with_committers(self):
 
597
        # ('ok',), body with committers
 
598
        responses = [(('ok', ),
 
599
                      'committers: 128\n'
 
600
                      'firstrev: 123456.300 3600\n'
 
601
                      'latestrev: 654231.400 0\n'
 
602
                      'revisions: 2\n'
 
603
                      'size: 18\n')]
 
604
        transport_path = 'buick'
 
605
        revid = u'\xc8'.encode('utf8')
 
606
        repo, client = self.setup_fake_client_and_repository(
 
607
            responses, transport_path)
 
608
        result = repo.gather_stats(revid, True)
 
609
        self.assertEqual(
 
610
            [('call_expecting_body', 'Repository.gather_stats',
 
611
              ('buick/', revid, 'yes'))],
 
612
            client._calls)
 
613
        self.assertEqual({'revisions': 2, 'size': 18,
 
614
                          'committers': 128,
 
615
                          'firstrev': (123456.300, 3600),
 
616
                          'latestrev': (654231.400, 0),},
 
617
                         result)
 
618
 
 
619
 
 
620
class TestRepositoryGetGraph(TestRemoteRepository):
 
621
 
 
622
    def test_get_graph(self):
 
623
        # get_graph returns a graph with the repository as the
 
624
        # parents_provider.
 
625
        responses = []
 
626
        transport_path = 'quack'
 
627
        repo, client = self.setup_fake_client_and_repository(
 
628
            responses, transport_path)
 
629
        graph = repo.get_graph()
 
630
        self.assertEqual(graph._parents_provider, repo)
 
631
 
 
632
 
 
633
class TestRepositoryGetParentMap(TestRemoteRepository):
 
634
 
 
635
    def test_get_parent_map_caching(self):
 
636
        # get_parent_map returns from cache until unlock()
 
637
        # setup a reponse with two revisions
 
638
        r1 = u'\u0e33'.encode('utf8')
 
639
        r2 = u'\u0dab'.encode('utf8')
 
640
        lines = [' '.join([r2, r1]), r1]
 
641
        encoded_body = bz2.compress('\n'.join(lines))
 
642
        responses = [(('ok', ), encoded_body), (('ok', ), encoded_body)]
 
643
 
 
644
        transport_path = 'quack'
 
645
        repo, client = self.setup_fake_client_and_repository(
 
646
            responses, transport_path)
 
647
        repo.lock_read()
 
648
        graph = repo.get_graph()
 
649
        parents = graph.get_parent_map([r2])
 
650
        self.assertEqual({r2: (r1,)}, parents)
 
651
        # locking and unlocking deeper should not reset
 
652
        repo.lock_read()
 
653
        repo.unlock()
 
654
        parents = graph.get_parent_map([r1])
 
655
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
656
        self.assertEqual(
 
657
            [('call_with_body_bytes_expecting_body',
 
658
              'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
 
659
            client._calls)
 
660
        repo.unlock()
 
661
        # now we call again, and it should use the second response.
 
662
        repo.lock_read()
 
663
        graph = repo.get_graph()
 
664
        parents = graph.get_parent_map([r1])
 
665
        self.assertEqual({r1: (NULL_REVISION,)}, parents)
 
666
        self.assertEqual(
 
667
            [('call_with_body_bytes_expecting_body',
 
668
              'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
 
669
             ('call_with_body_bytes_expecting_body',
 
670
              'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
 
671
            ],
 
672
            client._calls)
 
673
        repo.unlock()
 
674
 
 
675
    def test_get_parent_map_reconnects_if_unknown_method(self):
 
676
        error_msg = (
 
677
            "Generic bzr smart protocol error: "
 
678
            "bad request 'Repository.get_parent_map'")
 
679
        responses = [
 
680
            (('error', error_msg), ''),
 
681
            (('ok',), '')]
 
682
        transport_path = 'quack'
 
683
        repo, client = self.setup_fake_client_and_repository(
 
684
            responses, transport_path)
 
685
        rev_id = 'revision-id'
 
686
        parents = repo.get_parent_map([rev_id])
 
687
        self.assertEqual(
 
688
            [('call_with_body_bytes_expecting_body',
 
689
              'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
 
690
             ('disconnect medium',),
 
691
             ('call_expecting_body', 'Repository.get_revision_graph',
 
692
              ('quack/', ''))],
 
693
            client._calls)
 
694
 
 
695
 
 
696
 
 
697
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
 
698
    
 
699
    def test_null_revision(self):
 
700
        # a null revision has the predictable result {}, we should have no wire
 
701
        # traffic when calling it with this argument
 
702
        responses = [(('notused', ), '')]
 
703
        transport_path = 'empty'
 
704
        repo, client = self.setup_fake_client_and_repository(
 
705
            responses, transport_path)
 
706
        result = repo.get_revision_graph(NULL_REVISION)
 
707
        self.assertEqual([], client._calls)
 
708
        self.assertEqual({}, result)
 
709
 
 
710
    def test_none_revision(self):
 
711
        # with none we want the entire graph
 
712
        r1 = u'\u0e33'.encode('utf8')
 
713
        r2 = u'\u0dab'.encode('utf8')
 
714
        lines = [' '.join([r2, r1]), r1]
 
715
        encoded_body = '\n'.join(lines)
 
716
 
 
717
        responses = [(('ok', ), encoded_body)]
 
718
        transport_path = 'sinhala'
 
719
        repo, client = self.setup_fake_client_and_repository(
 
720
            responses, transport_path)
 
721
        result = repo.get_revision_graph()
 
722
        self.assertEqual(
 
723
            [('call_expecting_body', 'Repository.get_revision_graph',
 
724
             ('sinhala/', ''))],
 
725
            client._calls)
 
726
        self.assertEqual({r1: (), r2: (r1, )}, result)
 
727
 
 
728
    def test_specific_revision(self):
 
729
        # with a specific revision we want the graph for that
 
730
        # with none we want the entire graph
 
731
        r11 = u'\u0e33'.encode('utf8')
 
732
        r12 = u'\xc9'.encode('utf8')
 
733
        r2 = u'\u0dab'.encode('utf8')
 
734
        lines = [' '.join([r2, r11, r12]), r11, r12]
 
735
        encoded_body = '\n'.join(lines)
 
736
 
 
737
        responses = [(('ok', ), encoded_body)]
 
738
        transport_path = 'sinhala'
 
739
        repo, client = self.setup_fake_client_and_repository(
 
740
            responses, transport_path)
 
741
        result = repo.get_revision_graph(r2)
 
742
        self.assertEqual(
 
743
            [('call_expecting_body', 'Repository.get_revision_graph',
 
744
             ('sinhala/', r2))],
 
745
            client._calls)
 
746
        self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
 
747
 
 
748
    def test_no_such_revision(self):
 
749
        revid = '123'
 
750
        responses = [(('nosuchrevision', revid), '')]
 
751
        transport_path = 'sinhala'
 
752
        repo, client = self.setup_fake_client_and_repository(
 
753
            responses, transport_path)
 
754
        # also check that the right revision is reported in the error
 
755
        self.assertRaises(errors.NoSuchRevision,
 
756
            repo.get_revision_graph, revid)
 
757
        self.assertEqual(
 
758
            [('call_expecting_body', 'Repository.get_revision_graph',
 
759
             ('sinhala/', revid))],
 
760
            client._calls)
 
761
 
 
762
        
 
763
class TestRepositoryIsShared(TestRemoteRepository):
 
764
 
 
765
    def test_is_shared(self):
 
766
        # ('yes', ) for Repository.is_shared -> 'True'.
 
767
        responses = [(('yes', ), )]
 
768
        transport_path = 'quack'
 
769
        repo, client = self.setup_fake_client_and_repository(
 
770
            responses, transport_path)
 
771
        result = repo.is_shared()
 
772
        self.assertEqual(
 
773
            [('call', 'Repository.is_shared', ('quack/',))],
 
774
            client._calls)
 
775
        self.assertEqual(True, result)
 
776
 
 
777
    def test_is_not_shared(self):
 
778
        # ('no', ) for Repository.is_shared -> 'False'.
 
779
        responses = [(('no', ), )]
 
780
        transport_path = 'qwack'
 
781
        repo, client = self.setup_fake_client_and_repository(
 
782
            responses, transport_path)
 
783
        result = repo.is_shared()
 
784
        self.assertEqual(
 
785
            [('call', 'Repository.is_shared', ('qwack/',))],
 
786
            client._calls)
 
787
        self.assertEqual(False, result)
 
788
 
 
789
 
 
790
class TestRepositoryLockWrite(TestRemoteRepository):
 
791
 
 
792
    def test_lock_write(self):
 
793
        responses = [(('ok', 'a token'), '')]
 
794
        transport_path = 'quack'
 
795
        repo, client = self.setup_fake_client_and_repository(
 
796
            responses, transport_path)
 
797
        result = repo.lock_write()
 
798
        self.assertEqual(
 
799
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
800
            client._calls)
 
801
        self.assertEqual('a token', result)
 
802
 
 
803
    def test_lock_write_already_locked(self):
 
804
        responses = [(('LockContention', ), '')]
 
805
        transport_path = 'quack'
 
806
        repo, client = self.setup_fake_client_and_repository(
 
807
            responses, transport_path)
 
808
        self.assertRaises(errors.LockContention, repo.lock_write)
 
809
        self.assertEqual(
 
810
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
811
            client._calls)
 
812
 
 
813
    def test_lock_write_unlockable(self):
 
814
        responses = [(('UnlockableTransport', ), '')]
 
815
        transport_path = 'quack'
 
816
        repo, client = self.setup_fake_client_and_repository(
 
817
            responses, transport_path)
 
818
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
 
819
        self.assertEqual(
 
820
            [('call', 'Repository.lock_write', ('quack/', ''))],
 
821
            client._calls)
 
822
 
 
823
 
 
824
class TestRepositoryUnlock(TestRemoteRepository):
 
825
 
 
826
    def test_unlock(self):
 
827
        responses = [(('ok', 'a token'), ''),
 
828
                     (('ok',), '')]
 
829
        transport_path = 'quack'
 
830
        repo, client = self.setup_fake_client_and_repository(
 
831
            responses, transport_path)
 
832
        repo.lock_write()
 
833
        repo.unlock()
 
834
        self.assertEqual(
 
835
            [('call', 'Repository.lock_write', ('quack/', '')),
 
836
             ('call', 'Repository.unlock', ('quack/', 'a token'))],
 
837
            client._calls)
 
838
 
 
839
    def test_unlock_wrong_token(self):
 
840
        # If somehow the token is wrong, unlock will raise TokenMismatch.
 
841
        responses = [(('ok', 'a token'), ''),
 
842
                     (('TokenMismatch',), '')]
 
843
        transport_path = 'quack'
 
844
        repo, client = self.setup_fake_client_and_repository(
 
845
            responses, transport_path)
 
846
        repo.lock_write()
 
847
        self.assertRaises(errors.TokenMismatch, repo.unlock)
 
848
 
 
849
 
 
850
class TestRepositoryHasRevision(TestRemoteRepository):
 
851
 
 
852
    def test_none(self):
 
853
        # repo.has_revision(None) should not cause any traffic.
 
854
        transport_path = 'quack'
 
855
        responses = None
 
856
        repo, client = self.setup_fake_client_and_repository(
 
857
            responses, transport_path)
 
858
 
 
859
        # The null revision is always there, so has_revision(None) == True.
 
860
        self.assertEqual(True, repo.has_revision(NULL_REVISION))
 
861
 
 
862
        # The remote repo shouldn't be accessed.
 
863
        self.assertEqual([], client._calls)
 
864
 
 
865
 
 
866
class TestRepositoryTarball(TestRemoteRepository):
 
867
 
 
868
    # This is a canned tarball reponse we can validate against
 
869
    tarball_content = (
 
870
        'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
 
871
        'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
 
872
        'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
 
873
        'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
 
874
        'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
 
875
        'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
 
876
        'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
 
877
        'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
 
878
        '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
 
879
        'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
 
880
        'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
 
881
        'nWQ7QH/F3JFOFCQ0aSPfA='
 
882
        ).decode('base64')
 
883
 
 
884
    def test_repository_tarball(self):
 
885
        # Test that Repository.tarball generates the right operations
 
886
        transport_path = 'repo'
 
887
        expected_responses = [(('ok',), self.tarball_content),
 
888
            ]
 
889
        expected_calls = [('call_expecting_body', 'Repository.tarball',
 
890
                           ('repo/', 'bz2',),),
 
891
            ]
 
892
        remote_repo, client = self.setup_fake_client_and_repository(
 
893
            expected_responses, transport_path)
 
894
        # Now actually ask for the tarball
 
895
        tarball_file = remote_repo._get_tarball('bz2')
 
896
        try:
 
897
            self.assertEqual(expected_calls, client._calls)
 
898
            self.assertEqual(self.tarball_content, tarball_file.read())
 
899
        finally:
 
900
            tarball_file.close()
 
901
 
 
902
 
 
903
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
 
904
    """RemoteRepository.copy_content_into optimizations"""
 
905
 
 
906
    def test_copy_content_remote_to_local(self):
 
907
        self.transport_server = server.SmartTCPServer_for_testing
 
908
        src_repo = self.make_repository('repo1')
 
909
        src_repo = repository.Repository.open(self.get_url('repo1'))
 
910
        # At the moment the tarball-based copy_content_into can't write back
 
911
        # into a smart server.  It would be good if it could upload the
 
912
        # tarball; once that works we'd have to create repositories of
 
913
        # different formats. -- mbp 20070410
 
914
        dest_url = self.get_vfs_only_url('repo2')
 
915
        dest_bzrdir = BzrDir.create(dest_url)
 
916
        dest_repo = dest_bzrdir.create_repository()
 
917
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
 
918
        self.assertTrue(isinstance(src_repo, RemoteRepository))
 
919
        src_repo.copy_content_into(dest_repo)
 
920
 
 
921
 
 
922
class TestRepositoryStreamKnitData(TestRemoteRepository):
 
923
 
 
924
    def make_pack_file(self, records):
 
925
        pack_file = StringIO()
 
926
        pack_writer = pack.ContainerWriter(pack_file.write)
 
927
        pack_writer.begin()
 
928
        for bytes, names in records:
 
929
            pack_writer.add_bytes_record(bytes, names)
 
930
        pack_writer.end()
 
931
        pack_file.seek(0)
 
932
        return pack_file
 
933
 
 
934
    def make_pack_stream(self, records):
 
935
        pack_serialiser = pack.ContainerSerialiser()
 
936
        yield pack_serialiser.begin()
 
937
        for bytes, names in records:
 
938
            yield pack_serialiser.bytes_record(bytes, names)
 
939
        yield pack_serialiser.end()
 
940
 
 
941
    def test_bad_pack_from_server(self):
 
942
        """A response with invalid data (e.g. it has a record with multiple
 
943
        names) triggers an exception.
 
944
        
 
945
        Not all possible errors will be caught at this stage, but obviously
 
946
        malformed data should be.
 
947
        """
 
948
        record = ('bytes', [('name1',), ('name2',)])
 
949
        pack_stream = self.make_pack_stream([record])
 
950
        responses = [(('ok',), pack_stream), ]
 
951
        transport_path = 'quack'
 
952
        repo, client = self.setup_fake_client_and_repository(
 
953
            responses, transport_path)
 
954
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
 
955
        stream = repo.get_data_stream_for_search(search)
 
956
        self.assertRaises(errors.SmartProtocolError, list, stream)
 
957
    
 
958
    def test_backwards_compatibility(self):
 
959
        """If the server doesn't recognise this request, fallback to VFS."""
 
960
        error_msg = (
 
961
            "Generic bzr smart protocol error: "
 
962
            "bad request 'Repository.stream_revisions_chunked'")
 
963
        responses = [
 
964
            (('error', error_msg), '')]
 
965
        repo, client = self.setup_fake_client_and_repository(
 
966
            responses, 'path')
 
967
        self.mock_called = False
 
968
        repo._real_repository = MockRealRepository(self)
 
969
        search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
 
970
        repo.get_data_stream_for_search(search)
 
971
        self.assertTrue(self.mock_called)
 
972
        self.failIf(client.expecting_body,
 
973
            "The protocol has been left in an unclean state that will cause "
 
974
            "TooManyConcurrentRequests errors.")
 
975
 
 
976
 
 
977
class MockRealRepository(object):
 
978
    """Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
 
979
 
 
980
    def __init__(self, test):
 
981
        self.test = test
 
982
 
 
983
    def get_data_stream_for_search(self, search):
 
984
        self.test.assertEqual(set(['revid']), search.get_keys())
 
985
        self.test.mock_called = True
 
986
 
 
987