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
35
from bzrlib.branch import Branch
36
from bzrlib.bzrdir import BzrDir, BzrDirFormat
37
from bzrlib.remote import (
43
from bzrlib.revision import NULL_REVISION
44
from bzrlib.smart import server, medium
45
from bzrlib.smart.client import _SmartClient
46
from bzrlib.transport.memory import MemoryTransport
47
from bzrlib.transport.remote import RemoteTransport
50
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
53
self.transport_server = server.SmartTCPServer_for_testing
54
super(BasicRemoteObjectTests, self).setUp()
55
self.transport = self.get_transport()
56
self.client = self.transport.get_smart_client()
57
# make a branch that can be opened over the smart transport
58
self.local_wt = BzrDir.create_standalone_workingtree('.')
61
self.transport.disconnect()
62
tests.TestCaseWithTransport.tearDown(self)
64
def test_create_remote_bzrdir(self):
65
b = remote.RemoteBzrDir(self.transport)
66
self.assertIsInstance(b, BzrDir)
68
def test_open_remote_branch(self):
69
# open a standalone branch in the working directory
70
b = remote.RemoteBzrDir(self.transport)
71
branch = b.open_branch()
72
self.assertIsInstance(branch, Branch)
74
def test_remote_repository(self):
75
b = BzrDir.open_from_transport(self.transport)
76
repo = b.open_repository()
77
revid = u'\xc823123123'.encode('utf8')
78
self.assertFalse(repo.has_revision(revid))
79
self.local_wt.commit(message='test commit', rev_id=revid)
80
self.assertTrue(repo.has_revision(revid))
82
def test_remote_branch_revision_history(self):
83
b = BzrDir.open_from_transport(self.transport).open_branch()
84
self.assertEqual([], b.revision_history())
85
r1 = self.local_wt.commit('1st commit')
86
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
87
self.assertEqual([r1, r2], b.revision_history())
89
def test_find_correct_format(self):
90
"""Should open a RemoteBzrDir over a RemoteTransport"""
91
fmt = BzrDirFormat.find_format(self.transport)
92
self.assertTrue(RemoteBzrDirFormat
93
in BzrDirFormat._control_server_formats)
94
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
96
def test_open_detected_smart_format(self):
97
fmt = BzrDirFormat.find_format(self.transport)
98
d = fmt.open(self.transport)
99
self.assertIsInstance(d, BzrDir)
101
def test_remote_branch_repr(self):
102
b = BzrDir.open_from_transport(self.transport).open_branch()
103
self.assertStartsWith(str(b), 'RemoteBranch(')
106
class FakeProtocol(object):
107
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
109
def __init__(self, body, fake_client):
110
self._body_buffer = StringIO(body)
111
self._fake_client = fake_client
113
def read_body_bytes(self, count=-1):
114
bytes = self._body_buffer.read(count)
115
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
116
self._fake_client.expecting_body = False
119
def cancel_read_body(self):
120
self._fake_client.expecting_body = False
123
class FakeClient(_SmartClient):
124
"""Lookalike for _SmartClient allowing testing."""
126
def __init__(self, responses):
127
# We don't call the super init because there is no medium.
128
"""Create a FakeClient.
130
:param respones: A list of response-tuple, body-data pairs to be sent
133
self.responses = responses
135
self.expecting_body = False
137
def call(self, method, *args):
138
self._calls.append(('call', method, args))
139
return self.responses.pop(0)[0]
141
def call_expecting_body(self, method, *args):
142
self._calls.append(('call_expecting_body', method, args))
143
result = self.responses.pop(0)
144
self.expecting_body = True
145
return result[0], FakeProtocol(result[1], self)
148
class TestBzrDirOpenBranch(tests.TestCase):
150
def test_branch_present(self):
151
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )])
152
transport = MemoryTransport()
153
transport.mkdir('quack')
154
transport = transport.clone('quack')
155
bzrdir = RemoteBzrDir(transport, _client=client)
156
result = bzrdir.open_branch()
158
[('call', 'BzrDir.open_branch', ('///quack/',)),
159
('call', 'BzrDir.find_repository', ('///quack/',))],
161
self.assertIsInstance(result, RemoteBranch)
162
self.assertEqual(bzrdir, result.bzrdir)
164
def test_branch_missing(self):
165
client = FakeClient([(('nobranch',), )])
166
transport = MemoryTransport()
167
transport.mkdir('quack')
168
transport = transport.clone('quack')
169
bzrdir = RemoteBzrDir(transport, _client=client)
170
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
172
[('call', 'BzrDir.open_branch', ('///quack/',))],
175
def check_open_repository(self, rich_root, subtrees):
177
rich_response = 'yes'
181
subtree_response = 'yes'
183
subtree_response = 'no'
184
client = FakeClient([(('ok', '', rich_response, subtree_response), ),])
185
transport = MemoryTransport()
186
transport.mkdir('quack')
187
transport = transport.clone('quack')
188
bzrdir = RemoteBzrDir(transport, _client=client)
189
result = bzrdir.open_repository()
191
[('call', 'BzrDir.find_repository', ('///quack/',))],
193
self.assertIsInstance(result, RemoteRepository)
194
self.assertEqual(bzrdir, result.bzrdir)
195
self.assertEqual(rich_root, result._format.rich_root_data)
196
self.assertEqual(subtrees, result._format.supports_tree_reference)
198
def test_open_repository_sets_format_attributes(self):
199
self.check_open_repository(True, True)
200
self.check_open_repository(False, True)
201
self.check_open_repository(True, False)
202
self.check_open_repository(False, False)
204
def test_old_server(self):
205
"""RemoteBzrDirFormat should fail to probe if the server version is too
208
self.assertRaises(errors.NotBranchError,
209
RemoteBzrDirFormat.probe_transport, OldServerTransport())
212
class OldSmartClient(object):
213
"""A fake smart client for test_old_version that just returns a version one
214
response to the 'hello' (query version) command.
217
def get_request(self):
218
input_file = StringIO('ok\x011\n')
219
output_file = StringIO()
220
client_medium = medium.SmartSimplePipesClientMedium(
221
input_file, output_file)
222
return medium.SmartClientStreamMediumRequest(client_medium)
225
class OldServerTransport(object):
226
"""A fake transport for test_old_server that reports it's smart server
227
protocol version as version one.
233
def get_smart_client(self):
234
return OldSmartClient()
237
class TestBranchLastRevisionInfo(tests.TestCase):
239
def test_empty_branch(self):
240
# in an empty branch we decode the response properly
241
client = FakeClient([(('ok', '0', 'null:'), )])
242
transport = MemoryTransport()
243
transport.mkdir('quack')
244
transport = transport.clone('quack')
245
# we do not want bzrdir to make any remote calls
246
bzrdir = RemoteBzrDir(transport, _client=False)
247
branch = RemoteBranch(bzrdir, None, _client=client)
248
result = branch.last_revision_info()
251
[('call', 'Branch.last_revision_info', ('///quack/',))],
253
self.assertEqual((0, NULL_REVISION), result)
255
def test_non_empty_branch(self):
256
# in a non-empty branch we also decode the response properly
257
revid = u'\xc8'.encode('utf8')
258
client = FakeClient([(('ok', '2', revid), )])
259
transport = MemoryTransport()
260
transport.mkdir('kwaak')
261
transport = transport.clone('kwaak')
262
# we do not want bzrdir to make any remote calls
263
bzrdir = RemoteBzrDir(transport, _client=False)
264
branch = RemoteBranch(bzrdir, None, _client=client)
265
result = branch.last_revision_info()
268
[('call', 'Branch.last_revision_info', ('///kwaak/',))],
270
self.assertEqual((2, revid), result)
273
class TestBranchSetLastRevision(tests.TestCase):
275
def test_set_empty(self):
276
# set_revision_history([]) is translated to calling
277
# Branch.set_last_revision(path, '') on the wire.
278
client = FakeClient([
280
(('ok', 'branch token', 'repo token'), ),
285
transport = MemoryTransport()
286
transport.mkdir('branch')
287
transport = transport.clone('branch')
289
bzrdir = RemoteBzrDir(transport, _client=False)
290
branch = RemoteBranch(bzrdir, None, _client=client)
291
# This is a hack to work around the problem that RemoteBranch currently
292
# unnecessarily invokes _ensure_real upon a call to lock_write.
293
branch._ensure_real = lambda: None
296
result = branch.set_revision_history([])
298
[('call', 'Branch.set_last_revision',
299
('///branch/', 'branch token', 'repo token', 'null:'))],
302
self.assertEqual(None, result)
304
def test_set_nonempty(self):
305
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
306
# Branch.set_last_revision(path, rev-idN) on the wire.
307
client = FakeClient([
309
(('ok', 'branch token', 'repo token'), ),
314
transport = MemoryTransport()
315
transport.mkdir('branch')
316
transport = transport.clone('branch')
318
bzrdir = RemoteBzrDir(transport, _client=False)
319
branch = RemoteBranch(bzrdir, None, _client=client)
320
# This is a hack to work around the problem that RemoteBranch currently
321
# unnecessarily invokes _ensure_real upon a call to lock_write.
322
branch._ensure_real = lambda: None
323
# Lock the branch, reset the record of remote calls.
327
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
329
[('call', 'Branch.set_last_revision',
330
('///branch/', 'branch token', 'repo token', 'rev-id2'))],
333
self.assertEqual(None, result)
335
def test_no_such_revision(self):
336
# A response of 'NoSuchRevision' is translated into an exception.
337
client = FakeClient([
339
(('ok', 'branch token', 'repo token'), ),
341
(('NoSuchRevision', 'rev-id'), ),
344
transport = MemoryTransport()
345
transport.mkdir('branch')
346
transport = transport.clone('branch')
348
bzrdir = RemoteBzrDir(transport, _client=False)
349
branch = RemoteBranch(bzrdir, None, _client=client)
350
branch._ensure_real = lambda: None
355
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
359
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
360
"""Test branch.control_files api munging...
362
We special case RemoteBranch.control_files.get('branch.conf') to
363
call a specific API so that RemoteBranch's can intercept configuration
364
file reading, allowing them to signal to the client about things like
365
'email is configured for commits'.
368
def test_get_branch_conf(self):
369
# in an empty branch we decode the response properly
370
client = FakeClient([(('ok', ), 'config file body')])
371
# we need to make a real branch because the remote_branch.control_files
372
# will trigger _ensure_real.
373
branch = self.make_branch('quack')
374
transport = branch.bzrdir.root_transport
375
# we do not want bzrdir to make any remote calls
376
bzrdir = RemoteBzrDir(transport, _client=False)
377
branch = RemoteBranch(bzrdir, None, _client=client)
378
result = branch.control_files.get('branch.conf')
380
[('call_expecting_body', 'Branch.get_config_file', ('///quack/',))],
382
self.assertEqual('config file body', result.read())
385
class TestBranchLockWrite(tests.TestCase):
387
def test_lock_write_unlockable(self):
388
client = FakeClient([(('UnlockableTransport', ), '')])
389
transport = MemoryTransport()
390
transport.mkdir('quack')
391
transport = transport.clone('quack')
392
# we do not want bzrdir to make any remote calls
393
bzrdir = RemoteBzrDir(transport, _client=False)
394
branch = RemoteBranch(bzrdir, None, _client=client)
395
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
397
[('call', 'Branch.lock_write', ('///quack/', '', ''))],
401
class TestTransportIsReadonly(tests.TestCase):
404
client = FakeClient([(('yes',), '')])
405
transport = RemoteTransport('bzr://example.com/', medium=False,
407
self.assertEqual(True, transport.is_readonly())
409
[('call', 'Transport.is_readonly', ())],
412
def test_false(self):
413
client = FakeClient([(('no',), '')])
414
transport = RemoteTransport('bzr://example.com/', medium=False,
416
self.assertEqual(False, transport.is_readonly())
418
[('call', 'Transport.is_readonly', ())],
421
def test_error_from_old_server(self):
422
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
424
Clients should treat it as a "no" response, because is_readonly is only
425
advisory anyway (a transport could be read-write, but then the
426
underlying filesystem could be readonly anyway).
428
client = FakeClient([(
429
('error', "Generic bzr smart protocol error: "
430
"bad request 'Transport.is_readonly'"), '')])
431
transport = RemoteTransport('bzr://example.com/', medium=False,
433
self.assertEqual(False, transport.is_readonly())
435
[('call', 'Transport.is_readonly', ())],
438
def test_error_from_old_0_11_server(self):
439
"""Same as test_error_from_old_server, but with the slightly different
440
error message from bzr 0.11 servers.
442
client = FakeClient([(
443
('error', "Generic bzr smart protocol error: "
444
"bad request u'Transport.is_readonly'"), '')])
445
transport = RemoteTransport('bzr://example.com/', medium=False,
447
self.assertEqual(False, transport.is_readonly())
449
[('call', 'Transport.is_readonly', ())],
453
class TestRemoteRepository(tests.TestCase):
454
"""Base for testing RemoteRepository protocol usage.
456
These tests contain frozen requests and responses. We want any changes to
457
what is sent or expected to be require a thoughtful update to these tests
458
because they might break compatibility with different-versioned servers.
461
def setup_fake_client_and_repository(self, responses, transport_path):
462
"""Create the fake client and repository for testing with.
464
There's no real server here; we just have canned responses sent
467
:param transport_path: Path below the root of the MemoryTransport
468
where the repository will be created.
470
client = FakeClient(responses)
471
transport = MemoryTransport()
472
transport.mkdir(transport_path)
473
transport = transport.clone(transport_path)
474
# we do not want bzrdir to make any remote calls
475
bzrdir = RemoteBzrDir(transport, _client=False)
476
repo = RemoteRepository(bzrdir, None, _client=client)
480
class TestRepositoryGatherStats(TestRemoteRepository):
482
def test_revid_none(self):
483
# ('ok',), body with revisions and size
484
responses = [(('ok', ), 'revisions: 2\nsize: 18\n')]
485
transport_path = 'quack'
486
repo, client = self.setup_fake_client_and_repository(
487
responses, transport_path)
488
result = repo.gather_stats(None)
490
[('call_expecting_body', 'Repository.gather_stats',
491
('///quack/','','no'))],
493
self.assertEqual({'revisions': 2, 'size': 18}, result)
495
def test_revid_no_committers(self):
496
# ('ok',), body without committers
497
responses = [(('ok', ),
498
'firstrev: 123456.300 3600\n'
499
'latestrev: 654231.400 0\n'
502
transport_path = 'quick'
503
revid = u'\xc8'.encode('utf8')
504
repo, client = self.setup_fake_client_and_repository(
505
responses, transport_path)
506
result = repo.gather_stats(revid)
508
[('call_expecting_body', 'Repository.gather_stats',
509
('///quick/', revid, 'no'))],
511
self.assertEqual({'revisions': 2, 'size': 18,
512
'firstrev': (123456.300, 3600),
513
'latestrev': (654231.400, 0),},
516
def test_revid_with_committers(self):
517
# ('ok',), body with committers
518
responses = [(('ok', ),
520
'firstrev: 123456.300 3600\n'
521
'latestrev: 654231.400 0\n'
524
transport_path = 'buick'
525
revid = u'\xc8'.encode('utf8')
526
repo, client = self.setup_fake_client_and_repository(
527
responses, transport_path)
528
result = repo.gather_stats(revid, True)
530
[('call_expecting_body', 'Repository.gather_stats',
531
('///buick/', revid, 'yes'))],
533
self.assertEqual({'revisions': 2, 'size': 18,
535
'firstrev': (123456.300, 3600),
536
'latestrev': (654231.400, 0),},
540
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
542
def test_null_revision(self):
543
# a null revision has the predictable result {}, we should have no wire
544
# traffic when calling it with this argument
545
responses = [(('notused', ), '')]
546
transport_path = 'empty'
547
repo, client = self.setup_fake_client_and_repository(
548
responses, transport_path)
549
result = repo.get_revision_graph(NULL_REVISION)
550
self.assertEqual([], client._calls)
551
self.assertEqual({}, result)
553
def test_none_revision(self):
554
# with none we want the entire graph
555
r1 = u'\u0e33'.encode('utf8')
556
r2 = u'\u0dab'.encode('utf8')
557
lines = [' '.join([r2, r1]), r1]
558
encoded_body = '\n'.join(lines)
560
responses = [(('ok', ), encoded_body)]
561
transport_path = 'sinhala'
562
repo, client = self.setup_fake_client_and_repository(
563
responses, transport_path)
564
result = repo.get_revision_graph()
566
[('call_expecting_body', 'Repository.get_revision_graph',
567
('///sinhala/', ''))],
569
self.assertEqual({r1: (), r2: (r1, )}, result)
571
def test_specific_revision(self):
572
# with a specific revision we want the graph for that
573
# with none we want the entire graph
574
r11 = u'\u0e33'.encode('utf8')
575
r12 = u'\xc9'.encode('utf8')
576
r2 = u'\u0dab'.encode('utf8')
577
lines = [' '.join([r2, r11, r12]), r11, r12]
578
encoded_body = '\n'.join(lines)
580
responses = [(('ok', ), encoded_body)]
581
transport_path = 'sinhala'
582
repo, client = self.setup_fake_client_and_repository(
583
responses, transport_path)
584
result = repo.get_revision_graph(r2)
586
[('call_expecting_body', 'Repository.get_revision_graph',
587
('///sinhala/', r2))],
589
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
591
def test_no_such_revision(self):
593
responses = [(('nosuchrevision', revid), '')]
594
transport_path = 'sinhala'
595
repo, client = self.setup_fake_client_and_repository(
596
responses, transport_path)
597
# also check that the right revision is reported in the error
598
self.assertRaises(errors.NoSuchRevision,
599
repo.get_revision_graph, revid)
601
[('call_expecting_body', 'Repository.get_revision_graph',
602
('///sinhala/', revid))],
606
class TestRepositoryIsShared(TestRemoteRepository):
608
def test_is_shared(self):
609
# ('yes', ) for Repository.is_shared -> 'True'.
610
responses = [(('yes', ), )]
611
transport_path = 'quack'
612
repo, client = self.setup_fake_client_and_repository(
613
responses, transport_path)
614
result = repo.is_shared()
616
[('call', 'Repository.is_shared', ('///quack/',))],
618
self.assertEqual(True, result)
620
def test_is_not_shared(self):
621
# ('no', ) for Repository.is_shared -> 'False'.
622
responses = [(('no', ), )]
623
transport_path = 'qwack'
624
repo, client = self.setup_fake_client_and_repository(
625
responses, transport_path)
626
result = repo.is_shared()
628
[('call', 'Repository.is_shared', ('///qwack/',))],
630
self.assertEqual(False, result)
633
class TestRepositoryLockWrite(TestRemoteRepository):
635
def test_lock_write(self):
636
responses = [(('ok', 'a token'), '')]
637
transport_path = 'quack'
638
repo, client = self.setup_fake_client_and_repository(
639
responses, transport_path)
640
result = repo.lock_write()
642
[('call', 'Repository.lock_write', ('///quack/', ''))],
644
self.assertEqual('a token', result)
646
def test_lock_write_already_locked(self):
647
responses = [(('LockContention', ), '')]
648
transport_path = 'quack'
649
repo, client = self.setup_fake_client_and_repository(
650
responses, transport_path)
651
self.assertRaises(errors.LockContention, repo.lock_write)
653
[('call', 'Repository.lock_write', ('///quack/', ''))],
656
def test_lock_write_unlockable(self):
657
responses = [(('UnlockableTransport', ), '')]
658
transport_path = 'quack'
659
repo, client = self.setup_fake_client_and_repository(
660
responses, transport_path)
661
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
663
[('call', 'Repository.lock_write', ('///quack/', ''))],
667
class TestRepositoryUnlock(TestRemoteRepository):
669
def test_unlock(self):
670
responses = [(('ok', 'a token'), ''),
672
transport_path = 'quack'
673
repo, client = self.setup_fake_client_and_repository(
674
responses, transport_path)
678
[('call', 'Repository.lock_write', ('///quack/', '')),
679
('call', 'Repository.unlock', ('///quack/', 'a token'))],
682
def test_unlock_wrong_token(self):
683
# If somehow the token is wrong, unlock will raise TokenMismatch.
684
responses = [(('ok', 'a token'), ''),
685
(('TokenMismatch',), '')]
686
transport_path = 'quack'
687
repo, client = self.setup_fake_client_and_repository(
688
responses, transport_path)
690
self.assertRaises(errors.TokenMismatch, repo.unlock)
693
class TestRepositoryHasRevision(TestRemoteRepository):
696
# repo.has_revision(None) should not cause any traffic.
697
transport_path = 'quack'
699
repo, client = self.setup_fake_client_and_repository(
700
responses, transport_path)
702
# The null revision is always there, so has_revision(None) == True.
703
self.assertEqual(True, repo.has_revision(None))
705
# The remote repo shouldn't be accessed.
706
self.assertEqual([], client._calls)
709
class TestRepositoryTarball(TestRemoteRepository):
711
# This is a canned tarball reponse we can validate against
713
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
714
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
715
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
716
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
717
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
718
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
719
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
720
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
721
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
722
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
723
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
724
'nWQ7QH/F3JFOFCQ0aSPfA='
727
def test_repository_tarball(self):
728
# Test that Repository.tarball generates the right operations
729
transport_path = 'repo'
730
expected_responses = [(('ok',), self.tarball_content),
732
expected_calls = [('call_expecting_body', 'Repository.tarball',
733
('///repo/', 'bz2',),),
735
remote_repo, client = self.setup_fake_client_and_repository(
736
expected_responses, transport_path)
737
# Now actually ask for the tarball
738
tarball_file = remote_repo._get_tarball('bz2')
740
self.assertEqual(expected_calls, client._calls)
741
self.assertEqual(self.tarball_content, tarball_file.read())
745
def test_sprout_uses_tarball(self):
746
# RemoteRepository.sprout should try to use the
747
# tarball command rather than accessing all the files
748
transport_path = 'srcrepo'
749
expected_responses = [(('ok',), self.tarball_content),
751
expected_calls = [('call2', 'Repository.tarball', ('///srcrepo/', 'bz2',),),
753
remote_repo, client = self.setup_fake_client_and_repository(
754
expected_responses, transport_path)
755
# make a regular local repository to receive the results
756
dest_transport = MemoryTransport()
757
dest_transport.mkdir('destrepo')
758
bzrdir_format = bzrdir.format_registry.make_bzrdir('default')
759
dest_bzrdir = bzrdir_format.initialize_on_transport(dest_transport)
761
remote_repo.sprout(dest_bzrdir)
763
def test_backwards_compatibility(self):
764
"""If the server doesn't recognise this request, fallback to VFS.
766
This happens when a current client talks to an older server that
767
doesn't implement 'Repository.tarball'.
769
# Make a regular local repository to receive the results
770
dest_transport = MemoryTransport()
771
dest_transport.mkdir('destrepo')
772
bzrdir_format = bzrdir.format_registry.make_bzrdir('default')
773
dest_bzrdir = bzrdir_format.initialize_on_transport(dest_transport)
776
"Generic bzr smart protocol error: "
777
"bad request 'Repository.tarball'")
778
responses = [(('error', error_msg), '')]
779
remote_repo, client = self.setup_fake_client_and_repository(
781
mock_real_repo = MockRealRepository()
782
remote_repo._real_repository = mock_real_repo
785
remote_repo.sprout(dest_bzrdir)
787
self.assertEqual([('sprout', dest_bzrdir, None)], mock_real_repo.calls,
788
"RemoteRepository didn't fallback to the real repository correctly")
789
self.failIf(client.expecting_body,
790
"The protocol has been left in an unclean state that will cause "
791
"TooManyConcurrentRequests errors.")
794
class MockRealRepository(object):
795
"""Mock of a RemoteRepository's '_real_repository' attribute.
797
Used by TestRepositoryTarball.test_backwards_compatibility.
803
def sprout(self, to_bzrdir, revision_id=None):
804
self.calls.append(('sprout', to_bzrdir, revision_id))
807
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
808
"""RemoteRepository.copy_content_into optimizations"""
810
def test_copy_content_remote_to_local(self):
811
self.transport_server = server.SmartTCPServer_for_testing
812
src_repo = self.make_repository('repo1')
813
src_repo = repository.Repository.open(self.get_url('repo1'))
814
# At the moment the tarball-based copy_content_into can't write back
815
# into a smart server. It would be good if it could upload the
816
# tarball; once that works we'd have to create repositories of
817
# different formats. -- mbp 20070410
818
dest_url = self.get_vfs_only_url('repo2')
819
dest_bzrdir = BzrDir.create(dest_url)
820
dest_repo = dest_bzrdir.create_repository()
821
self.assertFalse(isinstance(dest_repo, RemoteRepository))
822
self.assertTrue(isinstance(src_repo, RemoteRepository))
823
src_repo.copy_content_into(dest_repo)