1
# Copyright (C) 2006, 2007 Canonical Ltd
 
 
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.
 
 
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.
 
 
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
 
 
17
"""Tests for remote bzrdir/branch/repo/etc
 
 
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. 
 
 
23
These tests correspond to tests.test_smart, which exercises the server side.
 
 
26
from cStringIO import StringIO
 
 
36
from bzrlib.branch import Branch
 
 
37
from bzrlib.bzrdir import BzrDir, BzrDirFormat
 
 
38
from bzrlib.remote import (
 
 
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
 
 
51
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
 
 
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('.')
 
 
62
        self.transport.disconnect()
 
 
63
        tests.TestCaseWithTransport.tearDown(self)
 
 
65
    def test_create_remote_bzrdir(self):
 
 
66
        b = remote.RemoteBzrDir(self.transport)
 
 
67
        self.assertIsInstance(b, BzrDir)
 
 
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)
 
 
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))
 
 
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())
 
 
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)
 
 
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)
 
 
102
    def test_remote_branch_repr(self):
 
 
103
        b = BzrDir.open_from_transport(self.transport).open_branch()
 
 
104
        self.assertStartsWith(str(b), 'RemoteBranch(')
 
 
107
class FakeProtocol(object):
 
 
108
    """Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
 
 
110
    def __init__(self, body, fake_client):
 
 
111
        self._body_buffer = StringIO(body)
 
 
112
        self._fake_client = fake_client
 
 
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
 
 
120
    def cancel_read_body(self):
 
 
121
        self._fake_client.expecting_body = False
 
 
124
class FakeClient(_SmartClient):
 
 
125
    """Lookalike for _SmartClient allowing testing."""
 
 
127
    def __init__(self, responses):
 
 
128
        # We don't call the super init because there is no medium.
 
 
129
        """Create a FakeClient.
 
 
131
        :param respones: A list of response-tuple, body-data pairs to be sent
 
 
134
        self.responses = responses
 
 
136
        self.expecting_body = False
 
 
138
    def call(self, method, *args):
 
 
139
        self._calls.append(('call', method, args))
 
 
140
        return self.responses.pop(0)[0]
 
 
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)
 
 
149
class TestBzrDirOpenBranch(tests.TestCase):
 
 
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()
 
 
159
            [('call', 'BzrDir.open_branch', ('///quack/',)),
 
 
160
             ('call', 'BzrDir.find_repository', ('///quack/',))],
 
 
162
        self.assertIsInstance(result, RemoteBranch)
 
 
163
        self.assertEqual(bzrdir, result.bzrdir)
 
 
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)
 
 
173
            [('call', 'BzrDir.open_branch', ('///quack/',))],
 
 
176
    def check_open_repository(self, rich_root, subtrees):
 
 
178
            rich_response = 'yes'
 
 
182
            subtree_response = 'yes'
 
 
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()
 
 
192
            [('call', 'BzrDir.find_repository', ('///quack/',))],
 
 
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)
 
 
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)
 
 
205
    def test_old_server(self):
 
 
206
        """RemoteBzrDirFormat should fail to probe if the server version is too
 
 
209
        self.assertRaises(errors.NotBranchError,
 
 
210
            RemoteBzrDirFormat.probe_transport, OldServerTransport())
 
 
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.
 
 
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)
 
 
226
class OldServerTransport(object):
 
 
227
    """A fake transport for test_old_server that reports it's smart server
 
 
228
    protocol version as version one.
 
 
234
    def get_smart_client(self):
 
 
235
        return OldSmartClient()
 
 
238
class TestBranchLastRevisionInfo(tests.TestCase):
 
 
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()
 
 
252
            [('call', 'Branch.last_revision_info', ('///quack/',))],
 
 
254
        self.assertEqual((0, NULL_REVISION), result)
 
 
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()
 
 
269
            [('call', 'Branch.last_revision_info', ('///kwaak/',))],
 
 
271
        self.assertEqual((2, revid), result)
 
 
274
class TestBranchSetLastRevision(tests.TestCase):
 
 
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([
 
 
281
            (('ok', 'branch token', 'repo token'), ),
 
 
286
        transport = MemoryTransport()
 
 
287
        transport.mkdir('branch')
 
 
288
        transport = transport.clone('branch')
 
 
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
 
 
297
        result = branch.set_revision_history([])
 
 
299
            [('call', 'Branch.set_last_revision',
 
 
300
                ('///branch/', 'branch token', 'repo token', 'null:'))],
 
 
303
        self.assertEqual(None, result)
 
 
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([
 
 
310
            (('ok', 'branch token', 'repo token'), ),
 
 
315
        transport = MemoryTransport()
 
 
316
        transport.mkdir('branch')
 
 
317
        transport = transport.clone('branch')
 
 
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.
 
 
328
        result = branch.set_revision_history(['rev-id1', 'rev-id2'])
 
 
330
            [('call', 'Branch.set_last_revision',
 
 
331
                ('///branch/', 'branch token', 'repo token', 'rev-id2'))],
 
 
334
        self.assertEqual(None, result)
 
 
336
    def test_no_such_revision(self):
 
 
337
        # A response of 'NoSuchRevision' is translated into an exception.
 
 
338
        client = FakeClient([
 
 
340
            (('ok', 'branch token', 'repo token'), ),
 
 
342
            (('NoSuchRevision', 'rev-id'), ),
 
 
345
        transport = MemoryTransport()
 
 
346
        transport.mkdir('branch')
 
 
347
        transport = transport.clone('branch')
 
 
349
        bzrdir = RemoteBzrDir(transport, _client=False)
 
 
350
        branch = RemoteBranch(bzrdir, None, _client=client)
 
 
351
        branch._ensure_real = lambda: None
 
 
356
            errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
 
 
360
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
 
 
361
    """Test branch.control_files api munging...
 
 
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'.
 
 
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')
 
 
381
            [('call_expecting_body', 'Branch.get_config_file', ('///quack/',))],
 
 
