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 TestSmartServerBranchRequestSetLastRevision(tests.TestCaseWithMemoryTransport):
402
def test_empty(self):
403
backing = self.get_transport()
404
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
405
b = self.make_branch('.')
406
branch_token = b.lock_write()
407
repo_token = b.repository.lock_write()
408
b.repository.unlock()
410
self.assertEqual(SmartServerResponse(('ok',)),
412
'', branch_token, repo_token,
417
def test_not_present_revision_id(self):
418
backing = self.get_transport()
419
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
420
b = self.make_branch('.')
421
branch_token = b.lock_write()
422
repo_token = b.repository.lock_write()
423
b.repository.unlock()
425
revision_id = 'non-existent revision'
427
SmartServerResponse(('NoSuchRevision', revision_id)),
429
'', branch_token, repo_token,
434
def test_revision_id_present(self):
435
backing = self.get_transport()
436
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
437
tree = self.make_branch_and_memory_tree('.')
440
rev_id_utf8 = u'\xc8'.encode('utf-8')
441
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
442
r2 = tree.commit('2nd commit')
444
branch_token = tree.branch.lock_write()
445
repo_token = tree.branch.repository.lock_write()
446
tree.branch.repository.unlock()
449
SmartServerResponse(('ok',)),
451
'', branch_token, repo_token,
453
self.assertEqual([rev_id_utf8], tree.branch.revision_history())
457
def test_revision_id_present2(self):
458
backing = self.get_transport()
459
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
460
tree = self.make_branch_and_memory_tree('.')
463
rev_id_utf8 = u'\xc8'.encode('utf-8')
464
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
465
r2 = tree.commit('2nd commit')
467
tree.branch.set_revision_history([])
468
branch_token = tree.branch.lock_write()
469
repo_token = tree.branch.repository.lock_write()
470
tree.branch.repository.unlock()
473
SmartServerResponse(('ok',)),
475
'', branch_token, repo_token,
477
self.assertEqual([rev_id_utf8], tree.branch.revision_history())
482
class TestSmartServerBranchRequestSetLastRevisionInfo(tests.TestCaseWithTransport):
484
def lock_branch(self, branch):
485
branch_token = branch.lock_write()
486
repo_token = branch.repository.lock_write()
487
branch.repository.unlock()
488
self.addCleanup(branch.unlock)
489
return branch_token, repo_token
491
def make_locked_branch(self, format=None):
492
branch = self.make_branch('.', format=format)
493
branch_token, repo_token = self.lock_branch(branch)
494
return branch, branch_token, repo_token
496
def test_empty(self):
497
"""An empty branch can have its last revision set to 'null:'."""
498
b, branch_token, repo_token = self.make_locked_branch()
499
backing = self.get_transport()
500
request = smart.branch.SmartServerBranchRequestSetLastRevisionInfo(
502
response = request.execute('', branch_token, repo_token, '0', 'null:')
503
self.assertEqual(SmartServerResponse(('ok',)), response)
505
def assertBranchLastRevisionInfo(self, expected_info, branch_relpath):
506
branch = bzrdir.BzrDir.open(branch_relpath).open_branch()
507
self.assertEqual(expected_info, branch.last_revision_info())
509
def test_branch_revision_info_is_updated(self):
510
"""This method really does update the branch last revision info."""
511
tree = self.make_branch_and_memory_tree('.')
514
tree.commit('First commit', rev_id='revision-1')
515
tree.commit('Second commit', rev_id='revision-2')
519
branch_token, repo_token = self.lock_branch(branch)
520
backing = self.get_transport()
521
request = smart.branch.SmartServerBranchRequestSetLastRevisionInfo(
523
self.assertBranchLastRevisionInfo((2, 'revision-2'), '.')
524
response = request.execute(
525
'', branch_token, repo_token, '1', 'revision-1')
526
self.assertEqual(SmartServerResponse(('ok',)), response)
527
self.assertBranchLastRevisionInfo((1, 'revision-1'), '.')
529
def test_not_present_revid(self):
530
"""Some branch formats will check that the revision is present in the
531
repository. When that check fails, a NoSuchRevision error is returned
534
# Make a knit format branch, because that format checks the values
535
# given to set_last_revision_info.
536
b, branch_token, repo_token = self.make_locked_branch(format='knit')
537
backing = self.get_transport()
538
request = smart.branch.SmartServerBranchRequestSetLastRevisionInfo(
540
response = request.execute(
541
'', branch_token, repo_token, '1', 'not-present')
543
SmartServerResponse(('NoSuchRevision', 'not-present')), response)
546
class TestSmartServerBranchRequestLockWrite(tests.TestCaseWithMemoryTransport):
549
tests.TestCaseWithMemoryTransport.setUp(self)
551
def test_lock_write_on_unlocked_branch(self):
552
backing = self.get_transport()
553
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
554
branch = self.make_branch('.', format='knit')
555
repository = branch.repository
556
response = request.execute('')
557
branch_nonce = branch.control_files._lock.peek().get('nonce')
558
repository_nonce = repository.control_files._lock.peek().get('nonce')
560
SmartServerResponse(('ok', branch_nonce, repository_nonce)),
562
# The branch (and associated repository) is now locked. Verify that
563
# with a new branch object.
564
new_branch = repository.bzrdir.open_branch()
565
self.assertRaises(errors.LockContention, new_branch.lock_write)
567
def test_lock_write_on_locked_branch(self):
568
backing = self.get_transport()
569
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
570
branch = self.make_branch('.')
572
branch.leave_lock_in_place()
574
response = request.execute('')
576
SmartServerResponse(('LockContention',)), response)
578
def test_lock_write_with_tokens_on_locked_branch(self):
579
backing = self.get_transport()
580
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
581
branch = self.make_branch('.', format='knit')
582
branch_token = branch.lock_write()
583
repo_token = branch.repository.lock_write()
584
branch.repository.unlock()
585
branch.leave_lock_in_place()
586
branch.repository.leave_lock_in_place()
588
response = request.execute('',
589
branch_token, repo_token)
591
SmartServerResponse(('ok', branch_token, repo_token)), response)
593
def test_lock_write_with_mismatched_tokens_on_locked_branch(self):
594
backing = self.get_transport()
595
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
596
branch = self.make_branch('.', format='knit')
597
branch_token = branch.lock_write()
598
repo_token = branch.repository.lock_write()
599
branch.repository.unlock()
600
branch.leave_lock_in_place()
601
branch.repository.leave_lock_in_place()
603
response = request.execute('',
604
branch_token+'xxx', repo_token)
606
SmartServerResponse(('TokenMismatch',)), response)
608
def test_lock_write_on_locked_repo(self):
609
backing = self.get_transport()
610
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
611
branch = self.make_branch('.', format='knit')
612
branch.repository.lock_write()
613
branch.repository.leave_lock_in_place()
614
branch.repository.unlock()
615
response = request.execute('')
617
SmartServerResponse(('LockContention',)), response)
619
def test_lock_write_on_readonly_transport(self):
620
backing = self.get_readonly_transport()
621
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
622
branch = self.make_branch('.')
623
root = self.get_transport().clone('/')
624
path = urlutils.relative_url(root.base, self.get_transport().base)
625
response = request.execute(path)
626
error_name, lock_str, why_str = response.args
627
self.assertFalse(response.is_successful())
628
self.assertEqual('LockFailed', error_name)
631
class TestSmartServerBranchRequestUnlock(tests.TestCaseWithMemoryTransport):
634
tests.TestCaseWithMemoryTransport.setUp(self)
636
def test_unlock_on_locked_branch_and_repo(self):
637
backing = self.get_transport()
638
request = smart.branch.SmartServerBranchRequestUnlock(backing)
639
branch = self.make_branch('.', format='knit')
641
branch_token = branch.lock_write()
642
repo_token = branch.repository.lock_write()
643
branch.repository.unlock()
644
# Unlock the branch (and repo) object, leaving the physical locks
646
branch.leave_lock_in_place()
647
branch.repository.leave_lock_in_place()
649
response = request.execute('',
650
branch_token, repo_token)
652
SmartServerResponse(('ok',)), response)
653
# The branch is now unlocked. Verify that with a new branch
655
new_branch = branch.bzrdir.open_branch()
656
new_branch.lock_write()
659
def test_unlock_on_unlocked_branch_unlocked_repo(self):
660
backing = self.get_transport()
661
request = smart.branch.SmartServerBranchRequestUnlock(backing)
662
branch = self.make_branch('.', format='knit')
663
response = request.execute(
664
'', 'branch token', 'repo token')
666
SmartServerResponse(('TokenMismatch',)), response)
668
def test_unlock_on_unlocked_branch_locked_repo(self):
669
backing = self.get_transport()
670
request = smart.branch.SmartServerBranchRequestUnlock(backing)
671
branch = self.make_branch('.', format='knit')
672
# Lock the repository.
673
repo_token = branch.repository.lock_write()
674
branch.repository.leave_lock_in_place()
675
branch.repository.unlock()
676
# Issue branch lock_write request on the unlocked branch (with locked
678
response = request.execute(
679
'', 'branch token', repo_token)
681
SmartServerResponse(('TokenMismatch',)), response)
684
class TestSmartServerRepositoryRequest(tests.TestCaseWithMemoryTransport):
686
def test_no_repository(self):
687
"""Raise NoRepositoryPresent when there is a bzrdir and no repo."""
688
# we test this using a shared repository above the named path,
689
# thus checking the right search logic is used - that is, that
690
# its the exact path being looked at and the server is not
692
backing = self.get_transport()
693
request = smart.repository.SmartServerRepositoryRequest(backing)
694
self.make_repository('.', shared=True)
695
self.make_bzrdir('subdir')
696
self.assertRaises(errors.NoRepositoryPresent,
697
request.execute, 'subdir')
700
class TestSmartServerRepositoryGetParentMap(tests.TestCaseWithTransport):
702
def test_trivial_bzipped(self):
703
# This tests that the wire encoding is actually bzipped
704
backing = self.get_transport()
705
request = smart.repository.SmartServerRepositoryGetParentMap(backing)
706
tree = self.make_branch_and_memory_tree('.')
708
self.assertEqual(None,
709
request.execute('', 'missing-id'))
710
# Note that it returns a body (of '' bzipped).
712
SuccessfulSmartServerResponse(('ok', ), bz2.compress('')),
713
request.do_body('\n\n0\n'))
716
class TestSmartServerRepositoryGetRevisionGraph(tests.TestCaseWithMemoryTransport):
718
def test_none_argument(self):
719
backing = self.get_transport()
720
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
721
tree = self.make_branch_and_memory_tree('.')
724
r1 = tree.commit('1st commit')
725
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
728
# the lines of revision_id->revision_parent_list has no guaranteed
729
# order coming out of a dict, so sort both our test and response
730
lines = sorted([' '.join([r2, r1]), r1])
731
response = request.execute('', '')
732
response.body = '\n'.join(sorted(response.body.split('\n')))
735
SmartServerResponse(('ok', ), '\n'.join(lines)), response)
737
def test_specific_revision_argument(self):
738
backing = self.get_transport()
739
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
740
tree = self.make_branch_and_memory_tree('.')
743
rev_id_utf8 = u'\xc9'.encode('utf-8')
744
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
745
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
748
self.assertEqual(SmartServerResponse(('ok', ), rev_id_utf8),
749
request.execute('', rev_id_utf8))
751
def test_no_such_revision(self):
752
backing = self.get_transport()
753
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
754
tree = self.make_branch_and_memory_tree('.')
757
r1 = tree.commit('1st commit')
760
# Note that it still returns body (of zero bytes).
762
SmartServerResponse(('nosuchrevision', 'missingrevision', ), ''),
763
request.execute('', 'missingrevision'))
766
class TestSmartServerRequestHasRevision(tests.TestCaseWithMemoryTransport):
768
def test_missing_revision(self):
769
"""For a missing revision, ('no', ) is returned."""
770
backing = self.get_transport()
771
request = smart.repository.SmartServerRequestHasRevision(backing)
772
self.make_repository('.')
773
self.assertEqual(SmartServerResponse(('no', )),
774
request.execute('', 'revid'))
776
def test_present_revision(self):
777
"""For a present revision, ('yes', ) is returned."""
778
backing = self.get_transport()
779
request = smart.repository.SmartServerRequestHasRevision(backing)
780
tree = self.make_branch_and_memory_tree('.')
783
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
784
r1 = tree.commit('a commit', rev_id=rev_id_utf8)
786
self.assertTrue(tree.branch.repository.has_revision(rev_id_utf8))
787
self.assertEqual(SmartServerResponse(('yes', )),
788
request.execute('', rev_id_utf8))
791
class TestSmartServerRepositoryGatherStats(tests.TestCaseWithMemoryTransport):
793
def test_empty_revid(self):
794
"""With an empty revid, we get only size an number and revisions"""
795
backing = self.get_transport()
796
request = smart.repository.SmartServerRepositoryGatherStats(backing)
797
repository = self.make_repository('.')
798
stats = repository.gather_stats()
800
expected_body = 'revisions: 0\nsize: %d\n' % size
801
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
802
request.execute('', '', 'no'))
804
def test_revid_with_committers(self):
805
"""For a revid we get more infos."""
806
backing = self.get_transport()
807
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
808
request = smart.repository.SmartServerRepositoryGatherStats(backing)
809
tree = self.make_branch_and_memory_tree('.')
812
# Let's build a predictable result
813
tree.commit('a commit', timestamp=123456.2, timezone=3600)
814
tree.commit('a commit', timestamp=654321.4, timezone=0,
818
stats = tree.branch.repository.gather_stats()
820
expected_body = ('firstrev: 123456.200 3600\n'
821
'latestrev: 654321.400 0\n'
824
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
828
def test_not_empty_repository_with_committers(self):
829
"""For a revid and requesting committers we get the whole thing."""
830
backing = self.get_transport()
831
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
832
request = smart.repository.SmartServerRepositoryGatherStats(backing)
833
tree = self.make_branch_and_memory_tree('.')
836
# Let's build a predictable result
837
tree.commit('a commit', timestamp=123456.2, timezone=3600,
839
tree.commit('a commit', timestamp=654321.4, timezone=0,
840
committer='bar', rev_id=rev_id_utf8)
842
stats = tree.branch.repository.gather_stats()
845
expected_body = ('committers: 2\n'
846
'firstrev: 123456.200 3600\n'
847
'latestrev: 654321.400 0\n'
850
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
855
class TestSmartServerRepositoryIsShared(tests.TestCaseWithMemoryTransport):
857
def test_is_shared(self):
858
"""For a shared repository, ('yes', ) is returned."""
859
backing = self.get_transport()
860
request = smart.repository.SmartServerRepositoryIsShared(backing)
861
self.make_repository('.', shared=True)
862
self.assertEqual(SmartServerResponse(('yes', )),
863
request.execute('', ))
865
def test_is_not_shared(self):
866
"""For a shared repository, ('no', ) is returned."""
867
backing = self.get_transport()
868
request = smart.repository.SmartServerRepositoryIsShared(backing)
869
self.make_repository('.', shared=False)
870
self.assertEqual(SmartServerResponse(('no', )),
871
request.execute('', ))
874
class TestSmartServerRepositoryLockWrite(tests.TestCaseWithMemoryTransport):
877
tests.TestCaseWithMemoryTransport.setUp(self)
879
def test_lock_write_on_unlocked_repo(self):
880
backing = self.get_transport()
881
request = smart.repository.SmartServerRepositoryLockWrite(backing)
882
repository = self.make_repository('.', format='knit')
883
response = request.execute('')
884
nonce = repository.control_files._lock.peek().get('nonce')
885
self.assertEqual(SmartServerResponse(('ok', nonce)), response)
886
# The repository is now locked. Verify that with a new repository
888
new_repo = repository.bzrdir.open_repository()
889
self.assertRaises(errors.LockContention, new_repo.lock_write)
891
def test_lock_write_on_locked_repo(self):
892
backing = self.get_transport()
893
request = smart.repository.SmartServerRepositoryLockWrite(backing)
894
repository = self.make_repository('.', format='knit')
895
repository.lock_write()
896
repository.leave_lock_in_place()
898
response = request.execute('')
900
SmartServerResponse(('LockContention',)), response)
902
def test_lock_write_on_readonly_transport(self):
903
backing = self.get_readonly_transport()
904
request = smart.repository.SmartServerRepositoryLockWrite(backing)
905
repository = self.make_repository('.', format='knit')
906
response = request.execute('')
907
self.assertFalse(response.is_successful())
908
self.assertEqual('LockFailed', response.args[0])
911
class TestSmartServerRepositoryUnlock(tests.TestCaseWithMemoryTransport):
914
tests.TestCaseWithMemoryTransport.setUp(self)
916
def test_unlock_on_locked_repo(self):
917
backing = self.get_transport()
918
request = smart.repository.SmartServerRepositoryUnlock(backing)
919
repository = self.make_repository('.', format='knit')
920
token = repository.lock_write()
921
repository.leave_lock_in_place()
923
response = request.execute('', token)
925
SmartServerResponse(('ok',)), response)
926
# The repository is now unlocked. Verify that with a new repository
928
new_repo = repository.bzrdir.open_repository()
929
new_repo.lock_write()
932
def test_unlock_on_unlocked_repo(self):
933
backing = self.get_transport()
934
request = smart.repository.SmartServerRepositoryUnlock(backing)
935
repository = self.make_repository('.', format='knit')
936
response = request.execute('', 'some token')
938
SmartServerResponse(('TokenMismatch',)), response)
941
class TestSmartServerRepositoryTarball(tests.TestCaseWithTransport):
943
def test_repository_tarball(self):
944
backing = self.get_transport()
945
request = smart.repository.SmartServerRepositoryTarball(backing)
946
repository = self.make_repository('.')
947
# make some extraneous junk in the repository directory which should
949
self.build_tree(['.bzr/repository/extra-junk'])
950
response = request.execute('', 'bz2')
951
self.assertEqual(('ok',), response.args)
952
# body should be a tbz2
953
body_file = StringIO(response.body)
954
body_tar = tarfile.open('body_tar.tbz2', fileobj=body_file,
956
# let's make sure there are some key repository components inside it.
957
# the tarfile returns directories with trailing slashes...
958
names = set([n.rstrip('/') for n in body_tar.getnames()])
959
self.assertTrue('.bzr/repository/lock' in names)
960
self.assertTrue('.bzr/repository/format' in names)
961
self.assertTrue('.bzr/repository/extra-junk' not in names,
962
"extraneous file present in tar file")
965
class TestSmartServerRepositoryStreamKnitData(tests.TestCaseWithMemoryTransport):
967
def test_fetch_revisions(self):
968
backing = self.get_transport()
969
request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
970
tree = self.make_branch_and_memory_tree('.')
973
rev_id1_utf8 = u'\xc8'.encode('utf-8')
974
rev_id2_utf8 = u'\xc9'.encode('utf-8')
975
r1 = tree.commit('1st commit', rev_id=rev_id1_utf8)
976
r1 = tree.commit('2nd commit', rev_id=rev_id2_utf8)
979
response = request.execute('', rev_id2_utf8)
980
self.assertEqual(('ok',), response.args)
981
unpacker = pack.ContainerReader(StringIO(response.body))
983
for [name], read_bytes in unpacker.iter_records():
985
bytes = read_bytes(None)
986
# The bytes should be a valid bencoded string.
987
bencode.bdecode(bytes)
988
# XXX: assert that the bencoded knit records have the right
991
def test_no_such_revision_error(self):
992
backing = self.get_transport()
993
request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
994
repo = self.make_repository('.')
995
rev_id1_utf8 = u'\xc8'.encode('utf-8')
996
response = request.execute('', rev_id1_utf8)
998
SmartServerResponse(('NoSuchRevision', rev_id1_utf8)),
1002
class TestSmartServerRepositoryStreamRevisionsChunked(tests.TestCaseWithMemoryTransport):
1004
def test_fetch_revisions(self):
1005
backing = self.get_transport()
1006
request = smart.repository.SmartServerRepositoryStreamRevisionsChunked(
1008
tree = self.make_branch_and_memory_tree('.')
1011
rev_id1_utf8 = u'\xc8'.encode('utf-8')
1012
rev_id2_utf8 = u'\xc9'.encode('utf-8')
1013
tree.commit('1st commit', rev_id=rev_id1_utf8)
1014
tree.commit('2nd commit', rev_id=rev_id2_utf8)
1017
response = request.execute('')
1018
self.assertEqual(None, response)
1019
response = request.do_body("%s\n%s\n1" % (rev_id2_utf8, rev_id1_utf8))
1020
self.assertEqual(('ok',), response.args)
1021
parser = pack.ContainerPushParser()
1023
for stream_bytes in response.body_stream:
1024
parser.accept_bytes(stream_bytes)
1025
for [name], record_bytes in parser.read_pending_records():
1027
# The bytes should be a valid bencoded string.
1028
bencode.bdecode(record_bytes)
1029
# XXX: assert that the bencoded knit records have the right
1032
def test_no_such_revision_error(self):
1033
backing = self.get_transport()
1034
request = smart.repository.SmartServerRepositoryStreamRevisionsChunked(
1036
repo = self.make_repository('.')
1037
rev_id1_utf8 = u'\xc8'.encode('utf-8')
1038
response = request.execute('')
1039
self.assertEqual(None, response)
1040
response = request.do_body("%s\n\n1" % (rev_id1_utf8,))
1042
FailedSmartServerResponse(('NoSuchRevision', )),
1046
class TestSmartServerIsReadonly(tests.TestCaseWithMemoryTransport):
1048
def test_is_readonly_no(self):
1049
backing = self.get_transport()
1050
request = smart.request.SmartServerIsReadonly(backing)
1051
response = request.execute()
1053
SmartServerResponse(('no',)), response)
1055
def test_is_readonly_yes(self):
1056
backing = self.get_readonly_transport()
1057
request = smart.request.SmartServerIsReadonly(backing)
1058
response = request.execute()
1060
SmartServerResponse(('yes',)), response)
1063
class TestHandlers(tests.TestCase):
1064
"""Tests for the request.request_handlers object."""
1066
def test_registered_methods(self):
1067
"""Test that known methods are registered to the correct object."""
1069
smart.request.request_handlers.get('Branch.get_config_file'),
1070
smart.branch.SmartServerBranchGetConfigFile)
1072
smart.request.request_handlers.get('Branch.lock_write'),
1073
smart.branch.SmartServerBranchRequestLockWrite)
1075
smart.request.request_handlers.get('Branch.last_revision_info'),
1076
smart.branch.SmartServerBranchRequestLastRevisionInfo)
1078
smart.request.request_handlers.get('Branch.revision_history'),
1079
smart.branch.SmartServerRequestRevisionHistory)
1081
smart.request.request_handlers.get('Branch.set_last_revision'),
1082
smart.branch.SmartServerBranchRequestSetLastRevision)
1084
smart.request.request_handlers.get('Branch.set_last_revision_info'),
1085
smart.branch.SmartServerBranchRequestSetLastRevisionInfo)
1087
smart.request.request_handlers.get('Branch.unlock'),
1088
smart.branch.SmartServerBranchRequestUnlock)
1090
smart.request.request_handlers.get('BzrDir.find_repository'),
1091
smart.bzrdir.SmartServerRequestFindRepositoryV1)
1093
smart.request.request_handlers.get('BzrDir.find_repositoryV2'),
1094
smart.bzrdir.SmartServerRequestFindRepositoryV2)
1096
smart.request.request_handlers.get('BzrDirFormat.initialize'),
1097
smart.bzrdir.SmartServerRequestInitializeBzrDir)
1099
smart.request.request_handlers.get('BzrDir.open_branch'),
1100
smart.bzrdir.SmartServerRequestOpenBranch)
1102
smart.request.request_handlers.get('Repository.gather_stats'),
1103
smart.repository.SmartServerRepositoryGatherStats)
1105
smart.request.request_handlers.get('Repository.get_parent_map'),
1106
smart.repository.SmartServerRepositoryGetParentMap)
1108
smart.request.request_handlers.get(
1109
'Repository.get_revision_graph'),
1110
smart.repository.SmartServerRepositoryGetRevisionGraph)
1112
smart.request.request_handlers.get('Repository.has_revision'),
1113
smart.repository.SmartServerRequestHasRevision)
1115
smart.request.request_handlers.get('Repository.is_shared'),
1116
smart.repository.SmartServerRepositoryIsShared)
1118
smart.request.request_handlers.get('Repository.lock_write'),
1119
smart.repository.SmartServerRepositoryLockWrite)
1121
smart.request.request_handlers.get('Repository.tarball'),
1122
smart.repository.SmartServerRepositoryTarball)
1124
smart.request.request_handlers.get('Repository.unlock'),
1125
smart.repository.SmartServerRepositoryUnlock)
1127
smart.request.request_handlers.get('Transport.is_readonly'),
1128
smart.request.SmartServerIsReadonly)