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.
27
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.symbol_versioning import one_four
49
from bzrlib.transport.memory import MemoryTransport
50
from bzrlib.transport.remote import RemoteTransport
53
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
56
self.transport_server = server.SmartTCPServer_for_testing
57
super(BasicRemoteObjectTests, self).setUp()
58
self.transport = self.get_transport()
59
self.client = self.transport.get_smart_client()
60
# make a branch that can be opened over the smart transport
61
self.local_wt = BzrDir.create_standalone_workingtree('.')
64
self.transport.disconnect()
65
tests.TestCaseWithTransport.tearDown(self)
67
def test_create_remote_bzrdir(self):
68
b = remote.RemoteBzrDir(self.transport)
69
self.assertIsInstance(b, BzrDir)
71
def test_open_remote_branch(self):
72
# open a standalone branch in the working directory
73
b = remote.RemoteBzrDir(self.transport)
74
branch = b.open_branch()
75
self.assertIsInstance(branch, Branch)
77
def test_remote_repository(self):
78
b = BzrDir.open_from_transport(self.transport)
79
repo = b.open_repository()
80
revid = u'\xc823123123'.encode('utf8')
81
self.assertFalse(repo.has_revision(revid))
82
self.local_wt.commit(message='test commit', rev_id=revid)
83
self.assertTrue(repo.has_revision(revid))
85
def test_remote_branch_revision_history(self):
86
b = BzrDir.open_from_transport(self.transport).open_branch()
87
self.assertEqual([], b.revision_history())
88
r1 = self.local_wt.commit('1st commit')
89
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
90
self.assertEqual([r1, r2], b.revision_history())
92
def test_find_correct_format(self):
93
"""Should open a RemoteBzrDir over a RemoteTransport"""
94
fmt = BzrDirFormat.find_format(self.transport)
95
self.assertTrue(RemoteBzrDirFormat
96
in BzrDirFormat._control_server_formats)
97
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
99
def test_open_detected_smart_format(self):
100
fmt = BzrDirFormat.find_format(self.transport)
101
d = fmt.open(self.transport)
102
self.assertIsInstance(d, BzrDir)
104
def test_remote_branch_repr(self):
105
b = BzrDir.open_from_transport(self.transport).open_branch()
106
self.assertStartsWith(str(b), 'RemoteBranch(')
109
class FakeProtocol(object):
110
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
112
def __init__(self, body, fake_client):
114
self._body_buffer = None
115
self._fake_client = fake_client
117
def read_body_bytes(self, count=-1):
118
if self._body_buffer is None:
119
self._body_buffer = StringIO(self.body)
120
bytes = self._body_buffer.read(count)
121
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
122
self._fake_client.expecting_body = False
125
def cancel_read_body(self):
126
self._fake_client.expecting_body = False
128
def read_streamed_body(self):
132
class FakeClient(_SmartClient):
133
"""Lookalike for _SmartClient allowing testing."""
135
def __init__(self, responses, fake_medium_base='fake base'):
136
"""Create a FakeClient.
138
:param responses: A list of response-tuple, body-data pairs to be sent
141
self.responses = responses
143
self.expecting_body = False
144
_SmartClient.__init__(self, FakeMedium(fake_medium_base, self._calls))
146
def call(self, method, *args):
147
self._calls.append(('call', method, args))
148
return self.responses.pop(0)[0]
150
def call_expecting_body(self, method, *args):
151
self._calls.append(('call_expecting_body', method, args))
152
result = self.responses.pop(0)
153
self.expecting_body = True
154
return result[0], FakeProtocol(result[1], self)
156
def call_with_body_bytes_expecting_body(self, method, args, body):
157
self._calls.append(('call_with_body_bytes_expecting_body', method,
159
result = self.responses.pop(0)
160
self.expecting_body = True
161
return result[0], FakeProtocol(result[1], self)
164
class FakeMedium(object):
166
def __init__(self, base, client_calls):
168
self.connection = FakeConnection(client_calls)
169
self._client_calls = client_calls
172
class FakeConnection(object):
174
def __init__(self, client_calls):
175
self._remote_is_at_least_1_2 = True
176
self._client_calls = client_calls
178
def disconnect(self):
179
self._client_calls.append(('disconnect medium',))
182
class TestVfsHas(tests.TestCase):
184
def test_unicode_path(self):
185
client = FakeClient([(('yes',), )], '/')
186
transport = RemoteTransport('bzr://localhost/', _client=client)
187
filename = u'/hell\u00d8'.encode('utf8')
188
result = transport.has(filename)
190
[('call', 'has', (filename,))],
192
self.assertTrue(result)
195
class TestBzrDirOpenBranch(tests.TestCase):
197
def test_branch_present(self):
198
transport = MemoryTransport()
199
transport.mkdir('quack')
200
transport = transport.clone('quack')
201
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no', 'no'), )],
203
bzrdir = RemoteBzrDir(transport, _client=client)
204
result = bzrdir.open_branch()
206
[('call', 'BzrDir.open_branch', ('quack/',)),
207
('call', 'BzrDir.find_repositoryV2', ('quack/',))],
209
self.assertIsInstance(result, RemoteBranch)
210
self.assertEqual(bzrdir, result.bzrdir)
212
def test_branch_missing(self):
213
transport = MemoryTransport()
214
transport.mkdir('quack')
215
transport = transport.clone('quack')
216
client = FakeClient([(('nobranch',), )], transport.base)
217
bzrdir = RemoteBzrDir(transport, _client=client)
218
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
220
[('call', 'BzrDir.open_branch', ('quack/',))],
223
def test__get_tree_branch(self):
224
# _get_tree_branch is a form of open_branch, but it should only ask for
225
# branch opening, not any other network requests.
228
calls.append("Called")
230
transport = MemoryTransport()
231
# no requests on the network - catches other api calls being made.
232
client = FakeClient([], transport.base)
233
bzrdir = RemoteBzrDir(transport, _client=client)
234
# patch the open_branch call to record that it was called.
235
bzrdir.open_branch = open_branch
236
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
237
self.assertEqual(["Called"], calls)
238
self.assertEqual([], client._calls)
240
def test_url_quoting_of_path(self):
241
# Relpaths on the wire should not be URL-escaped. So "~" should be
242
# transmitted as "~", not "%7E".
243
transport = RemoteTransport('bzr://localhost/~hello/')
244
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no', 'no'), )],
246
bzrdir = RemoteBzrDir(transport, _client=client)
247
result = bzrdir.open_branch()
249
[('call', 'BzrDir.open_branch', ('~hello/',)),
250
('call', 'BzrDir.find_repositoryV2', ('~hello/',))],
253
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
254
transport = MemoryTransport()
255
transport.mkdir('quack')
256
transport = transport.clone('quack')
258
rich_response = 'yes'
262
subtree_response = 'yes'
264
subtree_response = 'no'
266
[(('ok', '', rich_response, subtree_response, external_lookup), ),],
268
bzrdir = RemoteBzrDir(transport, _client=client)
269
result = bzrdir.open_repository()
271
[('call', 'BzrDir.find_repositoryV2', ('quack/',))],
273
self.assertIsInstance(result, RemoteRepository)
274
self.assertEqual(bzrdir, result.bzrdir)
275
self.assertEqual(rich_root, result._format.rich_root_data)
276
self.assertEqual(subtrees, result._format.supports_tree_reference)
278
def test_open_repository_sets_format_attributes(self):
279
self.check_open_repository(True, True)
280
self.check_open_repository(False, True)
281
self.check_open_repository(True, False)
282
self.check_open_repository(False, False)
283
self.check_open_repository(False, False, 'yes')
285
def test_old_server(self):
286
"""RemoteBzrDirFormat should fail to probe if the server version is too
289
self.assertRaises(errors.NotBranchError,
290
RemoteBzrDirFormat.probe_transport, OldServerTransport())
293
class OldSmartClient(object):
294
"""A fake smart client for test_old_version that just returns a version one
295
response to the 'hello' (query version) command.
298
def get_request(self):
299
input_file = StringIO('ok\x011\n')
300
output_file = StringIO()
301
client_medium = medium.SmartSimplePipesClientMedium(
302
input_file, output_file)
303
return medium.SmartClientStreamMediumRequest(client_medium)
306
class OldServerTransport(object):
307
"""A fake transport for test_old_server that reports it's smart server
308
protocol version as version one.
314
def get_smart_client(self):
315
return OldSmartClient()
318
class TestBranchLastRevisionInfo(tests.TestCase):
320
def test_empty_branch(self):
321
# in an empty branch we decode the response properly
322
transport = MemoryTransport()
323
client = FakeClient([(('ok', '0', 'null:'), )], transport.base)
324
transport.mkdir('quack')
325
transport = transport.clone('quack')
326
# we do not want bzrdir to make any remote calls
327
bzrdir = RemoteBzrDir(transport, _client=False)
328
branch = RemoteBranch(bzrdir, None, _client=client)
329
result = branch.last_revision_info()
332
[('call', 'Branch.last_revision_info', ('quack/',))],
334
self.assertEqual((0, NULL_REVISION), result)
336
def test_non_empty_branch(self):
337
# in a non-empty branch we also decode the response properly
338
revid = u'\xc8'.encode('utf8')
339
transport = MemoryTransport()
340
client = FakeClient([(('ok', '2', revid), )], transport.base)
341
transport.mkdir('kwaak')
342
transport = transport.clone('kwaak')
343
# we do not want bzrdir to make any remote calls
344
bzrdir = RemoteBzrDir(transport, _client=False)
345
branch = RemoteBranch(bzrdir, None, _client=client)
346
result = branch.last_revision_info()
349
[('call', 'Branch.last_revision_info', ('kwaak/',))],
351
self.assertEqual((2, revid), result)
354
class TestBranchSetLastRevision(tests.TestCase):
356
def test_set_empty(self):
357
# set_revision_history([]) is translated to calling
358
# Branch.set_last_revision(path, '') on the wire.
359
transport = MemoryTransport()
360
transport.mkdir('branch')
361
transport = transport.clone('branch')
363
client = FakeClient([
365
(('ok', 'branch token', 'repo token'), ),
371
bzrdir = RemoteBzrDir(transport, _client=False)
372
branch = RemoteBranch(bzrdir, None, _client=client)
373
# This is a hack to work around the problem that RemoteBranch currently
374
# unnecessarily invokes _ensure_real upon a call to lock_write.
375
branch._ensure_real = lambda: None
378
result = branch.set_revision_history([])
380
[('call', 'Branch.set_last_revision',
381
('branch/', 'branch token', 'repo token', 'null:'))],
384
self.assertEqual(None, result)
386
def test_set_nonempty(self):
387
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
388
# Branch.set_last_revision(path, rev-idN) on the wire.
389
transport = MemoryTransport()
390
transport.mkdir('branch')
391
transport = transport.clone('branch')
393
client = FakeClient([
395
(('ok', 'branch token', 'repo token'), ),
401
bzrdir = RemoteBzrDir(transport, _client=False)
402
branch = RemoteBranch(bzrdir, None, _client=client)
403
# This is a hack to work around the problem that RemoteBranch currently
404
# unnecessarily invokes _ensure_real upon a call to lock_write.
405
branch._ensure_real = lambda: None
406
# Lock the branch, reset the record of remote calls.
410
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
412
[('call', 'Branch.set_last_revision',
413
('branch/', 'branch token', 'repo token', 'rev-id2'))],
416
self.assertEqual(None, result)
418
def test_no_such_revision(self):
419
# A response of 'NoSuchRevision' is translated into an exception.
420
client = FakeClient([
422
(('ok', 'branch token', 'repo token'), ),
424
(('NoSuchRevision', 'rev-id'), ),
427
transport = MemoryTransport()
428
transport.mkdir('branch')
429
transport = transport.clone('branch')
431
bzrdir = RemoteBzrDir(transport, _client=False)
432
branch = RemoteBranch(bzrdir, None, _client=client)
433
branch._ensure_real = lambda: None
438
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
442
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
443
"""Test branch.control_files api munging...
445
We special case RemoteBranch.control_files.get('branch.conf') to
446
call a specific API so that RemoteBranch's can intercept configuration
447
file reading, allowing them to signal to the client about things like
448
'email is configured for commits'.
451
def test_get_branch_conf(self):
452
# in an empty branch we decode the response properly
453
client = FakeClient([(('ok', ), 'config file body')], self.get_url())
454
# we need to make a real branch because the remote_branch.control_files
455
# will trigger _ensure_real.
456
branch = self.make_branch('quack')
457
transport = branch.bzrdir.root_transport
458
# we do not want bzrdir to make any remote calls
459
bzrdir = RemoteBzrDir(transport, _client=False)
460
branch = RemoteBranch(bzrdir, None, _client=client)
461
result = branch.control_files.get('branch.conf')
463
[('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
465
self.assertEqual('config file body', result.read())
468
class TestBranchLockWrite(tests.TestCase):
470
def test_lock_write_unlockable(self):
471
transport = MemoryTransport()
472
client = FakeClient([(('UnlockableTransport', ), '')], transport.base)
473
transport.mkdir('quack')
474
transport = transport.clone('quack')
475
# we do not want bzrdir to make any remote calls
476
bzrdir = RemoteBzrDir(transport, _client=False)
477
branch = RemoteBranch(bzrdir, None, _client=client)
478
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
480
[('call', 'Branch.lock_write', ('quack/', '', ''))],
484
class TestTransportIsReadonly(tests.TestCase):
487
client = FakeClient([(('yes',), '')])
488
transport = RemoteTransport('bzr://example.com/', medium=False,
490
self.assertEqual(True, transport.is_readonly())
492
[('call', 'Transport.is_readonly', ())],
495
def test_false(self):
496
client = FakeClient([(('no',), '')])
497
transport = RemoteTransport('bzr://example.com/', medium=False,
499
self.assertEqual(False, transport.is_readonly())
501
[('call', 'Transport.is_readonly', ())],
504
def test_error_from_old_server(self):
505
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
507
Clients should treat it as a "no" response, because is_readonly is only
508
advisory anyway (a transport could be read-write, but then the
509
underlying filesystem could be readonly anyway).
511
client = FakeClient([(
512
('error', "Generic bzr smart protocol error: "
513
"bad request 'Transport.is_readonly'"), '')])
514
transport = RemoteTransport('bzr://example.com/', medium=False,
516
self.assertEqual(False, transport.is_readonly())
518
[('call', 'Transport.is_readonly', ())],
521
def test_error_from_old_0_11_server(self):
522
"""Same as test_error_from_old_server, but with the slightly different
523
error message from bzr 0.11 servers.
525
client = FakeClient([(
526
('error', "Generic bzr smart protocol error: "
527
"bad request u'Transport.is_readonly'"), '')])
528
transport = RemoteTransport('bzr://example.com/', medium=False,
530
self.assertEqual(False, transport.is_readonly())
532
[('call', 'Transport.is_readonly', ())],
536
class TestRemoteRepository(tests.TestCase):
537
"""Base for testing RemoteRepository protocol usage.
539
These tests contain frozen requests and responses. We want any changes to
540
what is sent or expected to be require a thoughtful update to these tests
541
because they might break compatibility with different-versioned servers.
544
def setup_fake_client_and_repository(self, responses, transport_path):
545
"""Create the fake client and repository for testing with.
547
There's no real server here; we just have canned responses sent
550
:param transport_path: Path below the root of the MemoryTransport
551
where the repository will be created.
553
transport = MemoryTransport()
554
transport.mkdir(transport_path)
555
client = FakeClient(responses, transport.base)
556
transport = transport.clone(transport_path)
557
# we do not want bzrdir to make any remote calls
558
bzrdir = RemoteBzrDir(transport, _client=False)
559
repo = RemoteRepository(bzrdir, None, _client=client)
563
class TestRepositoryGatherStats(TestRemoteRepository):
565
def test_revid_none(self):
566
# ('ok',), body with revisions and size
567
responses = [(('ok', ), 'revisions: 2\nsize: 18\n')]
568
transport_path = 'quack'
569
repo, client = self.setup_fake_client_and_repository(
570
responses, transport_path)
571
result = repo.gather_stats(None)
573
[('call_expecting_body', 'Repository.gather_stats',
574
('quack/','','no'))],
576
self.assertEqual({'revisions': 2, 'size': 18}, result)
578
def test_revid_no_committers(self):
579
# ('ok',), body without committers
580
responses = [(('ok', ),
581
'firstrev: 123456.300 3600\n'
582
'latestrev: 654231.400 0\n'
585
transport_path = 'quick'
586
revid = u'\xc8'.encode('utf8')
587
repo, client = self.setup_fake_client_and_repository(
588
responses, transport_path)
589
result = repo.gather_stats(revid)
591
[('call_expecting_body', 'Repository.gather_stats',
592
('quick/', revid, 'no'))],
594
self.assertEqual({'revisions': 2, 'size': 18,
595
'firstrev': (123456.300, 3600),
596
'latestrev': (654231.400, 0),},
599
def test_revid_with_committers(self):
600
# ('ok',), body with committers
601
responses = [(('ok', ),
603
'firstrev: 123456.300 3600\n'
604
'latestrev: 654231.400 0\n'
607
transport_path = 'buick'
608
revid = u'\xc8'.encode('utf8')
609
repo, client = self.setup_fake_client_and_repository(
610
responses, transport_path)
611
result = repo.gather_stats(revid, True)
613
[('call_expecting_body', 'Repository.gather_stats',
614
('buick/', revid, 'yes'))],
616
self.assertEqual({'revisions': 2, 'size': 18,
618
'firstrev': (123456.300, 3600),
619
'latestrev': (654231.400, 0),},
623
class TestRepositoryGetGraph(TestRemoteRepository):
625
def test_get_graph(self):
626
# get_graph returns a graph with the repository as the
629
transport_path = 'quack'
630
repo, client = self.setup_fake_client_and_repository(
631
responses, transport_path)
632
graph = repo.get_graph()
633
self.assertEqual(graph._parents_provider, repo)
636
class TestRepositoryGetParentMap(TestRemoteRepository):
638
def test_get_parent_map_caching(self):
639
# get_parent_map returns from cache until unlock()
640
# setup a reponse with two revisions
641
r1 = u'\u0e33'.encode('utf8')
642
r2 = u'\u0dab'.encode('utf8')
643
lines = [' '.join([r2, r1]), r1]
644
encoded_body = bz2.compress('\n'.join(lines))
645
responses = [(('ok', ), encoded_body), (('ok', ), encoded_body)]
647
transport_path = 'quack'
648
repo, client = self.setup_fake_client_and_repository(
649
responses, transport_path)
651
graph = repo.get_graph()
652
parents = graph.get_parent_map([r2])
653
self.assertEqual({r2: (r1,)}, parents)
654
# locking and unlocking deeper should not reset
657
parents = graph.get_parent_map([r1])
658
self.assertEqual({r1: (NULL_REVISION,)}, parents)
660
[('call_with_body_bytes_expecting_body',
661
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
664
# now we call again, and it should use the second response.
666
graph = repo.get_graph()
667
parents = graph.get_parent_map([r1])
668
self.assertEqual({r1: (NULL_REVISION,)}, parents)
670
[('call_with_body_bytes_expecting_body',
671
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
672
('call_with_body_bytes_expecting_body',
673
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
678
def test_get_parent_map_reconnects_if_unknown_method(self):
680
"Generic bzr smart protocol error: "
681
"bad request 'Repository.get_parent_map'")
683
(('error', error_msg), ''),
685
transport_path = 'quack'
686
repo, client = self.setup_fake_client_and_repository(
687
responses, transport_path)
688
rev_id = 'revision-id'
689
parents = repo.get_parent_map([rev_id])
691
[('call_with_body_bytes_expecting_body',
692
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
693
('disconnect medium',),
694
('call_expecting_body', 'Repository.get_revision_graph',
700
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
702
def test_null_revision(self):
703
# a null revision has the predictable result {}, we should have no wire
704
# traffic when calling it with this argument
705
responses = [(('notused', ), '')]
706
transport_path = 'empty'
707
repo, client = self.setup_fake_client_and_repository(
708
responses, transport_path)
709
result = self.applyDeprecated(one_four, repo.get_revision_graph,
711
self.assertEqual([], client._calls)
712
self.assertEqual({}, result)
714
def test_none_revision(self):
715
# with none we want the entire graph
716
r1 = u'\u0e33'.encode('utf8')
717
r2 = u'\u0dab'.encode('utf8')
718
lines = [' '.join([r2, r1]), r1]
719
encoded_body = '\n'.join(lines)
721
responses = [(('ok', ), encoded_body)]
722
transport_path = 'sinhala'
723
repo, client = self.setup_fake_client_and_repository(
724
responses, transport_path)
725
result = self.applyDeprecated(one_four, repo.get_revision_graph)
727
[('call_expecting_body', 'Repository.get_revision_graph',
730
self.assertEqual({r1: (), r2: (r1, )}, result)
732
def test_specific_revision(self):
733
# with a specific revision we want the graph for that
734
# with none we want the entire graph
735
r11 = u'\u0e33'.encode('utf8')
736
r12 = u'\xc9'.encode('utf8')
737
r2 = u'\u0dab'.encode('utf8')
738
lines = [' '.join([r2, r11, r12]), r11, r12]
739
encoded_body = '\n'.join(lines)
741
responses = [(('ok', ), encoded_body)]
742
transport_path = 'sinhala'
743
repo, client = self.setup_fake_client_and_repository(
744
responses, transport_path)
745
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
747
[('call_expecting_body', 'Repository.get_revision_graph',
750
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
752
def test_no_such_revision(self):
754
responses = [(('nosuchrevision', revid), '')]
755
transport_path = 'sinhala'
756
repo, client = self.setup_fake_client_and_repository(
757
responses, transport_path)
758
# also check that the right revision is reported in the error
759
self.assertRaises(errors.NoSuchRevision,
760
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
762
[('call_expecting_body', 'Repository.get_revision_graph',
763
('sinhala/', revid))],
767
class TestRepositoryIsShared(TestRemoteRepository):
769
def test_is_shared(self):
770
# ('yes', ) for Repository.is_shared -> 'True'.
771
responses = [(('yes', ), )]
772
transport_path = 'quack'
773
repo, client = self.setup_fake_client_and_repository(
774
responses, transport_path)
775
result = repo.is_shared()
777
[('call', 'Repository.is_shared', ('quack/',))],
779
self.assertEqual(True, result)
781
def test_is_not_shared(self):
782
# ('no', ) for Repository.is_shared -> 'False'.
783
responses = [(('no', ), )]
784
transport_path = 'qwack'
785
repo, client = self.setup_fake_client_and_repository(
786
responses, transport_path)
787
result = repo.is_shared()
789
[('call', 'Repository.is_shared', ('qwack/',))],
791
self.assertEqual(False, result)
794
class TestRepositoryLockWrite(TestRemoteRepository):
796
def test_lock_write(self):
797
responses = [(('ok', 'a token'), '')]
798
transport_path = 'quack'
799
repo, client = self.setup_fake_client_and_repository(
800
responses, transport_path)
801
result = repo.lock_write()
803
[('call', 'Repository.lock_write', ('quack/', ''))],
805
self.assertEqual('a token', result)
807
def test_lock_write_already_locked(self):
808
responses = [(('LockContention', ), '')]
809
transport_path = 'quack'
810
repo, client = self.setup_fake_client_and_repository(
811
responses, transport_path)
812
self.assertRaises(errors.LockContention, repo.lock_write)
814
[('call', 'Repository.lock_write', ('quack/', ''))],
817
def test_lock_write_unlockable(self):
818
responses = [(('UnlockableTransport', ), '')]
819
transport_path = 'quack'
820
repo, client = self.setup_fake_client_and_repository(
821
responses, transport_path)
822
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
824
[('call', 'Repository.lock_write', ('quack/', ''))],
828
class TestRepositoryUnlock(TestRemoteRepository):
830
def test_unlock(self):
831
responses = [(('ok', 'a token'), ''),
833
transport_path = 'quack'
834
repo, client = self.setup_fake_client_and_repository(
835
responses, transport_path)
839
[('call', 'Repository.lock_write', ('quack/', '')),
840
('call', 'Repository.unlock', ('quack/', 'a token'))],
843
def test_unlock_wrong_token(self):
844
# If somehow the token is wrong, unlock will raise TokenMismatch.
845
responses = [(('ok', 'a token'), ''),
846
(('TokenMismatch',), '')]
847
transport_path = 'quack'
848
repo, client = self.setup_fake_client_and_repository(
849
responses, transport_path)
851
self.assertRaises(errors.TokenMismatch, repo.unlock)
854
class TestRepositoryHasRevision(TestRemoteRepository):
857
# repo.has_revision(None) should not cause any traffic.
858
transport_path = 'quack'
860
repo, client = self.setup_fake_client_and_repository(
861
responses, transport_path)
863
# The null revision is always there, so has_revision(None) == True.
864
self.assertEqual(True, repo.has_revision(NULL_REVISION))
866
# The remote repo shouldn't be accessed.
867
self.assertEqual([], client._calls)
870
class TestRepositoryTarball(TestRemoteRepository):
872
# This is a canned tarball reponse we can validate against
874
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
875
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
876
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
877
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
878
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
879
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
880
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
881
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
882
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
883
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
884
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
885
'nWQ7QH/F3JFOFCQ0aSPfA='
888
def test_repository_tarball(self):
889
# Test that Repository.tarball generates the right operations
890
transport_path = 'repo'
891
expected_responses = [(('ok',), self.tarball_content),
893
expected_calls = [('call_expecting_body', 'Repository.tarball',
896
remote_repo, client = self.setup_fake_client_and_repository(
897
expected_responses, transport_path)
898
# Now actually ask for the tarball
899
tarball_file = remote_repo._get_tarball('bz2')
901
self.assertEqual(expected_calls, client._calls)
902
self.assertEqual(self.tarball_content, tarball_file.read())
907
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
908
"""RemoteRepository.copy_content_into optimizations"""
910
def test_copy_content_remote_to_local(self):
911
self.transport_server = server.SmartTCPServer_for_testing
912
src_repo = self.make_repository('repo1')
913
src_repo = repository.Repository.open(self.get_url('repo1'))
914
# At the moment the tarball-based copy_content_into can't write back
915
# into a smart server. It would be good if it could upload the
916
# tarball; once that works we'd have to create repositories of
917
# different formats. -- mbp 20070410
918
dest_url = self.get_vfs_only_url('repo2')
919
dest_bzrdir = BzrDir.create(dest_url)
920
dest_repo = dest_bzrdir.create_repository()
921
self.assertFalse(isinstance(dest_repo, RemoteRepository))
922
self.assertTrue(isinstance(src_repo, RemoteRepository))
923
src_repo.copy_content_into(dest_repo)
926
class TestRepositoryStreamKnitData(TestRemoteRepository):
928
def make_pack_file(self, records):
929
pack_file = StringIO()
930
pack_writer = pack.ContainerWriter(pack_file.write)
932
for bytes, names in records:
933
pack_writer.add_bytes_record(bytes, names)
938
def make_pack_stream(self, records):
939
pack_serialiser = pack.ContainerSerialiser()
940
yield pack_serialiser.begin()
941
for bytes, names in records:
942
yield pack_serialiser.bytes_record(bytes, names)
943
yield pack_serialiser.end()
945
def test_bad_pack_from_server(self):
946
"""A response with invalid data (e.g. it has a record with multiple
947
names) triggers an exception.
949
Not all possible errors will be caught at this stage, but obviously
950
malformed data should be.
952
record = ('bytes', [('name1',), ('name2',)])
953
pack_stream = self.make_pack_stream([record])
954
responses = [(('ok',), pack_stream), ]
955
transport_path = 'quack'
956
repo, client = self.setup_fake_client_and_repository(
957
responses, transport_path)
958
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
959
stream = repo.get_data_stream_for_search(search)
960
self.assertRaises(errors.SmartProtocolError, list, stream)
962
def test_backwards_compatibility(self):
963
"""If the server doesn't recognise this request, fallback to VFS."""
965
"Generic bzr smart protocol error: "
966
"bad request 'Repository.stream_revisions_chunked'")
968
(('error', error_msg), '')]
969
repo, client = self.setup_fake_client_and_repository(
971
self.mock_called = False
972
repo._real_repository = MockRealRepository(self)
973
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
974
repo.get_data_stream_for_search(search)
975
self.assertTrue(self.mock_called)
976
self.failIf(client.expecting_body,
977
"The protocol has been left in an unclean state that will cause "
978
"TooManyConcurrentRequests errors.")
981
class MockRealRepository(object):
982
"""Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
984
def __init__(self, test):
987
def get_data_stream_for_search(self, search):
988
self.test.assertEqual(set(['revid']), search.get_keys())
989
self.test.mock_called = True