~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: John Arbash Meinel
  • Date: 2007-07-13 02:23:34 UTC
  • mfrom: (2592 +trunk) (2612 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2614.
  • Revision ID: john@arbash-meinel.com-20070713022334-qb6ewgo6v4251yd9
[merge] bzr.dev 2612

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