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 = smart.server.SmartTCPServer_for_testing
109
def get_smart_medium(self):
110
"""Get a smart medium to use in tests."""
111
return self.get_transport().get_smart_medium()
114
class TestSmartServerResponse(tests.TestCase):
116
def test__eq__(self):
117
self.assertEqual(SmartServerResponse(('ok', )),
118
SmartServerResponse(('ok', )))
119
self.assertEqual(SmartServerResponse(('ok', ), 'body'),
120
SmartServerResponse(('ok', ), 'body'))
121
self.assertNotEqual(SmartServerResponse(('ok', )),
122
SmartServerResponse(('notok', )))
123
self.assertNotEqual(SmartServerResponse(('ok', ), 'body'),
124
SmartServerResponse(('ok', )))
125
self.assertNotEqual(None,
126
SmartServerResponse(('ok', )))
128
def test__str__(self):
129
"""SmartServerResponses can be stringified."""
131
"<SmartServerResponse status=OK args=('args',) body='body'>",
132
str(SuccessfulSmartServerResponse(('args',), 'body')))
134
"<SmartServerResponse status=ERR args=('args',) body='body'>",
135
str(FailedSmartServerResponse(('args',), 'body')))
138
class TestSmartServerRequest(tests.TestCaseWithMemoryTransport):
140
def test_translate_client_path(self):
141
transport = self.get_transport()
142
request = SmartServerRequest(transport, 'foo/')
143
self.assertEqual('./', request.translate_client_path('foo/'))
145
errors.InvalidURLJoin, request.translate_client_path, 'foo/..')
147
errors.PathNotChild, request.translate_client_path, '/')
149
errors.PathNotChild, request.translate_client_path, 'bar/')
150
self.assertEqual('./baz', request.translate_client_path('foo/baz'))
152
def test_transport_from_client_path(self):
153
transport = self.get_transport()
154
request = SmartServerRequest(transport, 'foo/')
157
request.transport_from_client_path('foo/').base)
160
class TestSmartServerRequestFindRepository(tests.TestCaseWithMemoryTransport):
161
"""Tests for BzrDir.find_repository."""
163
def test_no_repository(self):
164
"""When there is no repository to be found, ('norepository', ) is returned."""
165
backing = self.get_transport()
166
request = self._request_class(backing)
167
self.make_bzrdir('.')
168
self.assertEqual(SmartServerResponse(('norepository', )),
171
def test_nonshared_repository(self):
172
# nonshared repositorys only allow 'find' to return a handle when the
173
# path the repository is being searched on is the same as that that
174
# the repository is at.
175
backing = self.get_transport()
176
request = self._request_class(backing)
177
result = self._make_repository_and_result()
178
self.assertEqual(result, request.execute(''))
179
self.make_bzrdir('subdir')
180
self.assertEqual(SmartServerResponse(('norepository', )),
181
request.execute('subdir'))
183
def _make_repository_and_result(self, shared=False, format=None):
184
"""Convenience function to setup a repository.
186
:result: The SmartServerResponse to expect when opening it.
188
repo = self.make_repository('.', shared=shared, format=format)
189
if repo.supports_rich_root():
193
if repo._format.supports_tree_reference:
197
if (smart.bzrdir.SmartServerRequestFindRepositoryV2 ==
198
self._request_class):
199
# All tests so far are on formats, and for non-external
201
return SuccessfulSmartServerResponse(
202
('ok', '', rich_root, subtrees, 'no'))
204
return SuccessfulSmartServerResponse(('ok', '', rich_root, subtrees))
206
def test_shared_repository(self):
207
"""When there is a shared repository, we get 'ok', 'relpath-to-repo'."""
208
backing = self.get_transport()
209
request = self._request_class(backing)
210
result = self._make_repository_and_result(shared=True)
211
self.assertEqual(result, request.execute(''))
212
self.make_bzrdir('subdir')
213
result2 = SmartServerResponse(result.args[0:1] + ('..', ) + result.args[2:])
214
self.assertEqual(result2,
215
request.execute('subdir'))
216
self.make_bzrdir('subdir/deeper')
217
result3 = SmartServerResponse(result.args[0:1] + ('../..', ) + result.args[2:])
218
self.assertEqual(result3,
219
request.execute('subdir/deeper'))
221
def test_rich_root_and_subtree_encoding(self):
222
"""Test for the format attributes for rich root and subtree support."""
223
backing = self.get_transport()
224
request = self._request_class(backing)
225
result = self._make_repository_and_result(format='dirstate-with-subtree')
226
# check the test will be valid
227
self.assertEqual('yes', result.args[2])
228
self.assertEqual('yes', result.args[3])
229
self.assertEqual(result, request.execute(''))
231
def test_supports_external_lookups_no_v2(self):
232
"""Test for the supports_external_lookups attribute."""
233
backing = self.get_transport()
234
request = self._request_class(backing)
235
result = self._make_repository_and_result(format='dirstate-with-subtree')
236
# check the test will be valid
237
self.assertEqual('no', result.args[4])
238
self.assertEqual(result, request.execute(''))
241
class TestSmartServerRequestInitializeBzrDir(tests.TestCaseWithMemoryTransport):
243
def test_empty_dir(self):
244
"""Initializing an empty dir should succeed and do it."""
245
backing = self.get_transport()
246
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
247
self.assertEqual(SmartServerResponse(('ok', )),
249
made_dir = bzrdir.BzrDir.open_from_transport(backing)
250
# no branch, tree or repository is expected with the current
252
self.assertRaises(errors.NoWorkingTree, made_dir.open_workingtree)
253
self.assertRaises(errors.NotBranchError, made_dir.open_branch)
254
self.assertRaises(errors.NoRepositoryPresent, made_dir.open_repository)
256
def test_missing_dir(self):
257
"""Initializing a missing directory should fail like the bzrdir api."""
258
backing = self.get_transport()
259
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
260
self.assertRaises(errors.NoSuchFile,
261
request.execute, 'subdir')
263
def test_initialized_dir(self):
264
"""Initializing an extant bzrdir should fail like the bzrdir api."""
265
backing = self.get_transport()
266
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
267
self.make_bzrdir('subdir')
268
self.assertRaises(errors.FileExists,
269
request.execute, 'subdir')
272
class TestSmartServerRequestOpenBranch(TestCaseWithChrootedTransport):
274
def test_no_branch(self):
275
"""When there is no branch, ('nobranch', ) is returned."""
276
backing = self.get_transport()
277
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
278
self.make_bzrdir('.')
279
self.assertEqual(SmartServerResponse(('nobranch', )),
282
def test_branch(self):
283
"""When there is a branch, 'ok' is returned."""
284
backing = self.get_transport()
285
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
286
self.make_branch('.')
287
self.assertEqual(SmartServerResponse(('ok', '')),
290
def test_branch_reference(self):
291
"""When there is a branch reference, the reference URL is returned."""
292
backing = self.get_transport()
293
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
294
branch = self.make_branch('branch')
295
checkout = branch.create_checkout('reference',lightweight=True)
296
reference_url = BranchReferenceFormat().get_reference(checkout.bzrdir)
297
self.assertFileEqual(reference_url, 'reference/.bzr/branch/location')
298
self.assertEqual(SmartServerResponse(('ok', reference_url)),
299
request.execute('reference'))
302
class TestSmartServerRequestRevisionHistory(tests.TestCaseWithMemoryTransport):
304
def test_empty(self):
305
"""For an empty branch, the body is empty."""
306
backing = self.get_transport()
307
request = smart.branch.SmartServerRequestRevisionHistory(backing)
308
self.make_branch('.')
309
self.assertEqual(SmartServerResponse(('ok', ), ''),
312
def test_not_empty(self):
313
"""For a non-empty branch, the body is empty."""
314
backing = self.get_transport()
315
request = smart.branch.SmartServerRequestRevisionHistory(backing)
316
tree = self.make_branch_and_memory_tree('.')
319
r1 = tree.commit('1st commit')
320
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
323
SmartServerResponse(('ok', ), ('\x00'.join([r1, r2]))),
327
class TestSmartServerBranchRequest(tests.TestCaseWithMemoryTransport):
329
def test_no_branch(self):
330
"""When there is a bzrdir and no branch, NotBranchError is raised."""
331
backing = self.get_transport()
332
request = smart.branch.SmartServerBranchRequest(backing)
333
self.make_bzrdir('.')
334
self.assertRaises(errors.NotBranchError,
337
def test_branch_reference(self):
338
"""When there is a branch reference, NotBranchError is raised."""
339
backing = self.get_transport()
340
request = smart.branch.SmartServerBranchRequest(backing)
341
branch = self.make_branch('branch')
342
checkout = branch.create_checkout('reference',lightweight=True)
343
self.assertRaises(errors.NotBranchError,
344
request.execute, 'checkout')
347
class TestSmartServerBranchRequestLastRevisionInfo(tests.TestCaseWithMemoryTransport):
349
def test_empty(self):
350
"""For an empty branch, the result is ('ok', '0', 'null:')."""
351
backing = self.get_transport()
352
request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
353
self.make_branch('.')
354
self.assertEqual(SmartServerResponse(('ok', '0', 'null:')),
357
def test_not_empty(self):
358
"""For a non-empty branch, the result is ('ok', 'revno', 'revid')."""
359
backing = self.get_transport()
360
request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
361
tree = self.make_branch_and_memory_tree('.')
364
rev_id_utf8 = u'\xc8'.encode('utf-8')
365
r1 = tree.commit('1st commit')
366
r2 = tree.commit('2nd commit', rev_id=rev_id_utf8)
369
SmartServerResponse(('ok', '2', rev_id_utf8)),
373
class TestSmartServerBranchRequestGetConfigFile(tests.TestCaseWithMemoryTransport):
375
def test_default(self):
376
"""With no file, we get empty content."""
377
backing = self.get_transport()
378
request = smart.branch.SmartServerBranchGetConfigFile(backing)
379
branch = self.make_branch('.')
380
# there should be no file by default
382
self.assertEqual(SmartServerResponse(('ok', ), content),
385
def test_with_content(self):
386
# SmartServerBranchGetConfigFile should return the content from
387
# branch.control_files.get('branch.conf') for now - in the future it may
388
# perform more complex processing.
389
backing = self.get_transport()
390
request = smart.branch.SmartServerBranchGetConfigFile(backing)
391
branch = self.make_branch('.')
392
branch.control_files.put_utf8('branch.conf', 'foo bar baz')
393
self.assertEqual(SmartServerResponse(('ok', ), 'foo bar baz'),
397
class TestSmartServerBranchRequestSetLastRevision(tests.TestCaseWithMemoryTransport):
399
def test_empty(self):
400
backing = self.get_transport()
401
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
402
b = self.make_branch('.')
403
branch_token = b.lock_write()
404
repo_token = b.repository.lock_write()
405
b.repository.unlock()
407
self.assertEqual(SmartServerResponse(('ok',)),
409
'', branch_token, repo_token,
414
def test_not_present_revision_id(self):
415
backing = self.get_transport()
416
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
417
b = self.make_branch('.')
418
branch_token = b.lock_write()
419
repo_token = b.repository.lock_write()
420
b.repository.unlock()
422
revision_id = 'non-existent revision'
424
SmartServerResponse(('NoSuchRevision', revision_id)),
426
'', branch_token, repo_token,
431
def test_revision_id_present(self):
432
backing = self.get_transport()
433
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
434
tree = self.make_branch_and_memory_tree('.')
437
rev_id_utf8 = u'\xc8'.encode('utf-8')
438
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
439
r2 = tree.commit('2nd commit')
441
branch_token = tree.branch.lock_write()
442
repo_token = tree.branch.repository.lock_write()
443
tree.branch.repository.unlock()
446
SmartServerResponse(('ok',)),
448
'', branch_token, repo_token,
450
self.assertEqual([rev_id_utf8], tree.branch.revision_history())
454
def test_revision_id_present2(self):
455
backing = self.get_transport()
456
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
457
tree = self.make_branch_and_memory_tree('.')
460
rev_id_utf8 = u'\xc8'.encode('utf-8')
461
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
462
r2 = tree.commit('2nd commit')
464
tree.branch.set_revision_history([])
465
branch_token = tree.branch.lock_write()
466
repo_token = tree.branch.repository.lock_write()
467
tree.branch.repository.unlock()
470
SmartServerResponse(('ok',)),
472
'', branch_token, repo_token,
474
self.assertEqual([rev_id_utf8], tree.branch.revision_history())
479
class TestSmartServerBranchRequestSetLastRevisionInfo(tests.TestCaseWithTransport):
481
def lock_branch(self, branch):
482
branch_token = branch.lock_write()
483
repo_token = branch.repository.lock_write()
484
branch.repository.unlock()
485
self.addCleanup(branch.unlock)
486
return branch_token, repo_token
488
def make_locked_branch(self, format=None):
489
branch = self.make_branch('.', format=format)
490
branch_token, repo_token = self.lock_branch(branch)
491
return branch, branch_token, repo_token
493
def test_empty(self):
494
"""An empty branch can have its last revision set to 'null:'."""
495
b, branch_token, repo_token = self.make_locked_branch()
496
backing = self.get_transport()
497
request = smart.branch.SmartServerBranchRequestSetLastRevisionInfo(
499
response = request.execute('', branch_token, repo_token, '0', 'null:')
500
self.assertEqual(SmartServerResponse(('ok',)), response)
502
def assertBranchLastRevisionInfo(self, expected_info, branch_relpath):
503
branch = bzrdir.BzrDir.open(branch_relpath).open_branch()
504
self.assertEqual(expected_info, branch.last_revision_info())
506
def test_branch_revision_info_is_updated(self):
507
"""This method really does update the branch last revision info."""
508
tree = self.make_branch_and_memory_tree('.')
511
tree.commit('First commit', rev_id='revision-1')
512
tree.commit('Second commit', rev_id='revision-2')
516
branch_token, repo_token = self.lock_branch(branch)
517
backing = self.get_transport()
518
request = smart.branch.SmartServerBranchRequestSetLastRevisionInfo(
520
self.assertBranchLastRevisionInfo((2, 'revision-2'), '.')
521
response = request.execute(
522
'', branch_token, repo_token, '1', 'revision-1')
523
self.assertEqual(SmartServerResponse(('ok',)), response)
524
self.assertBranchLastRevisionInfo((1, 'revision-1'), '.')
526
def test_not_present_revid(self):
527
"""Some branch formats will check that the revision is present in the
528
repository. When that check fails, a NoSuchRevision error is returned
531
# Make a knit format branch, because that format checks the values
532
# given to set_last_revision_info.
533
b, branch_token, repo_token = self.make_locked_branch(format='knit')
534
backing = self.get_transport()
535
request = smart.branch.SmartServerBranchRequestSetLastRevisionInfo(
537
response = request.execute(
538
'', branch_token, repo_token, '1', 'not-present')
540
SmartServerResponse(('NoSuchRevision', 'not-present')), response)
543
class TestSmartServerBranchRequestLockWrite(tests.TestCaseWithMemoryTransport):
546
tests.TestCaseWithMemoryTransport.setUp(self)
548
def test_lock_write_on_unlocked_branch(self):
549
backing = self.get_transport()
550
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
551
branch = self.make_branch('.', format='knit')
552
repository = branch.repository
553
response = request.execute('')
554
branch_nonce = branch.control_files._lock.peek().get('nonce')
555
repository_nonce = repository.control_files._lock.peek().get('nonce')
557
SmartServerResponse(('ok', branch_nonce, repository_nonce)),
559
# The branch (and associated repository) is now locked. Verify that
560
# with a new branch object.
561
new_branch = repository.bzrdir.open_branch()
562
self.assertRaises(errors.LockContention, new_branch.lock_write)
564
def test_lock_write_on_locked_branch(self):
565
backing = self.get_transport()
566
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
567
branch = self.make_branch('.')
569
branch.leave_lock_in_place()
571
response = request.execute('')
573
SmartServerResponse(('LockContention',)), response)
575
def test_lock_write_with_tokens_on_locked_branch(self):
576
backing = self.get_transport()
577
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
578
branch = self.make_branch('.', format='knit')
579
branch_token = branch.lock_write()
580
repo_token = branch.repository.lock_write()
581
branch.repository.unlock()
582
branch.leave_lock_in_place()
583
branch.repository.leave_lock_in_place()
585
response = request.execute('',
586
branch_token, repo_token)
588
SmartServerResponse(('ok', branch_token, repo_token)), response)
590
def test_lock_write_with_mismatched_tokens_on_locked_branch(self):
591
backing = self.get_transport()
592
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
593
branch = self.make_branch('.', format='knit')
594
branch_token = branch.lock_write()
595
repo_token = branch.repository.lock_write()
596
branch.repository.unlock()
597
branch.leave_lock_in_place()
598
branch.repository.leave_lock_in_place()
600
response = request.execute('',
601
branch_token+'xxx', repo_token)
603
SmartServerResponse(('TokenMismatch',)), response)
605
def test_lock_write_on_locked_repo(self):
606
backing = self.get_transport()
607
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
608
branch = self.make_branch('.', format='knit')
609
branch.repository.lock_write()
610
branch.repository.leave_lock_in_place()
611
branch.repository.unlock()
612
response = request.execute('')
614
SmartServerResponse(('LockContention',)), response)
616
def test_lock_write_on_readonly_transport(self):
617
backing = self.get_readonly_transport()
618
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
619
branch = self.make_branch('.')
620
root = self.get_transport().clone('/')
621
path = urlutils.relative_url(root.base, self.get_transport().base)
622
response = request.execute(path)
623
error_name, lock_str, why_str = response.args
624
self.assertFalse(response.is_successful())
625
self.assertEqual('LockFailed', error_name)
628
class TestSmartServerBranchRequestUnlock(tests.TestCaseWithMemoryTransport):
631
tests.TestCaseWithMemoryTransport.setUp(self)
633
def test_unlock_on_locked_branch_and_repo(self):
634
backing = self.get_transport()
635
request = smart.branch.SmartServerBranchRequestUnlock(backing)
636
branch = self.make_branch('.', format='knit')
638
branch_token = branch.lock_write()
639
repo_token = branch.repository.lock_write()
640
branch.repository.unlock()
641
# Unlock the branch (and repo) object, leaving the physical locks
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',)), response)
650
# The branch is now unlocked. Verify that with a new branch
652
new_branch = branch.bzrdir.open_branch()
653
new_branch.lock_write()
656
def test_unlock_on_unlocked_branch_unlocked_repo(self):
657
backing = self.get_transport()
658
request = smart.branch.SmartServerBranchRequestUnlock(backing)
659
branch = self.make_branch('.', format='knit')
660
response = request.execute(
661
'', 'branch token', 'repo token')
663
SmartServerResponse(('TokenMismatch',)), response)
665
def test_unlock_on_unlocked_branch_locked_repo(self):
666
backing = self.get_transport()
667
request = smart.branch.SmartServerBranchRequestUnlock(backing)
668
branch = self.make_branch('.', format='knit')
669
# Lock the repository.
670
repo_token = branch.repository.lock_write()
671
branch.repository.leave_lock_in_place()
672
branch.repository.unlock()
673
# Issue branch lock_write request on the unlocked branch (with locked
675
response = request.execute(
676
'', 'branch token', repo_token)
678
SmartServerResponse(('TokenMismatch',)), response)
681
class TestSmartServerRepositoryRequest(tests.TestCaseWithMemoryTransport):
683
def test_no_repository(self):
684
"""Raise NoRepositoryPresent when there is a bzrdir and no repo."""
685
# we test this using a shared repository above the named path,
686
# thus checking the right search logic is used - that is, that
687
# its the exact path being looked at and the server is not
689
backing = self.get_transport()
690
request = smart.repository.SmartServerRepositoryRequest(backing)
691
self.make_repository('.', shared=True)
692
self.make_bzrdir('subdir')
693
self.assertRaises(errors.NoRepositoryPresent,
694
request.execute, 'subdir')
697
class TestSmartServerRepositoryGetParentMap(tests.TestCaseWithTransport):
699
def test_trivial_bzipped(self):
700
# This tests that the wire encoding is actually bzipped
701
backing = self.get_transport()
702
request = smart.repository.SmartServerRepositoryGetParentMap(backing)
703
tree = self.make_branch_and_memory_tree('.')
705
self.assertEqual(None,
706
request.execute('', 'missing-id'))
707
# Note that it returns a body (of '' bzipped).
709
SuccessfulSmartServerResponse(('ok', ), bz2.compress('')),
710
request.do_body('\n\n0\n'))
713
class TestSmartServerRepositoryGetRevisionGraph(tests.TestCaseWithMemoryTransport):
715
def test_none_argument(self):
716
backing = self.get_transport()
717
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
718
tree = self.make_branch_and_memory_tree('.')
721
r1 = tree.commit('1st commit')
722
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
725
# the lines of revision_id->revision_parent_list has no guaranteed
726
# order coming out of a dict, so sort both our test and response
727
lines = sorted([' '.join([r2, r1]), r1])
728
response = request.execute('', '')
729
response.body = '\n'.join(sorted(response.body.split('\n')))
732
SmartServerResponse(('ok', ), '\n'.join(lines)), response)
734
def test_specific_revision_argument(self):
735
backing = self.get_transport()
736
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
737
tree = self.make_branch_and_memory_tree('.')
740
rev_id_utf8 = u'\xc9'.encode('utf-8')
741
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
742
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
745
self.assertEqual(SmartServerResponse(('ok', ), rev_id_utf8),
746
request.execute('', rev_id_utf8))
748
def test_no_such_revision(self):
749
backing = self.get_transport()
750
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
751
tree = self.make_branch_and_memory_tree('.')
754
r1 = tree.commit('1st commit')
757
# Note that it still returns body (of zero bytes).
759
SmartServerResponse(('nosuchrevision', 'missingrevision', ), ''),
760
request.execute('', 'missingrevision'))
763
class TestSmartServerRequestHasRevision(tests.TestCaseWithMemoryTransport):
765
def test_missing_revision(self):
766
"""For a missing revision, ('no', ) is returned."""
767
backing = self.get_transport()
768
request = smart.repository.SmartServerRequestHasRevision(backing)
769
self.make_repository('.')
770
self.assertEqual(SmartServerResponse(('no', )),
771
request.execute('', 'revid'))
773
def test_present_revision(self):
774
"""For a present revision, ('yes', ) is returned."""
775
backing = self.get_transport()
776
request = smart.repository.SmartServerRequestHasRevision(backing)
777
tree = self.make_branch_and_memory_tree('.')
780
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
781
r1 = tree.commit('a commit', rev_id=rev_id_utf8)
783
self.assertTrue(tree.branch.repository.has_revision(rev_id_utf8))
784
self.assertEqual(SmartServerResponse(('yes', )),
785
request.execute('', rev_id_utf8))
788
class TestSmartServerRepositoryGatherStats(tests.TestCaseWithMemoryTransport):
790
def test_empty_revid(self):
791
"""With an empty revid, we get only size an number and revisions"""
792
backing = self.get_transport()
793
request = smart.repository.SmartServerRepositoryGatherStats(backing)
794
repository = self.make_repository('.')
795
stats = repository.gather_stats()
797
expected_body = 'revisions: 0\nsize: %d\n' % size
798
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
799
request.execute('', '', 'no'))
801
def test_revid_with_committers(self):
802
"""For a revid we get more infos."""
803
backing = self.get_transport()
804
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
805
request = smart.repository.SmartServerRepositoryGatherStats(backing)
806
tree = self.make_branch_and_memory_tree('.')
809
# Let's build a predictable result
810
tree.commit('a commit', timestamp=123456.2, timezone=3600)
811
tree.commit('a commit', timestamp=654321.4, timezone=0,
815
stats = tree.branch.repository.gather_stats()
817
expected_body = ('firstrev: 123456.200 3600\n'
818
'latestrev: 654321.400 0\n'
821
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
825
def test_not_empty_repository_with_committers(self):
826
"""For a revid and requesting committers we get the whole thing."""
827
backing = self.get_transport()
828
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
829
request = smart.repository.SmartServerRepositoryGatherStats(backing)
830
tree = self.make_branch_and_memory_tree('.')
833
# Let's build a predictable result
834
tree.commit('a commit', timestamp=123456.2, timezone=3600,
836
tree.commit('a commit', timestamp=654321.4, timezone=0,
837
committer='bar', rev_id=rev_id_utf8)
839
stats = tree.branch.repository.gather_stats()
842
expected_body = ('committers: 2\n'
843
'firstrev: 123456.200 3600\n'
844
'latestrev: 654321.400 0\n'
847
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
852
class TestSmartServerRepositoryIsShared(tests.TestCaseWithMemoryTransport):
854
def test_is_shared(self):
855
"""For a shared repository, ('yes', ) is returned."""
856
backing = self.get_transport()
857
request = smart.repository.SmartServerRepositoryIsShared(backing)
858
self.make_repository('.', shared=True)
859
self.assertEqual(SmartServerResponse(('yes', )),
860
request.execute('', ))
862
def test_is_not_shared(self):
863
"""For a shared repository, ('no', ) is returned."""
864
backing = self.get_transport()
865
request = smart.repository.SmartServerRepositoryIsShared(backing)
866
self.make_repository('.', shared=False)
867
self.assertEqual(SmartServerResponse(('no', )),
868
request.execute('', ))
871
class TestSmartServerRepositoryLockWrite(tests.TestCaseWithMemoryTransport):
874
tests.TestCaseWithMemoryTransport.setUp(self)
876
def test_lock_write_on_unlocked_repo(self):
877
backing = self.get_transport()
878
request = smart.repository.SmartServerRepositoryLockWrite(backing)
879
repository = self.make_repository('.', format='knit')
880
response = request.execute('')
881
nonce = repository.control_files._lock.peek().get('nonce')
882
self.assertEqual(SmartServerResponse(('ok', nonce)), response)
883
# The repository is now locked. Verify that with a new repository
885
new_repo = repository.bzrdir.open_repository()
886
self.assertRaises(errors.LockContention, new_repo.lock_write)
888
def test_lock_write_on_locked_repo(self):
889
backing = self.get_transport()
890
request = smart.repository.SmartServerRepositoryLockWrite(backing)
891
repository = self.make_repository('.', format='knit')
892
repository.lock_write()
893
repository.leave_lock_in_place()
895
response = request.execute('')
897
SmartServerResponse(('LockContention',)), response)
899
def test_lock_write_on_readonly_transport(self):
900
backing = self.get_readonly_transport()
901
request = smart.repository.SmartServerRepositoryLockWrite(backing)
902
repository = self.make_repository('.', format='knit')
903
response = request.execute('')
904
self.assertFalse(response.is_successful())
905
self.assertEqual('LockFailed', response.args[0])
908
class TestSmartServerRepositoryUnlock(tests.TestCaseWithMemoryTransport):
911
tests.TestCaseWithMemoryTransport.setUp(self)
913
def test_unlock_on_locked_repo(self):
914
backing = self.get_transport()
915
request = smart.repository.SmartServerRepositoryUnlock(backing)
916
repository = self.make_repository('.', format='knit')
917
token = repository.lock_write()
918
repository.leave_lock_in_place()
920
response = request.execute('', token)
922
SmartServerResponse(('ok',)), response)
923
# The repository is now unlocked. Verify that with a new repository
925
new_repo = repository.bzrdir.open_repository()
926
new_repo.lock_write()
929
def test_unlock_on_unlocked_repo(self):
930
backing = self.get_transport()
931
request = smart.repository.SmartServerRepositoryUnlock(backing)
932
repository = self.make_repository('.', format='knit')
933
response = request.execute('', 'some token')
935
SmartServerResponse(('TokenMismatch',)), response)
938
class TestSmartServerRepositoryTarball(tests.TestCaseWithTransport):
940
def test_repository_tarball(self):
941
backing = self.get_transport()
942
request = smart.repository.SmartServerRepositoryTarball(backing)
943
repository = self.make_repository('.')
944
# make some extraneous junk in the repository directory which should
946
self.build_tree(['.bzr/repository/extra-junk'])
947
response = request.execute('', 'bz2')
948
self.assertEqual(('ok',), response.args)
949
# body should be a tbz2
950
body_file = StringIO(response.body)
951
body_tar = tarfile.open('body_tar.tbz2', fileobj=body_file,
953
# let's make sure there are some key repository components inside it.
954
# the tarfile returns directories with trailing slashes...
955
names = set([n.rstrip('/') for n in body_tar.getnames()])
956
self.assertTrue('.bzr/repository/lock' in names)
957
self.assertTrue('.bzr/repository/format' in names)
958
self.assertTrue('.bzr/repository/extra-junk' not in names,
959
"extraneous file present in tar file")
962
class TestSmartServerRepositoryStreamKnitData(tests.TestCaseWithMemoryTransport):
964
def test_fetch_revisions(self):
965
backing = self.get_transport()
966
request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
967
tree = self.make_branch_and_memory_tree('.')
970
rev_id1_utf8 = u'\xc8'.encode('utf-8')
971
rev_id2_utf8 = u'\xc9'.encode('utf-8')
972
r1 = tree.commit('1st commit', rev_id=rev_id1_utf8)
973
r1 = tree.commit('2nd commit', rev_id=rev_id2_utf8)
976
response = request.execute('', rev_id2_utf8)
977
self.assertEqual(('ok',), response.args)
978
unpacker = pack.ContainerReader(StringIO(response.body))
980
for [name], read_bytes in unpacker.iter_records():
982
bytes = read_bytes(None)
983
# The bytes should be a valid bencoded string.
984
bencode.bdecode(bytes)
985
# XXX: assert that the bencoded knit records have the right
988
def test_no_such_revision_error(self):
989
backing = self.get_transport()
990
request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
991
repo = self.make_repository('.')
992
rev_id1_utf8 = u'\xc8'.encode('utf-8')
993
response = request.execute('', rev_id1_utf8)
995
SmartServerResponse(('NoSuchRevision', rev_id1_utf8)),
999
class TestSmartServerRepositoryStreamRevisionsChunked(tests.TestCaseWithMemoryTransport):
1001
def test_fetch_revisions(self):
1002
backing = self.get_transport()
1003
request = smart.repository.SmartServerRepositoryStreamRevisionsChunked(
1005
tree = self.make_branch_and_memory_tree('.')
1008
rev_id1_utf8 = u'\xc8'.encode('utf-8')
1009
rev_id2_utf8 = u'\xc9'.encode('utf-8')
1010
tree.commit('1st commit', rev_id=rev_id1_utf8)
1011
tree.commit('2nd commit', rev_id=rev_id2_utf8)
1014
response = request.execute('')
1015
self.assertEqual(None, response)
1016
response = request.do_body("%s\n%s\n1" % (rev_id2_utf8, rev_id1_utf8))
1017
self.assertEqual(('ok',), response.args)
1018
parser = pack.ContainerPushParser()
1020
for stream_bytes in response.body_stream:
1021
parser.accept_bytes(stream_bytes)
1022
for [name], record_bytes in parser.read_pending_records():
1024
# The bytes should be a valid bencoded string.
1025
bencode.bdecode(record_bytes)
1026
# XXX: assert that the bencoded knit records have the right
1029
def test_no_such_revision_error(self):
1030
backing = self.get_transport()
1031
request = smart.repository.SmartServerRepositoryStreamRevisionsChunked(
1033
repo = self.make_repository('.')
1034
rev_id1_utf8 = u'\xc8'.encode('utf-8')
1035
response = request.execute('')
1036
self.assertEqual(None, response)
1037
response = request.do_body("%s\n\n1" % (rev_id1_utf8,))
1039
FailedSmartServerResponse(('NoSuchRevision', )),
1043
class TestSmartServerIsReadonly(tests.TestCaseWithMemoryTransport):
1045
def test_is_readonly_no(self):
1046
backing = self.get_transport()
1047
request = smart.request.SmartServerIsReadonly(backing)
1048
response = request.execute()
1050
SmartServerResponse(('no',)), response)
1052
def test_is_readonly_yes(self):
1053
backing = self.get_readonly_transport()
1054
request = smart.request.SmartServerIsReadonly(backing)
1055
response = request.execute()
1057
SmartServerResponse(('yes',)), response)
1060
class TestHandlers(tests.TestCase):
1061
"""Tests for the request.request_handlers object."""
1063
def test_registered_methods(self):
1064
"""Test that known methods are registered to the correct object."""
1066
smart.request.request_handlers.get('Branch.get_config_file'),
1067
smart.branch.SmartServerBranchGetConfigFile)
1069
smart.request.request_handlers.get('Branch.lock_write'),
1070
smart.branch.SmartServerBranchRequestLockWrite)
1072
smart.request.request_handlers.get('Branch.last_revision_info'),
1073
smart.branch.SmartServerBranchRequestLastRevisionInfo)
1075
smart.request.request_handlers.get('Branch.revision_history'),
1076
smart.branch.SmartServerRequestRevisionHistory)
1078
smart.request.request_handlers.get('Branch.set_last_revision'),
1079
smart.branch.SmartServerBranchRequestSetLastRevision)
1081
smart.request.request_handlers.get('Branch.set_last_revision_info'),
1082
smart.branch.SmartServerBranchRequestSetLastRevisionInfo)
1084
smart.request.request_handlers.get('Branch.unlock'),
1085
smart.branch.SmartServerBranchRequestUnlock)
1087
smart.request.request_handlers.get('BzrDir.find_repository'),
1088
smart.bzrdir.SmartServerRequestFindRepositoryV1)
1090
smart.request.request_handlers.get('BzrDir.find_repositoryV2'),
1091
smart.bzrdir.SmartServerRequestFindRepositoryV2)
1093
smart.request.request_handlers.get('BzrDirFormat.initialize'),
1094
smart.bzrdir.SmartServerRequestInitializeBzrDir)
1096
smart.request.request_handlers.get('BzrDir.open_branch'),
1097
smart.bzrdir.SmartServerRequestOpenBranch)
1099
smart.request.request_handlers.get('Repository.gather_stats'),
1100
smart.repository.SmartServerRepositoryGatherStats)
1102
smart.request.request_handlers.get('Repository.get_parent_map'),
1103
smart.repository.SmartServerRepositoryGetParentMap)
1105
smart.request.request_handlers.get(
1106
'Repository.get_revision_graph'),
1107
smart.repository.SmartServerRepositoryGetRevisionGraph)
1109
smart.request.request_handlers.get('Repository.has_revision'),
1110
smart.repository.SmartServerRequestHasRevision)
1112
smart.request.request_handlers.get('Repository.is_shared'),
1113
smart.repository.SmartServerRepositoryIsShared)
1115
smart.request.request_handlers.get('Repository.lock_write'),
1116
smart.repository.SmartServerRepositoryLockWrite)
1118
smart.request.request_handlers.get('Repository.tarball'),
1119
smart.repository.SmartServerRepositoryTarball)
1121
smart.request.request_handlers.get('Repository.unlock'),
1122
smart.repository.SmartServerRepositoryUnlock)
1124
smart.request.request_handlers.get('Transport.is_readonly'),
1125
smart.request.SmartServerIsReadonly)