/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-14 04:46:08 UTC
  • mto: This revision was merged to the branch mainline in revision 3184.
  • Revision ID: robertc@robertcollins.net-20080114044608-bmse3mmsnp1663rf
Create new smart server verb Repository.get_parent_map.

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