/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-02-06 16:38:04 UTC
  • mfrom: (3207.1.2 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080206163804-6zyjbbfpsm8txfdm
(Lukas) give a better error when using version-info --custom without
        --template

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