~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Aaron Bentley
  • Date: 2007-12-12 15:17:13 UTC
  • mto: This revision was merged to the branch mainline in revision 3113.
  • Revision ID: abentley@panoramicfeedback.com-20071212151713-ox5n8rlx8m3nsspy
Add support for reconfiguring repositories into branches or trees

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