/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_remote.py

  • Committer: Robert Collins
  • Date: 2008-01-25 11:06:32 UTC
  • mto: This revision was merged to the branch mainline in revision 3211.
  • Revision ID: robertc@robertcollins.net-20080125110632-0tp2cjae2ol63zuq
 * The launchpad plugin now uses the ``edge`` xmlrpc server to avoid
   interacting badly with a bug on the launchpad side. (Robert Collins)

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