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 import get_transport
50
from bzrlib.transport.memory import MemoryTransport
51
from bzrlib.transport.remote import RemoteTransport
54
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
57
self.transport_server = server.SmartTCPServer_for_testing
58
super(BasicRemoteObjectTests, self).setUp()
59
self.transport = self.get_transport()
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(self._calls), fake_medium_base)
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, client_calls):
167
self._remote_is_at_least_1_2 = True
168
self._client_calls = client_calls
170
def disconnect(self):
171
self._client_calls.append(('disconnect medium',))
174
class TestVfsHas(tests.TestCase):
176
def test_unicode_path(self):
177
client = FakeClient([(('yes',), )], '/')
178
transport = RemoteTransport('bzr://localhost/', _client=client)
179
filename = u'/hell\u00d8'.encode('utf8')
180
result = transport.has(filename)
182
[('call', 'has', (filename,))],
184
self.assertTrue(result)
187
class Test_SmartClient_remote_path_from_transport(tests.TestCase):
188
"""Tests for the behaviour of _SmartClient.remote_path_from_transport."""
190
def assertRemotePath(self, expected, client_base, transport_base):
191
"""Assert that the result of _SmartClient.remote_path_from_transport
192
is the expected value for a given client_base and transport_base.
194
dummy_medium = 'dummy medium'
195
client = _SmartClient(dummy_medium, client_base)
196
transport = get_transport(transport_base)
197
result = client.remote_path_from_transport(transport)
198
self.assertEqual(expected, result)
200
def test_remote_path_from_transport(self):
201
"""_SmartClient.remote_path_from_transport calculates a URL for the
202
given transport relative to the root of the client base URL.
204
self.assertRemotePath('xyz/', 'bzr://host/path', 'bzr://host/xyz')
205
self.assertRemotePath(
206
'path/xyz/', 'bzr://host/path', 'bzr://host/path/xyz')
208
def test_remote_path_from_transport_http(self):
209
"""Remote paths for HTTP transports are calculated differently to other
210
transports. They are just relative to the client base, not the root
211
directory of the host.
213
for scheme in ['http:', 'https:', 'bzr+http:', 'bzr+https:']:
214
self.assertRemotePath(
215
'../xyz/', scheme + '//host/path', scheme + '//host/xyz')
216
self.assertRemotePath(
217
'xyz/', scheme + '//host/path', scheme + '//host/path/xyz')
220
class TestBzrDirOpenBranch(tests.TestCase):
222
def test_branch_present(self):
223
transport = MemoryTransport()
224
transport.mkdir('quack')
225
transport = transport.clone('quack')
226
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no', 'no'), )],
228
bzrdir = RemoteBzrDir(transport, _client=client)
229
result = bzrdir.open_branch()
231
[('call', 'BzrDir.open_branch', ('quack/',)),
232
('call', 'BzrDir.find_repositoryV2', ('quack/',))],
234
self.assertIsInstance(result, RemoteBranch)
235
self.assertEqual(bzrdir, result.bzrdir)
237
def test_branch_missing(self):
238
transport = MemoryTransport()
239
transport.mkdir('quack')
240
transport = transport.clone('quack')
241
client = FakeClient([(('nobranch',), )], transport.base)
242
bzrdir = RemoteBzrDir(transport, _client=client)
243
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
245
[('call', 'BzrDir.open_branch', ('quack/',))],
248
def test__get_tree_branch(self):
249
# _get_tree_branch is a form of open_branch, but it should only ask for
250
# branch opening, not any other network requests.
253
calls.append("Called")
255
transport = MemoryTransport()
256
# no requests on the network - catches other api calls being made.
257
client = FakeClient([], transport.base)
258
bzrdir = RemoteBzrDir(transport, _client=client)
259
# patch the open_branch call to record that it was called.
260
bzrdir.open_branch = open_branch
261
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
262
self.assertEqual(["Called"], calls)
263
self.assertEqual([], client._calls)
265
def test_url_quoting_of_path(self):
266
# Relpaths on the wire should not be URL-escaped. So "~" should be
267
# transmitted as "~", not "%7E".
268
transport = RemoteTransport('bzr://localhost/~hello/')
269
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no', 'no'), )],
271
bzrdir = RemoteBzrDir(transport, _client=client)
272
result = bzrdir.open_branch()
274
[('call', 'BzrDir.open_branch', ('~hello/',)),
275
('call', 'BzrDir.find_repositoryV2', ('~hello/',))],
278
def check_open_repository(self, rich_root, subtrees, external_lookup='no'):
279
transport = MemoryTransport()
280
transport.mkdir('quack')
281
transport = transport.clone('quack')
283
rich_response = 'yes'
287
subtree_response = 'yes'
289
subtree_response = 'no'
291
[(('ok', '', rich_response, subtree_response, external_lookup), ),],
293
bzrdir = RemoteBzrDir(transport, _client=client)
294
result = bzrdir.open_repository()
296
[('call', 'BzrDir.find_repositoryV2', ('quack/',))],
298
self.assertIsInstance(result, RemoteRepository)
299
self.assertEqual(bzrdir, result.bzrdir)
300
self.assertEqual(rich_root, result._format.rich_root_data)
301
self.assertEqual(subtrees, result._format.supports_tree_reference)
303
def test_open_repository_sets_format_attributes(self):
304
self.check_open_repository(True, True)
305
self.check_open_repository(False, True)
306
self.check_open_repository(True, False)
307
self.check_open_repository(False, False)
308
self.check_open_repository(False, False, 'yes')
310
def test_old_server(self):
311
"""RemoteBzrDirFormat should fail to probe if the server version is too
314
self.assertRaises(errors.NotBranchError,
315
RemoteBzrDirFormat.probe_transport, OldServerTransport())
318
class OldSmartClient(object):
319
"""A fake smart client for test_old_version that just returns a version one
320
response to the 'hello' (query version) command.
323
def get_request(self):
324
input_file = StringIO('ok\x011\n')
325
output_file = StringIO()
326
client_medium = medium.SmartSimplePipesClientMedium(
327
input_file, output_file)
328
return medium.SmartClientStreamMediumRequest(client_medium)
330
def protocol_version(self):
334
class OldServerTransport(object):
335
"""A fake transport for test_old_server that reports it's smart server
336
protocol version as version one.
342
def get_smart_client(self):
343
return OldSmartClient()
346
class TestBranchLastRevisionInfo(tests.TestCase):
348
def test_empty_branch(self):
349
# in an empty branch we decode the response properly
350
transport = MemoryTransport()
351
client = FakeClient([(('ok', '0', 'null:'), )], transport.base)
352
transport.mkdir('quack')
353
transport = transport.clone('quack')
354
# we do not want bzrdir to make any remote calls
355
bzrdir = RemoteBzrDir(transport, _client=False)
356
branch = RemoteBranch(bzrdir, None, _client=client)
357
result = branch.last_revision_info()
360
[('call', 'Branch.last_revision_info', ('quack/',))],
362
self.assertEqual((0, NULL_REVISION), result)
364
def test_non_empty_branch(self):
365
# in a non-empty branch we also decode the response properly
366
revid = u'\xc8'.encode('utf8')
367
transport = MemoryTransport()
368
client = FakeClient([(('ok', '2', revid), )], transport.base)
369
transport.mkdir('kwaak')
370
transport = transport.clone('kwaak')
371
# we do not want bzrdir to make any remote calls
372
bzrdir = RemoteBzrDir(transport, _client=False)
373
branch = RemoteBranch(bzrdir, None, _client=client)
374
result = branch.last_revision_info()
377
[('call', 'Branch.last_revision_info', ('kwaak/',))],
379
self.assertEqual((2, revid), result)
382
class TestBranchSetLastRevision(tests.TestCase):
384
def test_set_empty(self):
385
# set_revision_history([]) is translated to calling
386
# Branch.set_last_revision(path, '') on the wire.
387
transport = MemoryTransport()
388
transport.mkdir('branch')
389
transport = transport.clone('branch')
391
client = FakeClient([
393
(('ok', 'branch token', 'repo token'), ),
399
bzrdir = RemoteBzrDir(transport, _client=False)
400
branch = RemoteBranch(bzrdir, None, _client=client)
401
# This is a hack to work around the problem that RemoteBranch currently
402
# unnecessarily invokes _ensure_real upon a call to lock_write.
403
branch._ensure_real = lambda: None
406
result = branch.set_revision_history([])
408
[('call', 'Branch.set_last_revision',
409
('branch/', 'branch token', 'repo token', 'null:'))],
412
self.assertEqual(None, result)
414
def test_set_nonempty(self):
415
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
416
# Branch.set_last_revision(path, rev-idN) on the wire.
417
transport = MemoryTransport()
418
transport.mkdir('branch')
419
transport = transport.clone('branch')
421
client = FakeClient([
423
(('ok', 'branch token', 'repo token'), ),
429
bzrdir = RemoteBzrDir(transport, _client=False)
430
branch = RemoteBranch(bzrdir, None, _client=client)
431
# This is a hack to work around the problem that RemoteBranch currently
432
# unnecessarily invokes _ensure_real upon a call to lock_write.
433
branch._ensure_real = lambda: None
434
# Lock the branch, reset the record of remote calls.
438
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
440
[('call', 'Branch.set_last_revision',
441
('branch/', 'branch token', 'repo token', 'rev-id2'))],
444
self.assertEqual(None, result)
446
def test_no_such_revision(self):
447
# A response of 'NoSuchRevision' is translated into an exception.
448
client = FakeClient([
450
(('ok', 'branch token', 'repo token'), ),
452
(('NoSuchRevision', 'rev-id'), ),
455
transport = MemoryTransport()
456
transport.mkdir('branch')
457
transport = transport.clone('branch')
459
bzrdir = RemoteBzrDir(transport, _client=False)
460
branch = RemoteBranch(bzrdir, None, _client=client)
461
branch._ensure_real = lambda: None
466
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
470
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
471
"""Test branch.control_files api munging...
473
We special case RemoteBranch.control_files.get('branch.conf') to
474
call a specific API so that RemoteBranch's can intercept configuration
475
file reading, allowing them to signal to the client about things like
476
'email is configured for commits'.
479
def test_get_branch_conf(self):
480
# in an empty branch we decode the response properly
481
client = FakeClient([(('ok', ), 'config file body')], self.get_url())
482
# we need to make a real branch because the remote_branch.control_files
483
# will trigger _ensure_real.
484
branch = self.make_branch('quack')
485
transport = branch.bzrdir.root_transport
486
# we do not want bzrdir to make any remote calls
487
bzrdir = RemoteBzrDir(transport, _client=False)
488
branch = RemoteBranch(bzrdir, None, _client=client)
489
result = branch.control_files.get('branch.conf')
491
[('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
493
self.assertEqual('config file body', result.read())
496
class TestBranchLockWrite(tests.TestCase):
498
def test_lock_write_unlockable(self):
499
transport = MemoryTransport()
500
client = FakeClient([(('UnlockableTransport', ), '')], transport.base)
501
transport.mkdir('quack')
502
transport = transport.clone('quack')
503
# we do not want bzrdir to make any remote calls
504
bzrdir = RemoteBzrDir(transport, _client=False)
505
branch = RemoteBranch(bzrdir, None, _client=client)
506
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
508
[('call', 'Branch.lock_write', ('quack/', '', ''))],
512
class TestTransportIsReadonly(tests.TestCase):
515
client = FakeClient([(('yes',), '')])
516
transport = RemoteTransport('bzr://example.com/', medium=False,
518
self.assertEqual(True, transport.is_readonly())
520
[('call', 'Transport.is_readonly', ())],
523
def test_false(self):
524
client = FakeClient([(('no',), '')])
525
transport = RemoteTransport('bzr://example.com/', medium=False,
527
self.assertEqual(False, transport.is_readonly())
529
[('call', 'Transport.is_readonly', ())],
532
def test_error_from_old_server(self):
533
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
535
Clients should treat it as a "no" response, because is_readonly is only
536
advisory anyway (a transport could be read-write, but then the
537
underlying filesystem could be readonly anyway).
539
client = FakeClient([(
540
('error', "Generic bzr smart protocol error: "
541
"bad request 'Transport.is_readonly'"), '')])
542
transport = RemoteTransport('bzr://example.com/', medium=False,
544
self.assertEqual(False, transport.is_readonly())
546
[('call', 'Transport.is_readonly', ())],
549
def test_error_from_old_0_11_server(self):
550
"""Same as test_error_from_old_server, but with the slightly different
551
error message from bzr 0.11 servers.
553
client = FakeClient([(
554
('error', "Generic bzr smart protocol error: "
555
"bad request u'Transport.is_readonly'"), '')])
556
transport = RemoteTransport('bzr://example.com/', medium=False,
558
self.assertEqual(False, transport.is_readonly())
560
[('call', 'Transport.is_readonly', ())],
564
class TestRemoteRepository(tests.TestCase):
565
"""Base for testing RemoteRepository protocol usage.
567
These tests contain frozen requests and responses. We want any changes to
568
what is sent or expected to be require a thoughtful update to these tests
569
because they might break compatibility with different-versioned servers.
572
def setup_fake_client_and_repository(self, responses, transport_path):
573
"""Create the fake client and repository for testing with.
575
There's no real server here; we just have canned responses sent
578
:param transport_path: Path below the root of the MemoryTransport
579
where the repository will be created.
581
transport = MemoryTransport()
582
transport.mkdir(transport_path)
583
client = FakeClient(responses, transport.base)
584
transport = transport.clone(transport_path)
585
# we do not want bzrdir to make any remote calls
586
bzrdir = RemoteBzrDir(transport, _client=False)
587
repo = RemoteRepository(bzrdir, None, _client=client)
591
class TestRepositoryGatherStats(TestRemoteRepository):
593
def test_revid_none(self):
594
# ('ok',), body with revisions and size
595
responses = [(('ok', ), 'revisions: 2\nsize: 18\n')]
596
transport_path = 'quack'
597
repo, client = self.setup_fake_client_and_repository(
598
responses, transport_path)
599
result = repo.gather_stats(None)
601
[('call_expecting_body', 'Repository.gather_stats',
602
('quack/','','no'))],
604
self.assertEqual({'revisions': 2, 'size': 18}, result)
606
def test_revid_no_committers(self):
607
# ('ok',), body without committers
608
responses = [(('ok', ),
609
'firstrev: 123456.300 3600\n'
610
'latestrev: 654231.400 0\n'
613
transport_path = 'quick'
614
revid = u'\xc8'.encode('utf8')
615
repo, client = self.setup_fake_client_and_repository(
616
responses, transport_path)
617
result = repo.gather_stats(revid)
619
[('call_expecting_body', 'Repository.gather_stats',
620
('quick/', revid, 'no'))],
622
self.assertEqual({'revisions': 2, 'size': 18,
623
'firstrev': (123456.300, 3600),
624
'latestrev': (654231.400, 0),},
627
def test_revid_with_committers(self):
628
# ('ok',), body with committers
629
responses = [(('ok', ),
631
'firstrev: 123456.300 3600\n'
632
'latestrev: 654231.400 0\n'
635
transport_path = 'buick'
636
revid = u'\xc8'.encode('utf8')
637
repo, client = self.setup_fake_client_and_repository(
638
responses, transport_path)
639
result = repo.gather_stats(revid, True)
641
[('call_expecting_body', 'Repository.gather_stats',
642
('buick/', revid, 'yes'))],
644
self.assertEqual({'revisions': 2, 'size': 18,
646
'firstrev': (123456.300, 3600),
647
'latestrev': (654231.400, 0),},
651
class TestRepositoryGetGraph(TestRemoteRepository):
653
def test_get_graph(self):
654
# get_graph returns a graph with the repository as the
657
transport_path = 'quack'
658
repo, client = self.setup_fake_client_and_repository(
659
responses, transport_path)
660
graph = repo.get_graph()
661
self.assertEqual(graph._parents_provider, repo)
664
class TestRepositoryGetParentMap(TestRemoteRepository):
666
def test_get_parent_map_caching(self):
667
# get_parent_map returns from cache until unlock()
668
# setup a reponse with two revisions
669
r1 = u'\u0e33'.encode('utf8')
670
r2 = u'\u0dab'.encode('utf8')
671
lines = [' '.join([r2, r1]), r1]
672
encoded_body = bz2.compress('\n'.join(lines))
673
responses = [(('ok', ), encoded_body), (('ok', ), encoded_body)]
675
transport_path = 'quack'
676
repo, client = self.setup_fake_client_and_repository(
677
responses, transport_path)
679
graph = repo.get_graph()
680
parents = graph.get_parent_map([r2])
681
self.assertEqual({r2: (r1,)}, parents)
682
# locking and unlocking deeper should not reset
685
parents = graph.get_parent_map([r1])
686
self.assertEqual({r1: (NULL_REVISION,)}, parents)
688
[('call_with_body_bytes_expecting_body',
689
'Repository.get_parent_map', ('quack/', r2), '\n\n0')],
692
# now we call again, and it should use the second response.
694
graph = repo.get_graph()
695
parents = graph.get_parent_map([r1])
696
self.assertEqual({r1: (NULL_REVISION,)}, parents)
698
[('call_with_body_bytes_expecting_body',
699
'Repository.get_parent_map', ('quack/', r2), '\n\n0'),
700
('call_with_body_bytes_expecting_body',
701
'Repository.get_parent_map', ('quack/', r1), '\n\n0'),
706
def test_get_parent_map_reconnects_if_unknown_method(self):
708
"Generic bzr smart protocol error: "
709
"bad request 'Repository.get_parent_map'")
711
(('error', error_msg), ''),
713
transport_path = 'quack'
714
repo, client = self.setup_fake_client_and_repository(
715
responses, transport_path)
716
rev_id = 'revision-id'
717
parents = repo.get_parent_map([rev_id])
719
[('call_with_body_bytes_expecting_body',
720
'Repository.get_parent_map', ('quack/', rev_id), '\n\n0'),
721
('disconnect medium',),
722
('call_expecting_body', 'Repository.get_revision_graph',
726
def test_get_parent_map_unexpected_response(self):
728
(('something unexpected!',), '')]
729
repo, client = self.setup_fake_client_and_repository(responses, 'path')
731
errors.UnexpectedSmartServerResponse,
732
repo.get_parent_map, ['a-revision-id'])
735
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
737
def test_null_revision(self):
738
# a null revision has the predictable result {}, we should have no wire
739
# traffic when calling it with this argument
740
responses = [(('notused', ), '')]
741
transport_path = 'empty'
742
repo, client = self.setup_fake_client_and_repository(
743
responses, transport_path)
744
result = self.applyDeprecated(one_four, repo.get_revision_graph,
746
self.assertEqual([], client._calls)
747
self.assertEqual({}, result)
749
def test_none_revision(self):
750
# with none we want the entire graph
751
r1 = u'\u0e33'.encode('utf8')
752
r2 = u'\u0dab'.encode('utf8')
753
lines = [' '.join([r2, r1]), r1]
754
encoded_body = '\n'.join(lines)
756
responses = [(('ok', ), encoded_body)]
757
transport_path = 'sinhala'
758
repo, client = self.setup_fake_client_and_repository(
759
responses, transport_path)
760
result = self.applyDeprecated(one_four, repo.get_revision_graph)
762
[('call_expecting_body', 'Repository.get_revision_graph',
765
self.assertEqual({r1: (), r2: (r1, )}, result)
767
def test_specific_revision(self):
768
# with a specific revision we want the graph for that
769
# with none we want the entire graph
770
r11 = u'\u0e33'.encode('utf8')
771
r12 = u'\xc9'.encode('utf8')
772
r2 = u'\u0dab'.encode('utf8')
773
lines = [' '.join([r2, r11, r12]), r11, r12]
774
encoded_body = '\n'.join(lines)
776
responses = [(('ok', ), encoded_body)]
777
transport_path = 'sinhala'
778
repo, client = self.setup_fake_client_and_repository(
779
responses, transport_path)
780
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
782
[('call_expecting_body', 'Repository.get_revision_graph',
785
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
787
def test_no_such_revision(self):
789
responses = [(('nosuchrevision', revid), '')]
790
transport_path = 'sinhala'
791
repo, client = self.setup_fake_client_and_repository(
792
responses, transport_path)
793
# also check that the right revision is reported in the error
794
self.assertRaises(errors.NoSuchRevision,
795
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
797
[('call_expecting_body', 'Repository.get_revision_graph',
798
('sinhala/', revid))],
802
class TestRepositoryIsShared(TestRemoteRepository):
804
def test_is_shared(self):
805
# ('yes', ) for Repository.is_shared -> 'True'.
806
responses = [(('yes', ), )]
807
transport_path = 'quack'
808
repo, client = self.setup_fake_client_and_repository(
809
responses, transport_path)
810
result = repo.is_shared()
812
[('call', 'Repository.is_shared', ('quack/',))],
814
self.assertEqual(True, result)
816
def test_is_not_shared(self):
817
# ('no', ) for Repository.is_shared -> 'False'.
818
responses = [(('no', ), )]
819
transport_path = 'qwack'
820
repo, client = self.setup_fake_client_and_repository(
821
responses, transport_path)
822
result = repo.is_shared()
824
[('call', 'Repository.is_shared', ('qwack/',))],
826
self.assertEqual(False, result)
829
class TestRepositoryLockWrite(TestRemoteRepository):
831
def test_lock_write(self):
832
responses = [(('ok', 'a token'), '')]
833
transport_path = 'quack'
834
repo, client = self.setup_fake_client_and_repository(
835
responses, transport_path)
836
result = repo.lock_write()
838
[('call', 'Repository.lock_write', ('quack/', ''))],
840
self.assertEqual('a token', result)
842
def test_lock_write_already_locked(self):
843
responses = [(('LockContention', ), '')]
844
transport_path = 'quack'
845
repo, client = self.setup_fake_client_and_repository(
846
responses, transport_path)
847
self.assertRaises(errors.LockContention, repo.lock_write)
849
[('call', 'Repository.lock_write', ('quack/', ''))],
852
def test_lock_write_unlockable(self):
853
responses = [(('UnlockableTransport', ), '')]
854
transport_path = 'quack'
855
repo, client = self.setup_fake_client_and_repository(
856
responses, transport_path)
857
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
859
[('call', 'Repository.lock_write', ('quack/', ''))],
863
class TestRepositoryUnlock(TestRemoteRepository):
865
def test_unlock(self):
866
responses = [(('ok', 'a token'), ''),
868
transport_path = 'quack'
869
repo, client = self.setup_fake_client_and_repository(
870
responses, transport_path)
874
[('call', 'Repository.lock_write', ('quack/', '')),
875
('call', 'Repository.unlock', ('quack/', 'a token'))],
878
def test_unlock_wrong_token(self):
879
# If somehow the token is wrong, unlock will raise TokenMismatch.
880
responses = [(('ok', 'a token'), ''),
881
(('TokenMismatch',), '')]
882
transport_path = 'quack'
883
repo, client = self.setup_fake_client_and_repository(
884
responses, transport_path)
886
self.assertRaises(errors.TokenMismatch, repo.unlock)
889
class TestRepositoryHasRevision(TestRemoteRepository):
892
# repo.has_revision(None) should not cause any traffic.
893
transport_path = 'quack'
895
repo, client = self.setup_fake_client_and_repository(
896
responses, transport_path)
898
# The null revision is always there, so has_revision(None) == True.
899
self.assertEqual(True, repo.has_revision(NULL_REVISION))
901
# The remote repo shouldn't be accessed.
902
self.assertEqual([], client._calls)
905
class TestRepositoryTarball(TestRemoteRepository):
907
# This is a canned tarball reponse we can validate against
909
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
910
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
911
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
912
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
913
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
914
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
915
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
916
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
917
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
918
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
919
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
920
'nWQ7QH/F3JFOFCQ0aSPfA='
923
def test_repository_tarball(self):
924
# Test that Repository.tarball generates the right operations
925
transport_path = 'repo'
926
expected_responses = [(('ok',), self.tarball_content),
928
expected_calls = [('call_expecting_body', 'Repository.tarball',
931
remote_repo, client = self.setup_fake_client_and_repository(
932
expected_responses, transport_path)
933
# Now actually ask for the tarball
934
tarball_file = remote_repo._get_tarball('bz2')
936
self.assertEqual(expected_calls, client._calls)
937
self.assertEqual(self.tarball_content, tarball_file.read())
942
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
943
"""RemoteRepository.copy_content_into optimizations"""
945
def test_copy_content_remote_to_local(self):
946
self.transport_server = server.SmartTCPServer_for_testing
947
src_repo = self.make_repository('repo1')
948
src_repo = repository.Repository.open(self.get_url('repo1'))
949
# At the moment the tarball-based copy_content_into can't write back
950
# into a smart server. It would be good if it could upload the
951
# tarball; once that works we'd have to create repositories of
952
# different formats. -- mbp 20070410
953
dest_url = self.get_vfs_only_url('repo2')
954
dest_bzrdir = BzrDir.create(dest_url)
955
dest_repo = dest_bzrdir.create_repository()
956
self.assertFalse(isinstance(dest_repo, RemoteRepository))
957
self.assertTrue(isinstance(src_repo, RemoteRepository))
958
src_repo.copy_content_into(dest_repo)
961
class TestRepositoryStreamKnitData(TestRemoteRepository):
963
def make_pack_file(self, records):
964
pack_file = StringIO()
965
pack_writer = pack.ContainerWriter(pack_file.write)
967
for bytes, names in records:
968
pack_writer.add_bytes_record(bytes, names)
973
def make_pack_stream(self, records):
974
pack_serialiser = pack.ContainerSerialiser()
975
yield pack_serialiser.begin()
976
for bytes, names in records:
977
yield pack_serialiser.bytes_record(bytes, names)
978
yield pack_serialiser.end()
980
def test_bad_pack_from_server(self):
981
"""A response with invalid data (e.g. it has a record with multiple
982
names) triggers an exception.
984
Not all possible errors will be caught at this stage, but obviously
985
malformed data should be.
987
record = ('bytes', [('name1',), ('name2',)])
988
pack_stream = self.make_pack_stream([record])
989
responses = [(('ok',), pack_stream), ]
990
transport_path = 'quack'
991
repo, client = self.setup_fake_client_and_repository(
992
responses, transport_path)
993
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
994
stream = repo.get_data_stream_for_search(search)
995
self.assertRaises(errors.SmartProtocolError, list, stream)
997
def test_backwards_compatibility(self):
998
"""If the server doesn't recognise this request, fallback to VFS."""
1000
"Generic bzr smart protocol error: "
1001
"bad request 'Repository.stream_revisions_chunked'")
1003
(('error', error_msg), '')]
1004
repo, client = self.setup_fake_client_and_repository(
1006
self.mock_called = False
1007
repo._real_repository = MockRealRepository(self)
1008
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
1009
repo.get_data_stream_for_search(search)
1010
self.assertTrue(self.mock_called)
1011
self.failIf(client.expecting_body,
1012
"The protocol has been left in an unclean state that will cause "
1013
"TooManyConcurrentRequests errors.")
1016
class MockRealRepository(object):
1017
"""Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
1019
def __init__(self, test):
1022
def get_data_stream_for_search(self, search):
1023
self.test.assertEqual(set(['revid']), search.get_keys())
1024
self.test.mock_called = True