/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: Alexander Belchenko
  • Date: 2008-01-21 10:46:32 UTC
  • mto: This revision was merged to the branch mainline in revision 3201.
  • Revision ID: bialix@ukr.net-20080121104632-ipdnpm6z4k5piv3q
show path to plugin module as *.py instead of *.pyc if python source available

(e.g. C:\bzr.dev\bzrlib\plugins\multiparent.py)

Show diffs side-by-side

added added

removed removed

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