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',
728
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
730
def test_null_revision(self):
731
# a null revision has the predictable result {}, we should have no wire
732
# traffic when calling it with this argument
733
responses = [(('notused', ), '')]
734
transport_path = 'empty'
735
repo, client = self.setup_fake_client_and_repository(
736
responses, transport_path)
737
result = self.applyDeprecated(one_four, repo.get_revision_graph,
739
self.assertEqual([], client._calls)
740
self.assertEqual({}, result)
742
def test_none_revision(self):
743
# with none we want the entire graph
744
r1 = u'\u0e33'.encode('utf8')
745
r2 = u'\u0dab'.encode('utf8')
746
lines = [' '.join([r2, r1]), r1]
747
encoded_body = '\n'.join(lines)
749
responses = [(('ok', ), encoded_body)]
750
transport_path = 'sinhala'
751
repo, client = self.setup_fake_client_and_repository(
752
responses, transport_path)
753
result = self.applyDeprecated(one_four, repo.get_revision_graph)
755
[('call_expecting_body', 'Repository.get_revision_graph',
758
self.assertEqual({r1: (), r2: (r1, )}, result)
760
def test_specific_revision(self):
761
# with a specific revision we want the graph for that
762
# with none we want the entire graph
763
r11 = u'\u0e33'.encode('utf8')
764
r12 = u'\xc9'.encode('utf8')
765
r2 = u'\u0dab'.encode('utf8')
766
lines = [' '.join([r2, r11, r12]), r11, r12]
767
encoded_body = '\n'.join(lines)
769
responses = [(('ok', ), encoded_body)]
770
transport_path = 'sinhala'
771
repo, client = self.setup_fake_client_and_repository(
772
responses, transport_path)
773
result = self.applyDeprecated(one_four, repo.get_revision_graph, r2)
775
[('call_expecting_body', 'Repository.get_revision_graph',
778
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
780
def test_no_such_revision(self):
782
responses = [(('nosuchrevision', revid), '')]
783
transport_path = 'sinhala'
784
repo, client = self.setup_fake_client_and_repository(
785
responses, transport_path)
786
# also check that the right revision is reported in the error
787
self.assertRaises(errors.NoSuchRevision,
788
self.applyDeprecated, one_four, repo.get_revision_graph, revid)
790
[('call_expecting_body', 'Repository.get_revision_graph',
791
('sinhala/', revid))],
795
class TestRepositoryIsShared(TestRemoteRepository):
797
def test_is_shared(self):
798
# ('yes', ) for Repository.is_shared -> 'True'.
799
responses = [(('yes', ), )]
800
transport_path = 'quack'
801
repo, client = self.setup_fake_client_and_repository(
802
responses, transport_path)
803
result = repo.is_shared()
805
[('call', 'Repository.is_shared', ('quack/',))],
807
self.assertEqual(True, result)
809
def test_is_not_shared(self):
810
# ('no', ) for Repository.is_shared -> 'False'.
811
responses = [(('no', ), )]
812
transport_path = 'qwack'
813
repo, client = self.setup_fake_client_and_repository(
814
responses, transport_path)
815
result = repo.is_shared()
817
[('call', 'Repository.is_shared', ('qwack/',))],
819
self.assertEqual(False, result)
822
class TestRepositoryLockWrite(TestRemoteRepository):
824
def test_lock_write(self):
825
responses = [(('ok', 'a token'), '')]
826
transport_path = 'quack'
827
repo, client = self.setup_fake_client_and_repository(
828
responses, transport_path)
829
result = repo.lock_write()
831
[('call', 'Repository.lock_write', ('quack/', ''))],
833
self.assertEqual('a token', result)
835
def test_lock_write_already_locked(self):
836
responses = [(('LockContention', ), '')]
837
transport_path = 'quack'
838
repo, client = self.setup_fake_client_and_repository(
839
responses, transport_path)
840
self.assertRaises(errors.LockContention, repo.lock_write)
842
[('call', 'Repository.lock_write', ('quack/', ''))],
845
def test_lock_write_unlockable(self):
846
responses = [(('UnlockableTransport', ), '')]
847
transport_path = 'quack'
848
repo, client = self.setup_fake_client_and_repository(
849
responses, transport_path)
850
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
852
[('call', 'Repository.lock_write', ('quack/', ''))],
856
class TestRepositoryUnlock(TestRemoteRepository):
858
def test_unlock(self):
859
responses = [(('ok', 'a token'), ''),
861
transport_path = 'quack'
862
repo, client = self.setup_fake_client_and_repository(
863
responses, transport_path)
867
[('call', 'Repository.lock_write', ('quack/', '')),
868
('call', 'Repository.unlock', ('quack/', 'a token'))],
871
def test_unlock_wrong_token(self):
872
# If somehow the token is wrong, unlock will raise TokenMismatch.
873
responses = [(('ok', 'a token'), ''),
874
(('TokenMismatch',), '')]
875
transport_path = 'quack'
876
repo, client = self.setup_fake_client_and_repository(
877
responses, transport_path)
879
self.assertRaises(errors.TokenMismatch, repo.unlock)
882
class TestRepositoryHasRevision(TestRemoteRepository):
885
# repo.has_revision(None) should not cause any traffic.
886
transport_path = 'quack'
888
repo, client = self.setup_fake_client_and_repository(
889
responses, transport_path)
891
# The null revision is always there, so has_revision(None) == True.
892
self.assertEqual(True, repo.has_revision(NULL_REVISION))
894
# The remote repo shouldn't be accessed.
895
self.assertEqual([], client._calls)
898
class TestRepositoryTarball(TestRemoteRepository):
900
# This is a canned tarball reponse we can validate against
902
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
903
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
904
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
905
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
906
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
907
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
908
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
909
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
910
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
911
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
912
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
913
'nWQ7QH/F3JFOFCQ0aSPfA='
916
def test_repository_tarball(self):
917
# Test that Repository.tarball generates the right operations
918
transport_path = 'repo'
919
expected_responses = [(('ok',), self.tarball_content),
921
expected_calls = [('call_expecting_body', 'Repository.tarball',
924
remote_repo, client = self.setup_fake_client_and_repository(
925
expected_responses, transport_path)
926
# Now actually ask for the tarball
927
tarball_file = remote_repo._get_tarball('bz2')
929
self.assertEqual(expected_calls, client._calls)
930
self.assertEqual(self.tarball_content, tarball_file.read())
935
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
936
"""RemoteRepository.copy_content_into optimizations"""
938
def test_copy_content_remote_to_local(self):
939
self.transport_server = server.SmartTCPServer_for_testing
940
src_repo = self.make_repository('repo1')
941
src_repo = repository.Repository.open(self.get_url('repo1'))
942
# At the moment the tarball-based copy_content_into can't write back
943
# into a smart server. It would be good if it could upload the
944
# tarball; once that works we'd have to create repositories of
945
# different formats. -- mbp 20070410
946
dest_url = self.get_vfs_only_url('repo2')
947
dest_bzrdir = BzrDir.create(dest_url)
948
dest_repo = dest_bzrdir.create_repository()
949
self.assertFalse(isinstance(dest_repo, RemoteRepository))
950
self.assertTrue(isinstance(src_repo, RemoteRepository))
951
src_repo.copy_content_into(dest_repo)
954
class TestRepositoryStreamKnitData(TestRemoteRepository):
956
def make_pack_file(self, records):
957
pack_file = StringIO()
958
pack_writer = pack.ContainerWriter(pack_file.write)
960
for bytes, names in records:
961
pack_writer.add_bytes_record(bytes, names)
966
def make_pack_stream(self, records):
967
pack_serialiser = pack.ContainerSerialiser()
968
yield pack_serialiser.begin()
969
for bytes, names in records:
970
yield pack_serialiser.bytes_record(bytes, names)
971
yield pack_serialiser.end()
973
def test_bad_pack_from_server(self):
974
"""A response with invalid data (e.g. it has a record with multiple
975
names) triggers an exception.
977
Not all possible errors will be caught at this stage, but obviously
978
malformed data should be.
980
record = ('bytes', [('name1',), ('name2',)])
981
pack_stream = self.make_pack_stream([record])
982
responses = [(('ok',), pack_stream), ]
983
transport_path = 'quack'
984
repo, client = self.setup_fake_client_and_repository(
985
responses, transport_path)
986
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
987
stream = repo.get_data_stream_for_search(search)
988
self.assertRaises(errors.SmartProtocolError, list, stream)
990
def test_backwards_compatibility(self):
991
"""If the server doesn't recognise this request, fallback to VFS."""
993
"Generic bzr smart protocol error: "
994
"bad request 'Repository.stream_revisions_chunked'")
996
(('error', error_msg), '')]
997
repo, client = self.setup_fake_client_and_repository(
999
self.mock_called = False
1000
repo._real_repository = MockRealRepository(self)
1001
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
1002
repo.get_data_stream_for_search(search)
1003
self.assertTrue(self.mock_called)
1004
self.failIf(client.expecting_body,
1005
"The protocol has been left in an unclean state that will cause "
1006
"TooManyConcurrentRequests errors.")
1009
class MockRealRepository(object):
1010
"""Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
1012
def __init__(self, test):
1015
def get_data_stream_for_search(self, search):
1016
self.test.assertEqual(set(['revid']), search.get_keys())
1017
self.test.mock_called = True