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 TestSmartServerBranchRequestLockWrite(tests.TestCaseWithMemoryTransport):
482
tests.TestCaseWithMemoryTransport.setUp(self)
484
def test_lock_write_on_unlocked_branch(self):
485
backing = self.get_transport()
486
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
487
branch = self.make_branch('.', format='knit')
488
repository = branch.repository
489
response = request.execute('')
490
branch_nonce = branch.control_files._lock.peek().get('nonce')
491
repository_nonce = repository.control_files._lock.peek().get('nonce')
493
SmartServerResponse(('ok', branch_nonce, repository_nonce)),
495
# The branch (and associated repository) is now locked. Verify that
496
# with a new branch object.
497
new_branch = repository.bzrdir.open_branch()
498
self.assertRaises(errors.LockContention, new_branch.lock_write)
500
def test_lock_write_on_locked_branch(self):
501
backing = self.get_transport()
502
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
503
branch = self.make_branch('.')
505
branch.leave_lock_in_place()
507
response = request.execute('')
509
SmartServerResponse(('LockContention',)), response)
511
def test_lock_write_with_tokens_on_locked_branch(self):
512
backing = self.get_transport()
513
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
514
branch = self.make_branch('.', format='knit')
515
branch_token = branch.lock_write()
516
repo_token = branch.repository.lock_write()
517
branch.repository.unlock()
518
branch.leave_lock_in_place()
519
branch.repository.leave_lock_in_place()
521
response = request.execute('',
522
branch_token, repo_token)
524
SmartServerResponse(('ok', branch_token, repo_token)), response)
526
def test_lock_write_with_mismatched_tokens_on_locked_branch(self):
527
backing = self.get_transport()
528
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
529
branch = self.make_branch('.', format='knit')
530
branch_token = branch.lock_write()
531
repo_token = branch.repository.lock_write()
532
branch.repository.unlock()
533
branch.leave_lock_in_place()
534
branch.repository.leave_lock_in_place()
536
response = request.execute('',
537
branch_token+'xxx', repo_token)
539
SmartServerResponse(('TokenMismatch',)), response)
541
def test_lock_write_on_locked_repo(self):
542
backing = self.get_transport()
543
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
544
branch = self.make_branch('.', format='knit')
545
branch.repository.lock_write()
546
branch.repository.leave_lock_in_place()
547
branch.repository.unlock()
548
response = request.execute('')
550
SmartServerResponse(('LockContention',)), response)
552
def test_lock_write_on_readonly_transport(self):
553
backing = self.get_readonly_transport()
554
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
555
branch = self.make_branch('.')
556
root = self.get_transport().clone('/')
557
path = urlutils.relative_url(root.base, self.get_transport().base)
558
response = request.execute(path)
559
error_name, lock_str, why_str = response.args
560
self.assertFalse(response.is_successful())
561
self.assertEqual('LockFailed', error_name)
564
class TestSmartServerBranchRequestUnlock(tests.TestCaseWithMemoryTransport):
567
tests.TestCaseWithMemoryTransport.setUp(self)
569
def test_unlock_on_locked_branch_and_repo(self):
570
backing = self.get_transport()
571
request = smart.branch.SmartServerBranchRequestUnlock(backing)
572
branch = self.make_branch('.', format='knit')
574
branch_token = branch.lock_write()
575
repo_token = branch.repository.lock_write()
576
branch.repository.unlock()
577
# Unlock the branch (and repo) object, leaving the physical locks
579
branch.leave_lock_in_place()
580
branch.repository.leave_lock_in_place()
582
response = request.execute('',
583
branch_token, repo_token)
585
SmartServerResponse(('ok',)), response)
586
# The branch is now unlocked. Verify that with a new branch
588
new_branch = branch.bzrdir.open_branch()
589
new_branch.lock_write()
592
def test_unlock_on_unlocked_branch_unlocked_repo(self):
593
backing = self.get_transport()
594
request = smart.branch.SmartServerBranchRequestUnlock(backing)
595
branch = self.make_branch('.', format='knit')
596
response = request.execute(
597
'', 'branch token', 'repo token')
599
SmartServerResponse(('TokenMismatch',)), response)
601
def test_unlock_on_unlocked_branch_locked_repo(self):
602
backing = self.get_transport()
603
request = smart.branch.SmartServerBranchRequestUnlock(backing)
604
branch = self.make_branch('.', format='knit')
605
# Lock the repository.
606
repo_token = branch.repository.lock_write()
607
branch.repository.leave_lock_in_place()
608
branch.repository.unlock()
609
# Issue branch lock_write request on the unlocked branch (with locked
611
response = request.execute(
612
'', 'branch token', repo_token)
614
SmartServerResponse(('TokenMismatch',)), response)
617
class TestSmartServerRepositoryRequest(tests.TestCaseWithMemoryTransport):
619
def test_no_repository(self):
620
"""Raise NoRepositoryPresent when there is a bzrdir and no repo."""
621
# we test this using a shared repository above the named path,
622
# thus checking the right search logic is used - that is, that
623
# its the exact path being looked at and the server is not
625
backing = self.get_transport()
626
request = smart.repository.SmartServerRepositoryRequest(backing)
627
self.make_repository('.', shared=True)
628
self.make_bzrdir('subdir')
629
self.assertRaises(errors.NoRepositoryPresent,
630
request.execute, 'subdir')
633
class TestSmartServerRepositoryGetParentMap(tests.TestCaseWithTransport):
635
def test_trivial_bzipped(self):
636
# This tests that the wire encoding is actually bzipped
637
backing = self.get_transport()
638
request = smart.repository.SmartServerRepositoryGetParentMap(backing)
639
tree = self.make_branch_and_memory_tree('.')
641
self.assertEqual(None,
642
request.execute('', 'missing-id'))
643
# Note that it returns a body (of '' bzipped).
645
SuccessfulSmartServerResponse(('ok', ), bz2.compress('')),
646
request.do_body('\n\n0\n'))
649
class TestSmartServerRepositoryGetRevisionGraph(tests.TestCaseWithMemoryTransport):
651
def test_none_argument(self):
652
backing = self.get_transport()
653
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
654
tree = self.make_branch_and_memory_tree('.')
657
r1 = tree.commit('1st commit')
658
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
661
# the lines of revision_id->revision_parent_list has no guaranteed
662
# order coming out of a dict, so sort both our test and response
663
lines = sorted([' '.join([r2, r1]), r1])
664
response = request.execute('', '')
665
response.body = '\n'.join(sorted(response.body.split('\n')))
668
SmartServerResponse(('ok', ), '\n'.join(lines)), response)
670
def test_specific_revision_argument(self):
671
backing = self.get_transport()
672
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
673
tree = self.make_branch_and_memory_tree('.')
676
rev_id_utf8 = u'\xc9'.encode('utf-8')
677
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
678
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
681
self.assertEqual(SmartServerResponse(('ok', ), rev_id_utf8),
682
request.execute('', rev_id_utf8))
684
def test_no_such_revision(self):
685
backing = self.get_transport()
686
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
687
tree = self.make_branch_and_memory_tree('.')
690
r1 = tree.commit('1st commit')
693
# Note that it still returns body (of zero bytes).
695
SmartServerResponse(('nosuchrevision', 'missingrevision', ), ''),
696
request.execute('', 'missingrevision'))
699
class TestSmartServerRequestHasRevision(tests.TestCaseWithMemoryTransport):
701
def test_missing_revision(self):
702
"""For a missing revision, ('no', ) is returned."""
703
backing = self.get_transport()
704
request = smart.repository.SmartServerRequestHasRevision(backing)
705
self.make_repository('.')
706
self.assertEqual(SmartServerResponse(('no', )),
707
request.execute('', 'revid'))
709
def test_present_revision(self):
710
"""For a present revision, ('yes', ) is returned."""
711
backing = self.get_transport()
712
request = smart.repository.SmartServerRequestHasRevision(backing)
713
tree = self.make_branch_and_memory_tree('.')
716
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
717
r1 = tree.commit('a commit', rev_id=rev_id_utf8)
719
self.assertTrue(tree.branch.repository.has_revision(rev_id_utf8))
720
self.assertEqual(SmartServerResponse(('yes', )),
721
request.execute('', rev_id_utf8))
724
class TestSmartServerRepositoryGatherStats(tests.TestCaseWithMemoryTransport):
726
def test_empty_revid(self):
727
"""With an empty revid, we get only size an number and revisions"""
728
backing = self.get_transport()
729
request = smart.repository.SmartServerRepositoryGatherStats(backing)
730
repository = self.make_repository('.')
731
stats = repository.gather_stats()
733
expected_body = 'revisions: 0\nsize: %d\n' % size
734
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
735
request.execute('', '', 'no'))
737
def test_revid_with_committers(self):
738
"""For a revid we get more infos."""
739
backing = self.get_transport()
740
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
741
request = smart.repository.SmartServerRepositoryGatherStats(backing)
742
tree = self.make_branch_and_memory_tree('.')
745
# Let's build a predictable result
746
tree.commit('a commit', timestamp=123456.2, timezone=3600)
747
tree.commit('a commit', timestamp=654321.4, timezone=0,
751
stats = tree.branch.repository.gather_stats()
753
expected_body = ('firstrev: 123456.200 3600\n'
754
'latestrev: 654321.400 0\n'
757
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
761
def test_not_empty_repository_with_committers(self):
762
"""For a revid and requesting committers we get the whole thing."""
763
backing = self.get_transport()
764
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
765
request = smart.repository.SmartServerRepositoryGatherStats(backing)
766
tree = self.make_branch_and_memory_tree('.')
769
# Let's build a predictable result
770
tree.commit('a commit', timestamp=123456.2, timezone=3600,
772
tree.commit('a commit', timestamp=654321.4, timezone=0,
773
committer='bar', rev_id=rev_id_utf8)
775
stats = tree.branch.repository.gather_stats()
778
expected_body = ('committers: 2\n'
779
'firstrev: 123456.200 3600\n'
780
'latestrev: 654321.400 0\n'
783
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
788
class TestSmartServerRepositoryIsShared(tests.TestCaseWithMemoryTransport):
790
def test_is_shared(self):
791
"""For a shared repository, ('yes', ) is returned."""
792
backing = self.get_transport()
793
request = smart.repository.SmartServerRepositoryIsShared(backing)
794
self.make_repository('.', shared=True)
795
self.assertEqual(SmartServerResponse(('yes', )),
796
request.execute('', ))
798
def test_is_not_shared(self):
799
"""For a shared repository, ('no', ) is returned."""
800
backing = self.get_transport()
801
request = smart.repository.SmartServerRepositoryIsShared(backing)
802
self.make_repository('.', shared=False)
803
self.assertEqual(SmartServerResponse(('no', )),
804
request.execute('', ))
807
class TestSmartServerRepositoryLockWrite(tests.TestCaseWithMemoryTransport):
810
tests.TestCaseWithMemoryTransport.setUp(self)
812
def test_lock_write_on_unlocked_repo(self):
813
backing = self.get_transport()
814
request = smart.repository.SmartServerRepositoryLockWrite(backing)
815
repository = self.make_repository('.', format='knit')
816
response = request.execute('')
817
nonce = repository.control_files._lock.peek().get('nonce')
818
self.assertEqual(SmartServerResponse(('ok', nonce)), response)
819
# The repository is now locked. Verify that with a new repository
821
new_repo = repository.bzrdir.open_repository()
822
self.assertRaises(errors.LockContention, new_repo.lock_write)
824
def test_lock_write_on_locked_repo(self):
825
backing = self.get_transport()
826
request = smart.repository.SmartServerRepositoryLockWrite(backing)
827
repository = self.make_repository('.', format='knit')
828
repository.lock_write()
829
repository.leave_lock_in_place()
831
response = request.execute('')
833
SmartServerResponse(('LockContention',)), response)
835
def test_lock_write_on_readonly_transport(self):
836
backing = self.get_readonly_transport()
837
request = smart.repository.SmartServerRepositoryLockWrite(backing)
838
repository = self.make_repository('.', format='knit')
839
response = request.execute('')
840
self.assertFalse(response.is_successful())
841
self.assertEqual('LockFailed', response.args[0])
844
class TestSmartServerRepositoryUnlock(tests.TestCaseWithMemoryTransport):
847
tests.TestCaseWithMemoryTransport.setUp(self)
849
def test_unlock_on_locked_repo(self):
850
backing = self.get_transport()
851
request = smart.repository.SmartServerRepositoryUnlock(backing)
852
repository = self.make_repository('.', format='knit')
853
token = repository.lock_write()
854
repository.leave_lock_in_place()
856
response = request.execute('', token)
858
SmartServerResponse(('ok',)), response)
859
# The repository is now unlocked. Verify that with a new repository
861
new_repo = repository.bzrdir.open_repository()
862
new_repo.lock_write()
865
def test_unlock_on_unlocked_repo(self):
866
backing = self.get_transport()
867
request = smart.repository.SmartServerRepositoryUnlock(backing)
868
repository = self.make_repository('.', format='knit')
869
response = request.execute('', 'some token')
871
SmartServerResponse(('TokenMismatch',)), response)
874
class TestSmartServerRepositoryTarball(tests.TestCaseWithTransport):
876
def test_repository_tarball(self):
877
backing = self.get_transport()
878
request = smart.repository.SmartServerRepositoryTarball(backing)
879
repository = self.make_repository('.')
880
# make some extraneous junk in the repository directory which should
882
self.build_tree(['.bzr/repository/extra-junk'])
883
response = request.execute('', 'bz2')
884
self.assertEqual(('ok',), response.args)
885
# body should be a tbz2
886
body_file = StringIO(response.body)
887
body_tar = tarfile.open('body_tar.tbz2', fileobj=body_file,
889
# let's make sure there are some key repository components inside it.
890
# the tarfile returns directories with trailing slashes...
891
names = set([n.rstrip('/') for n in body_tar.getnames()])
892
self.assertTrue('.bzr/repository/lock' in names)
893
self.assertTrue('.bzr/repository/format' in names)
894
self.assertTrue('.bzr/repository/extra-junk' not in names,
895
"extraneous file present in tar file")
898
class TestSmartServerRepositoryStreamKnitData(tests.TestCaseWithMemoryTransport):
900
def test_fetch_revisions(self):
901
backing = self.get_transport()
902
request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
903
tree = self.make_branch_and_memory_tree('.')
906
rev_id1_utf8 = u'\xc8'.encode('utf-8')
907
rev_id2_utf8 = u'\xc9'.encode('utf-8')
908
r1 = tree.commit('1st commit', rev_id=rev_id1_utf8)
909
r1 = tree.commit('2nd commit', rev_id=rev_id2_utf8)
912
response = request.execute('', rev_id2_utf8)
913
self.assertEqual(('ok',), response.args)
914
unpacker = pack.ContainerReader(StringIO(response.body))
916
for [name], read_bytes in unpacker.iter_records():
918
bytes = read_bytes(None)
919
# The bytes should be a valid bencoded string.
920
bencode.bdecode(bytes)
921
# XXX: assert that the bencoded knit records have the right
924
def test_no_such_revision_error(self):
925
backing = self.get_transport()
926
request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
927
repo = self.make_repository('.')
928
rev_id1_utf8 = u'\xc8'.encode('utf-8')
929
response = request.execute('', rev_id1_utf8)
931
SmartServerResponse(('NoSuchRevision', rev_id1_utf8)),
935
class TestSmartServerRepositoryStreamRevisionsChunked(tests.TestCaseWithMemoryTransport):
937
def test_fetch_revisions(self):
938
backing = self.get_transport()
939
request = smart.repository.SmartServerRepositoryStreamRevisionsChunked(
941
tree = self.make_branch_and_memory_tree('.')
944
rev_id1_utf8 = u'\xc8'.encode('utf-8')
945
rev_id2_utf8 = u'\xc9'.encode('utf-8')
946
tree.commit('1st commit', rev_id=rev_id1_utf8)
947
tree.commit('2nd commit', rev_id=rev_id2_utf8)
950
response = request.execute('')
951
self.assertEqual(None, response)
952
response = request.do_body("%s\n%s\n1" % (rev_id2_utf8, rev_id1_utf8))
953
self.assertEqual(('ok',), response.args)
954
parser = pack.ContainerPushParser()
956
for stream_bytes in response.body_stream:
957
parser.accept_bytes(stream_bytes)
958
for [name], record_bytes in parser.read_pending_records():
960
# The bytes should be a valid bencoded string.
961
bencode.bdecode(record_bytes)
962
# XXX: assert that the bencoded knit records have the right
965
def test_no_such_revision_error(self):
966
backing = self.get_transport()
967
request = smart.repository.SmartServerRepositoryStreamRevisionsChunked(
969
repo = self.make_repository('.')
970
rev_id1_utf8 = u'\xc8'.encode('utf-8')
971
response = request.execute('')
972
self.assertEqual(None, response)
973
response = request.do_body("%s\n\n1" % (rev_id1_utf8,))
975
FailedSmartServerResponse(('NoSuchRevision', )),
979
class TestSmartServerIsReadonly(tests.TestCaseWithMemoryTransport):
981
def test_is_readonly_no(self):
982
backing = self.get_transport()
983
request = smart.request.SmartServerIsReadonly(backing)
984
response = request.execute()
986
SmartServerResponse(('no',)), response)
988
def test_is_readonly_yes(self):
989
backing = self.get_readonly_transport()
990
request = smart.request.SmartServerIsReadonly(backing)
991
response = request.execute()
993
SmartServerResponse(('yes',)), response)
996
class TestHandlers(tests.TestCase):
997
"""Tests for the request.request_handlers object."""
999
def test_registered_methods(self):
1000
"""Test that known methods are registered to the correct object."""
1002
smart.request.request_handlers.get('Branch.get_config_file'),
1003
smart.branch.SmartServerBranchGetConfigFile)
1005
smart.request.request_handlers.get('Branch.lock_write'),
1006
smart.branch.SmartServerBranchRequestLockWrite)
1008
smart.request.request_handlers.get('Branch.last_revision_info'),
1009
smart.branch.SmartServerBranchRequestLastRevisionInfo)
1011
smart.request.request_handlers.get('Branch.revision_history'),
1012
smart.branch.SmartServerRequestRevisionHistory)
1014
smart.request.request_handlers.get('Branch.set_last_revision'),
1015
smart.branch.SmartServerBranchRequestSetLastRevision)
1017
smart.request.request_handlers.get('Branch.unlock'),
1018
smart.branch.SmartServerBranchRequestUnlock)
1020
smart.request.request_handlers.get('BzrDir.find_repository'),
1021
smart.bzrdir.SmartServerRequestFindRepositoryV1)
1023
smart.request.request_handlers.get('BzrDir.find_repositoryV2'),
1024
smart.bzrdir.SmartServerRequestFindRepositoryV2)
1026
smart.request.request_handlers.get('BzrDirFormat.initialize'),
1027
smart.bzrdir.SmartServerRequestInitializeBzrDir)
1029
smart.request.request_handlers.get('BzrDir.open_branch'),
1030
smart.bzrdir.SmartServerRequestOpenBranch)
1032
smart.request.request_handlers.get('Repository.gather_stats'),
1033
smart.repository.SmartServerRepositoryGatherStats)
1035
smart.request.request_handlers.get('Repository.get_parent_map'),
1036
smart.repository.SmartServerRepositoryGetParentMap)
1038
smart.request.request_handlers.get(
1039
'Repository.get_revision_graph'),
1040
smart.repository.SmartServerRepositoryGetRevisionGraph)
1042
smart.request.request_handlers.get('Repository.has_revision'),
1043
smart.repository.SmartServerRequestHasRevision)
1045
smart.request.request_handlers.get('Repository.is_shared'),
1046
smart.repository.SmartServerRepositoryIsShared)
1048
smart.request.request_handlers.get('Repository.lock_write'),
1049
smart.repository.SmartServerRepositoryLockWrite)
1051
smart.request.request_handlers.get('Repository.tarball'),
1052
smart.repository.SmartServerRepositoryTarball)
1054
smart.request.request_handlers.get('Repository.unlock'),
1055
smart.repository.SmartServerRepositoryUnlock)
1057
smart.request.request_handlers.get('Transport.is_readonly'),
1058
smart.request.SmartServerIsReadonly)