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 BranchReferenceFormat
40
import bzrlib.smart.branch
41
import bzrlib.smart.bzrdir
42
import bzrlib.smart.repository
43
from bzrlib.smart.request import (
44
FailedSmartServerResponse,
47
SuccessfulSmartServerResponse,
49
from bzrlib.tests import (
54
from bzrlib.transport import chroot, get_transport
55
from bzrlib.util import bencode
58
def load_tests(standard_tests, module, loader):
59
"""Multiply tests version and protocol consistency."""
60
# FindRepository tests.
61
bzrdir_mod = bzrlib.smart.bzrdir
62
applier = TestScenarioApplier()
65
"_request_class":bzrdir_mod.SmartServerRequestFindRepositoryV1}),
66
("find_repositoryV2", {
67
"_request_class":bzrdir_mod.SmartServerRequestFindRepositoryV2}),
69
to_adapt, result = split_suite_by_re(standard_tests,
70
"TestSmartServerRequestFindRepository")
71
v2_only, v1_and_2 = split_suite_by_re(to_adapt,
73
for test in iter_suite_tests(v1_and_2):
74
result.addTests(applier.adapt(test))
75
del applier.scenarios[0]
76
for test in iter_suite_tests(v2_only):
77
result.addTests(applier.adapt(test))
81
class TestCaseWithChrootedTransport(tests.TestCaseWithTransport):
84
tests.TestCaseWithTransport.setUp(self)
85
self._chroot_server = None
87
def get_transport(self, relpath=None):
88
if self._chroot_server is None:
89
backing_transport = tests.TestCaseWithTransport.get_transport(self)
90
self._chroot_server = chroot.ChrootServer(backing_transport)
91
self._chroot_server.setUp()
92
self.addCleanup(self._chroot_server.tearDown)
93
t = get_transport(self._chroot_server.get_url())
94
if relpath is not None:
99
class TestCaseWithSmartMedium(tests.TestCaseWithTransport):
102
super(TestCaseWithSmartMedium, self).setUp()
103
# We're allowed to set the transport class here, so that we don't use
104
# the default or a parameterized class, but rather use the
105
# TestCaseWithTransport infrastructure to set up a smart server and
107
self.transport_server = self.make_transport_server
109
def make_transport_server(self):
110
return smart.server.SmartTCPServer_for_testing('-' + self.id())
112
def get_smart_medium(self):
113
"""Get a smart medium to use in tests."""
114
return self.get_transport().get_smart_medium()
117
class TestSmartServerResponse(tests.TestCase):
119
def test__eq__(self):
120
self.assertEqual(SmartServerResponse(('ok', )),
121
SmartServerResponse(('ok', )))
122
self.assertEqual(SmartServerResponse(('ok', ), 'body'),
123
SmartServerResponse(('ok', ), 'body'))
124
self.assertNotEqual(SmartServerResponse(('ok', )),
125
SmartServerResponse(('notok', )))
126
self.assertNotEqual(SmartServerResponse(('ok', ), 'body'),
127
SmartServerResponse(('ok', )))
128
self.assertNotEqual(None,
129
SmartServerResponse(('ok', )))
131
def test__str__(self):
132
"""SmartServerResponses can be stringified."""
134
"<SmartServerResponse status=OK args=('args',) body='body'>",
135
str(SuccessfulSmartServerResponse(('args',), 'body')))
137
"<SmartServerResponse status=ERR args=('args',) body='body'>",
138
str(FailedSmartServerResponse(('args',), 'body')))
141
class TestSmartServerRequest(tests.TestCaseWithMemoryTransport):
143
def test_translate_client_path(self):
144
transport = self.get_transport()
145
request = SmartServerRequest(transport, 'foo/')
146
self.assertEqual('./', request.translate_client_path('foo/'))
148
errors.InvalidURLJoin, request.translate_client_path, 'foo/..')
150
errors.PathNotChild, request.translate_client_path, '/')
152
errors.PathNotChild, request.translate_client_path, 'bar/')
153
self.assertEqual('./baz', request.translate_client_path('foo/baz'))
155
def test_transport_from_client_path(self):
156
transport = self.get_transport()
157
request = SmartServerRequest(transport, 'foo/')
160
request.transport_from_client_path('foo/').base)
163
class TestSmartServerRequestFindRepository(tests.TestCaseWithMemoryTransport):
164
"""Tests for BzrDir.find_repository."""
166
def test_no_repository(self):
167
"""When there is no repository to be found, ('norepository', ) is returned."""
168
backing = self.get_transport()
169
request = self._request_class(backing)
170
self.make_bzrdir('.')
171
self.assertEqual(SmartServerResponse(('norepository', )),
174
def test_nonshared_repository(self):
175
# nonshared repositorys only allow 'find' to return a handle when the
176
# path the repository is being searched on is the same as that that
177
# the repository is at.
178
backing = self.get_transport()
179
request = self._request_class(backing)
180
result = self._make_repository_and_result()
181
self.assertEqual(result, request.execute(''))
182
self.make_bzrdir('subdir')
183
self.assertEqual(SmartServerResponse(('norepository', )),
184
request.execute('subdir'))
186
def _make_repository_and_result(self, shared=False, format=None):
187
"""Convenience function to setup a repository.
189
:result: The SmartServerResponse to expect when opening it.
191
repo = self.make_repository('.', shared=shared, format=format)
192
if repo.supports_rich_root():
196
if repo._format.supports_tree_reference:
200
if (smart.bzrdir.SmartServerRequestFindRepositoryV2 ==
201
self._request_class):
202
# All tests so far are on formats, and for non-external
204
return SuccessfulSmartServerResponse(
205
('ok', '', rich_root, subtrees, 'no'))
207
return SuccessfulSmartServerResponse(('ok', '', rich_root, subtrees))
209
def test_shared_repository(self):
210
"""When there is a shared repository, we get 'ok', 'relpath-to-repo'."""
211
backing = self.get_transport()
212
request = self._request_class(backing)
213
result = self._make_repository_and_result(shared=True)
214
self.assertEqual(result, request.execute(''))
215
self.make_bzrdir('subdir')
216
result2 = SmartServerResponse(result.args[0:1] + ('..', ) + result.args[2:])
217
self.assertEqual(result2,
218
request.execute('subdir'))
219
self.make_bzrdir('subdir/deeper')
220
result3 = SmartServerResponse(result.args[0:1] + ('../..', ) + result.args[2:])
221
self.assertEqual(result3,
222
request.execute('subdir/deeper'))
224
def test_rich_root_and_subtree_encoding(self):
225
"""Test for the format attributes for rich root and subtree support."""
226
backing = self.get_transport()
227
request = self._request_class(backing)
228
result = self._make_repository_and_result(format='dirstate-with-subtree')
229
# check the test will be valid
230
self.assertEqual('yes', result.args[2])
231
self.assertEqual('yes', result.args[3])
232
self.assertEqual(result, request.execute(''))
234
def test_supports_external_lookups_no_v2(self):
235
"""Test for the supports_external_lookups attribute."""
236
backing = self.get_transport()
237
request = self._request_class(backing)
238
result = self._make_repository_and_result(format='dirstate-with-subtree')
239
# check the test will be valid
240
self.assertEqual('no', result.args[4])
241
self.assertEqual(result, request.execute(''))
244
class TestSmartServerRequestInitializeBzrDir(tests.TestCaseWithMemoryTransport):
246
def test_empty_dir(self):
247
"""Initializing an empty dir should succeed and do it."""
248
backing = self.get_transport()
249
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
250
self.assertEqual(SmartServerResponse(('ok', )),
252
made_dir = bzrdir.BzrDir.open_from_transport(backing)
253
# no branch, tree or repository is expected with the current
255
self.assertRaises(errors.NoWorkingTree, made_dir.open_workingtree)
256
self.assertRaises(errors.NotBranchError, made_dir.open_branch)
257
self.assertRaises(errors.NoRepositoryPresent, made_dir.open_repository)
259
def test_missing_dir(self):
260
"""Initializing a missing directory should fail like the bzrdir api."""
261
backing = self.get_transport()
262
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
263
self.assertRaises(errors.NoSuchFile,
264
request.execute, 'subdir')
266
def test_initialized_dir(self):
267
"""Initializing an extant bzrdir should fail like the bzrdir api."""
268
backing = self.get_transport()
269
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
270
self.make_bzrdir('subdir')
271
self.assertRaises(errors.FileExists,
272
request.execute, 'subdir')
275
class TestSmartServerRequestOpenBranch(TestCaseWithChrootedTransport):
277
def test_no_branch(self):
278
"""When there is no branch, ('nobranch', ) is returned."""
279
backing = self.get_transport()
280
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
281
self.make_bzrdir('.')
282
self.assertEqual(SmartServerResponse(('nobranch', )),
285
def test_branch(self):
286
"""When there is a branch, 'ok' is returned."""
287
backing = self.get_transport()
288
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
289
self.make_branch('.')
290
self.assertEqual(SmartServerResponse(('ok', '')),
293
def test_branch_reference(self):
294
"""When there is a branch reference, the reference URL is returned."""
295
backing = self.get_transport()
296
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
297
branch = self.make_branch('branch')
298
checkout = branch.create_checkout('reference',lightweight=True)
299
reference_url = BranchReferenceFormat().get_reference(checkout.bzrdir)
300
self.assertFileEqual(reference_url, 'reference/.bzr/branch/location')
301
self.assertEqual(SmartServerResponse(('ok', reference_url)),
302
request.execute('reference'))
305
class TestSmartServerRequestRevisionHistory(tests.TestCaseWithMemoryTransport):
307
def test_empty(self):
308
"""For an empty branch, the body is empty."""
309
backing = self.get_transport()
310
request = smart.branch.SmartServerRequestRevisionHistory(backing)
311
self.make_branch('.')
312
self.assertEqual(SmartServerResponse(('ok', ), ''),
315
def test_not_empty(self):
316
"""For a non-empty branch, the body is empty."""
317
backing = self.get_transport()
318
request = smart.branch.SmartServerRequestRevisionHistory(backing)
319
tree = self.make_branch_and_memory_tree('.')
322
r1 = tree.commit('1st commit')
323
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
326
SmartServerResponse(('ok', ), ('\x00'.join([r1, r2]))),
330
class TestSmartServerBranchRequest(tests.TestCaseWithMemoryTransport):
332
def test_no_branch(self):
333
"""When there is a bzrdir and no branch, NotBranchError is raised."""
334
backing = self.get_transport()
335
request = smart.branch.SmartServerBranchRequest(backing)
336
self.make_bzrdir('.')
337
self.assertRaises(errors.NotBranchError,
340
def test_branch_reference(self):
341
"""When there is a branch reference, NotBranchError is raised."""
342
backing = self.get_transport()
343
request = smart.branch.SmartServerBranchRequest(backing)
344
branch = self.make_branch('branch')
345
checkout = branch.create_checkout('reference',lightweight=True)
346
self.assertRaises(errors.NotBranchError,
347
request.execute, 'checkout')
350
class TestSmartServerBranchRequestLastRevisionInfo(tests.TestCaseWithMemoryTransport):
352
def test_empty(self):
353
"""For an empty branch, the result is ('ok', '0', 'null:')."""
354
backing = self.get_transport()
355
request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
356
self.make_branch('.')
357
self.assertEqual(SmartServerResponse(('ok', '0', 'null:')),
360
def test_not_empty(self):
361
"""For a non-empty branch, the result is ('ok', 'revno', 'revid')."""
362
backing = self.get_transport()
363
request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
364
tree = self.make_branch_and_memory_tree('.')
367
rev_id_utf8 = u'\xc8'.encode('utf-8')
368
r1 = tree.commit('1st commit')
369
r2 = tree.commit('2nd commit', rev_id=rev_id_utf8)
372
SmartServerResponse(('ok', '2', rev_id_utf8)),
376
class TestSmartServerBranchRequestGetConfigFile(tests.TestCaseWithMemoryTransport):
378
def test_default(self):
379
"""With no file, we get empty content."""
380
backing = self.get_transport()
381
request = smart.branch.SmartServerBranchGetConfigFile(backing)
382
branch = self.make_branch('.')
383
# there should be no file by default
385
self.assertEqual(SmartServerResponse(('ok', ), content),
388
def test_with_content(self):
389
# SmartServerBranchGetConfigFile should return the content from
390
# branch.control_files.get('branch.conf') for now - in the future it may
391
# perform more complex processing.
392
backing = self.get_transport()
393
request = smart.branch.SmartServerBranchGetConfigFile(backing)
394
branch = self.make_branch('.')
395
branch._transport.put_bytes('branch.conf', 'foo bar baz')
396
self.assertEqual(SmartServerResponse(('ok', ), 'foo bar baz'),
400
class SetLastRevisionTestBase(tests.TestCaseWithMemoryTransport):
401
"""Base test case for verbs that implement set_last_revision."""
404
tests.TestCaseWithMemoryTransport.setUp(self)
405
backing_transport = self.get_transport()
406
self.request = self.request_class(backing_transport)
407
self.tree = self.make_branch_and_memory_tree('.')
409
def lock_branch(self):
411
branch_token = b.lock_write()
412
repo_token = b.repository.lock_write()
413
b.repository.unlock()
414
return branch_token, repo_token
416
def unlock_branch(self):
417
self.tree.branch.unlock()
419
def set_last_revision(self, revision_id, revno):
420
branch_token, repo_token = self.lock_branch()
421
response = self._set_last_revision(
422
revision_id, revno, branch_token, repo_token)
426
def assertRequestSucceeds(self, revision_id, revno):
427
response = self.set_last_revision(revision_id, revno)
428
self.assertEqual(SuccessfulSmartServerResponse(('ok',)), response)
431
class TestSetLastRevisionVerbMixin(object):
432
"""Mixin test case for verbs that implement set_last_revision."""
434
def test_set_null_to_null(self):
435
"""An empty branch can have its last revision set to 'null:'."""
436
self.assertRequestSucceeds('null:', 0)
438
def test_NoSuchRevision(self):
439
"""If the revision_id is not present, the verb returns NoSuchRevision.
441
revision_id = 'non-existent revision'
443
FailedSmartServerResponse(('NoSuchRevision', revision_id)),
444
self.set_last_revision(revision_id, 1))
446
def make_tree_with_two_commits(self):
447
self.tree.lock_write()
449
rev_id_utf8 = u'\xc8'.encode('utf-8')
450
r1 = self.tree.commit('1st commit', rev_id=rev_id_utf8)
451
r2 = self.tree.commit('2nd commit', rev_id='rev-2')
454
def test_branch_last_revision_info_is_updated(self):
455
"""A branch's tip can be set to a revision that is present in its
458
# Make a branch with an empty revision history, but two revisions in
460
self.make_tree_with_two_commits()
461
rev_id_utf8 = u'\xc8'.encode('utf-8')
462
self.tree.branch.set_revision_history([])
464
(0, 'null:'), self.tree.branch.last_revision_info())
465
# We can update the branch to a revision that is present in the
467
self.assertRequestSucceeds(rev_id_utf8, 1)
469
(1, rev_id_utf8), self.tree.branch.last_revision_info())
471
def test_branch_last_revision_info_rewind(self):
472
"""A branch's tip can be set to a revision that is an ancestor of the
475
self.make_tree_with_two_commits()
476
rev_id_utf8 = u'\xc8'.encode('utf-8')
478
(2, 'rev-2'), self.tree.branch.last_revision_info())
479
self.assertRequestSucceeds(rev_id_utf8, 1)
481
(1, rev_id_utf8), self.tree.branch.last_revision_info())
484
class TestSmartServerBranchRequestSetLastRevision(
485
SetLastRevisionTestBase, TestSetLastRevisionVerbMixin):
486
"""Tests for Branch.set_last_revision verb."""
488
request_class = smart.branch.SmartServerBranchRequestSetLastRevision
490
def _set_last_revision(self, revision_id, revno, branch_token, repo_token):
491
return self.request.execute(
492
'', branch_token, repo_token, revision_id)
495
class TestSmartServerBranchRequestSetLastRevisionInfo(
496
SetLastRevisionTestBase, TestSetLastRevisionVerbMixin):
497
"""Tests for Branch.set_last_revision_info verb."""
499
request_class = smart.branch.SmartServerBranchRequestSetLastRevisionInfo
501
def _set_last_revision(self, revision_id, revno, branch_token, repo_token):
502
return self.request.execute(
503
'', branch_token, repo_token, revno, revision_id)
505
def test_NoSuchRevision(self):
506
"""Branch.set_last_revision_info does not have to return
507
NoSuchRevision if the revision_id is absent.
509
raise tests.TestNotApplicable()
512
class TestSmartServerBranchRequestSetLastRevisionEx(
513
SetLastRevisionTestBase, TestSetLastRevisionVerbMixin):
514
"""Tests for Branch.set_last_revision_ex verb."""
516
request_class = smart.branch.SmartServerBranchRequestSetLastRevisionEx
518
def _set_last_revision(self, revision_id, revno, branch_token, repo_token):
519
return self.request.execute(
520
'', branch_token, repo_token, revision_id, 0, 0)
522
def assertRequestSucceeds(self, revision_id, revno):
523
response = self.set_last_revision(revision_id, revno)
525
SuccessfulSmartServerResponse(('ok', revno, revision_id)),
528
def test_branch_last_revision_info_rewind(self):
529
"""A branch's tip can be set to a revision that is an ancestor of the
530
current tip, but only if allow_overwrite_descendant is passed.
532
self.make_tree_with_two_commits()
533
rev_id_utf8 = u'\xc8'.encode('utf-8')
535
(2, 'rev-2'), self.tree.branch.last_revision_info())
536
# If allow_overwrite_descendant flag is 0, then trying to set the tip
537
# to an older revision ID has no effect.
538
branch_token, repo_token = self.lock_branch()
539
response = self.request.execute(
540
'', branch_token, repo_token, rev_id_utf8, 0, 0)
542
SuccessfulSmartServerResponse(('ok', 2, 'rev-2')),
545
(2, 'rev-2'), self.tree.branch.last_revision_info())
547
# If allow_overwrite_descendant flag is 1, then setting the tip to an
549
response = self.request.execute(
550
'', branch_token, repo_token, rev_id_utf8, 0, 1)
552
SuccessfulSmartServerResponse(('ok', 1, rev_id_utf8)),
556
(1, rev_id_utf8), self.tree.branch.last_revision_info())
558
def make_branch_with_divergent_history(self):
559
"""Make a branch with divergent history in its repo.
561
The branch's tip will be 'child-2', and the repo will also contain
562
'child-1', which diverges from a common base revision.
564
self.tree.lock_write()
566
r1 = self.tree.commit('1st commit')
567
revno_1, revid_1 = self.tree.branch.last_revision_info()
568
r2 = self.tree.commit('2nd commit', rev_id='child-1')
569
# Undo the second commit
570
self.tree.branch.set_last_revision_info(revno_1, revid_1)
571
self.tree.set_parent_ids([revid_1])
572
# Make a new second commit, child-2. child-2 has diverged from
574
new_r2 = self.tree.commit('2nd commit', rev_id='child-2')
577
def test_not_allow_diverged(self):
578
"""If allow_diverged is not passed, then setting a divergent history
579
returns a Diverged error.
581
self.make_branch_with_divergent_history()
583
FailedSmartServerResponse(('Diverged',)),
584
self.set_last_revision('child-1', 2))
585
# The branch tip was not changed.
586
self.assertEqual('child-2', self.tree.branch.last_revision())
588
def test_allow_diverged(self):
589
"""If allow_diverged is passed, then setting a divergent history
592
self.make_branch_with_divergent_history()
593
branch_token, repo_token = self.lock_branch()
594
response = self.request.execute(
595
'', branch_token, repo_token, 'child-1', 1, 0)
597
SuccessfulSmartServerResponse(('ok', 2, 'child-1')),
600
# The branch tip was changed.
601
self.assertEqual('child-1', self.tree.branch.last_revision())
604
class TestSmartServerBranchRequestLockWrite(tests.TestCaseWithMemoryTransport):
607
tests.TestCaseWithMemoryTransport.setUp(self)
609
def test_lock_write_on_unlocked_branch(self):
610
backing = self.get_transport()
611
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
612
branch = self.make_branch('.', format='knit')
613
repository = branch.repository
614
response = request.execute('')
615
branch_nonce = branch.control_files._lock.peek().get('nonce')
616
repository_nonce = repository.control_files._lock.peek().get('nonce')
618
SmartServerResponse(('ok', branch_nonce, repository_nonce)),
620
# The branch (and associated repository) is now locked. Verify that
621
# with a new branch object.
622
new_branch = repository.bzrdir.open_branch()
623
self.assertRaises(errors.LockContention, new_branch.lock_write)
625
def test_lock_write_on_locked_branch(self):
626
backing = self.get_transport()
627
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
628
branch = self.make_branch('.')
630
branch.leave_lock_in_place()
632
response = request.execute('')
634
SmartServerResponse(('LockContention',)), response)
636
def test_lock_write_with_tokens_on_locked_branch(self):
637
backing = self.get_transport()
638
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
639
branch = self.make_branch('.', format='knit')
640
branch_token = branch.lock_write()
641
repo_token = branch.repository.lock_write()
642
branch.repository.unlock()
643
branch.leave_lock_in_place()
644
branch.repository.leave_lock_in_place()
646
response = request.execute('',
647
branch_token, repo_token)
649
SmartServerResponse(('ok', branch_token, repo_token)), response)
651
def test_lock_write_with_mismatched_tokens_on_locked_branch(self):
652
backing = self.get_transport()
653
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
654
branch = self.make_branch('.', format='knit')
655
branch_token = branch.lock_write()
656
repo_token = branch.repository.lock_write()
657
branch.repository.unlock()
658
branch.leave_lock_in_place()
659
branch.repository.leave_lock_in_place()
661
response = request.execute('',
662
branch_token+'xxx', repo_token)
664
SmartServerResponse(('TokenMismatch',)), response)
666
def test_lock_write_on_locked_repo(self):
667
backing = self.get_transport()
668
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
669
branch = self.make_branch('.', format='knit')
670
branch.repository.lock_write()
671
branch.repository.leave_lock_in_place()
672
branch.repository.unlock()
673
response = request.execute('')
675
SmartServerResponse(('LockContention',)), response)
677
def test_lock_write_on_readonly_transport(self):
678
backing = self.get_readonly_transport()
679
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
680
branch = self.make_branch('.')
681
root = self.get_transport().clone('/')
682
path = urlutils.relative_url(root.base, self.get_transport().base)
683
response = request.execute(path)
684
error_name, lock_str, why_str = response.args
685
self.assertFalse(response.is_successful())
686
self.assertEqual('LockFailed', error_name)
689
class TestSmartServerBranchRequestUnlock(tests.TestCaseWithMemoryTransport):
692
tests.TestCaseWithMemoryTransport.setUp(self)
694
def test_unlock_on_locked_branch_and_repo(self):
695
backing = self.get_transport()
696
request = smart.branch.SmartServerBranchRequestUnlock(backing)
697
branch = self.make_branch('.', format='knit')
699
branch_token = branch.lock_write()
700
repo_token = branch.repository.lock_write()
701
branch.repository.unlock()
702
# Unlock the branch (and repo) object, leaving the physical locks
704
branch.leave_lock_in_place()
705
branch.repository.leave_lock_in_place()
707
response = request.execute('',
708
branch_token, repo_token)
710
SmartServerResponse(('ok',)), response)
711
# The branch is now unlocked. Verify that with a new branch
713
new_branch = branch.bzrdir.open_branch()
714
new_branch.lock_write()
717
def test_unlock_on_unlocked_branch_unlocked_repo(self):
718
backing = self.get_transport()
719
request = smart.branch.SmartServerBranchRequestUnlock(backing)
720
branch = self.make_branch('.', format='knit')
721
response = request.execute(
722
'', 'branch token', 'repo token')
724
SmartServerResponse(('TokenMismatch',)), response)
726
def test_unlock_on_unlocked_branch_locked_repo(self):
727
backing = self.get_transport()
728
request = smart.branch.SmartServerBranchRequestUnlock(backing)
729
branch = self.make_branch('.', format='knit')
730
# Lock the repository.
731
repo_token = branch.repository.lock_write()
732
branch.repository.leave_lock_in_place()
733
branch.repository.unlock()
734
# Issue branch lock_write request on the unlocked branch (with locked
736
response = request.execute(
737
'', 'branch token', repo_token)
739
SmartServerResponse(('TokenMismatch',)), response)
742
class TestSmartServerRepositoryRequest(tests.TestCaseWithMemoryTransport):
744
def test_no_repository(self):
745
"""Raise NoRepositoryPresent when there is a bzrdir and no repo."""
746
# we test this using a shared repository above the named path,
747
# thus checking the right search logic is used - that is, that
748
# its the exact path being looked at and the server is not
750
backing = self.get_transport()
751
request = smart.repository.SmartServerRepositoryRequest(backing)
752
self.make_repository('.', shared=True)
753
self.make_bzrdir('subdir')
754
self.assertRaises(errors.NoRepositoryPresent,
755
request.execute, 'subdir')
758
class TestSmartServerRepositoryGetParentMap(tests.TestCaseWithMemoryTransport):
760
def test_trivial_bzipped(self):
761
# This tests that the wire encoding is actually bzipped
762
backing = self.get_transport()
763
request = smart.repository.SmartServerRepositoryGetParentMap(backing)
764
tree = self.make_branch_and_memory_tree('.')
766
self.assertEqual(None,
767
request.execute('', 'missing-id'))
768
# Note that it returns a body (of '' bzipped).
770
SuccessfulSmartServerResponse(('ok', ), bz2.compress('')),
771
request.do_body('\n\n0\n'))
774
class TestSmartServerRepositoryGetRevisionGraph(tests.TestCaseWithMemoryTransport):
776
def test_none_argument(self):
777
backing = self.get_transport()
778
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
779
tree = self.make_branch_and_memory_tree('.')
782
r1 = tree.commit('1st commit')
783
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
786
# the lines of revision_id->revision_parent_list has no guaranteed
787
# order coming out of a dict, so sort both our test and response
788
lines = sorted([' '.join([r2, r1]), r1])
789
response = request.execute('', '')
790
response.body = '\n'.join(sorted(response.body.split('\n')))
793
SmartServerResponse(('ok', ), '\n'.join(lines)), response)
795
def test_specific_revision_argument(self):
796
backing = self.get_transport()
797
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
798
tree = self.make_branch_and_memory_tree('.')
801
rev_id_utf8 = u'\xc9'.encode('utf-8')
802
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
803
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
806
self.assertEqual(SmartServerResponse(('ok', ), rev_id_utf8),
807
request.execute('', rev_id_utf8))
809
def test_no_such_revision(self):
810
backing = self.get_transport()
811
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
812
tree = self.make_branch_and_memory_tree('.')
815
r1 = tree.commit('1st commit')
818
# Note that it still returns body (of zero bytes).
820
SmartServerResponse(('nosuchrevision', 'missingrevision', ), ''),
821
request.execute('', 'missingrevision'))
824
class TestSmartServerRequestHasRevision(tests.TestCaseWithMemoryTransport):
826
def test_missing_revision(self):
827
"""For a missing revision, ('no', ) is returned."""
828
backing = self.get_transport()
829
request = smart.repository.SmartServerRequestHasRevision(backing)
830
self.make_repository('.')
831
self.assertEqual(SmartServerResponse(('no', )),
832
request.execute('', 'revid'))
834
def test_present_revision(self):
835
"""For a present revision, ('yes', ) is returned."""
836
backing = self.get_transport()
837
request = smart.repository.SmartServerRequestHasRevision(backing)
838
tree = self.make_branch_and_memory_tree('.')
841
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
842
r1 = tree.commit('a commit', rev_id=rev_id_utf8)
844
self.assertTrue(tree.branch.repository.has_revision(rev_id_utf8))
845
self.assertEqual(SmartServerResponse(('yes', )),
846
request.execute('', rev_id_utf8))
849
class TestSmartServerRepositoryGatherStats(tests.TestCaseWithMemoryTransport):
851
def test_empty_revid(self):
852
"""With an empty revid, we get only size an number and revisions"""
853
backing = self.get_transport()
854
request = smart.repository.SmartServerRepositoryGatherStats(backing)
855
repository = self.make_repository('.')
856
stats = repository.gather_stats()
857
expected_body = 'revisions: 0\n'
858
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
859
request.execute('', '', 'no'))
861
def test_revid_with_committers(self):
862
"""For a revid we get more infos."""
863
backing = self.get_transport()
864
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
865
request = smart.repository.SmartServerRepositoryGatherStats(backing)
866
tree = self.make_branch_and_memory_tree('.')
869
# Let's build a predictable result
870
tree.commit('a commit', timestamp=123456.2, timezone=3600)
871
tree.commit('a commit', timestamp=654321.4, timezone=0,
875
stats = tree.branch.repository.gather_stats()
876
expected_body = ('firstrev: 123456.200 3600\n'
877
'latestrev: 654321.400 0\n'
879
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
883
def test_not_empty_repository_with_committers(self):
884
"""For a revid and requesting committers we get the whole thing."""
885
backing = self.get_transport()
886
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
887
request = smart.repository.SmartServerRepositoryGatherStats(backing)
888
tree = self.make_branch_and_memory_tree('.')
891
# Let's build a predictable result
892
tree.commit('a commit', timestamp=123456.2, timezone=3600,
894
tree.commit('a commit', timestamp=654321.4, timezone=0,
895
committer='bar', rev_id=rev_id_utf8)
897
stats = tree.branch.repository.gather_stats()
899
expected_body = ('committers: 2\n'
900
'firstrev: 123456.200 3600\n'
901
'latestrev: 654321.400 0\n'
903
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
908
class TestSmartServerRepositoryIsShared(tests.TestCaseWithMemoryTransport):
910
def test_is_shared(self):
911
"""For a shared repository, ('yes', ) is returned."""
912
backing = self.get_transport()
913
request = smart.repository.SmartServerRepositoryIsShared(backing)
914
self.make_repository('.', shared=True)
915
self.assertEqual(SmartServerResponse(('yes', )),
916
request.execute('', ))
918
def test_is_not_shared(self):
919
"""For a shared repository, ('no', ) is returned."""
920
backing = self.get_transport()
921
request = smart.repository.SmartServerRepositoryIsShared(backing)
922
self.make_repository('.', shared=False)
923
self.assertEqual(SmartServerResponse(('no', )),
924
request.execute('', ))
927
class TestSmartServerRepositoryLockWrite(tests.TestCaseWithMemoryTransport):
930
tests.TestCaseWithMemoryTransport.setUp(self)
932
def test_lock_write_on_unlocked_repo(self):
933
backing = self.get_transport()
934
request = smart.repository.SmartServerRepositoryLockWrite(backing)
935
repository = self.make_repository('.', format='knit')
936
response = request.execute('')
937
nonce = repository.control_files._lock.peek().get('nonce')
938
self.assertEqual(SmartServerResponse(('ok', nonce)), response)
939
# The repository is now locked. Verify that with a new repository
941
new_repo = repository.bzrdir.open_repository()
942
self.assertRaises(errors.LockContention, new_repo.lock_write)
944
def test_lock_write_on_locked_repo(self):
945
backing = self.get_transport()
946
request = smart.repository.SmartServerRepositoryLockWrite(backing)
947
repository = self.make_repository('.', format='knit')
948
repository.lock_write()
949
repository.leave_lock_in_place()
951
response = request.execute('')
953
SmartServerResponse(('LockContention',)), response)
955
def test_lock_write_on_readonly_transport(self):
956
backing = self.get_readonly_transport()
957
request = smart.repository.SmartServerRepositoryLockWrite(backing)
958
repository = self.make_repository('.', format='knit')
959
response = request.execute('')
960
self.assertFalse(response.is_successful())
961
self.assertEqual('LockFailed', response.args[0])
964
class TestSmartServerRepositoryUnlock(tests.TestCaseWithMemoryTransport):
967
tests.TestCaseWithMemoryTransport.setUp(self)
969
def test_unlock_on_locked_repo(self):
970
backing = self.get_transport()
971
request = smart.repository.SmartServerRepositoryUnlock(backing)
972
repository = self.make_repository('.', format='knit')
973
token = repository.lock_write()
974
repository.leave_lock_in_place()
976
response = request.execute('', token)
978
SmartServerResponse(('ok',)), response)
979
# The repository is now unlocked. Verify that with a new repository
981
new_repo = repository.bzrdir.open_repository()
982
new_repo.lock_write()
985
def test_unlock_on_unlocked_repo(self):
986
backing = self.get_transport()
987
request = smart.repository.SmartServerRepositoryUnlock(backing)
988
repository = self.make_repository('.', format='knit')
989
response = request.execute('', 'some token')
991
SmartServerResponse(('TokenMismatch',)), response)
994
class TestSmartServerIsReadonly(tests.TestCaseWithMemoryTransport):
996
def test_is_readonly_no(self):
997
backing = self.get_transport()
998
request = smart.request.SmartServerIsReadonly(backing)
999
response = request.execute()
1001
SmartServerResponse(('no',)), response)
1003
def test_is_readonly_yes(self):
1004
backing = self.get_readonly_transport()
1005
request = smart.request.SmartServerIsReadonly(backing)
1006
response = request.execute()
1008
SmartServerResponse(('yes',)), response)
1011
class TestHandlers(tests.TestCase):
1012
"""Tests for the request.request_handlers object."""
1014
def test_registered_methods(self):
1015
"""Test that known methods are registered to the correct object."""
1017
smart.request.request_handlers.get('Branch.get_config_file'),
1018
smart.branch.SmartServerBranchGetConfigFile)
1020
smart.request.request_handlers.get('Branch.lock_write'),
1021
smart.branch.SmartServerBranchRequestLockWrite)
1023
smart.request.request_handlers.get('Branch.last_revision_info'),
1024
smart.branch.SmartServerBranchRequestLastRevisionInfo)
1026
smart.request.request_handlers.get('Branch.revision_history'),
1027
smart.branch.SmartServerRequestRevisionHistory)
1029
smart.request.request_handlers.get('Branch.set_last_revision'),
1030
smart.branch.SmartServerBranchRequestSetLastRevision)
1032
smart.request.request_handlers.get('Branch.set_last_revision_info'),
1033
smart.branch.SmartServerBranchRequestSetLastRevisionInfo)
1035
smart.request.request_handlers.get('Branch.unlock'),
1036
smart.branch.SmartServerBranchRequestUnlock)
1038
smart.request.request_handlers.get('BzrDir.find_repository'),
1039
smart.bzrdir.SmartServerRequestFindRepositoryV1)
1041
smart.request.request_handlers.get('BzrDir.find_repositoryV2'),
1042
smart.bzrdir.SmartServerRequestFindRepositoryV2)
1044
smart.request.request_handlers.get('BzrDirFormat.initialize'),
1045
smart.bzrdir.SmartServerRequestInitializeBzrDir)
1047
smart.request.request_handlers.get('BzrDir.open_branch'),
1048
smart.bzrdir.SmartServerRequestOpenBranch)
1050
smart.request.request_handlers.get('Repository.gather_stats'),
1051
smart.repository.SmartServerRepositoryGatherStats)
1053
smart.request.request_handlers.get('Repository.get_parent_map'),
1054
smart.repository.SmartServerRepositoryGetParentMap)
1056
smart.request.request_handlers.get(
1057
'Repository.get_revision_graph'),
1058
smart.repository.SmartServerRepositoryGetRevisionGraph)
1060
smart.request.request_handlers.get('Repository.has_revision'),
1061
smart.repository.SmartServerRequestHasRevision)
1063
smart.request.request_handlers.get('Repository.is_shared'),
1064
smart.repository.SmartServerRepositoryIsShared)
1066
smart.request.request_handlers.get('Repository.lock_write'),
1067
smart.repository.SmartServerRepositoryLockWrite)
1069
smart.request.request_handlers.get('Repository.tarball'),
1070
smart.repository.SmartServerRepositoryTarball)
1072
smart.request.request_handlers.get('Repository.unlock'),
1073
smart.repository.SmartServerRepositoryUnlock)
1075
smart.request.request_handlers.get('Transport.is_readonly'),
1076
smart.request.SmartServerIsReadonly)