1
# Copyright (C) 2006, 2007 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests for remote bzrdir/branch/repo/etc
19
These are proxy objects which act on remote objects by sending messages
20
through a smart client. The proxies are to be created when attempting to open
21
the object given a transport that supports smartserver rpc operations.
23
These tests correspond to tests.test_smart, which exercises the server side.
26
from cStringIO import StringIO
36
from bzrlib.branch import Branch
37
from bzrlib.bzrdir import BzrDir, BzrDirFormat
38
from bzrlib.remote import (
44
from bzrlib.revision import NULL_REVISION
45
from bzrlib.smart import server, medium
46
from bzrlib.smart.client import _SmartClient
47
from bzrlib.transport.memory import MemoryTransport
48
from bzrlib.transport.remote import RemoteTransport
51
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
54
self.transport_server = server.SmartTCPServer_for_testing
55
super(BasicRemoteObjectTests, self).setUp()
56
self.transport = self.get_transport()
57
self.client = self.transport.get_smart_client()
58
# make a branch that can be opened over the smart transport
59
self.local_wt = BzrDir.create_standalone_workingtree('.')
62
self.transport.disconnect()
63
tests.TestCaseWithTransport.tearDown(self)
65
def test_create_remote_bzrdir(self):
66
b = remote.RemoteBzrDir(self.transport)
67
self.assertIsInstance(b, BzrDir)
69
def test_open_remote_branch(self):
70
# open a standalone branch in the working directory
71
b = remote.RemoteBzrDir(self.transport)
72
branch = b.open_branch()
73
self.assertIsInstance(branch, Branch)
75
def test_remote_repository(self):
76
b = BzrDir.open_from_transport(self.transport)
77
repo = b.open_repository()
78
revid = u'\xc823123123'.encode('utf8')
79
self.assertFalse(repo.has_revision(revid))
80
self.local_wt.commit(message='test commit', rev_id=revid)
81
self.assertTrue(repo.has_revision(revid))
83
def test_remote_branch_revision_history(self):
84
b = BzrDir.open_from_transport(self.transport).open_branch()
85
self.assertEqual([], b.revision_history())
86
r1 = self.local_wt.commit('1st commit')
87
r2 = self.local_wt.commit('1st commit', rev_id=u'\xc8'.encode('utf8'))
88
self.assertEqual([r1, r2], b.revision_history())
90
def test_find_correct_format(self):
91
"""Should open a RemoteBzrDir over a RemoteTransport"""
92
fmt = BzrDirFormat.find_format(self.transport)
93
self.assertTrue(RemoteBzrDirFormat
94
in BzrDirFormat._control_server_formats)
95
self.assertIsInstance(fmt, remote.RemoteBzrDirFormat)
97
def test_open_detected_smart_format(self):
98
fmt = BzrDirFormat.find_format(self.transport)
99
d = fmt.open(self.transport)
100
self.assertIsInstance(d, BzrDir)
102
def test_remote_branch_repr(self):
103
b = BzrDir.open_from_transport(self.transport).open_branch()
104
self.assertStartsWith(str(b), 'RemoteBranch(')
107
class FakeProtocol(object):
108
"""Lookalike SmartClientRequestProtocolOne allowing body reading tests."""
110
def __init__(self, body, fake_client):
112
self._body_buffer = None
113
self._fake_client = fake_client
115
def read_body_bytes(self, count=-1):
116
if self._body_buffer is None:
117
self._body_buffer = StringIO(self.body)
118
bytes = self._body_buffer.read(count)
119
if self._body_buffer.tell() == len(self._body_buffer.getvalue()):
120
self._fake_client.expecting_body = False
123
def cancel_read_body(self):
124
self._fake_client.expecting_body = False
126
def read_streamed_body(self):
130
class FakeClient(_SmartClient):
131
"""Lookalike for _SmartClient allowing testing."""
133
def __init__(self, responses, fake_medium_base='fake base'):
134
"""Create a FakeClient.
136
:param responses: A list of response-tuple, body-data pairs to be sent
139
self.responses = responses
141
self.expecting_body = False
142
_SmartClient.__init__(self, FakeMedium(fake_medium_base))
144
def call(self, method, *args):
145
self._calls.append(('call', method, args))
146
return self.responses.pop(0)[0]
148
def call_expecting_body(self, method, *args):
149
self._calls.append(('call_expecting_body', method, args))
150
result = self.responses.pop(0)
151
self.expecting_body = True
152
return result[0], FakeProtocol(result[1], self)
154
def call_with_body_bytes_expecting_body(self, method, args, body):
155
self._calls.append(('call_with_body_bytes_expecting_body', method,
157
result = self.responses.pop(0)
158
self.expecting_body = True
159
return result[0], FakeProtocol(result[1], self)
162
class FakeMedium(object):
164
def __init__(self, base):
168
class TestVfsHas(tests.TestCase):
170
def test_unicode_path(self):
171
client = FakeClient([(('yes',), )], '/')
172
transport = RemoteTransport('bzr://localhost/', _client=client)
173
filename = u'/hell\u00d8'.encode('utf8')
174
result = transport.has(filename)
176
[('call', 'has', (filename,))],
178
self.assertTrue(result)
181
class TestBzrDirOpenBranch(tests.TestCase):
183
def test_branch_present(self):
184
transport = MemoryTransport()
185
transport.mkdir('quack')
186
transport = transport.clone('quack')
187
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )],
189
bzrdir = RemoteBzrDir(transport, _client=client)
190
result = bzrdir.open_branch()
192
[('call', 'BzrDir.open_branch', ('quack/',)),
193
('call', 'BzrDir.find_repository', ('quack/',))],
195
self.assertIsInstance(result, RemoteBranch)
196
self.assertEqual(bzrdir, result.bzrdir)
198
def test_branch_missing(self):
199
transport = MemoryTransport()
200
transport.mkdir('quack')
201
transport = transport.clone('quack')
202
client = FakeClient([(('nobranch',), )], transport.base)
203
bzrdir = RemoteBzrDir(transport, _client=client)
204
self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
206
[('call', 'BzrDir.open_branch', ('quack/',))],
209
def test__get_tree_branch(self):
210
# _get_tree_branch is a form of open_branch, but it should only ask for
211
# branch opening, not any other network requests.
214
calls.append("Called")
216
transport = MemoryTransport()
217
# no requests on the network - catches other api calls being made.
218
client = FakeClient([], transport.base)
219
bzrdir = RemoteBzrDir(transport, _client=client)
220
# patch the open_branch call to record that it was called.
221
bzrdir.open_branch = open_branch
222
self.assertEqual((None, "a-branch"), bzrdir._get_tree_branch())
223
self.assertEqual(["Called"], calls)
224
self.assertEqual([], client._calls)
226
def test_url_quoting_of_path(self):
227
# Relpaths on the wire should not be URL-escaped. So "~" should be
228
# transmitted as "~", not "%7E".
229
transport = RemoteTransport('bzr://localhost/~hello/')
230
client = FakeClient([(('ok', ''), ), (('ok', '', 'no', 'no'), )],
232
bzrdir = RemoteBzrDir(transport, _client=client)
233
result = bzrdir.open_branch()
235
[('call', 'BzrDir.open_branch', ('~hello/',)),
236
('call', 'BzrDir.find_repository', ('~hello/',))],
239
def check_open_repository(self, rich_root, subtrees):
240
transport = MemoryTransport()
241
transport.mkdir('quack')
242
transport = transport.clone('quack')
244
rich_response = 'yes'
248
subtree_response = 'yes'
250
subtree_response = 'no'
251
client = FakeClient([(('ok', '', rich_response, subtree_response), ),],
253
bzrdir = RemoteBzrDir(transport, _client=client)
254
result = bzrdir.open_repository()
256
[('call', 'BzrDir.find_repository', ('quack/',))],
258
self.assertIsInstance(result, RemoteRepository)
259
self.assertEqual(bzrdir, result.bzrdir)
260
self.assertEqual(rich_root, result._format.rich_root_data)
261
self.assertEqual(subtrees, result._format.supports_tree_reference)
263
def test_open_repository_sets_format_attributes(self):
264
self.check_open_repository(True, True)
265
self.check_open_repository(False, True)
266
self.check_open_repository(True, False)
267
self.check_open_repository(False, False)
269
def test_old_server(self):
270
"""RemoteBzrDirFormat should fail to probe if the server version is too
273
self.assertRaises(errors.NotBranchError,
274
RemoteBzrDirFormat.probe_transport, OldServerTransport())
277
class OldSmartClient(object):
278
"""A fake smart client for test_old_version that just returns a version one
279
response to the 'hello' (query version) command.
282
def get_request(self):
283
input_file = StringIO('ok\x011\n')
284
output_file = StringIO()
285
client_medium = medium.SmartSimplePipesClientMedium(
286
input_file, output_file)
287
return medium.SmartClientStreamMediumRequest(client_medium)
290
class OldServerTransport(object):
291
"""A fake transport for test_old_server that reports it's smart server
292
protocol version as version one.
298
def get_smart_client(self):
299
return OldSmartClient()
302
class TestBranchLastRevisionInfo(tests.TestCase):
304
def test_empty_branch(self):
305
# in an empty branch we decode the response properly
306
transport = MemoryTransport()
307
client = FakeClient([(('ok', '0', 'null:'), )], transport.base)
308
transport.mkdir('quack')
309
transport = transport.clone('quack')
310
# we do not want bzrdir to make any remote calls
311
bzrdir = RemoteBzrDir(transport, _client=False)
312
branch = RemoteBranch(bzrdir, None, _client=client)
313
result = branch.last_revision_info()
316
[('call', 'Branch.last_revision_info', ('quack/',))],
318
self.assertEqual((0, NULL_REVISION), result)
320
def test_non_empty_branch(self):
321
# in a non-empty branch we also decode the response properly
322
revid = u'\xc8'.encode('utf8')
323
transport = MemoryTransport()
324
client = FakeClient([(('ok', '2', revid), )], transport.base)
325
transport.mkdir('kwaak')
326
transport = transport.clone('kwaak')
327
# we do not want bzrdir to make any remote calls
328
bzrdir = RemoteBzrDir(transport, _client=False)
329
branch = RemoteBranch(bzrdir, None, _client=client)
330
result = branch.last_revision_info()
333
[('call', 'Branch.last_revision_info', ('kwaak/',))],
335
self.assertEqual((2, revid), result)
338
class TestBranchSetLastRevision(tests.TestCase):
340
def test_set_empty(self):
341
# set_revision_history([]) is translated to calling
342
# Branch.set_last_revision(path, '') on the wire.
343
transport = MemoryTransport()
344
transport.mkdir('branch')
345
transport = transport.clone('branch')
347
client = FakeClient([
349
(('ok', 'branch token', 'repo token'), ),
355
bzrdir = RemoteBzrDir(transport, _client=False)
356
branch = RemoteBranch(bzrdir, None, _client=client)
357
# This is a hack to work around the problem that RemoteBranch currently
358
# unnecessarily invokes _ensure_real upon a call to lock_write.
359
branch._ensure_real = lambda: None
362
result = branch.set_revision_history([])
364
[('call', 'Branch.set_last_revision',
365
('branch/', 'branch token', 'repo token', 'null:'))],
368
self.assertEqual(None, result)
370
def test_set_nonempty(self):
371
# set_revision_history([rev-id1, ..., rev-idN]) is translated to calling
372
# Branch.set_last_revision(path, rev-idN) on the wire.
373
transport = MemoryTransport()
374
transport.mkdir('branch')
375
transport = transport.clone('branch')
377
client = FakeClient([
379
(('ok', 'branch token', 'repo token'), ),
385
bzrdir = RemoteBzrDir(transport, _client=False)
386
branch = RemoteBranch(bzrdir, None, _client=client)
387
# This is a hack to work around the problem that RemoteBranch currently
388
# unnecessarily invokes _ensure_real upon a call to lock_write.
389
branch._ensure_real = lambda: None
390
# Lock the branch, reset the record of remote calls.
394
result = branch.set_revision_history(['rev-id1', 'rev-id2'])
396
[('call', 'Branch.set_last_revision',
397
('branch/', 'branch token', 'repo token', 'rev-id2'))],
400
self.assertEqual(None, result)
402
def test_no_such_revision(self):
403
# A response of 'NoSuchRevision' is translated into an exception.
404
client = FakeClient([
406
(('ok', 'branch token', 'repo token'), ),
408
(('NoSuchRevision', 'rev-id'), ),
411
transport = MemoryTransport()
412
transport.mkdir('branch')
413
transport = transport.clone('branch')
415
bzrdir = RemoteBzrDir(transport, _client=False)
416
branch = RemoteBranch(bzrdir, None, _client=client)
417
branch._ensure_real = lambda: None
422
errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
426
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
427
"""Test branch.control_files api munging...
429
We special case RemoteBranch.control_files.get('branch.conf') to
430
call a specific API so that RemoteBranch's can intercept configuration
431
file reading, allowing them to signal to the client about things like
432
'email is configured for commits'.
435
def test_get_branch_conf(self):
436
# in an empty branch we decode the response properly
437
client = FakeClient([(('ok', ), 'config file body')], self.get_url())
438
# we need to make a real branch because the remote_branch.control_files
439
# will trigger _ensure_real.
440
branch = self.make_branch('quack')
441
transport = branch.bzrdir.root_transport
442
# we do not want bzrdir to make any remote calls
443
bzrdir = RemoteBzrDir(transport, _client=False)
444
branch = RemoteBranch(bzrdir, None, _client=client)
445
result = branch.control_files.get('branch.conf')
447
[('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
449
self.assertEqual('config file body', result.read())
452
class TestBranchLockWrite(tests.TestCase):
454
def test_lock_write_unlockable(self):
455
transport = MemoryTransport()
456
client = FakeClient([(('UnlockableTransport', ), '')], transport.base)
457
transport.mkdir('quack')
458
transport = transport.clone('quack')
459
# we do not want bzrdir to make any remote calls
460
bzrdir = RemoteBzrDir(transport, _client=False)
461
branch = RemoteBranch(bzrdir, None, _client=client)
462
self.assertRaises(errors.UnlockableTransport, branch.lock_write)
464
[('call', 'Branch.lock_write', ('quack/', '', ''))],
468
class TestTransportIsReadonly(tests.TestCase):
471
client = FakeClient([(('yes',), '')])
472
transport = RemoteTransport('bzr://example.com/', medium=False,
474
self.assertEqual(True, transport.is_readonly())
476
[('call', 'Transport.is_readonly', ())],
479
def test_false(self):
480
client = FakeClient([(('no',), '')])
481
transport = RemoteTransport('bzr://example.com/', medium=False,
483
self.assertEqual(False, transport.is_readonly())
485
[('call', 'Transport.is_readonly', ())],
488
def test_error_from_old_server(self):
489
"""bzr 0.15 and earlier servers don't recognise the is_readonly verb.
491
Clients should treat it as a "no" response, because is_readonly is only
492
advisory anyway (a transport could be read-write, but then the
493
underlying filesystem could be readonly anyway).
495
client = FakeClient([(
496
('error', "Generic bzr smart protocol error: "
497
"bad request 'Transport.is_readonly'"), '')])
498
transport = RemoteTransport('bzr://example.com/', medium=False,
500
self.assertEqual(False, transport.is_readonly())
502
[('call', 'Transport.is_readonly', ())],
505
def test_error_from_old_0_11_server(self):
506
"""Same as test_error_from_old_server, but with the slightly different
507
error message from bzr 0.11 servers.
509
client = FakeClient([(
510
('error', "Generic bzr smart protocol error: "
511
"bad request u'Transport.is_readonly'"), '')])
512
transport = RemoteTransport('bzr://example.com/', medium=False,
514
self.assertEqual(False, transport.is_readonly())
516
[('call', 'Transport.is_readonly', ())],
520
class TestRemoteRepository(tests.TestCase):
521
"""Base for testing RemoteRepository protocol usage.
523
These tests contain frozen requests and responses. We want any changes to
524
what is sent or expected to be require a thoughtful update to these tests
525
because they might break compatibility with different-versioned servers.
528
def setup_fake_client_and_repository(self, responses, transport_path):
529
"""Create the fake client and repository for testing with.
531
There's no real server here; we just have canned responses sent
534
:param transport_path: Path below the root of the MemoryTransport
535
where the repository will be created.
537
transport = MemoryTransport()
538
transport.mkdir(transport_path)
539
client = FakeClient(responses, transport.base)
540
transport = transport.clone(transport_path)
541
# we do not want bzrdir to make any remote calls
542
bzrdir = RemoteBzrDir(transport, _client=False)
543
repo = RemoteRepository(bzrdir, None, _client=client)
547
class TestRepositoryGatherStats(TestRemoteRepository):
549
def test_revid_none(self):
550
# ('ok',), body with revisions and size
551
responses = [(('ok', ), 'revisions: 2\nsize: 18\n')]
552
transport_path = 'quack'
553
repo, client = self.setup_fake_client_and_repository(
554
responses, transport_path)
555
result = repo.gather_stats(None)
557
[('call_expecting_body', 'Repository.gather_stats',
558
('quack/','','no'))],
560
self.assertEqual({'revisions': 2, 'size': 18}, result)
562
def test_revid_no_committers(self):
563
# ('ok',), body without committers
564
responses = [(('ok', ),
565
'firstrev: 123456.300 3600\n'
566
'latestrev: 654231.400 0\n'
569
transport_path = 'quick'
570
revid = u'\xc8'.encode('utf8')
571
repo, client = self.setup_fake_client_and_repository(
572
responses, transport_path)
573
result = repo.gather_stats(revid)
575
[('call_expecting_body', 'Repository.gather_stats',
576
('quick/', revid, 'no'))],
578
self.assertEqual({'revisions': 2, 'size': 18,
579
'firstrev': (123456.300, 3600),
580
'latestrev': (654231.400, 0),},
583
def test_revid_with_committers(self):
584
# ('ok',), body with committers
585
responses = [(('ok', ),
587
'firstrev: 123456.300 3600\n'
588
'latestrev: 654231.400 0\n'
591
transport_path = 'buick'
592
revid = u'\xc8'.encode('utf8')
593
repo, client = self.setup_fake_client_and_repository(
594
responses, transport_path)
595
result = repo.gather_stats(revid, True)
597
[('call_expecting_body', 'Repository.gather_stats',
598
('buick/', revid, 'yes'))],
600
self.assertEqual({'revisions': 2, 'size': 18,
602
'firstrev': (123456.300, 3600),
603
'latestrev': (654231.400, 0),},
607
class TestRepositoryGetGraph(TestRemoteRepository):
609
def test_get_graph(self):
610
# get_graph returns a graph with the repository as the
613
transport_path = 'quack'
614
repo, client = self.setup_fake_client_and_repository(
615
responses, transport_path)
616
graph = repo.get_graph()
617
self.assertEqual(graph._parents_provider, repo)
620
class TestRepositoryGetParentMap(TestRemoteRepository):
622
def test_get_parent_map_caching(self):
623
# get_parent_map returns from cache until unlock()
624
# setup a reponse with two revisions
625
r1 = u'\u0e33'.encode('utf8')
626
r2 = u'\u0dab'.encode('utf8')
627
lines = [' '.join([r2, r1]), r1]
628
encoded_body = '\n'.join(lines)
629
responses = [(('ok', ), encoded_body), (('ok', ), encoded_body)]
631
transport_path = 'quack'
632
repo, client = self.setup_fake_client_and_repository(
633
responses, transport_path)
635
graph = repo.get_graph()
636
parents = graph.get_parent_map([r2])
637
self.assertEqual({r2: (r1,)}, parents)
638
# locking and unlocking deeper should not reset
641
parents = graph.get_parent_map([r1])
642
self.assertEqual({r1: (NULL_REVISION,)}, parents)
644
[('call_expecting_body', 'Repository.get_parent_map',
648
# now we call again, and it should use the second response.
650
graph = repo.get_graph()
651
parents = graph.get_parent_map([r1])
652
self.assertEqual({r1: (NULL_REVISION,)}, parents)
654
[('call_expecting_body', 'Repository.get_parent_map',
656
('call_expecting_body', 'Repository.get_parent_map',
663
class TestRepositoryGetRevisionGraph(TestRemoteRepository):
665
def test_null_revision(self):
666
# a null revision has the predictable result {}, we should have no wire
667
# traffic when calling it with this argument
668
responses = [(('notused', ), '')]
669
transport_path = 'empty'
670
repo, client = self.setup_fake_client_and_repository(
671
responses, transport_path)
672
result = repo.get_revision_graph(NULL_REVISION)
673
self.assertEqual([], client._calls)
674
self.assertEqual({}, result)
676
def test_none_revision(self):
677
# with none we want the entire graph
678
r1 = u'\u0e33'.encode('utf8')
679
r2 = u'\u0dab'.encode('utf8')
680
lines = [' '.join([r2, r1]), r1]
681
encoded_body = '\n'.join(lines)
683
responses = [(('ok', ), encoded_body)]
684
transport_path = 'sinhala'
685
repo, client = self.setup_fake_client_and_repository(
686
responses, transport_path)
687
result = repo.get_revision_graph()
689
[('call_expecting_body', 'Repository.get_revision_graph',
692
self.assertEqual({r1: (), r2: (r1, )}, result)
694
def test_specific_revision(self):
695
# with a specific revision we want the graph for that
696
# with none we want the entire graph
697
r11 = u'\u0e33'.encode('utf8')
698
r12 = u'\xc9'.encode('utf8')
699
r2 = u'\u0dab'.encode('utf8')
700
lines = [' '.join([r2, r11, r12]), r11, r12]
701
encoded_body = '\n'.join(lines)
703
responses = [(('ok', ), encoded_body)]
704
transport_path = 'sinhala'
705
repo, client = self.setup_fake_client_and_repository(
706
responses, transport_path)
707
result = repo.get_revision_graph(r2)
709
[('call_expecting_body', 'Repository.get_revision_graph',
712
self.assertEqual({r11: (), r12: (), r2: (r11, r12), }, result)
714
def test_no_such_revision(self):
716
responses = [(('nosuchrevision', revid), '')]
717
transport_path = 'sinhala'
718
repo, client = self.setup_fake_client_and_repository(
719
responses, transport_path)
720
# also check that the right revision is reported in the error
721
self.assertRaises(errors.NoSuchRevision,
722
repo.get_revision_graph, revid)
724
[('call_expecting_body', 'Repository.get_revision_graph',
725
('sinhala/', revid))],
729
class TestRepositoryIsShared(TestRemoteRepository):
731
def test_is_shared(self):
732
# ('yes', ) for Repository.is_shared -> 'True'.
733
responses = [(('yes', ), )]
734
transport_path = 'quack'
735
repo, client = self.setup_fake_client_and_repository(
736
responses, transport_path)
737
result = repo.is_shared()
739
[('call', 'Repository.is_shared', ('quack/',))],
741
self.assertEqual(True, result)
743
def test_is_not_shared(self):
744
# ('no', ) for Repository.is_shared -> 'False'.
745
responses = [(('no', ), )]
746
transport_path = 'qwack'
747
repo, client = self.setup_fake_client_and_repository(
748
responses, transport_path)
749
result = repo.is_shared()
751
[('call', 'Repository.is_shared', ('qwack/',))],
753
self.assertEqual(False, result)
756
class TestRepositoryLockWrite(TestRemoteRepository):
758
def test_lock_write(self):
759
responses = [(('ok', 'a token'), '')]
760
transport_path = 'quack'
761
repo, client = self.setup_fake_client_and_repository(
762
responses, transport_path)
763
result = repo.lock_write()
765
[('call', 'Repository.lock_write', ('quack/', ''))],
767
self.assertEqual('a token', result)
769
def test_lock_write_already_locked(self):
770
responses = [(('LockContention', ), '')]
771
transport_path = 'quack'
772
repo, client = self.setup_fake_client_and_repository(
773
responses, transport_path)
774
self.assertRaises(errors.LockContention, repo.lock_write)
776
[('call', 'Repository.lock_write', ('quack/', ''))],
779
def test_lock_write_unlockable(self):
780
responses = [(('UnlockableTransport', ), '')]
781
transport_path = 'quack'
782
repo, client = self.setup_fake_client_and_repository(
783
responses, transport_path)
784
self.assertRaises(errors.UnlockableTransport, repo.lock_write)
786
[('call', 'Repository.lock_write', ('quack/', ''))],
790
class TestRepositoryUnlock(TestRemoteRepository):
792
def test_unlock(self):
793
responses = [(('ok', 'a token'), ''),
795
transport_path = 'quack'
796
repo, client = self.setup_fake_client_and_repository(
797
responses, transport_path)
801
[('call', 'Repository.lock_write', ('quack/', '')),
802
('call', 'Repository.unlock', ('quack/', 'a token'))],
805
def test_unlock_wrong_token(self):
806
# If somehow the token is wrong, unlock will raise TokenMismatch.
807
responses = [(('ok', 'a token'), ''),
808
(('TokenMismatch',), '')]
809
transport_path = 'quack'
810
repo, client = self.setup_fake_client_and_repository(
811
responses, transport_path)
813
self.assertRaises(errors.TokenMismatch, repo.unlock)
816
class TestRepositoryHasRevision(TestRemoteRepository):
819
# repo.has_revision(None) should not cause any traffic.
820
transport_path = 'quack'
822
repo, client = self.setup_fake_client_and_repository(
823
responses, transport_path)
825
# The null revision is always there, so has_revision(None) == True.
826
self.assertEqual(True, repo.has_revision(NULL_REVISION))
828
# The remote repo shouldn't be accessed.
829
self.assertEqual([], client._calls)
832
class TestRepositoryTarball(TestRemoteRepository):
834
# This is a canned tarball reponse we can validate against
836
'QlpoOTFBWSZTWdGkj3wAAWF/k8aQACBIB//A9+8cIX/v33AACEAYABAECEACNz'
837
'JqsgJJFPTSnk1A3qh6mTQAAAANPUHkagkSTEkaA09QaNAAAGgAAAcwCYCZGAEY'
838
'mJhMJghpiaYBUkKammSHqNMZQ0NABkNAeo0AGneAevnlwQoGzEzNVzaYxp/1Uk'
839
'xXzA1CQX0BJMZZLcPBrluJir5SQyijWHYZ6ZUtVqqlYDdB2QoCwa9GyWwGYDMA'
840
'OQYhkpLt/OKFnnlT8E0PmO8+ZNSo2WWqeCzGB5fBXZ3IvV7uNJVE7DYnWj6qwB'
841
'k5DJDIrQ5OQHHIjkS9KqwG3mc3t+F1+iujb89ufyBNIKCgeZBWrl5cXxbMGoMs'
842
'c9JuUkg5YsiVcaZJurc6KLi6yKOkgCUOlIlOpOoXyrTJjK8ZgbklReDdwGmFgt'
843
'dkVsAIslSVCd4AtACSLbyhLHryfb14PKegrVDba+U8OL6KQtzdM5HLjAc8/p6n'
844
'0lgaWU8skgO7xupPTkyuwheSckejFLK5T4ZOo0Gda9viaIhpD1Qn7JqqlKAJqC'
845
'QplPKp2nqBWAfwBGaOwVrz3y1T+UZZNismXHsb2Jq18T+VaD9k4P8DqE3g70qV'
846
'JLurpnDI6VS5oqDDPVbtVjMxMxMg4rzQVipn2Bv1fVNK0iq3Gl0hhnnHKm/egy'
847
'nWQ7QH/F3JFOFCQ0aSPfA='
850
def test_repository_tarball(self):
851
# Test that Repository.tarball generates the right operations
852
transport_path = 'repo'
853
expected_responses = [(('ok',), self.tarball_content),
855
expected_calls = [('call_expecting_body', 'Repository.tarball',
858
remote_repo, client = self.setup_fake_client_and_repository(
859
expected_responses, transport_path)
860
# Now actually ask for the tarball
861
tarball_file = remote_repo._get_tarball('bz2')
863
self.assertEqual(expected_calls, client._calls)
864
self.assertEqual(self.tarball_content, tarball_file.read())
869
class TestRemoteRepositoryCopyContent(tests.TestCaseWithTransport):
870
"""RemoteRepository.copy_content_into optimizations"""
872
def test_copy_content_remote_to_local(self):
873
self.transport_server = server.SmartTCPServer_for_testing
874
src_repo = self.make_repository('repo1')
875
src_repo = repository.Repository.open(self.get_url('repo1'))
876
# At the moment the tarball-based copy_content_into can't write back
877
# into a smart server. It would be good if it could upload the
878
# tarball; once that works we'd have to create repositories of
879
# different formats. -- mbp 20070410
880
dest_url = self.get_vfs_only_url('repo2')
881
dest_bzrdir = BzrDir.create(dest_url)
882
dest_repo = dest_bzrdir.create_repository()
883
self.assertFalse(isinstance(dest_repo, RemoteRepository))
884
self.assertTrue(isinstance(src_repo, RemoteRepository))
885
src_repo.copy_content_into(dest_repo)
888
class TestRepositoryStreamKnitData(TestRemoteRepository):
890
def make_pack_file(self, records):
891
pack_file = StringIO()
892
pack_writer = pack.ContainerWriter(pack_file.write)
894
for bytes, names in records:
895
pack_writer.add_bytes_record(bytes, names)
900
def make_pack_stream(self, records):
901
pack_serialiser = pack.ContainerSerialiser()
902
yield pack_serialiser.begin()
903
for bytes, names in records:
904
yield pack_serialiser.bytes_record(bytes, names)
905
yield pack_serialiser.end()
907
def test_bad_pack_from_server(self):
908
"""A response with invalid data (e.g. it has a record with multiple
909
names) triggers an exception.
911
Not all possible errors will be caught at this stage, but obviously
912
malformed data should be.
914
record = ('bytes', [('name1',), ('name2',)])
915
pack_stream = self.make_pack_stream([record])
916
responses = [(('ok',), pack_stream), ]
917
transport_path = 'quack'
918
repo, client = self.setup_fake_client_and_repository(
919
responses, transport_path)
920
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
921
stream = repo.get_data_stream_for_search(search)
922
self.assertRaises(errors.SmartProtocolError, list, stream)
924
def test_backwards_compatibility(self):
925
"""If the server doesn't recognise this request, fallback to VFS."""
927
"Generic bzr smart protocol error: "
928
"bad request 'Repository.stream_revisions_chunked'")
930
(('error', error_msg), '')]
931
repo, client = self.setup_fake_client_and_repository(
933
self.mock_called = False
934
repo._real_repository = MockRealRepository(self)
935
search = graph.SearchResult(set(['revid']), set(), 1, set(['revid']))
936
repo.get_data_stream_for_search(search)
937
self.assertTrue(self.mock_called)
938
self.failIf(client.expecting_body,
939
"The protocol has been left in an unclean state that will cause "
940
"TooManyConcurrentRequests errors.")
943
class MockRealRepository(object):
944
"""Helper class for TestRepositoryStreamKnitData.test_unknown_method."""
946
def __init__(self, test):
949
def get_data_stream_for_search(self, search):
950
self.test.assertEqual(set(['revid']), search.get_keys())
951
self.test.mock_called = True