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