383
        self.assertEqual('config file body', result.read())
 
 
386
class TestBranchLockWrite(tests.TestCase):
 
 
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)
 
 
398
            [('call', 'Branch.lock_write', ('///quack/', '', ''))],
 
 
402
class TestTransportIsReadonly(tests.TestCase):
 
 
405
        client = FakeClient([(('yes',), '')])
 
 
406
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
 
408
        self.assertEqual(True, transport.is_readonly())
 
 
410
            [('call', 'Transport.is_readonly', ())],
 
 
413
    def test_false(self):
 
 
414
        client = FakeClient([(('no',), '')])
 
 
415
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
 
417
        self.assertEqual(False, transport.is_readonly())
 
 
419
            [('call', 'Transport.is_readonly', ())],
 
 
422
    def test_error_from_old_server(self):
 
 
423
        """bzr 0.15 and earlier servers don't recognise the is_readonly verb.
 
 
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).
 
 
429
        client = FakeClient([(
 
 
430
            ('error', "Generic bzr smart protocol error: "
 
 
431
                      "bad request 'Transport.is_readonly'"), '')])
 
 
432
        transport = RemoteTransport('bzr://example.com/', medium=False,
 
 
434
        self.assertEqual(False, transport.is_readonly())
 
 
436
            [('call', 'Transport.is_readonly', ())],
 
 
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.
 
 
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,
 
 
448
        self.assertEqual(False, transport.is_readonly())
 
 
450
            [('call', 'Transport.is_readonly', ())],
 
 
454
class TestRemoteRepository(tests.TestCase):
 
 
455
    """Base for testing RemoteRepository protocol usage.
 
 
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.
 
 
462
    def setup_fake_client_and_repository(self, responses, transport_path):
 
 
