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.
27
from StringIO import StringIO
31
from bzrlib import bzrdir, errors, pack, smart, tests
32
from bzrlib.smart.request import SmartServerResponse
33
import bzrlib.smart.bzrdir
34
import bzrlib.smart.branch
35
import bzrlib.smart.repository
36
from bzrlib.util import bencode
39
class TestCaseWithSmartMedium(tests.TestCaseWithTransport):
42
super(TestCaseWithSmartMedium, self).setUp()
43
# We're allowed to set the transport class here, so that we don't use
44
# the default or a parameterized class, but rather use the
45
# TestCaseWithTransport infrastructure to set up a smart server and
47
self.transport_server = smart.server.SmartTCPServer_for_testing
49
def get_smart_medium(self):
50
"""Get a smart medium to use in tests."""
51
return self.get_transport().get_smart_medium()
54
class TestSmartServerResponse(tests.TestCase):
57
self.assertEqual(SmartServerResponse(('ok', )),
58
SmartServerResponse(('ok', )))
59
self.assertEqual(SmartServerResponse(('ok', ), 'body'),
60
SmartServerResponse(('ok', ), 'body'))
61
self.assertNotEqual(SmartServerResponse(('ok', )),
62
SmartServerResponse(('notok', )))
63
self.assertNotEqual(SmartServerResponse(('ok', ), 'body'),
64
SmartServerResponse(('ok', )))
65
self.assertNotEqual(None,
66
SmartServerResponse(('ok', )))
69
class TestSmartServerRequestFindRepository(tests.TestCaseWithTransport):
70
"""Tests for BzrDir.find_repository."""
72
def test_no_repository(self):
73
"""When there is no repository to be found, ('norepository', ) is returned."""
74
backing = self.get_transport()
75
request = smart.bzrdir.SmartServerRequestFindRepository(backing)
77
self.assertEqual(SmartServerResponse(('norepository', )),
78
request.execute(backing.local_abspath('')))
80
def test_nonshared_repository(self):
81
# nonshared repositorys only allow 'find' to return a handle when the
82
# path the repository is being searched on is the same as that that
83
# the repository is at.
84
backing = self.get_transport()
85
request = smart.bzrdir.SmartServerRequestFindRepository(backing)
86
result = self._make_repository_and_result()
87
self.assertEqual(result, request.execute(backing.local_abspath('')))
88
self.make_bzrdir('subdir')
89
self.assertEqual(SmartServerResponse(('norepository', )),
90
request.execute(backing.local_abspath('subdir')))
92
def _make_repository_and_result(self, shared=False, format=None):
93
"""Convenience function to setup a repository.
95
:result: The SmartServerResponse to expect when opening it.
97
repo = self.make_repository('.', shared=shared, format=format)
98
if repo.supports_rich_root():
102
if repo._format.supports_tree_reference:
106
return SmartServerResponse(('ok', '', rich_root, subtrees))
108
def test_shared_repository(self):
109
"""When there is a shared repository, we get 'ok', 'relpath-to-repo'."""
110
backing = self.get_transport()
111
request = smart.bzrdir.SmartServerRequestFindRepository(backing)
112
result = self._make_repository_and_result(shared=True)
113
self.assertEqual(result, request.execute(backing.local_abspath('')))
114
self.make_bzrdir('subdir')
115
result2 = SmartServerResponse(result.args[0:1] + ('..', ) + result.args[2:])
116
self.assertEqual(result2,
117
request.execute(backing.local_abspath('subdir')))
118
self.make_bzrdir('subdir/deeper')
119
result3 = SmartServerResponse(result.args[0:1] + ('../..', ) + result.args[2:])
120
self.assertEqual(result3,
121
request.execute(backing.local_abspath('subdir/deeper')))
123
def test_rich_root_and_subtree_encoding(self):
124
"""Test for the format attributes for rich root and subtree support."""
125
backing = self.get_transport()
126
request = smart.bzrdir.SmartServerRequestFindRepository(backing)
127
result = self._make_repository_and_result(format='dirstate-with-subtree')
128
# check the test will be valid
129
self.assertEqual('yes', result.args[2])
130
self.assertEqual('yes', result.args[3])
131
self.assertEqual(result, request.execute(backing.local_abspath('')))
134
class TestSmartServerRequestInitializeBzrDir(tests.TestCaseWithTransport):
136
def test_empty_dir(self):
137
"""Initializing an empty dir should succeed and do it."""
138
backing = self.get_transport()
139
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
140
self.assertEqual(SmartServerResponse(('ok', )),
141
request.execute(backing.local_abspath('.')))
142
made_dir = bzrdir.BzrDir.open_from_transport(backing)
143
# no branch, tree or repository is expected with the current
145
self.assertRaises(errors.NoWorkingTree, made_dir.open_workingtree)
146
self.assertRaises(errors.NotBranchError, made_dir.open_branch)
147
self.assertRaises(errors.NoRepositoryPresent, made_dir.open_repository)
149
def test_missing_dir(self):
150
"""Initializing a missing directory should fail like the bzrdir api."""
151
backing = self.get_transport()
152
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
153
self.assertRaises(errors.NoSuchFile,
154
request.execute, backing.local_abspath('subdir'))
156
def test_initialized_dir(self):
157
"""Initializing an extant bzrdir should fail like the bzrdir api."""
158
backing = self.get_transport()
159
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
160
self.make_bzrdir('subdir')
161
self.assertRaises(errors.FileExists,
162
request.execute, backing.local_abspath('subdir'))
165
class TestSmartServerRequestOpenBranch(tests.TestCaseWithTransport):
167
def test_no_branch(self):
168
"""When there is no branch, ('nobranch', ) is returned."""
169
backing = self.get_transport()
170
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
171
self.make_bzrdir('.')
172
self.assertEqual(SmartServerResponse(('nobranch', )),
173
request.execute(backing.local_abspath('')))
175
def test_branch(self):
176
"""When there is a branch, 'ok' is returned."""
177
backing = self.get_transport()
178
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
179
self.make_branch('.')
180
self.assertEqual(SmartServerResponse(('ok', '')),
181
request.execute(backing.local_abspath('')))
183
def test_branch_reference(self):
184
"""When there is a branch reference, the reference URL is returned."""
185
backing = self.get_transport()
186
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
187
branch = self.make_branch('branch')
188
checkout = branch.create_checkout('reference',lightweight=True)
189
# TODO: once we have an API to probe for references of any sort, we
191
reference_url = backing.abspath('branch') + '/'
192
self.assertFileEqual(reference_url, 'reference/.bzr/branch/location')
193
self.assertEqual(SmartServerResponse(('ok', reference_url)),
194
request.execute(backing.local_abspath('reference')))
197
class TestSmartServerRequestRevisionHistory(tests.TestCaseWithTransport):
199
def test_empty(self):
200
"""For an empty branch, the body is empty."""
201
backing = self.get_transport()
202
request = smart.branch.SmartServerRequestRevisionHistory(backing)
203
self.make_branch('.')
204
self.assertEqual(SmartServerResponse(('ok', ), ''),
205
request.execute(backing.local_abspath('')))
207
def test_not_empty(self):
208
"""For a non-empty branch, the body is empty."""
209
backing = self.get_transport()
210
request = smart.branch.SmartServerRequestRevisionHistory(backing)
211
tree = self.make_branch_and_memory_tree('.')
214
r1 = tree.commit('1st commit')
215
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
218
SmartServerResponse(('ok', ), ('\x00'.join([r1, r2]))),
219
request.execute(backing.local_abspath('')))
222
class TestSmartServerBranchRequest(tests.TestCaseWithTransport):
224
def test_no_branch(self):
225
"""When there is a bzrdir and no branch, NotBranchError is raised."""
226
backing = self.get_transport()
227
request = smart.branch.SmartServerBranchRequest(backing)
228
self.make_bzrdir('.')
229
self.assertRaises(errors.NotBranchError,
230
request.execute, backing.local_abspath(''))
232
def test_branch_reference(self):
233
"""When there is a branch reference, NotBranchError is raised."""
234
backing = self.get_transport()
235
request = smart.branch.SmartServerBranchRequest(backing)
236
branch = self.make_branch('branch')
237
checkout = branch.create_checkout('reference',lightweight=True)
238
self.assertRaises(errors.NotBranchError,
239
request.execute, backing.local_abspath('checkout'))
242
class TestSmartServerBranchRequestLastRevisionInfo(tests.TestCaseWithTransport):
244
def test_empty(self):
245
"""For an empty branch, the result is ('ok', '0', 'null:')."""
246
backing = self.get_transport()
247
request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
248
self.make_branch('.')
249
self.assertEqual(SmartServerResponse(('ok', '0', 'null:')),
250
request.execute(backing.local_abspath('')))
252
def test_not_empty(self):
253
"""For a non-empty branch, the result is ('ok', 'revno', 'revid')."""
254
backing = self.get_transport()
255
request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
256
tree = self.make_branch_and_memory_tree('.')
259
rev_id_utf8 = u'\xc8'.encode('utf-8')
260
r1 = tree.commit('1st commit')
261
r2 = tree.commit('2nd commit', rev_id=rev_id_utf8)
264
SmartServerResponse(('ok', '2', rev_id_utf8)),
265
request.execute(backing.local_abspath('')))
268
class TestSmartServerBranchRequestGetConfigFile(tests.TestCaseWithTransport):
270
def test_default(self):
271
"""With no file, we get empty content."""
272
backing = self.get_transport()
273
request = smart.branch.SmartServerBranchGetConfigFile(backing)
274
branch = self.make_branch('.')
275
# there should be no file by default
277
self.assertEqual(SmartServerResponse(('ok', ), content),
278
request.execute(backing.local_abspath('')))
280
def test_with_content(self):
281
# SmartServerBranchGetConfigFile should return the content from
282
# branch.control_files.get('branch.conf') for now - in the future it may
283
# perform more complex processing.
284
backing = self.get_transport()
285
request = smart.branch.SmartServerBranchGetConfigFile(backing)
286
branch = self.make_branch('.')
287
branch.control_files.put_utf8('branch.conf', 'foo bar baz')
288
self.assertEqual(SmartServerResponse(('ok', ), 'foo bar baz'),
289
request.execute(backing.local_abspath('')))
292
class TestSmartServerBranchRequestSetLastRevision(tests.TestCaseWithTransport):
294
def test_empty(self):
295
backing = self.get_transport()
296
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
297
b = self.make_branch('.')
298
branch_token = b.lock_write()
299
repo_token = b.repository.lock_write()
300
b.repository.unlock()
302
self.assertEqual(SmartServerResponse(('ok',)),
304
backing.local_abspath(''), branch_token, repo_token,
309
def test_not_present_revision_id(self):
310
backing = self.get_transport()
311
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
312
b = self.make_branch('.')
313
branch_token = b.lock_write()
314
repo_token = b.repository.lock_write()
315
b.repository.unlock()
317
revision_id = 'non-existent revision'
319
SmartServerResponse(('NoSuchRevision', revision_id)),
321
backing.local_abspath(''), branch_token, repo_token,
326
def test_revision_id_present(self):
327
backing = self.get_transport()
328
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
329
tree = self.make_branch_and_memory_tree('.')
332
rev_id_utf8 = u'\xc8'.encode('utf-8')
333
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
334
r2 = tree.commit('2nd commit')
336
branch_token = tree.branch.lock_write()
337
repo_token = tree.branch.repository.lock_write()
338
tree.branch.repository.unlock()
341
SmartServerResponse(('ok',)),
343
backing.local_abspath(''), branch_token, repo_token,
345
self.assertEqual([rev_id_utf8], tree.branch.revision_history())
349
def test_revision_id_present2(self):
350
backing = self.get_transport()
351
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
352
tree = self.make_branch_and_memory_tree('.')
355
rev_id_utf8 = u'\xc8'.encode('utf-8')
356
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
357
r2 = tree.commit('2nd commit')
359
tree.branch.set_revision_history([])
360
branch_token = tree.branch.lock_write()
361
repo_token = tree.branch.repository.lock_write()
362
tree.branch.repository.unlock()
365
SmartServerResponse(('ok',)),
367
backing.local_abspath(''), branch_token, repo_token,
369
self.assertEqual([rev_id_utf8], tree.branch.revision_history())
374
class TestSmartServerBranchRequestLockWrite(tests.TestCaseWithTransport):
377
tests.TestCaseWithTransport.setUp(self)
378
self.reduceLockdirTimeout()
380
def test_lock_write_on_unlocked_branch(self):
381
backing = self.get_transport()
382
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
383
branch = self.make_branch('.', format='knit')
384
repository = branch.repository
385
response = request.execute(backing.local_abspath(''))
386
branch_nonce = branch.control_files._lock.peek().get('nonce')
387
repository_nonce = repository.control_files._lock.peek().get('nonce')
389
SmartServerResponse(('ok', branch_nonce, repository_nonce)),
391
# The branch (and associated repository) is now locked. Verify that
392
# with a new branch object.
393
new_branch = repository.bzrdir.open_branch()
394
self.assertRaises(errors.LockContention, new_branch.lock_write)
396
def test_lock_write_on_locked_branch(self):
397
backing = self.get_transport()
398
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
399
branch = self.make_branch('.')
401
branch.leave_lock_in_place()
403
response = request.execute(backing.local_abspath(''))
405
SmartServerResponse(('LockContention',)), response)
407
def test_lock_write_with_tokens_on_locked_branch(self):
408
backing = self.get_transport()
409
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
410
branch = self.make_branch('.', format='knit')
411
branch_token = branch.lock_write()
412
repo_token = branch.repository.lock_write()
413
branch.repository.unlock()
414
branch.leave_lock_in_place()
415
branch.repository.leave_lock_in_place()
417
response = request.execute(backing.local_abspath(''),
418
branch_token, repo_token)
420
SmartServerResponse(('ok', branch_token, repo_token)), response)
422
def test_lock_write_with_mismatched_tokens_on_locked_branch(self):
423
backing = self.get_transport()
424
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
425
branch = self.make_branch('.', format='knit')
426
branch_token = branch.lock_write()
427
repo_token = branch.repository.lock_write()
428
branch.repository.unlock()
429
branch.leave_lock_in_place()
430
branch.repository.leave_lock_in_place()
432
response = request.execute(backing.local_abspath(''),
433
branch_token+'xxx', repo_token)
435
SmartServerResponse(('TokenMismatch',)), response)
437
def test_lock_write_on_locked_repo(self):
438
backing = self.get_transport()
439
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
440
branch = self.make_branch('.', format='knit')
441
branch.repository.lock_write()
442
branch.repository.leave_lock_in_place()
443
branch.repository.unlock()
444
response = request.execute(backing.local_abspath(''))
446
SmartServerResponse(('LockContention',)), response)
448
def test_lock_write_on_readonly_transport(self):
449
backing = self.get_readonly_transport()
450
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
451
branch = self.make_branch('.')
452
response = request.execute('')
453
error_name, lock_str, why_str = response.args
454
self.assertFalse(response.is_successful())
455
self.assertEqual('LockFailed', error_name)
458
class TestSmartServerBranchRequestUnlock(tests.TestCaseWithTransport):
461
tests.TestCaseWithTransport.setUp(self)
462
self.reduceLockdirTimeout()
464
def test_unlock_on_locked_branch_and_repo(self):
465
backing = self.get_transport()
466
request = smart.branch.SmartServerBranchRequestUnlock(backing)
467
branch = self.make_branch('.', format='knit')
469
branch_token = branch.lock_write()
470
repo_token = branch.repository.lock_write()
471
branch.repository.unlock()
472
# Unlock the branch (and repo) object, leaving the physical locks
474
branch.leave_lock_in_place()
475
branch.repository.leave_lock_in_place()
477
response = request.execute(backing.local_abspath(''),
478
branch_token, repo_token)
480
SmartServerResponse(('ok',)), response)
481
# The branch is now unlocked. Verify that with a new branch
483
new_branch = branch.bzrdir.open_branch()
484
new_branch.lock_write()
487
def test_unlock_on_unlocked_branch_unlocked_repo(self):
488
backing = self.get_transport()
489
request = smart.branch.SmartServerBranchRequestUnlock(backing)
490
branch = self.make_branch('.', format='knit')
491
response = request.execute(
492
backing.local_abspath(''), 'branch token', 'repo token')
494
SmartServerResponse(('TokenMismatch',)), response)
496
def test_unlock_on_unlocked_branch_locked_repo(self):
497
backing = self.get_transport()
498
request = smart.branch.SmartServerBranchRequestUnlock(backing)
499
branch = self.make_branch('.', format='knit')
500
# Lock the repository.
501
repo_token = branch.repository.lock_write()
502
branch.repository.leave_lock_in_place()
503
branch.repository.unlock()
504
# Issue branch lock_write request on the unlocked branch (with locked
506
response = request.execute(
507
backing.local_abspath(''), 'branch token', repo_token)
509
SmartServerResponse(('TokenMismatch',)), response)
512
class TestSmartServerRepositoryRequest(tests.TestCaseWithTransport):
514
def test_no_repository(self):
515
"""Raise NoRepositoryPresent when there is a bzrdir and no repo."""
516
# we test this using a shared repository above the named path,
517
# thus checking the right search logic is used - that is, that
518
# its the exact path being looked at and the server is not
520
backing = self.get_transport()
521
request = smart.repository.SmartServerRepositoryRequest(backing)
522
self.make_repository('.', shared=True)
523
self.make_bzrdir('subdir')
524
self.assertRaises(errors.NoRepositoryPresent,
525
request.execute, backing.local_abspath('subdir'))
528
class TestSmartServerRepositoryGetRevisionGraph(tests.TestCaseWithTransport):
530
def test_none_argument(self):
531
backing = self.get_transport()
532
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
533
tree = self.make_branch_and_memory_tree('.')
536
r1 = tree.commit('1st commit')
537
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
540
# the lines of revision_id->revision_parent_list has no guaranteed
541
# order coming out of a dict, so sort both our test and response
542
lines = sorted([' '.join([r2, r1]), r1])
543
response = request.execute(backing.local_abspath(''), '')
544
response.body = '\n'.join(sorted(response.body.split('\n')))
547
SmartServerResponse(('ok', ), '\n'.join(lines)), response)
549
def test_specific_revision_argument(self):
550
backing = self.get_transport()
551
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
552
tree = self.make_branch_and_memory_tree('.')
555
rev_id_utf8 = u'\xc9'.encode('utf-8')
556
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
557
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
560
self.assertEqual(SmartServerResponse(('ok', ), rev_id_utf8),
561
request.execute(backing.local_abspath(''), rev_id_utf8))
563
def test_no_such_revision(self):
564
backing = self.get_transport()
565
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
566
tree = self.make_branch_and_memory_tree('.')
569
r1 = tree.commit('1st commit')
572
# Note that it still returns body (of zero bytes).
574
SmartServerResponse(('nosuchrevision', 'missingrevision', ), ''),
575
request.execute(backing.local_abspath(''), 'missingrevision'))
578
class TestSmartServerRequestHasRevision(tests.TestCaseWithTransport):
580
def test_missing_revision(self):
581
"""For a missing revision, ('no', ) is returned."""
582
backing = self.get_transport()
583
request = smart.repository.SmartServerRequestHasRevision(backing)
584
self.make_repository('.')
585
self.assertEqual(SmartServerResponse(('no', )),
586
request.execute(backing.local_abspath(''), 'revid'))
588
def test_present_revision(self):
589
"""For a present revision, ('yes', ) is returned."""
590
backing = self.get_transport()
591
request = smart.repository.SmartServerRequestHasRevision(backing)
592
tree = self.make_branch_and_memory_tree('.')
595
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
596
r1 = tree.commit('a commit', rev_id=rev_id_utf8)
598
self.assertTrue(tree.branch.repository.has_revision(rev_id_utf8))
599
self.assertEqual(SmartServerResponse(('yes', )),
600
request.execute(backing.local_abspath(''), rev_id_utf8))
603
class TestSmartServerRepositoryGatherStats(tests.TestCaseWithTransport):
605
def test_empty_revid(self):
606
"""With an empty revid, we get only size an number and revisions"""
607
backing = self.get_transport()
608
request = smart.repository.SmartServerRepositoryGatherStats(backing)
609
repository = self.make_repository('.')
610
stats = repository.gather_stats()
612
expected_body = 'revisions: 0\nsize: %d\n' % size
613
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
614
request.execute(backing.local_abspath(''), '', 'no'))
616
def test_revid_with_committers(self):
617
"""For a revid we get more infos."""
618
backing = self.get_transport()
619
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
620
request = smart.repository.SmartServerRepositoryGatherStats(backing)
621
tree = self.make_branch_and_memory_tree('.')
624
# Let's build a predictable result
625
tree.commit('a commit', timestamp=123456.2, timezone=3600)
626
tree.commit('a commit', timestamp=654321.4, timezone=0,
630
stats = tree.branch.repository.gather_stats()
632
expected_body = ('firstrev: 123456.200 3600\n'
633
'latestrev: 654321.400 0\n'
636
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
637
request.execute(backing.local_abspath(''),
640
def test_not_empty_repository_with_committers(self):
641
"""For a revid and requesting committers we get the whole thing."""
642
backing = self.get_transport()
643
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
644
request = smart.repository.SmartServerRepositoryGatherStats(backing)
645
tree = self.make_branch_and_memory_tree('.')
648
# Let's build a predictable result
649
tree.commit('a commit', timestamp=123456.2, timezone=3600,
651
tree.commit('a commit', timestamp=654321.4, timezone=0,
652
committer='bar', rev_id=rev_id_utf8)
654
stats = tree.branch.repository.gather_stats()
657
expected_body = ('committers: 2\n'
658
'firstrev: 123456.200 3600\n'
659
'latestrev: 654321.400 0\n'
662
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
663
request.execute(backing.local_abspath(''),
667
class TestSmartServerRepositoryIsShared(tests.TestCaseWithTransport):
669
def test_is_shared(self):
670
"""For a shared repository, ('yes', ) is returned."""
671
backing = self.get_transport()
672
request = smart.repository.SmartServerRepositoryIsShared(backing)
673
self.make_repository('.', shared=True)
674
self.assertEqual(SmartServerResponse(('yes', )),
675
request.execute(backing.local_abspath(''), ))
677
def test_is_not_shared(self):
678
"""For a shared repository, ('no', ) is returned."""
679
backing = self.get_transport()
680
request = smart.repository.SmartServerRepositoryIsShared(backing)
681
self.make_repository('.', shared=False)
682
self.assertEqual(SmartServerResponse(('no', )),
683
request.execute(backing.local_abspath(''), ))
686
class TestSmartServerRepositoryLockWrite(tests.TestCaseWithTransport):
689
tests.TestCaseWithTransport.setUp(self)
690
self.reduceLockdirTimeout()
692
def test_lock_write_on_unlocked_repo(self):
693
backing = self.get_transport()
694
request = smart.repository.SmartServerRepositoryLockWrite(backing)
695
repository = self.make_repository('.', format='knit')
696
response = request.execute(backing.local_abspath(''))
697
nonce = repository.control_files._lock.peek().get('nonce')
698
self.assertEqual(SmartServerResponse(('ok', nonce)), response)
699
# The repository is now locked. Verify that with a new repository
701
new_repo = repository.bzrdir.open_repository()
702
self.assertRaises(errors.LockContention, new_repo.lock_write)
704
def test_lock_write_on_locked_repo(self):
705
backing = self.get_transport()
706
request = smart.repository.SmartServerRepositoryLockWrite(backing)
707
repository = self.make_repository('.', format='knit')
708
repository.lock_write()
709
repository.leave_lock_in_place()
711
response = request.execute(backing.local_abspath(''))
713
SmartServerResponse(('LockContention',)), response)
715
def test_lock_write_on_readonly_transport(self):
716
backing = self.get_readonly_transport()
717
request = smart.repository.SmartServerRepositoryLockWrite(backing)
718
repository = self.make_repository('.', format='knit')
719
response = request.execute('')
720
self.assertFalse(response.is_successful())
721
self.assertEqual('LockFailed', response.args[0])
724
class TestSmartServerRepositoryUnlock(tests.TestCaseWithTransport):
727
tests.TestCaseWithTransport.setUp(self)
728
self.reduceLockdirTimeout()
730
def test_unlock_on_locked_repo(self):
731
backing = self.get_transport()
732
request = smart.repository.SmartServerRepositoryUnlock(backing)
733
repository = self.make_repository('.', format='knit')
734
token = repository.lock_write()
735
repository.leave_lock_in_place()
737
response = request.execute(backing.local_abspath(''), token)
739
SmartServerResponse(('ok',)), response)
740
# The repository is now unlocked. Verify that with a new repository
742
new_repo = repository.bzrdir.open_repository()
743
new_repo.lock_write()
746
def test_unlock_on_unlocked_repo(self):
747
backing = self.get_transport()
748
request = smart.repository.SmartServerRepositoryUnlock(backing)
749
repository = self.make_repository('.', format='knit')
750
response = request.execute(backing.local_abspath(''), 'some token')
752
SmartServerResponse(('TokenMismatch',)), response)
755
class TestSmartServerRepositoryTarball(tests.TestCaseWithTransport):
757
def test_repository_tarball(self):
758
backing = self.get_transport()
759
request = smart.repository.SmartServerRepositoryTarball(backing)
760
repository = self.make_repository('.')
761
# make some extraneous junk in the repository directory which should
763
self.build_tree(['.bzr/repository/extra-junk'])
764
response = request.execute(backing.local_abspath(''), 'bz2')
765
self.assertEqual(('ok',), response.args)
766
# body should be a tbz2
767
body_file = StringIO(response.body)
768
body_tar = tarfile.open('body_tar.tbz2', fileobj=body_file,
770
# let's make sure there are some key repository components inside it.
771
# the tarfile returns directories with trailing slashes...
772
names = set([n.rstrip('/') for n in body_tar.getnames()])
773
self.assertTrue('.bzr/repository/lock' in names)
774
self.assertTrue('.bzr/repository/format' in names)
775
self.assertTrue('.bzr/repository/extra-junk' not in names,
776
"extraneous file present in tar file")
779
class TestSmartServerRepositoryStreamKnitData(tests.TestCaseWithTransport):
781
def test_fetch_revisions(self):
782
backing = self.get_transport()
783
request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
784
tree = self.make_branch_and_memory_tree('.')
787
rev_id1_utf8 = u'\xc8'.encode('utf-8')
788
rev_id2_utf8 = u'\xc9'.encode('utf-8')
789
r1 = tree.commit('1st commit', rev_id=rev_id1_utf8)
790
r1 = tree.commit('2nd commit', rev_id=rev_id2_utf8)
793
response = request.execute(backing.local_abspath(''), rev_id2_utf8)
794
self.assertEqual(('ok',), response.args)
795
from cStringIO import StringIO
796
unpacker = pack.ContainerReader(StringIO(response.body))
798
for [name], read_bytes in unpacker.iter_records():
800
bytes = read_bytes(None)
801
# The bytes should be a valid bencoded string.
802
bencode.bdecode(bytes)
803
# XXX: assert that the bencoded knit records have the right
806
def test_no_such_revision_error(self):
807
backing = self.get_transport()
808
request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
809
repo = self.make_repository('.')
810
rev_id1_utf8 = u'\xc8'.encode('utf-8')
811
response = request.execute(backing.local_abspath(''), rev_id1_utf8)
813
SmartServerResponse(('NoSuchRevision', rev_id1_utf8)),
817
class TestSmartServerIsReadonly(tests.TestCaseWithTransport):
819
def test_is_readonly_no(self):
820
backing = self.get_transport()
821
request = smart.request.SmartServerIsReadonly(backing)
822
response = request.execute()
824
SmartServerResponse(('no',)), response)
826
def test_is_readonly_yes(self):
827
backing = self.get_readonly_transport()
828
request = smart.request.SmartServerIsReadonly(backing)
829
response = request.execute()
831
SmartServerResponse(('yes',)), response)
834
class TestHandlers(tests.TestCase):
835
"""Tests for the request.request_handlers object."""
837
def test_registered_methods(self):
838
"""Test that known methods are registered to the correct object."""
840
smart.request.request_handlers.get('Branch.get_config_file'),
841
smart.branch.SmartServerBranchGetConfigFile)
843
smart.request.request_handlers.get('Branch.lock_write'),
844
smart.branch.SmartServerBranchRequestLockWrite)
846
smart.request.request_handlers.get('Branch.last_revision_info'),
847
smart.branch.SmartServerBranchRequestLastRevisionInfo)
849
smart.request.request_handlers.get('Branch.revision_history'),
850
smart.branch.SmartServerRequestRevisionHistory)
852
smart.request.request_handlers.get('Branch.set_last_revision'),
853
smart.branch.SmartServerBranchRequestSetLastRevision)
855
smart.request.request_handlers.get('Branch.unlock'),
856
smart.branch.SmartServerBranchRequestUnlock)
858
smart.request.request_handlers.get('BzrDir.find_repository'),
859
smart.bzrdir.SmartServerRequestFindRepository)
861
smart.request.request_handlers.get('BzrDirFormat.initialize'),
862
smart.bzrdir.SmartServerRequestInitializeBzrDir)
864
smart.request.request_handlers.get('BzrDir.open_branch'),
865
smart.bzrdir.SmartServerRequestOpenBranch)
867
smart.request.request_handlers.get('Repository.gather_stats'),
868
smart.repository.SmartServerRepositoryGatherStats)
870
smart.request.request_handlers.get(
871
'Repository.get_revision_graph'),
872
smart.repository.SmartServerRepositoryGetRevisionGraph)
874
smart.request.request_handlers.get('Repository.has_revision'),
875
smart.repository.SmartServerRequestHasRevision)
877
smart.request.request_handlers.get('Repository.is_shared'),
878
smart.repository.SmartServerRepositoryIsShared)
880
smart.request.request_handlers.get('Repository.lock_write'),
881
smart.repository.SmartServerRepositoryLockWrite)
883
smart.request.request_handlers.get(
884
'Repository.stream_knit_data_for_revisions'),
885
smart.repository.SmartServerRepositoryStreamKnitDataForRevisions)
887
smart.request.request_handlers.get('Repository.tarball'),
888
smart.repository.SmartServerRepositoryTarball)
890
smart.request.request_handlers.get('Repository.unlock'),
891
smart.repository.SmartServerRepositoryUnlock)
893
smart.request.request_handlers.get('Transport.is_readonly'),
894
smart.request.SmartServerIsReadonly)