/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: Robert Collins
  • Date: 2008-01-17 22:41:32 UTC
  • mto: (3184.3.1 use-SearchResult)
  • mto: This revision was merged to the branch mainline in revision 3192.
  • Revision ID: robertc@robertcollins.net-20080117224132-z2plxu9lh0g3rpz7
Handle stopping ghosts in searches properly.

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