/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: Martin Pool
  • Date: 2007-05-04 08:46:39 UTC
  • mto: (2483.1.1 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 2484.
  • Revision ID: mbp@sourcefrog.net-20070504084639-8v8mzetmr1y74xer
Rename push/pull back to 'run_hooks' (jameinel)

Reorganize Branch.push into some template methods: public push,
_push_with_bound_branches, and _basic_push.  This fixes the case 
where the destination of push is bound, but the source branch
format doesn't support binding.

Run push and pull hook tests with a local branch that does support binding,
rather than skipping if the branch can't be bound to another of the same
format.

(broken) because the hooks are given the wrong parameters when 
pushing into something bound to a remote branch.

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
 
 
430
class TestRemoteRepository(tests.TestCase):
 
431
    """Base for testing RemoteRepository protocol usage.
 
432
    
 
433
    These tests contain frozen requests and responses.  We want any changes to 
 
434
    what is sent or expected to be require a thoughtful update to these tests
 
435
    because they might break compatibility with different-versioned servers.
 
436
    """
 
437
 
 
438
    def setup_fake_client_and_repository(self, responses, transport_path):
 
439
        """Create the fake client and repository for testing with.
 
440
        
 
441
        There's no real server here; we just have canned responses sent
 
442
        back one by one.
 
443
        
 
444
        :param transport_path: Path below the root of the MemoryTransport
 
445
            where the repository will be created.
 
446
        """
 
447
        client = FakeClient(responses)
 
448
        transport = MemoryTransport()
 
449
        transport.mkdir(transport_path)
 
450
        transport = transport.clone(transport_path)
 
451
        # we do not want bzrdir to make any remote calls
 
452
        bzrdir = RemoteBzrDir(transport, _client=False)
 
453
        repo = RemoteRepository(bzrdir, None, _client=client)
 
454
        return repo, client
 
455
 
 
456
 
 
457
class TestRepositoryGatherStats(TestRemoteRepository):
 
458
 
 
459
    def test_revid_none(self):
 
460
        # ('ok',), body with revisions and size
 
461
        responses = [(('ok', ), 'revisions: 2\nsize: 18\n')]
 
462
        transport_path = 'quack'
 
463
        repo, client = self.setup_fake_client_and_repository(
 
464
            responses, transport_path)
 
465
        result = repo.gather_stats(None)
 
466
        self.assertEqual(
 
467
            [('call_expecting_body', 'Repository.gather_stats',
 
468
             ('///quack/','','no'))],
 
469
            client._calls)
 
470
        self.assertEqual({'revisions': 2, 'size': 18}, result)
 
471
 
 
472
    def test_revid_no_committers(self):
 
473
        # ('ok',), body without committers
 
474
        responses = [(('ok', ),
 
475
                      'firstrev: 123456.300 3600\n'
 
476
                      'latestrev: 654231.400 0\n'
 
477
                      'revisions: 2\n'
 
478
                      'size: 18\n')]
 
479
        transport_path = 'quick'
 
480
        revid = u'\xc8'.encode('utf8')
 
481
        repo, client = self.setup_fake_client_and_repository(
 
482
            responses, transport_path)
 
483
        result = repo.gather_stats(revid)
 
484
        self.assertEqual(
 
485
            [('call_expecting_body', 'Repository.gather_stats',
 
486
              ('///quick/', revid, 'no'))],
 
487
            client._calls)
 
488
        self.assertEqual({'revisions': 2, 'size': 18,
 
489
                          'firstrev': (123456.300, 3600),
 
490
                          'latestrev': (654231.400, 0),},
 
491
                         result)
 
492
 
 
493
    def test_revid_with_committers(self):
 
494
        # ('ok',), body with committers
 
495
        responses = [(('ok', ),
 
496
                      'committers: 128\n'
 
497
                      'firstrev: 123456.300 3600\n'
 
498
                      'latestrev: 654231.400 0\n'
 
499
                      'revisions: 2\n'
 
500
                      'size: 18\n')]
 
501
        transport_path = 'buick'
 
502
        revid = u'\xc8'.encode('utf8')
 
503
        repo, client = self.setup_fake_client_and_repository(
 
504
            responses, transport_path)
 
505
        result = repo.gather_stats(revid, True)
 
506
        self.assertEqual(
 
507
            [('call_expecting_body', 'Repository.gather_stats',
 
508
              ('///buick/', revid, 'yes'))],
 
509
            client._calls)
 
510
        self.assertEqual({'revisions': 2, 'size': 18,
 
511
                          'committers': 128,
 
512
                          'firstrev': (123456.300, 3600),
 
513
                          'latestrev': (654231.400, 0),},
 
514
                         result)
 
515
 
 
516
 
 
517
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
 
518
    
 
519
    def test_null_revision(self):
 
520
        # a null revision has the predictable result {}, we should have no wire
 
521
        # traffic when calling it with this argument
 
522
        responses = [(('notused', ), '')]
 
523
        transport_path = 'empty'
 
524
        repo, client = self.setup_fake_client_and_repository(
 
525
            responses, transport_path)
 
526
        result = repo.get_revision_graph(NULL_REVISION)
 
527
        self.assertEqual([], client._calls)
 
528
        self.assertEqual({}, result)
 
529
 
 
530
    def test_none_revision(self):
 
531
        # with none we want the entire graph
 
532
        r1 = u'\u0e33'.encode('utf8')
 
533
        r2 = u'\u0dab'.encode('utf8')
 
534
        lines = [' '.join([r2, r1]), r1]
 
535
        encoded_body = '\n'.join(lines)
 
536
 
 
537
        responses = [(('ok', ), encoded_body)]
 
538
        transport_path = 'sinhala'
 
539
        repo, client = self.setup_fake_client_and_repository(
 
540
            responses, transport_path)
 
541
        result = repo.get_revision_graph()
 
542
        self.assertEqual(
 
543
            [('call_expecting_body', 'Repository.get_revision_graph',
 
544
             ('///sinhala/', ''))],
 
545
            client._calls)
 
546
        self.assertEqual({r1: [], r2: [r1]}, result)
 
547
 
 
548
    def test_specific_revision(self):
 
549
        # with a specific revision we want the graph for that
 
550
        # with none we want the entire graph
 
551
        r11 = u'\u0e33'.encode('utf8')
 
552
        r12 = u'\xc9'.encode('utf8')
 
553
        r2 = u'\u0dab'.encode('utf8')
 
554
        lines = [' '.join([r2, r11, r12]), r11, r12]
 
555
        encoded_body = '\n'.join(lines)
 
556
 
 
557
        responses = [(('ok', ), encoded_body)]
 
558
        transport_path = 'sinhala'
 
559
        repo, client = self.setup_fake_client_and_repository(
 
560
            responses, transport_path)
 
561
        result = repo.get_revision_graph(r2)
 
562
        self.assertEqual(
 
563
            [('call_expecting_body', 'Repository.get_revision_graph',
 
564
             ('///sinhala/', r2))],
 
565
            client._calls)
 
566
        self.assertEqual({r11: [], r12: [], r2: [r11, r12], }, result)
 
567
 
 
568
    def test_no_such_revision(self):
 
569
        revid = '123'
 
570
        responses = [(('nosuchrevision', revid), '')]
 
571
        transport_path = 'sinhala'
 
572
        repo, client = self.setup_fake_client_and_repository(
 
573
            responses, transport_path)
 
574
        # also check that the right revision is reported in the error
 
575
        self.assertRaises(errors.NoSuchRevision,
 
576
            repo.get_revision_graph, revid)
 
577
        self.assertEqual(
 
578
            [('call_expecting_body', 'Repository.get_revision_graph',
 
579
             ('///sinhala/', revid))],
 
580
            client._calls)
 
581
 
 
582
        
 
583
class TestRepositoryIsShared(TestRemoteRepository):
 
584
 
 
585
    def test_is_shared(self):
 
586
        # ('yes', ) for Repository.is_shared -> 'True'.
 
587
        responses = [(('yes', ), )]
 
588
        transport_path = 'quack'
 
589
        repo, client = self.setup_fake_client_and_repository(
 
590
            responses, transport_path)
 
591
        result = repo.is_shared()
 
592
        self.assertEqual(
 
593
            [('call', 'Repository.is_shared', ('///quack/',))],
 
594
            client._calls)
 
595
        self.assertEqual(True, result)
 
596
 
 
597
    def test_is_not_shared(self):
 
598
        # ('no', ) for Repository.is_shared -> 'False'.
 
599
        responses = [(('no', ), )]
 
600
        transport_path = 'qwack'
 
601
        repo, client = self.setup_fake_client_and_repository(
 
602
            responses, transport_path)
 
603
        result = repo.is_shared()
 
604
        self.assertEqual(
 
605
            [('call', 'Repository.is_shared', ('///qwack/',))],
 
606
            client._calls)
 
607
        self.assertEqual(False, result)
 
608
 
 
609
 
 
610
class TestRepositoryLockWrite(TestRemoteRepository):
 
611
 
 
612
    def test_lock_write(self):
 
613
        responses = [(('ok', 'a token'), '')]
 
614
        transport_path = 'quack'
 
615
        repo, client = self.setup_fake_client_and_repository(
 
616
            responses, transport_path)
 
617
        result = repo.lock_write()
 
618
        self.assertEqual(
 
619
            [('call', 'Repository.lock_write', ('///quack/', ''))],
 
620
            client._calls)
 
621
        self.assertEqual('a token', result)
 
622
 
 
623
    def test_lock_write_already_locked(self):
 
624
        responses = [(('LockContention', ), '')]
 
625
        transport_path = 'quack'
 
626
        repo, client = self.setup_fake_client_and_repository(
 
627
            responses, transport_path)
 
628
        self.assertRaises(errors.LockContention, repo.lock_write)
 
629
        self.assertEqual(
 
630
            [('call', 'Repository.lock_write', ('///quack/', ''))],
 
631
            client._calls)
 
632
 
 
633
    def test_lock_write_unlockable(self):
 
634
        responses = [(('UnlockableTransport', ), '')]
 
635
        transport_path = 'quack'
 
636
        repo, client = self.setup_fake_client_and_repository(
 
637
            responses, transport_path)
 
638
        self.assertRaises(errors.UnlockableTransport, repo.lock_write)
 
639
        self.assertEqual(
 
640
            [('call', 'Repository.lock_write', ('///quack/', ''))],
 
641
            client._calls)
 
642
 
 
643
 
 
644
class TestRepositoryUnlock(TestRemoteRepository):
 
645
 
 
646
    def test_unlock(self):
 
647
        responses = [(('ok', 'a token'), ''),
 
648
                     (('ok',), '')]
 
649
        transport_path = 'quack'
 
650
        repo, client = self.setup_fake_client_and_repository(
 
651
            responses, transport_path)
 
652
        repo.lock_write()
 
653
        repo.unlock()
 
654
        self.assertEqual(
 
655
            [('call', 'Repository.lock_write', ('///quack/', '')),
 
656
             ('call', 'Repository.unlock', ('///quack/', 'a token'))],
 
657
            client._calls)
 
658
 
 
659
    def test_unlock_wrong_token(self):
 
660
        # If somehow the token is wrong, unlock will raise TokenMismatch.
 
661
        responses = [(('ok', 'a token'), ''),
 
662
                     (('TokenMismatch',), '')]
 
663
        transport_path = 'quack'
 
664
        repo, client = self.setup_fake_client_and_repository(
 
665
            responses, transport_path)
 
666
        repo.lock_write()
 
667
        self.assertRaises(errors.TokenMismatch, repo.unlock)
 
668
 
 
669
 
 
670
class TestRepositoryHasRevision(TestRemoteRepository):
 
671
 
 
672
    def test_none(self):
 
673
        # repo.has_revision(None) should not cause any traffic.
 
674
        transport_path = 'quack'
 
675
        responses = None
 
676
        repo, client = self.setup_fake_client_and_repository(
 
677
            responses, transport_path)
 
678
 
 
679
        # The null revision is always there, so has_revision(None) == True.
 
680
        self.assertEqual(True, repo.has_revision(None))
 
681
 
 
682
        # The remote repo shouldn't be accessed.
 
683
        self.assertEqual([], client._calls)
 
684
 
 
685
 
 
686
class TestRepositoryTarball(TestRemoteRepository):
 
687
 
 
688
    # This is a canned tarball reponse we can validate against
 
689
    tarball_content = (
 
690
        'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
 
691
        'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
 
692
        'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
 
693
        'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
 
694
        'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
 
695
        'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
 
696
        'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
 
697
        'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
 
698
        '0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
 
699
        'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
 
700
        'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
 
701
        'nWQ7QH/F3JFOFCQ0aSPfA='
 
702
        ).decode('base64')
 
703
 
 
704
    def test_repository_tarball(self):
 
705
        # Test that Repository.tarball generates the right operations
 
706
        transport_path = 'repo'
 
707
        expected_responses = [(('ok',), self.tarball_content),
 
708
            ]
 
709
        expected_calls = [('call_expecting_body', 'Repository.tarball',
 
710
                           ('///repo/', 'bz2',),),
 
711
            ]
 
712
        remote_repo, client = self.setup_fake_client_and_repository(
 
713
            expected_responses, transport_path)
 
714
        # Now actually ask for the tarball
 
715
        tarball_file = remote_repo._get_tarball('bz2')
 
716
        try:
 
717
            self.assertEqual(expected_calls, client._calls)
 
718
            self.assertEqual(self.tarball_content, tarball_file.read())
 
719
        finally:
 
720
            tarball_file.close()
 
721
 
 
722
    def test_sprout_uses_tarball(self):
 
723
        # RemoteRepository.sprout should try to use the
 
724
        # tarball command rather than accessing all the files
 
725
        transport_path = 'srcrepo'
 
726
        expected_responses = [(('ok',), self.tarball_content),
 
727
            ]
 
728
        expected_calls = [('call2', 'Repository.tarball', ('///srcrepo/', 'bz2',),),
 
729
            ]
 
730
        remote_repo, client = self.setup_fake_client_and_repository(
 
731
            expected_responses, transport_path)
 
732
        # make a regular local repository to receive the results
 
733
        dest_transport = MemoryTransport()
 
734
        dest_transport.mkdir('destrepo')
 
735
        bzrdir_format = bzrdir.format_registry.make_bzrdir('default')
 
736
        dest_bzrdir = bzrdir_format.initialize_on_transport(dest_transport)
 
737
        # try to copy...
 
738
        remote_repo.sprout(dest_bzrdir)
 
739
 
 
740
 
 
741
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
 
742
    """RemoteRepository.copy_content_into optimizations"""
 
743
 
 
744
    def test_copy_content_remote_to_local(self):
 
745
        self.transport_server = server.SmartTCPServer_for_testing
 
746
        src_repo = self.make_repository('repo1')
 
747
        src_repo = repository.Repository.open(self.get_url('repo1'))
 
748
        # At the moment the tarball-based copy_content_into can't write back
 
749
        # into a smart server.  It would be good if it could upload the
 
750
        # tarball; once that works we'd have to create repositories of
 
751
        # different formats. -- mbp 20070410
 
752
        dest_url = self.get_vfs_only_url('repo2')
 
753
        dest_bzrdir = BzrDir.create(dest_url)
 
754
        dest_repo = dest_bzrdir.create_repository()
 
755
        self.assertFalse(isinstance(dest_repo, RemoteRepository))
 
756
        self.assertTrue(isinstance(src_repo, RemoteRepository))
 
757
        src_repo.copy_content_into(dest_repo)