463
        """Create the fake client and repository for testing with.
 
 
465
        There's no real server here; we just have canned responses sent
 
 
468
        :param transport_path: Path below the root of the MemoryTransport
 
 
469
            where the repository will be created.
 
 
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)
 
 
481
class TestRepositoryGatherStats(TestRemoteRepository):
 
 
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)
 
 
491
            [('call_expecting_body', 'Repository.gather_stats',
 
 
492
             ('///quack/','','no'))],
 
 
494
        self.assertEqual({'revisions': 2, 'size': 18}, result)
 
 
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'
 
 
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)
 
 
509
            [('call_expecting_body', 'Repository.gather_stats',
 
 
510
              ('///quick/', revid, 'no'))],
 
 
512
        self.assertEqual({'revisions': 2, 'size': 18,
 
 
513
                          'firstrev': (123456.300, 3600),
 
 
514
                          'latestrev': (654231.400, 0),},
 
 
517
    def test_revid_with_committers(self):
 
 
518
        # ('ok',), body with committers
 
 
519
        responses = [(('ok', ),
 
 
521
                      'firstrev: 123456.300 3600\n'
 
 
522
                      'latestrev: 654231.400 0\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)
 
 
531
            [('call_expecting_body', 'Repository.gather_stats',
 
 
532
              ('///buick/', revid, 'yes'))],
 
 
534
        self.assertEqual({'revisions': 2, 'size': 18,
 
 
536
                          'firstrev': (123456.300, 3600),
 
 
537
                          'latestrev': (654231.400, 0),},
 
 
541
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
 
 
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)
 
 
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)
 
 
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()
 
 
567
            [('call_expecting_body', 'Repository.get_revision_graph',
 
 
568
             ('///sinhala/', ''))],
 
 
570
        self.assertEqual({r1: (), r2: (r1, )}, result)
 
 
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)
 
 
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)
 
 
587
            [('call_expecting_body', 'Repository.get_revision_graph',
 
 
588
             ('///sinhala/', r2))],
 
 
590
        self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
 
 
592
    def test_no_such_revision(self):
 
 
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)
 
 
602
            [('call_expecting_body', 'Repository.get_revision_graph',
 
 
603
             ('///sinhala/', revid))],
 
 
607
class TestRepositoryIsShared(TestRemoteRepository):
 
 
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()
 
 
617
            [('call', 'Repository.is_shared', ('///quack/',))],
 
 
619
        self.assertEqual(True, result)
 
 
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()
 
 
629
            [('call', 'Repository.is_shared', ('///qwack/',))],
 
 
631
        self.assertEqual(False, result)
 
 
634
class TestRepositoryLockWrite(TestRemoteRepository):
 
 
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()
 
 
643
            [('call', 'Repository.lock_write', ('///quack/', ''))],
 
 
645
        self.assertEqual('a token', result)
 
 
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)
 
 
654
            [('call', 'Repository.lock_write', ('///quack/', ''))],
 
 
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)
 
 
664
            [('call', 'Repository.lock_write', ('///quack/', ''))],
 
 
668
class TestRepositoryUnlock(TestRemoteRepository):
 
 
670
    def test_unlock(self):
 
 
671
        responses = [(('ok', 'a token'), ''),
 
 
673
        transport_path = 'quack'
 
 
674
        repo, client = self.setup_fake_client_and_repository(
 
 
675
            responses, transport_path)
 
 
679
            [('call', 'Repository.lock_write', ('///quack/', '')),
 
 
680
             ('call', 'Repository.unlock', ('///quack/', 'a token'))],
 
 
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)
 
 
691
        self.assertRaises(errors.TokenMismatch, repo.unlock)
 
 
694
class TestRepositoryHasRevision(TestRemoteRepository):
 
 
697
        # repo.has_revision(None) should not cause any traffic.
 
 
698
        transport_path = 'quack'
 
 
700
        repo, client = self.setup_fake_client_and_repository(
 
 
701
            responses, transport_path)
 
 
703
        # The null revision is always there, so has_revision(None) == True.
 
 
704
        self.assertEqual(True, repo.has_revision(None))
 
 
706
        # The remote repo shouldn't be accessed.
 
 
707
        self.assertEqual([], client._calls)
 
 
710
class TestRepositoryTarball(TestRemoteRepository):
 
 
712
    # This is a canned tarball reponse we can validate against
 
 
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='
 
 
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),
 
 
733
        expected_calls = [('call_expecting_body', 'Repository.tarball',
 
 
734
                           ('///repo/', 'bz2',),),
 
 
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')
 
 
741
            self.assertEqual(expected_calls, client._calls)
 
 
742
            self.assertEqual(self.tarball_content, tarball_file.read())
 
 
747
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
 
 
748
    """RemoteRepository.copy_content_into optimizations"""
 
 
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)
 
 
766
class TestRepositoryStreamKnitData(TestRemoteRepository):
 
 
768
    def make_pack_file(self, records):
 
 
769
        pack_file = StringIO()
 
 
770
        pack_writer = pack.ContainerWriter(pack_file.write)
 
 
772
        for bytes, names in records:
 
 
773
            pack_writer.add_bytes_record(bytes, names)
 
 
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.
 
 
782
        Not all possible errors will be caught at this stage, but obviously
 
 
783
        malformed data should be.
 
 
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)
 
 
794
    def test_backwards_compatibility(self):
 
 
795
        """If the server doesn't recognise this request, fallback to VFS."""
 
 
797
            "Generic bzr smart protocol error: "
 
 
798
            "bad request 'Repository.stream_knit_data_for_revisions'")
 
 
800
            (('error', error_msg), '')]
 
 
801
        repo, client = self.setup_fake_client_and_repository(
 
 
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.")
 
 
812
class MockRealRepository(object):
 
 
813
    """Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
 
 
815
    def __init__(self, test):
 
 
818
    def get_data_stream(self, revision_ids):
 
 
819
        self.test.assertEqual(['revid'], revision_ids)
 
 
820
        self.test.mock_called = True