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
37
from bzrlib.branch import Branch
38
from bzrlib.bzrdir import BzrDir, BzrDirFormat
39
from bzrlib.remote import (
45
from bzrlib.revision import NULL_REVISION
46
from bzrlib.smart import server, medium
47
from bzrlib.smart.client import _SmartClient
48
from bzrlib.transport.memory import MemoryTransport
49
from bzrlib.transport.remote import RemoteTransport
52
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
55
self.transport_server = server.SmartTCPServer_for_testing
56
super(BasicRemoteObjectTests, self).setUp()
57
self.transport = self.get_transport()
58
self.client = self.transport.get_smart_client()
59
# make a branch that can be opened over the smart transport
60
self.local_wt = BzrDir.create_standalone_workingtree('.')
63
self.transport.disconnect()
64
tests.TestCaseWithTransport.tearDown(self)
66
def test_create_remote_bzrdir(self):
67
b = remote.RemoteBzrDir(self.transport)
68
self.assertIsInstance(b, BzrDir)
70
def test_open_remote_branch(self):
71
# open a standalone branch in the working directory
72
b = remote.RemoteBzrDir(self.transport)
73
branch = b.open_branch()
74
self.assertIsInstance(branch, Branch)
76
def test_remote_repository(self):
77
b = BzrDir.open_from_transport(self.transport)
78
repo = b.open_repository()
79
revid = u'\xc823123123'.encode('utf8')
80
self.assertFalse(repo.has_revision(revid))
81
self.local_wt.commit(message='test commit', rev_id=revid)
82
self.assertTrue(repo.has_revision(revid))
84
def test_remote_branch_revision_history(self):
85
b = BzrDir.open_from_transport(self.transport).open_branch()
86
self.assertEqual([], b.revision_history())
87
r1 = self.local_wt.commit('1st commit')
88
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
89
self.assertEqual([r1, r2], b.revision_history())
91
def test_find_correct_format(self):
92
"""Should open a RemoteBzrDir over a RemoteTransport"""
93
fmt = BzrDirFormat.find_format(self.transport)
94
self.assertTrue(RemoteBzrDirFormat
95
in BzrDirFormat._control_server_formats)
96
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
98
def test_open_detected_smart_format(self):
99
fmt = BzrDirFormat.find_format(self.transport)
100
d = fmt.open(self.transport)
101
self.assertIsInstance(d, BzrDir)
103
def test_remote_branch_repr(self):
104
b = BzrDir.open_from_transport(self.transport).open_branch()
105
self.assertStartsWith(str(b), 'RemoteBranch(')
108
class FakeProtocol(object):
109
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
111
def __init__(self, body, fake_client):
113
self._body_buffer = None
114
self._fake_client = fake_client
116
def read_body_bytes(self, count=-1):
117
if self._body_buffer is None:
118
self._body_buffer = StringIO(self.body)
119
bytes = self._body_buffer.read(count)
120
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
121
self._fake_client.expecting_body = False
124
def cancel_read_body(self):
125
self._fake_client.expecting_body = False
127
def read_streamed_body(self):
131
class FakeClient(_SmartClient):
132
"""Lookalike for _SmartClient allowing testing."""
134
def __init__(self, responses, fake_medium_base='fake base'):
135
"""Create a FakeClient.
137
:param responses: A list of response-tuple, body-data pairs to be sent
140
self.responses = responses
142
self.expecting_body = False
143
_SmartClient.__init__(self, FakeMedium(fake_medium_base))
145
def call(self, method, *args):
146
self._calls.append(('call', method, args))
147
return self.responses.pop(0)[0]
149
def call_expecting_body(self, method, *args):
150
self._calls.append(('call_expecting_body', method, args))
151
result = self.responses.pop(0)
152
self.expecting_body = True
153
return result[0], FakeProtocol(result[1], self)
156
class FakeMedium(object):
158
def __init__(self, base):
162
class TestBzrDirOpenBranch(tests.TestCase):
164
def test_branch_present(self):
165
transport = MemoryTransport()
166
transport.mkdir('quack')
167
transport = transport.clone('quack')
168
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )],
170
bzrdir = RemoteBzrDir(transport, _client=client)
171
result = bzrdir.open_branch()
173
[('call', 'BzrDir.open_branch', ('quack/',)),
174
('call', 'BzrDir.find_repository', ('quack/',))],
176
self.assertIsInstance(result, RemoteBranch)
177
self.assertEqual(bzrdir, result.bzrdir)
179
def test_branch_missing(self):
180
transport = MemoryTransport()
181
transport.mkdir('quack')
182
transport = transport.clone('quack')
183
client = FakeClient([(('nobranch',), )], transport.base)
184
bzrdir = RemoteBzrDir(transport, _client=client)
185
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
187
[('call', 'BzrDir.open_branch', ('quack/',))],
190
def check_open_repository(self, rich_root, subtrees):
191
transport = MemoryTransport()
192
transport.mkdir('quack')
193
transport = transport.clone('quack')
195
rich_response = 'yes'
199
subtree_response = 'yes'
201
subtree_response = 'no'
202
client = FakeClient([(('ok', '', rich_response, subtree_response), ),],
204
bzrdir = RemoteBzrDir(transport, _client=client)
205
result = bzrdir.open_repository()
207
[('call', 'BzrDir.find_repository', ('quack/',))],
209
self.assertIsInstance(result, RemoteRepository)
210
self.assertEqual(bzrdir, result.bzrdir)
211
self.assertEqual(rich_root, result._format.rich_root_data)
212
self.assertEqual(subtrees, result._format.supports_tree_reference)
214
def test_open_repository_sets_format_attributes(self):
215
self.check_open_repository(True, True)
216
self.check_open_repository(False, True)
217
self.check_open_repository(True, False)
218
self.check_open_repository(False, False)
220
def test_old_server(self):
221
"""RemoteBzrDirFormat should fail to probe if the server version is too
224
self.assertRaises(errors.NotBranchError,
225
RemoteBzrDirFormat.probe_transport, OldServerTransport())
228
class OldSmartClient(object):
229
"""A fake smart client for test_old_version that just returns a version one
230
response to the 'hello' (query version) command.
233
def get_request(self):
234
input_file = StringIO('ok\x011\n')
235
output_file = StringIO()
236
client_medium = medium.SmartSimplePipesClientMedium(
237
input_file, output_file)
238
return medium.SmartClientStreamMediumRequest(client_medium)
241
class OldServerTransport(object):
242
"""A fake transport for test_old_server that reports it's smart server
243
protocol version as version one.
249
def get_smart_client(self):
250
return OldSmartClient()
253
class TestBranchLastRevisionInfo(tests.TestCase):
255
def test_empty_branch(self):
256
# in an empty branch we decode the response properly
257
transport = MemoryTransport()
258
client = FakeClient([(('ok', '0', 'null:'), )], transport.base)
259
transport.mkdir('quack')
260
transport = transport.clone('quack')
261
# we do not want bzrdir to make any remote calls
262
bzrdir = RemoteBzrDir(transport, _client=False)
263
branch = RemoteBranch(bzrdir, None, _client=client)
264
result = branch.last_revision_info()
267
[('call', 'Branch.last_revision_info', ('quack/',))],
269
self.assertEqual((0, NULL_REVISION), result)
271
def test_non_empty_branch(self):
272
# in a non-empty branch we also decode the response properly
273
revid = u'\xc8'.encode('utf8')
274
transport = MemoryTransport()
275
client = FakeClient([(('ok', '2', revid), )], transport.base)
276
transport.mkdir('kwaak')
277
transport = transport.clone('kwaak')
278
# we do not want bzrdir to make any remote calls
279
bzrdir = RemoteBzrDir(transport, _client=False)
280
branch = RemoteBranch(bzrdir, None, _client=client)
281
result = branch.last_revision_info()
284
[('call', 'Branch.last_revision_info', ('kwaak/',))],
286
self.assertEqual((2, revid), result)
289
class TestBranchSetLastRevision(tests.TestCase):
291
def test_set_empty(self):
292
# set_revision_history([]) is translated to calling
293
# Branch.set_last_revision(path, '') on the wire.
294
transport = MemoryTransport()
295
transport.mkdir('branch')
296
transport = transport.clone('branch')
298
client = FakeClient([
300
(('ok', 'branch token', 'repo token'), ),
306
bzrdir = RemoteBzrDir(transport, _client=False)
307
branch = RemoteBranch(bzrdir, None, _client=client)
308
# This is a hack to work around the problem that RemoteBranch currently
309
# unnecessarily invokes _ensure_real upon a call to lock_write.
310
branch._ensure_real = lambda: None
313
result = branch.set_revision_history([])
315
[('call', 'Branch.set_last_revision',
316
('branch/', 'branch token', 'repo token', 'null:'))],
319
self.assertEqual(None, result)
321
def test_set_nonempty(self):
322
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
323
# Branch.set_last_revision(path, rev-idN) on the wire.
324
transport = MemoryTransport()
325
transport.mkdir('branch')
326
transport = transport.clone('branch')
328
client = FakeClient([
330
(('ok', 'branch token', 'repo token'), ),
336
bzrdir = RemoteBzrDir(transport, _client=False)
337
branch = RemoteBranch(bzrdir, None, _client=client)
338
# This is a hack to work around the problem that RemoteBranch currently
339
# unnecessarily invokes _ensure_real upon a call to lock_write.
340
branch._ensure_real = lambda: None
341
# Lock the branch, reset the record of remote calls.
345
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
347
[('call', 'Branch.set_last_revision',
348
('branch/', 'branch token', 'repo token', 'rev-id2'))],
351
self.assertEqual(None, result)
353
def test_no_such_revision(self):
354
# A response of 'NoSuchRevision' is translated into an exception.
355
client = FakeClient([
357
(('ok', 'branch token', 'repo token'), ),
359
(('NoSuchRevision', 'rev-id'), ),
362
transport = MemoryTransport()
363
transport.mkdir('branch')
364
transport = transport.clone('branch')
366
bzrdir = RemoteBzrDir(transport, _client=False)
367
branch = RemoteBranch(bzrdir, None, _client=client)
368
branch._ensure_real = lambda: None
373
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
377
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
378
"""Test branch.control_files api munging...
380
We special case RemoteBranch.control_files.get('branch.conf') to
381
call a specific API so that RemoteBranch's can intercept configuration
382
file reading, allowing them to signal to the client about things like
383
'email is configured for commits'.
386
def test_get_branch_conf(self):
387
# in an empty branch we decode the response properly
388
client = FakeClient([(('ok', ), 'config file body')], self.get_url())
389
# we need to make a real branch because the remote_branch.control_files
390
# will trigger _ensure_real.
391
branch = self.make_branch('quack')
392
transport = branch.bzrdir.root_transport
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
result = branch.control_files.get('branch.conf')
398
[('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
400
self.assertEqual('config file body', result.read())
403
class TestBranchLockWrite(tests.TestCase):
405
def test_lock_write_unlockable(self):
406
transport = MemoryTransport()
407
client = FakeClient([(('UnlockableTransport', ), '')], transport.base)
408
transport.mkdir('quack')
409
transport = transport.clone('quack')
410
# we do not want bzrdir to make any remote calls
411
bzrdir = RemoteBzrDir(transport, _client=False)
412
branch = RemoteBranch(bzrdir, None, _client=client)
413
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
415
[('call', 'Branch.lock_write', ('quack/', '', ''))],
419
class TestTransportIsReadonly(tests.TestCase):
422
client = FakeClient([(('yes',), '')])
423
transport = RemoteTransport('bzr://example.com/', medium=False,
425
self.assertEqual(True, transport.is_readonly())
427
[('call', 'Transport.is_readonly', ())],
430
def test_false(self):
431
client = FakeClient([(('no',), '')])
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_server(self):
440
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
442
Clients should treat it as a "no" response, because is_readonly is only
443
advisory anyway (a transport could be read-write, but then the
444
underlying filesystem could be readonly anyway).
446
client = FakeClient([(
447
('error', "Generic bzr smart protocol error: "
448
"bad request 'Transport.is_readonly'"), '')])
449
transport = RemoteTransport('bzr://example.com/', medium=False,
451
self.assertEqual(False, transport.is_readonly())
453
[('call', 'Transport.is_readonly', ())],
456
def test_error_from_old_0_11_server(self):
457
"""Same as test_error_from_old_server, but with the slightly different
458
error message from bzr 0.11 servers.
460
client = FakeClient([(
461
('error', "Generic bzr smart protocol error: "
462
"bad request u'Transport.is_readonly'"), '')])
463
transport = RemoteTransport('bzr://example.com/', medium=False,
465
self.assertEqual(False, transport.is_readonly())
467
[('call', 'Transport.is_readonly', ())],
471
class TestRemoteRepository(tests.TestCase):
472
"""Base for testing RemoteRepository protocol usage.
474
These tests contain frozen requests and responses. We want any changes to
475
what is sent or expected to be require a thoughtful update to these tests
476
because they might break compatibility with different-versioned servers.
479
def setup_fake_client_and_repository(self, responses, transport_path):
480
"""Create the fake client and repository for testing with.
482
There's no real server here; we just have canned responses sent
485
:param transport_path: Path below the root of the MemoryTransport
486
where the repository will be created.
488
transport = MemoryTransport()
489
transport.mkdir(transport_path)
490
client = FakeClient(responses, transport.base)
491
transport = transport.clone(transport_path)
492
# we do not want bzrdir to make any remote calls
493
bzrdir = RemoteBzrDir(transport, _client=False)
494
repo = RemoteRepository(bzrdir, None, _client=client)
498
class TestRepositoryGatherStats(TestRemoteRepository):
500
def test_revid_none(self):
501
# ('ok',), body with revisions and size
502
responses = [(('ok', ), 'revisions: 2\nsize: 18\n')]
503
transport_path = 'quack'
504
repo, client = self.setup_fake_client_and_repository(
505
responses, transport_path)
506
result = repo.gather_stats(None)
508
[('call_expecting_body', 'Repository.gather_stats',
509
('quack/','','no'))],
511
self.assertEqual({'revisions': 2, 'size': 18}, result)
513
def test_revid_no_committers(self):
514
# ('ok',), body without committers
515
responses = [(('ok', ),
516
'firstrev: 123456.300 3600\n'
517
'latestrev: 654231.400 0\n'
520
transport_path = 'quick'
521
revid = u'\xc8'.encode('utf8')
522
repo, client = self.setup_fake_client_and_repository(
523
responses, transport_path)
524
result = repo.gather_stats(revid)
526
[('call_expecting_body', 'Repository.gather_stats',
527
('quick/', revid, 'no'))],
529
self.assertEqual({'revisions': 2, 'size': 18,
530
'firstrev': (123456.300, 3600),
531
'latestrev': (654231.400, 0),},
534
def test_revid_with_committers(self):
535
# ('ok',), body with committers
536
responses = [(('ok', ),
538
'firstrev: 123456.300 3600\n'
539
'latestrev: 654231.400 0\n'
542
transport_path = 'buick'
543
revid = u'\xc8'.encode('utf8')
544
repo, client = self.setup_fake_client_and_repository(
545
responses, transport_path)
546
result = repo.gather_stats(revid, True)
548
[('call_expecting_body', 'Repository.gather_stats',
549
('buick/', revid, 'yes'))],
551
self.assertEqual({'revisions': 2, 'size': 18,
553
'firstrev': (123456.300, 3600),
554
'latestrev': (654231.400, 0),},
558
class TestRepositoryGetGraph(TestRemoteRepository):
560
def test_get_graph(self):
561
# get_graph returns a graph with the repository as the
564
transport_path = 'quack'
565
repo, client = self.setup_fake_client_and_repository(
566
responses, transport_path)
567
graph = repo.get_graph()
568
self.assertEqual(graph._parents_provider, repo)
571
class TestRepositoryGetParentMap(TestRemoteRepository):
573
def test_get_parent_map_caching(self):
574
# get_parent_map returns from cache until unlock()
575
# setup a reponse with two revisions
576
r1 = u'\u0e33'.encode('utf8')
577
r2 = u'\u0dab'.encode('utf8')
578
lines = [' '.join([r2, r1]), r1]
579
encoded_body = '\n'.join(lines)
580
responses = [(('ok', ), encoded_body), (('ok', ), encoded_body)]
582
transport_path = 'quack'
583
repo, client = self.setup_fake_client_and_repository(
584
responses, transport_path)
586
graph = repo.get_graph()
587
parents = graph.get_parent_map([r2])
588
self.assertEqual({r2: (r1,)}, parents)
589
# locking and unlocking deeper should not reset
592
parents = graph.get_parent_map([r1])
593
self.assertEqual({r1: (NULL_REVISION,)}, parents)
595
[('call_expecting_body', 'Repository.get_parent_map',
599
# now we call again, and it should use the second response.
601
graph = repo.get_graph()
602
parents = graph.get_parent_map([r1])
603
self.assertEqual({r1: (NULL_REVISION,)}, parents)
605
[('call_expecting_body', 'Repository.get_parent_map',
607
('call_expecting_body', 'Repository.get_parent_map',
614
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
616
def test_null_revision(self):
617
# a null revision has the predictable result {}, we should have no wire
618
# traffic when calling it with this argument
619
responses = [(('notused', ), '')]
620
transport_path = 'empty'
621
repo, client = self.setup_fake_client_and_repository(
622
responses, transport_path)
623
result = repo.get_revision_graph(NULL_REVISION)
624
self.assertEqual([], client._calls)
625
self.assertEqual({}, result)
627
def test_none_revision(self):
628
# with none we want the entire graph
629
r1 = u'\u0e33'.encode('utf8')
630
r2 = u'\u0dab'.encode('utf8')
631
lines = [' '.join([r2, r1]), r1]
632
encoded_body = '\n'.join(lines)
634
responses = [(('ok', ), encoded_body)]
635
transport_path = 'sinhala'
636
repo, client = self.setup_fake_client_and_repository(
637
responses, transport_path)
638
result = repo.get_revision_graph()
640
[('call_expecting_body', 'Repository.get_revision_graph',
643
self.assertEqual({r1: (), r2: (r1, )}, result)
645
def test_specific_revision(self):
646
# with a specific revision we want the graph for that
647
# with none we want the entire graph
648
r11 = u'\u0e33'.encode('utf8')
649
r12 = u'\xc9'.encode('utf8')
650
r2 = u'\u0dab'.encode('utf8')
651
lines = [' '.join([r2, r11, r12]), r11, r12]
652
encoded_body = '\n'.join(lines)
654
responses = [(('ok', ), encoded_body)]
655
transport_path = 'sinhala'
656
repo, client = self.setup_fake_client_and_repository(
657
responses, transport_path)
658
result = repo.get_revision_graph(r2)
660
[('call_expecting_body', 'Repository.get_revision_graph',
663
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
665
def test_no_such_revision(self):
667
responses = [(('nosuchrevision', revid), '')]
668
transport_path = 'sinhala'
669
repo, client = self.setup_fake_client_and_repository(
670
responses, transport_path)
671
# also check that the right revision is reported in the error
672
self.assertRaises(errors.NoSuchRevision,
673
repo.get_revision_graph, revid)
675
[('call_expecting_body', 'Repository.get_revision_graph',
676
('sinhala/', revid))],
680
class TestRepositoryIsShared(TestRemoteRepository):
682
def test_is_shared(self):
683
# ('yes', ) for Repository.is_shared -> 'True'.
684
responses = [(('yes', ), )]
685
transport_path = 'quack'
686
repo, client = self.setup_fake_client_and_repository(
687
responses, transport_path)
688
result = repo.is_shared()
690
[('call', 'Repository.is_shared', ('quack/',))],
692
self.assertEqual(True, result)
694
def test_is_not_shared(self):
695
# ('no', ) for Repository.is_shared -> 'False'.
696
responses = [(('no', ), )]
697
transport_path = 'qwack'
698
repo, client = self.setup_fake_client_and_repository(
699
responses, transport_path)
700
result = repo.is_shared()
702
[('call', 'Repository.is_shared', ('qwack/',))],
704
self.assertEqual(False, result)
707
class TestRepositoryLockWrite(TestRemoteRepository):
709
def test_lock_write(self):
710
responses = [(('ok', 'a token'), '')]
711
transport_path = 'quack'
712
repo, client = self.setup_fake_client_and_repository(
713
responses, transport_path)
714
result = repo.lock_write()
716
[('call', 'Repository.lock_write', ('quack/', ''))],
718
self.assertEqual('a token', result)
720
def test_lock_write_already_locked(self):
721
responses = [(('LockContention', ), '')]
722
transport_path = 'quack'
723
repo, client = self.setup_fake_client_and_repository(
724
responses, transport_path)
725
self.assertRaises(errors.LockContention, repo.lock_write)
727
[('call', 'Repository.lock_write', ('quack/', ''))],
730
def test_lock_write_unlockable(self):
731
responses = [(('UnlockableTransport', ), '')]
732
transport_path = 'quack'
733
repo, client = self.setup_fake_client_and_repository(
734
responses, transport_path)
735
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
737
[('call', 'Repository.lock_write', ('quack/', ''))],
741
class TestRepositoryUnlock(TestRemoteRepository):
743
def test_unlock(self):
744
responses = [(('ok', 'a token'), ''),
746
transport_path = 'quack'
747
repo, client = self.setup_fake_client_and_repository(
748
responses, transport_path)
752
[('call', 'Repository.lock_write', ('quack/', '')),
753
('call', 'Repository.unlock', ('quack/', 'a token'))],
756
def test_unlock_wrong_token(self):
757
# If somehow the token is wrong, unlock will raise TokenMismatch.
758
responses = [(('ok', 'a token'), ''),
759
(('TokenMismatch',), '')]
760
transport_path = 'quack'
761
repo, client = self.setup_fake_client_and_repository(
762
responses, transport_path)
764
self.assertRaises(errors.TokenMismatch, repo.unlock)
767
class TestRepositoryHasRevision(TestRemoteRepository):
770
# repo.has_revision(None) should not cause any traffic.
771
transport_path = 'quack'
773
repo, client = self.setup_fake_client_and_repository(
774
responses, transport_path)
776
# The null revision is always there, so has_revision(None) == True.
777
self.assertEqual(True, repo.has_revision(NULL_REVISION))
779
# The remote repo shouldn't be accessed.
780
self.assertEqual([], client._calls)
783
class TestRepositoryTarball(TestRemoteRepository):
785
# This is a canned tarball reponse we can validate against
787
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
788
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
789
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
790
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
791
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
792
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
793
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
794
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
795
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
796
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
797
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
798
'nWQ7QH/F3JFOFCQ0aSPfA='
801
def test_repository_tarball(self):
802
# Test that Repository.tarball generates the right operations
803
transport_path = 'repo'
804
expected_responses = [(('ok',), self.tarball_content),
806
expected_calls = [('call_expecting_body', 'Repository.tarball',
809
remote_repo, client = self.setup_fake_client_and_repository(
810
expected_responses, transport_path)
811
# Now actually ask for the tarball
812
tarball_file = remote_repo._get_tarball('bz2')
814
self.assertEqual(expected_calls, client._calls)
815
self.assertEqual(self.tarball_content, tarball_file.read())
820
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
821
"""RemoteRepository.copy_content_into optimizations"""
823
def test_copy_content_remote_to_local(self):
824
self.transport_server = server.SmartTCPServer_for_testing
825
src_repo = self.make_repository('repo1')
826
src_repo = repository.Repository.open(self.get_url('repo1'))
827
# At the moment the tarball-based copy_content_into can't write back
828
# into a smart server. It would be good if it could upload the
829
# tarball; once that works we'd have to create repositories of
830
# different formats. -- mbp 20070410
831
dest_url = self.get_vfs_only_url('repo2')
832
dest_bzrdir = BzrDir.create(dest_url)
833
dest_repo = dest_bzrdir.create_repository()
834
self.assertFalse(isinstance(dest_repo, RemoteRepository))
835
self.assertTrue(isinstance(src_repo, RemoteRepository))
836
src_repo.copy_content_into(dest_repo)
839
class TestRepositoryStreamKnitData(TestRemoteRepository):
841
def make_pack_file(self, records):
842
pack_file = StringIO()
843
pack_writer = pack.ContainerWriter(pack_file.write)
845
for bytes, names in records:
846
pack_writer.add_bytes_record(bytes, names)
851
def make_pack_stream(self, records):
852
pack_serialiser = pack.ContainerSerialiser()
853
yield pack_serialiser.begin()
854
for bytes, names in records:
855
yield pack_serialiser.bytes_record(bytes, names)
856
yield pack_serialiser.end()
858
def test_bad_pack_from_server(self):
859
"""A response with invalid data (e.g. it has a record with multiple
860
names) triggers an exception.
862
Not all possible errors will be caught at this stage, but obviously
863
malformed data should be.
865
record = ('bytes', [('name1',), ('name2',)])
866
pack_stream = self.make_pack_stream([record])
867
responses = [(('ok',), pack_stream), ]
868
transport_path = 'quack'
869
repo, client = self.setup_fake_client_and_repository(
870
responses, transport_path)
871
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
872
stream = repo.get_data_stream_for_search(search)
873
self.assertRaises(errors.SmartProtocolError, list, stream)
875
def test_backwards_compatibility(self):
876
"""If the server doesn't recognise this request, fallback to VFS."""
878
"Generic bzr smart protocol error: "
879
"bad request 'Repository.stream_revisions_chunked'")
881
(('error', error_msg), '')]
882
repo, client = self.setup_fake_client_and_repository(
884
self.mock_called = False
885
repo._real_repository = MockRealRepository(self)
886
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
887
repo.get_data_stream_for_search(search)
888
self.assertTrue(self.mock_called)
889
self.failIf(client.expecting_body,
890
"The protocol has been left in an unclean state that will cause "
891
"TooManyConcurrentRequests errors.")
894
class MockRealRepository(object):
895
"""Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
897
def __init__(self, test):
900
def get_data_stream_for_search(self, search):
901
self.test.assertEqual(set(['revid']), search.get_keys())
902
self.test.mock_called = True