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