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 the smart wire/domain protocol.
19
This module contains tests for the domain-level smart requests and responses,
20
such as the 'Branch.lock_write' request. Many of these use specific disk
21
formats to exercise calls that only make sense for formats with specific
24
Tests for low-level protocol encoding are found in test_smart_transport.
28
from cStringIO import StringIO
39
from bzrlib.branch import Branch, BranchReferenceFormat
40
import bzrlib.smart.branch
41
import bzrlib.smart.bzrdir, bzrlib.smart.bzrdir as smart_dir
42
import bzrlib.smart.packrepository
43
import bzrlib.smart.repository
44
from bzrlib.smart.request import (
45
FailedSmartServerResponse,
48
SuccessfulSmartServerResponse,
50
from bzrlib.tests import (
55
from bzrlib.transport import chroot, get_transport
56
from bzrlib.util import bencode
59
def load_tests(standard_tests, module, loader):
60
"""Multiply tests version and protocol consistency."""
61
# FindRepository tests.
62
bzrdir_mod = bzrlib.smart.bzrdir
63
applier = TestScenarioApplier()
66
"_request_class":bzrdir_mod.SmartServerRequestFindRepositoryV1}),
67
("find_repositoryV2", {
68
"_request_class":bzrdir_mod.SmartServerRequestFindRepositoryV2}),
69
("find_repositoryV3", {
70
"_request_class":bzrdir_mod.SmartServerRequestFindRepositoryV3}),
72
to_adapt, result = split_suite_by_re(standard_tests,
73
"TestSmartServerRequestFindRepository")
74
v2_only, v1_and_2 = split_suite_by_re(to_adapt,
76
for test in iter_suite_tests(v1_and_2):
77
result.addTests(applier.adapt(test))
78
del applier.scenarios[0]
79
for test in iter_suite_tests(v2_only):
80
result.addTests(applier.adapt(test))
84
class TestCaseWithChrootedTransport(tests.TestCaseWithTransport):
87
tests.TestCaseWithTransport.setUp(self)
88
self._chroot_server = None
90
def get_transport(self, relpath=None):
91
if self._chroot_server is None:
92
backing_transport = tests.TestCaseWithTransport.get_transport(self)
93
self._chroot_server = chroot.ChrootServer(backing_transport)
94
self._chroot_server.setUp()
95
self.addCleanup(self._chroot_server.tearDown)
96
t = get_transport(self._chroot_server.get_url())
97
if relpath is not None:
102
class TestCaseWithSmartMedium(tests.TestCaseWithTransport):
105
super(TestCaseWithSmartMedium, self).setUp()
106
# We're allowed to set the transport class here, so that we don't use
107
# the default or a parameterized class, but rather use the
108
# TestCaseWithTransport infrastructure to set up a smart server and
110
self.transport_server = self.make_transport_server
112
def make_transport_server(self):
113
return smart.server.SmartTCPServer_for_testing('-' + self.id())
115
def get_smart_medium(self):
116
"""Get a smart medium to use in tests."""
117
return self.get_transport().get_smart_medium()
120
class TestSmartServerResponse(tests.TestCase):
122
def test__eq__(self):
123
self.assertEqual(SmartServerResponse(('ok', )),
124
SmartServerResponse(('ok', )))
125
self.assertEqual(SmartServerResponse(('ok', ), 'body'),
126
SmartServerResponse(('ok', ), 'body'))
127
self.assertNotEqual(SmartServerResponse(('ok', )),
128
SmartServerResponse(('notok', )))
129
self.assertNotEqual(SmartServerResponse(('ok', ), 'body'),
130
SmartServerResponse(('ok', )))
131
self.assertNotEqual(None,
132
SmartServerResponse(('ok', )))
134
def test__str__(self):
135
"""SmartServerResponses can be stringified."""
137
"<SuccessfulSmartServerResponse args=('args',) body='body'>",
138
str(SuccessfulSmartServerResponse(('args',), 'body')))
140
"<FailedSmartServerResponse args=('args',) body='body'>",
141
str(FailedSmartServerResponse(('args',), 'body')))
144
class TestSmartServerRequest(tests.TestCaseWithMemoryTransport):
146
def test_translate_client_path(self):
147
transport = self.get_transport()
148
request = SmartServerRequest(transport, 'foo/')
149
self.assertEqual('./', request.translate_client_path('foo/'))
151
errors.InvalidURLJoin, request.translate_client_path, 'foo/..')
153
errors.PathNotChild, request.translate_client_path, '/')
155
errors.PathNotChild, request.translate_client_path, 'bar/')
156
self.assertEqual('./baz', request.translate_client_path('foo/baz'))
158
def test_transport_from_client_path(self):
159
transport = self.get_transport()
160
request = SmartServerRequest(transport, 'foo/')
163
request.transport_from_client_path('foo/').base)
166
class TestSmartServerBzrDirRequestCloningMetaDir(
167
tests.TestCaseWithMemoryTransport):
168
"""Tests for BzrDir.cloning_metadir."""
170
def test_cloning_metadir(self):
171
"""When there is a bzrdir present, the call succeeds."""
172
backing = self.get_transport()
173
dir = self.make_bzrdir('.')
174
local_result = dir.cloning_metadir()
175
request_class = smart_dir.SmartServerBzrDirRequestCloningMetaDir
176
request = request_class(backing)
177
expected = SuccessfulSmartServerResponse(
178
(local_result.network_name(),
179
local_result.repository_format.network_name(),
180
local_result.get_branch_format().network_name()))
181
self.assertEqual(expected, request.execute('', 'False'))
184
class TestSmartServerRequestCreateRepository(tests.TestCaseWithMemoryTransport):
185
"""Tests for BzrDir.create_repository."""
187
def test_makes_repository(self):
188
"""When there is a bzrdir present, the call succeeds."""
189
backing = self.get_transport()
190
self.make_bzrdir('.')
191
request_class = bzrlib.smart.bzrdir.SmartServerRequestCreateRepository
192
request = request_class(backing)
193
reference_bzrdir_format = bzrdir.format_registry.get('default')()
194
reference_format = reference_bzrdir_format.repository_format
195
network_name = reference_format.network_name()
196
expected = SuccessfulSmartServerResponse(
197
('ok', 'no', 'no', 'no', network_name))
198
self.assertEqual(expected, request.execute('', network_name, 'True'))
201
class TestSmartServerRequestFindRepository(tests.TestCaseWithMemoryTransport):
202
"""Tests for BzrDir.find_repository."""
204
def test_no_repository(self):
205
"""When there is no repository to be found, ('norepository', ) is returned."""
206
backing = self.get_transport()
207
request = self._request_class(backing)
208
self.make_bzrdir('.')
209
self.assertEqual(SmartServerResponse(('norepository', )),
212
def test_nonshared_repository(self):
213
# nonshared repositorys only allow 'find' to return a handle when the
214
# path the repository is being searched on is the same as that that
215
# the repository is at.
216
backing = self.get_transport()
217
request = self._request_class(backing)
218
result = self._make_repository_and_result()
219
self.assertEqual(result, request.execute(''))
220
self.make_bzrdir('subdir')
221
self.assertEqual(SmartServerResponse(('norepository', )),
222
request.execute('subdir'))
224
def _make_repository_and_result(self, shared=False, format=None):
225
"""Convenience function to setup a repository.
227
:result: The SmartServerResponse to expect when opening it.
229
repo = self.make_repository('.', shared=shared, format=format)
230
if repo.supports_rich_root():
234
if repo._format.supports_tree_reference:
238
if (smart.bzrdir.SmartServerRequestFindRepositoryV3 ==
239
self._request_class):
240
return SuccessfulSmartServerResponse(
241
('ok', '', rich_root, subtrees, 'no',
242
repo._format.network_name()))
243
elif (smart.bzrdir.SmartServerRequestFindRepositoryV2 ==
244
self._request_class):
245
# All tests so far are on formats, and for non-external
247
return SuccessfulSmartServerResponse(
248
('ok', '', rich_root, subtrees, 'no'))
250
return SuccessfulSmartServerResponse(('ok', '', rich_root, subtrees))
252
def test_shared_repository(self):
253
"""When there is a shared repository, we get 'ok', 'relpath-to-repo'."""
254
backing = self.get_transport()
255
request = self._request_class(backing)
256
result = self._make_repository_and_result(shared=True)
257
self.assertEqual(result, request.execute(''))
258
self.make_bzrdir('subdir')
259
result2 = SmartServerResponse(result.args[0:1] + ('..', ) + result.args[2:])
260
self.assertEqual(result2,
261
request.execute('subdir'))
262
self.make_bzrdir('subdir/deeper')
263
result3 = SmartServerResponse(result.args[0:1] + ('../..', ) + result.args[2:])
264
self.assertEqual(result3,
265
request.execute('subdir/deeper'))
267
def test_rich_root_and_subtree_encoding(self):
268
"""Test for the format attributes for rich root and subtree support."""
269
backing = self.get_transport()
270
request = self._request_class(backing)
271
result = self._make_repository_and_result(format='dirstate-with-subtree')
272
# check the test will be valid
273
self.assertEqual('yes', result.args[2])
274
self.assertEqual('yes', result.args[3])
275
self.assertEqual(result, request.execute(''))
277
def test_supports_external_lookups_no_v2(self):
278
"""Test for the supports_external_lookups attribute."""
279
backing = self.get_transport()
280
request = self._request_class(backing)
281
result = self._make_repository_and_result(format='dirstate-with-subtree')
282
# check the test will be valid
283
self.assertEqual('no', result.args[4])
284
self.assertEqual(result, request.execute(''))
287
class TestSmartServerRequestInitializeBzrDir(tests.TestCaseWithMemoryTransport):
289
def test_empty_dir(self):
290
"""Initializing an empty dir should succeed and do it."""
291
backing = self.get_transport()
292
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
293
self.assertEqual(SmartServerResponse(('ok', )),
295
made_dir = bzrdir.BzrDir.open_from_transport(backing)
296
# no branch, tree or repository is expected with the current
298
self.assertRaises(errors.NoWorkingTree, made_dir.open_workingtree)
299
self.assertRaises(errors.NotBranchError, made_dir.open_branch)
300
self.assertRaises(errors.NoRepositoryPresent, made_dir.open_repository)
302
def test_missing_dir(self):
303
"""Initializing a missing directory should fail like the bzrdir api."""
304
backing = self.get_transport()
305
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
306
self.assertRaises(errors.NoSuchFile,
307
request.execute, 'subdir')
309
def test_initialized_dir(self):
310
"""Initializing an extant bzrdir should fail like the bzrdir api."""
311
backing = self.get_transport()
312
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
313
self.make_bzrdir('subdir')
314
self.assertRaises(errors.FileExists,
315
request.execute, 'subdir')
318
class TestSmartServerRequestOpenBranch(TestCaseWithChrootedTransport):
320
def test_no_branch(self):
321
"""When there is no branch, ('nobranch', ) is returned."""
322
backing = self.get_transport()
323
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
324
self.make_bzrdir('.')
325
self.assertEqual(SmartServerResponse(('nobranch', )),
328
def test_branch(self):
329
"""When there is a branch, 'ok' is returned."""
330
backing = self.get_transport()
331
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
332
self.make_branch('.')
333
self.assertEqual(SmartServerResponse(('ok', '')),
336
def test_branch_reference(self):
337
"""When there is a branch reference, the reference URL is returned."""
338
backing = self.get_transport()
339
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
340
branch = self.make_branch('branch')
341
checkout = branch.create_checkout('reference',lightweight=True)
342
reference_url = BranchReferenceFormat().get_reference(checkout.bzrdir)
343
self.assertFileEqual(reference_url, 'reference/.bzr/branch/location')
344
self.assertEqual(SmartServerResponse(('ok', reference_url)),
345
request.execute('reference'))
348
class TestSmartServerRequestRevisionHistory(tests.TestCaseWithMemoryTransport):
350
def test_empty(self):
351
"""For an empty branch, the body is empty."""
352
backing = self.get_transport()
353
request = smart.branch.SmartServerRequestRevisionHistory(backing)
354
self.make_branch('.')
355
self.assertEqual(SmartServerResponse(('ok', ), ''),
358
def test_not_empty(self):
359
"""For a non-empty branch, the body is empty."""
360
backing = self.get_transport()
361
request = smart.branch.SmartServerRequestRevisionHistory(backing)
362
tree = self.make_branch_and_memory_tree('.')
365
r1 = tree.commit('1st commit')
366
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
369
SmartServerResponse(('ok', ), ('\x00'.join([r1, r2]))),
373
class TestSmartServerBranchRequest(tests.TestCaseWithMemoryTransport):
375
def test_no_branch(self):
376
"""When there is a bzrdir and no branch, NotBranchError is raised."""
377
backing = self.get_transport()
378
request = smart.branch.SmartServerBranchRequest(backing)
379
self.make_bzrdir('.')
380
self.assertRaises(errors.NotBranchError,
383
def test_branch_reference(self):
384
"""When there is a branch reference, NotBranchError is raised."""
385
backing = self.get_transport()
386
request = smart.branch.SmartServerBranchRequest(backing)
387
branch = self.make_branch('branch')
388
checkout = branch.create_checkout('reference',lightweight=True)
389
self.assertRaises(errors.NotBranchError,
390
request.execute, 'checkout')
393
class TestSmartServerBranchRequestLastRevisionInfo(tests.TestCaseWithMemoryTransport):
395
def test_empty(self):
396
"""For an empty branch, the result is ('ok', '0', 'null:')."""
397
backing = self.get_transport()
398
request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
399
self.make_branch('.')
400
self.assertEqual(SmartServerResponse(('ok', '0', 'null:')),
403
def test_not_empty(self):
404
"""For a non-empty branch, the result is ('ok', 'revno', 'revid')."""
405
backing = self.get_transport()
406
request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
407
tree = self.make_branch_and_memory_tree('.')
410
rev_id_utf8 = u'\xc8'.encode('utf-8')
411
r1 = tree.commit('1st commit')
412
r2 = tree.commit('2nd commit', rev_id=rev_id_utf8)
415
SmartServerResponse(('ok', '2', rev_id_utf8)),
419
class TestSmartServerBranchRequestGetConfigFile(tests.TestCaseWithMemoryTransport):
421
def test_default(self):
422
"""With no file, we get empty content."""
423
backing = self.get_transport()
424
request = smart.branch.SmartServerBranchGetConfigFile(backing)
425
branch = self.make_branch('.')
426
# there should be no file by default
428
self.assertEqual(SmartServerResponse(('ok', ), content),
431
def test_with_content(self):
432
# SmartServerBranchGetConfigFile should return the content from
433
# branch.control_files.get('branch.conf') for now - in the future it may
434
# perform more complex processing.
435
backing = self.get_transport()
436
request = smart.branch.SmartServerBranchGetConfigFile(backing)
437
branch = self.make_branch('.')
438
branch._transport.put_bytes('branch.conf', 'foo bar baz')
439
self.assertEqual(SmartServerResponse(('ok', ), 'foo bar baz'),
443
class SetLastRevisionTestBase(tests.TestCaseWithMemoryTransport):
444
"""Base test case for verbs that implement set_last_revision."""
447
tests.TestCaseWithMemoryTransport.setUp(self)
448
backing_transport = self.get_transport()
449
self.request = self.request_class(backing_transport)
450
self.tree = self.make_branch_and_memory_tree('.')
452
def lock_branch(self):
454
branch_token = b.lock_write()
455
repo_token = b.repository.lock_write()
456
b.repository.unlock()
457
return branch_token, repo_token
459
def unlock_branch(self):
460
self.tree.branch.unlock()
462
def set_last_revision(self, revision_id, revno):
463
branch_token, repo_token = self.lock_branch()
464
response = self._set_last_revision(
465
revision_id, revno, branch_token, repo_token)
469
def assertRequestSucceeds(self, revision_id, revno):
470
response = self.set_last_revision(revision_id, revno)
471
self.assertEqual(SuccessfulSmartServerResponse(('ok',)), response)
474
class TestSetLastRevisionVerbMixin(object):
475
"""Mixin test case for verbs that implement set_last_revision."""
477
def test_set_null_to_null(self):
478
"""An empty branch can have its last revision set to 'null:'."""
479
self.assertRequestSucceeds('null:', 0)
481
def test_NoSuchRevision(self):
482
"""If the revision_id is not present, the verb returns NoSuchRevision.
484
revision_id = 'non-existent revision'
486
FailedSmartServerResponse(('NoSuchRevision', revision_id)),
487
self.set_last_revision(revision_id, 1))
489
def make_tree_with_two_commits(self):
490
self.tree.lock_write()
492
rev_id_utf8 = u'\xc8'.encode('utf-8')
493
r1 = self.tree.commit('1st commit', rev_id=rev_id_utf8)
494
r2 = self.tree.commit('2nd commit', rev_id='rev-2')
497
def test_branch_last_revision_info_is_updated(self):
498
"""A branch's tip can be set to a revision that is present in its
501
# Make a branch with an empty revision history, but two revisions in
503
self.make_tree_with_two_commits()
504
rev_id_utf8 = u'\xc8'.encode('utf-8')
505
self.tree.branch.set_revision_history([])
507
(0, 'null:'), self.tree.branch.last_revision_info())
508
# We can update the branch to a revision that is present in the
510
self.assertRequestSucceeds(rev_id_utf8, 1)
512
(1, rev_id_utf8), self.tree.branch.last_revision_info())
514
def test_branch_last_revision_info_rewind(self):
515
"""A branch's tip can be set to a revision that is an ancestor of the
518
self.make_tree_with_two_commits()
519
rev_id_utf8 = u'\xc8'.encode('utf-8')
521
(2, 'rev-2'), self.tree.branch.last_revision_info())
522
self.assertRequestSucceeds(rev_id_utf8, 1)
524
(1, rev_id_utf8), self.tree.branch.last_revision_info())
526
def test_TipChangeRejected(self):
527
"""If a pre_change_branch_tip hook raises TipChangeRejected, the verb
528
returns TipChangeRejected.
530
rejection_message = u'rejection message\N{INTERROBANG}'
531
def hook_that_rejects(params):
532
raise errors.TipChangeRejected(rejection_message)
533
Branch.hooks.install_named_hook(
534
'pre_change_branch_tip', hook_that_rejects, None)
536
FailedSmartServerResponse(
537
('TipChangeRejected', rejection_message.encode('utf-8'))),
538
self.set_last_revision('null:', 0))
541
class TestSmartServerBranchRequestSetLastRevision(
542
SetLastRevisionTestBase, TestSetLastRevisionVerbMixin):
543
"""Tests for Branch.set_last_revision verb."""
545
request_class = smart.branch.SmartServerBranchRequestSetLastRevision
547
def _set_last_revision(self, revision_id, revno, branch_token, repo_token):
548
return self.request.execute(
549
'', branch_token, repo_token, revision_id)
552
class TestSmartServerBranchRequestSetLastRevisionInfo(
553
SetLastRevisionTestBase, TestSetLastRevisionVerbMixin):
554
"""Tests for Branch.set_last_revision_info verb."""
556
request_class = smart.branch.SmartServerBranchRequestSetLastRevisionInfo
558
def _set_last_revision(self, revision_id, revno, branch_token, repo_token):
559
return self.request.execute(
560
'', branch_token, repo_token, revno, revision_id)
562
def test_NoSuchRevision(self):
563
"""Branch.set_last_revision_info does not have to return
564
NoSuchRevision if the revision_id is absent.
566
raise tests.TestNotApplicable()
569
class TestSmartServerBranchRequestSetLastRevisionEx(
570
SetLastRevisionTestBase, TestSetLastRevisionVerbMixin):
571
"""Tests for Branch.set_last_revision_ex verb."""
573
request_class = smart.branch.SmartServerBranchRequestSetLastRevisionEx
575
def _set_last_revision(self, revision_id, revno, branch_token, repo_token):
576
return self.request.execute(
577
'', branch_token, repo_token, revision_id, 0, 0)
579
def assertRequestSucceeds(self, revision_id, revno):
580
response = self.set_last_revision(revision_id, revno)
582
SuccessfulSmartServerResponse(('ok', revno, revision_id)),
585
def test_branch_last_revision_info_rewind(self):
586
"""A branch's tip can be set to a revision that is an ancestor of the
587
current tip, but only if allow_overwrite_descendant is passed.
589
self.make_tree_with_two_commits()
590
rev_id_utf8 = u'\xc8'.encode('utf-8')
592
(2, 'rev-2'), self.tree.branch.last_revision_info())
593
# If allow_overwrite_descendant flag is 0, then trying to set the tip
594
# to an older revision ID has no effect.
595
branch_token, repo_token = self.lock_branch()
596
response = self.request.execute(
597
'', branch_token, repo_token, rev_id_utf8, 0, 0)
599
SuccessfulSmartServerResponse(('ok', 2, 'rev-2')),
602
(2, 'rev-2'), self.tree.branch.last_revision_info())
604
# If allow_overwrite_descendant flag is 1, then setting the tip to an
606
response = self.request.execute(
607
'', branch_token, repo_token, rev_id_utf8, 0, 1)
609
SuccessfulSmartServerResponse(('ok', 1, rev_id_utf8)),
613
(1, rev_id_utf8), self.tree.branch.last_revision_info())
615
def make_branch_with_divergent_history(self):
616
"""Make a branch with divergent history in its repo.
618
The branch's tip will be 'child-2', and the repo will also contain
619
'child-1', which diverges from a common base revision.
621
self.tree.lock_write()
623
r1 = self.tree.commit('1st commit')
624
revno_1, revid_1 = self.tree.branch.last_revision_info()
625
r2 = self.tree.commit('2nd commit', rev_id='child-1')
626
# Undo the second commit
627
self.tree.branch.set_last_revision_info(revno_1, revid_1)
628
self.tree.set_parent_ids([revid_1])
629
# Make a new second commit, child-2. child-2 has diverged from
631
new_r2 = self.tree.commit('2nd commit', rev_id='child-2')
634
def test_not_allow_diverged(self):
635
"""If allow_diverged is not passed, then setting a divergent history
636
returns a Diverged error.
638
self.make_branch_with_divergent_history()
640
FailedSmartServerResponse(('Diverged',)),
641
self.set_last_revision('child-1', 2))
642
# The branch tip was not changed.
643
self.assertEqual('child-2', self.tree.branch.last_revision())
645
def test_allow_diverged(self):
646
"""If allow_diverged is passed, then setting a divergent history
649
self.make_branch_with_divergent_history()
650
branch_token, repo_token = self.lock_branch()
651
response = self.request.execute(
652
'', branch_token, repo_token, 'child-1', 1, 0)
654
SuccessfulSmartServerResponse(('ok', 2, 'child-1')),
657
# The branch tip was changed.
658
self.assertEqual('child-1', self.tree.branch.last_revision())
661
class TestSmartServerBranchRequestGetStackedOnURL(tests.TestCaseWithMemoryTransport):
663
def test_get_stacked_on_url(self):
664
base_branch = self.make_branch('base', format='1.6')
665
stacked_branch = self.make_branch('stacked', format='1.6')
666
# typically should be relative
667
stacked_branch.set_stacked_on_url('../base')
668
request = smart.branch.SmartServerBranchRequestGetStackedOnURL(
669
self.get_transport())
670
response = request.execute('stacked')
672
SmartServerResponse(('ok', '../base')),
676
class TestSmartServerBranchRequestLockWrite(tests.TestCaseWithMemoryTransport):
679
tests.TestCaseWithMemoryTransport.setUp(self)
681
def test_lock_write_on_unlocked_branch(self):
682
backing = self.get_transport()
683
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
684
branch = self.make_branch('.', format='knit')
685
repository = branch.repository
686
response = request.execute('')
687
branch_nonce = branch.control_files._lock.peek().get('nonce')
688
repository_nonce = repository.control_files._lock.peek().get('nonce')
690
SmartServerResponse(('ok', branch_nonce, repository_nonce)),
692
# The branch (and associated repository) is now locked. Verify that
693
# with a new branch object.
694
new_branch = repository.bzrdir.open_branch()
695
self.assertRaises(errors.LockContention, new_branch.lock_write)
697
def test_lock_write_on_locked_branch(self):
698
backing = self.get_transport()
699
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
700
branch = self.make_branch('.')
702
branch.leave_lock_in_place()
704
response = request.execute('')
706
SmartServerResponse(('LockContention',)), response)
708
def test_lock_write_with_tokens_on_locked_branch(self):
709
backing = self.get_transport()
710
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
711
branch = self.make_branch('.', format='knit')
712
branch_token = branch.lock_write()
713
repo_token = branch.repository.lock_write()
714
branch.repository.unlock()
715
branch.leave_lock_in_place()
716
branch.repository.leave_lock_in_place()
718
response = request.execute('',
719
branch_token, repo_token)
721
SmartServerResponse(('ok', branch_token, repo_token)), response)
723
def test_lock_write_with_mismatched_tokens_on_locked_branch(self):
724
backing = self.get_transport()
725
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
726
branch = self.make_branch('.', format='knit')
727
branch_token = branch.lock_write()
728
repo_token = branch.repository.lock_write()
729
branch.repository.unlock()
730
branch.leave_lock_in_place()
731
branch.repository.leave_lock_in_place()
733
response = request.execute('',
734
branch_token+'xxx', repo_token)
736
SmartServerResponse(('TokenMismatch',)), response)
738
def test_lock_write_on_locked_repo(self):
739
backing = self.get_transport()
740
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
741
branch = self.make_branch('.', format='knit')
742
branch.repository.lock_write()
743
branch.repository.leave_lock_in_place()
744
branch.repository.unlock()
745
response = request.execute('')
747
SmartServerResponse(('LockContention',)), response)
749
def test_lock_write_on_readonly_transport(self):
750
backing = self.get_readonly_transport()
751
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
752
branch = self.make_branch('.')
753
root = self.get_transport().clone('/')
754
path = urlutils.relative_url(root.base, self.get_transport().base)
755
response = request.execute(path)
756
error_name, lock_str, why_str = response.args
757
self.assertFalse(response.is_successful())
758
self.assertEqual('LockFailed', error_name)
761
class TestSmartServerBranchRequestUnlock(tests.TestCaseWithMemoryTransport):
764
tests.TestCaseWithMemoryTransport.setUp(self)
766
def test_unlock_on_locked_branch_and_repo(self):
767
backing = self.get_transport()
768
request = smart.branch.SmartServerBranchRequestUnlock(backing)
769
branch = self.make_branch('.', format='knit')
771
branch_token = branch.lock_write()
772
repo_token = branch.repository.lock_write()
773
branch.repository.unlock()
774
# Unlock the branch (and repo) object, leaving the physical locks
776
branch.leave_lock_in_place()
777
branch.repository.leave_lock_in_place()
779
response = request.execute('',
780
branch_token, repo_token)
782
SmartServerResponse(('ok',)), response)
783
# The branch is now unlocked. Verify that with a new branch
785
new_branch = branch.bzrdir.open_branch()
786
new_branch.lock_write()
789
def test_unlock_on_unlocked_branch_unlocked_repo(self):
790
backing = self.get_transport()
791
request = smart.branch.SmartServerBranchRequestUnlock(backing)
792
branch = self.make_branch('.', format='knit')
793
response = request.execute(
794
'', 'branch token', 'repo token')
796
SmartServerResponse(('TokenMismatch',)), response)
798
def test_unlock_on_unlocked_branch_locked_repo(self):
799
backing = self.get_transport()
800
request = smart.branch.SmartServerBranchRequestUnlock(backing)
801
branch = self.make_branch('.', format='knit')
802
# Lock the repository.
803
repo_token = branch.repository.lock_write()
804
branch.repository.leave_lock_in_place()
805
branch.repository.unlock()
806
# Issue branch lock_write request on the unlocked branch (with locked
808
response = request.execute(
809
'', 'branch token', repo_token)
811
SmartServerResponse(('TokenMismatch',)), response)
814
class TestSmartServerRepositoryRequest(tests.TestCaseWithMemoryTransport):
816
def test_no_repository(self):
817
"""Raise NoRepositoryPresent when there is a bzrdir and no repo."""
818
# we test this using a shared repository above the named path,
819
# thus checking the right search logic is used - that is, that
820
# its the exact path being looked at and the server is not
822
backing = self.get_transport()
823
request = smart.repository.SmartServerRepositoryRequest(backing)
824
self.make_repository('.', shared=True)
825
self.make_bzrdir('subdir')
826
self.assertRaises(errors.NoRepositoryPresent,
827
request.execute, 'subdir')
830
class TestSmartServerRepositoryGetParentMap(tests.TestCaseWithMemoryTransport):
832
def test_trivial_bzipped(self):
833
# This tests that the wire encoding is actually bzipped
834
backing = self.get_transport()
835
request = smart.repository.SmartServerRepositoryGetParentMap(backing)
836
tree = self.make_branch_and_memory_tree('.')
838
self.assertEqual(None,
839
request.execute('', 'missing-id'))
840
# Note that it returns a body (of '' bzipped).
842
SuccessfulSmartServerResponse(('ok', ), bz2.compress('')),
843
request.do_body('\n\n0\n'))
846
class TestSmartServerRepositoryGetRevisionGraph(tests.TestCaseWithMemoryTransport):
848
def test_none_argument(self):
849
backing = self.get_transport()
850
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
851
tree = self.make_branch_and_memory_tree('.')
854
r1 = tree.commit('1st commit')
855
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
858
# the lines of revision_id->revision_parent_list has no guaranteed
859
# order coming out of a dict, so sort both our test and response
860
lines = sorted([' '.join([r2, r1]), r1])
861
response = request.execute('', '')
862
response.body = '\n'.join(sorted(response.body.split('\n')))
865
SmartServerResponse(('ok', ), '\n'.join(lines)), response)
867
def test_specific_revision_argument(self):
868
backing = self.get_transport()
869
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
870
tree = self.make_branch_and_memory_tree('.')
873
rev_id_utf8 = u'\xc9'.encode('utf-8')
874
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
875
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
878
self.assertEqual(SmartServerResponse(('ok', ), rev_id_utf8),
879
request.execute('', rev_id_utf8))
881
def test_no_such_revision(self):
882
backing = self.get_transport()
883
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
884
tree = self.make_branch_and_memory_tree('.')
887
r1 = tree.commit('1st commit')
890
# Note that it still returns body (of zero bytes).
892
SmartServerResponse(('nosuchrevision', 'missingrevision', ), ''),
893
request.execute('', 'missingrevision'))
896
class TestSmartServerRequestHasRevision(tests.TestCaseWithMemoryTransport):
898
def test_missing_revision(self):
899
"""For a missing revision, ('no', ) is returned."""
900
backing = self.get_transport()
901
request = smart.repository.SmartServerRequestHasRevision(backing)
902
self.make_repository('.')
903
self.assertEqual(SmartServerResponse(('no', )),
904
request.execute('', 'revid'))
906
def test_present_revision(self):
907
"""For a present revision, ('yes', ) is returned."""
908
backing = self.get_transport()
909
request = smart.repository.SmartServerRequestHasRevision(backing)
910
tree = self.make_branch_and_memory_tree('.')
913
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
914
r1 = tree.commit('a commit', rev_id=rev_id_utf8)
916
self.assertTrue(tree.branch.repository.has_revision(rev_id_utf8))
917
self.assertEqual(SmartServerResponse(('yes', )),
918
request.execute('', rev_id_utf8))
921
class TestSmartServerRepositoryGatherStats(tests.TestCaseWithMemoryTransport):
923
def test_empty_revid(self):
924
"""With an empty revid, we get only size an number and revisions"""
925
backing = self.get_transport()
926
request = smart.repository.SmartServerRepositoryGatherStats(backing)
927
repository = self.make_repository('.')
928
stats = repository.gather_stats()
929
expected_body = 'revisions: 0\n'
930
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
931
request.execute('', '', 'no'))
933
def test_revid_with_committers(self):
934
"""For a revid we get more infos."""
935
backing = self.get_transport()
936
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
937
request = smart.repository.SmartServerRepositoryGatherStats(backing)
938
tree = self.make_branch_and_memory_tree('.')
941
# Let's build a predictable result
942
tree.commit('a commit', timestamp=123456.2, timezone=3600)
943
tree.commit('a commit', timestamp=654321.4, timezone=0,
947
stats = tree.branch.repository.gather_stats()
948
expected_body = ('firstrev: 123456.200 3600\n'
949
'latestrev: 654321.400 0\n'
951
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
955
def test_not_empty_repository_with_committers(self):
956
"""For a revid and requesting committers we get the whole thing."""
957
backing = self.get_transport()
958
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
959
request = smart.repository.SmartServerRepositoryGatherStats(backing)
960
tree = self.make_branch_and_memory_tree('.')
963
# Let's build a predictable result
964
tree.commit('a commit', timestamp=123456.2, timezone=3600,
966
tree.commit('a commit', timestamp=654321.4, timezone=0,
967
committer='bar', rev_id=rev_id_utf8)
969
stats = tree.branch.repository.gather_stats()
971
expected_body = ('committers: 2\n'
972
'firstrev: 123456.200 3600\n'
973
'latestrev: 654321.400 0\n'
975
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
980
class TestSmartServerRepositoryIsShared(tests.TestCaseWithMemoryTransport):
982
def test_is_shared(self):
983
"""For a shared repository, ('yes', ) is returned."""
984
backing = self.get_transport()
985
request = smart.repository.SmartServerRepositoryIsShared(backing)
986
self.make_repository('.', shared=True)
987
self.assertEqual(SmartServerResponse(('yes', )),
988
request.execute('', ))
990
def test_is_not_shared(self):
991
"""For a shared repository, ('no', ) is returned."""
992
backing = self.get_transport()
993
request = smart.repository.SmartServerRepositoryIsShared(backing)
994
self.make_repository('.', shared=False)
995
self.assertEqual(SmartServerResponse(('no', )),
996
request.execute('', ))
999
class TestSmartServerRepositoryLockWrite(tests.TestCaseWithMemoryTransport):
1002
tests.TestCaseWithMemoryTransport.setUp(self)
1004
def test_lock_write_on_unlocked_repo(self):
1005
backing = self.get_transport()
1006
request = smart.repository.SmartServerRepositoryLockWrite(backing)
1007
repository = self.make_repository('.', format='knit')
1008
response = request.execute('')
1009
nonce = repository.control_files._lock.peek().get('nonce')
1010
self.assertEqual(SmartServerResponse(('ok', nonce)), response)
1011
# The repository is now locked. Verify that with a new repository
1013
new_repo = repository.bzrdir.open_repository()
1014
self.assertRaises(errors.LockContention, new_repo.lock_write)
1016
def test_lock_write_on_locked_repo(self):
1017
backing = self.get_transport()
1018
request = smart.repository.SmartServerRepositoryLockWrite(backing)
1019
repository = self.make_repository('.', format='knit')
1020
repository.lock_write()
1021
repository.leave_lock_in_place()
1023
response = request.execute('')
1025
SmartServerResponse(('LockContention',)), response)
1027
def test_lock_write_on_readonly_transport(self):
1028
backing = self.get_readonly_transport()
1029
request = smart.repository.SmartServerRepositoryLockWrite(backing)
1030
repository = self.make_repository('.', format='knit')
1031
response = request.execute('')
1032
self.assertFalse(response.is_successful())
1033
self.assertEqual('LockFailed', response.args[0])
1036
class TestSmartServerRepositoryUnlock(tests.TestCaseWithMemoryTransport):
1039
tests.TestCaseWithMemoryTransport.setUp(self)
1041
def test_unlock_on_locked_repo(self):
1042
backing = self.get_transport()
1043
request = smart.repository.SmartServerRepositoryUnlock(backing)
1044
repository = self.make_repository('.', format='knit')
1045
token = repository.lock_write()
1046
repository.leave_lock_in_place()
1048
response = request.execute('', token)
1050
SmartServerResponse(('ok',)), response)
1051
# The repository is now unlocked. Verify that with a new repository
1053
new_repo = repository.bzrdir.open_repository()
1054
new_repo.lock_write()
1057
def test_unlock_on_unlocked_repo(self):
1058
backing = self.get_transport()
1059
request = smart.repository.SmartServerRepositoryUnlock(backing)
1060
repository = self.make_repository('.', format='knit')
1061
response = request.execute('', 'some token')
1063
SmartServerResponse(('TokenMismatch',)), response)
1066
class TestSmartServerIsReadonly(tests.TestCaseWithMemoryTransport):
1068
def test_is_readonly_no(self):
1069
backing = self.get_transport()
1070
request = smart.request.SmartServerIsReadonly(backing)
1071
response = request.execute()
1073
SmartServerResponse(('no',)), response)
1075
def test_is_readonly_yes(self):
1076
backing = self.get_readonly_transport()
1077
request = smart.request.SmartServerIsReadonly(backing)
1078
response = request.execute()
1080
SmartServerResponse(('yes',)), response)
1083
class TestSmartServerRepositorySetMakeWorkingTrees(tests.TestCaseWithMemoryTransport):
1085
def test_set_false(self):
1086
backing = self.get_transport()
1087
repo = self.make_repository('.', shared=True)
1088
repo.set_make_working_trees(True)
1089
request_class = smart.repository.SmartServerRepositorySetMakeWorkingTrees
1090
request = request_class(backing)
1091
self.assertEqual(SuccessfulSmartServerResponse(('ok',)),
1092
request.execute('', 'False'))
1093
repo = repo.bzrdir.open_repository()
1094
self.assertFalse(repo.make_working_trees())
1096
def test_set_true(self):
1097
backing = self.get_transport()
1098
repo = self.make_repository('.', shared=True)
1099
repo.set_make_working_trees(False)
1100
request_class = smart.repository.SmartServerRepositorySetMakeWorkingTrees
1101
request = request_class(backing)
1102
self.assertEqual(SuccessfulSmartServerResponse(('ok',)),
1103
request.execute('', 'True'))
1104
repo = repo.bzrdir.open_repository()
1105
self.assertTrue(repo.make_working_trees())
1108
class TestSmartServerPackRepositoryAutopack(tests.TestCaseWithTransport):
1110
def make_repo_needing_autopacking(self, path='.'):
1111
# Make a repo in need of autopacking.
1112
tree = self.make_branch_and_tree('.', format='pack-0.92')
1113
repo = tree.branch.repository
1114
# monkey-patch the pack collection to disable autopacking
1115
repo._pack_collection._max_pack_count = lambda count: count
1117
tree.commit('commit %s' % x)
1118
self.assertEqual(10, len(repo._pack_collection.names()))
1119
del repo._pack_collection._max_pack_count
1122
def test_autopack_needed(self):
1123
repo = self.make_repo_needing_autopacking()
1124
backing = self.get_transport()
1125
request = smart.packrepository.SmartServerPackRepositoryAutopack(
1127
response = request.execute('')
1128
self.assertEqual(SmartServerResponse(('ok',)), response)
1129
repo._pack_collection.reload_pack_names()
1130
self.assertEqual(1, len(repo._pack_collection.names()))
1132
def test_autopack_not_needed(self):
1133
tree = self.make_branch_and_tree('.', format='pack-0.92')
1134
repo = tree.branch.repository
1136
tree.commit('commit %s' % x)
1137
backing = self.get_transport()
1138
request = smart.packrepository.SmartServerPackRepositoryAutopack(
1140
response = request.execute('')
1141
self.assertEqual(SmartServerResponse(('ok',)), response)
1142
repo._pack_collection.reload_pack_names()
1143
self.assertEqual(9, len(repo._pack_collection.names()))
1145
def test_autopack_on_nonpack_format(self):
1146
"""A request to autopack a non-pack repo is a no-op."""
1147
repo = self.make_repository('.', format='knit')
1148
backing = self.get_transport()
1149
request = smart.packrepository.SmartServerPackRepositoryAutopack(
1151
response = request.execute('')
1152
self.assertEqual(SmartServerResponse(('ok',)), response)
1155
class TestHandlers(tests.TestCase):
1156
"""Tests for the request.request_handlers object."""
1158
def test_all_registrations_exist(self):
1159
"""All registered request_handlers can be found."""
1160
# If there's a typo in a register_lazy call, this loop will fail with
1161
# an AttributeError.
1162
for key, item in smart.request.request_handlers.iteritems():
1165
def test_registered_methods(self):
1166
"""Test that known methods are registered to the correct object."""
1168
smart.request.request_handlers.get('Branch.get_config_file'),
1169
smart.branch.SmartServerBranchGetConfigFile)
1171
smart.request.request_handlers.get('Branch.lock_write'),
1172
smart.branch.SmartServerBranchRequestLockWrite)
1174
smart.request.request_handlers.get('Branch.last_revision_info'),
1175
smart.branch.SmartServerBranchRequestLastRevisionInfo)
1177
smart.request.request_handlers.get('Branch.revision_history'),
1178
smart.branch.SmartServerRequestRevisionHistory)
1180
smart.request.request_handlers.get('Branch.set_last_revision'),
1181
smart.branch.SmartServerBranchRequestSetLastRevision)
1183
smart.request.request_handlers.get('Branch.set_last_revision_info'),
1184
smart.branch.SmartServerBranchRequestSetLastRevisionInfo)
1186
smart.request.request_handlers.get('Branch.unlock'),
1187
smart.branch.SmartServerBranchRequestUnlock)
1189
smart.request.request_handlers.get('BzrDir.find_repository'),
1190
smart.bzrdir.SmartServerRequestFindRepositoryV1)
1192
smart.request.request_handlers.get('BzrDir.find_repositoryV2'),
1193
smart.bzrdir.SmartServerRequestFindRepositoryV2)
1195
smart.request.request_handlers.get('BzrDirFormat.initialize'),
1196
smart.bzrdir.SmartServerRequestInitializeBzrDir)
1198
smart.request.request_handlers.get('BzrDir.cloning_metadir'),
1199
smart.bzrdir.SmartServerBzrDirRequestCloningMetaDir)
1201
smart.request.request_handlers.get('BzrDir.open_branch'),
1202
smart.bzrdir.SmartServerRequestOpenBranch)
1204
smart.request.request_handlers.get('PackRepository.autopack'),
1205
smart.packrepository.SmartServerPackRepositoryAutopack)
1207
smart.request.request_handlers.get('Repository.gather_stats'),
1208
smart.repository.SmartServerRepositoryGatherStats)
1210
smart.request.request_handlers.get('Repository.get_parent_map'),
1211
smart.repository.SmartServerRepositoryGetParentMap)
1213
smart.request.request_handlers.get(
1214
'Repository.get_revision_graph'),
1215
smart.repository.SmartServerRepositoryGetRevisionGraph)
1217
smart.request.request_handlers.get('Repository.has_revision'),
1218
smart.repository.SmartServerRequestHasRevision)
1220
smart.request.request_handlers.get('Repository.is_shared'),
1221
smart.repository.SmartServerRepositoryIsShared)
1223
smart.request.request_handlers.get('Repository.lock_write'),
1224
smart.repository.SmartServerRepositoryLockWrite)
1226
smart.request.request_handlers.get('Repository.get_stream'),
1227
smart.repository.SmartServerRepositoryGetStream)
1229
smart.request.request_handlers.get('Repository.tarball'),
1230
smart.repository.SmartServerRepositoryTarball)
1232
smart.request.request_handlers.get('Repository.unlock'),
1233
smart.repository.SmartServerRepositoryUnlock)
1235
smart.request.request_handlers.get('Transport.is_readonly'),
1236
smart.request.SmartServerIsReadonly)