~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Martin Pool
  • Date: 2005-11-04 01:46:31 UTC
  • mto: (1185.33.49 bzr.dev)
  • mto: This revision was merged to the branch mainline in revision 1512.
  • Revision ID: mbp@sourcefrog.net-20051104014631-750e0ad4172c952c
Make biobench directly executable

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