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 (
33
FailedSmartServerResponse,
35
SuccessfulSmartServerResponse,
37
import bzrlib.smart.bzrdir
38
import bzrlib.smart.branch
39
import bzrlib.smart.repository
40
from bzrlib.util import bencode
43
class TestCaseWithSmartMedium(tests.TestCaseWithTransport):
46
super(TestCaseWithSmartMedium, self).setUp()
47
# We're allowed to set the transport class here, so that we don't use
48
# the default or a parameterized class, but rather use the
49
# TestCaseWithTransport infrastructure to set up a smart server and
51
self.transport_server = smart.server.SmartTCPServer_for_testing
53
def get_smart_medium(self):
54
"""Get a smart medium to use in tests."""
55
return self.get_transport().get_smart_medium()
58
class TestSmartServerResponse(tests.TestCase):
61
self.assertEqual(SmartServerResponse(('ok', )),
62
SmartServerResponse(('ok', )))
63
self.assertEqual(SmartServerResponse(('ok', ), 'body'),
64
SmartServerResponse(('ok', ), 'body'))
65
self.assertNotEqual(SmartServerResponse(('ok', )),
66
SmartServerResponse(('notok', )))
67
self.assertNotEqual(SmartServerResponse(('ok', ), 'body'),
68
SmartServerResponse(('ok', )))
69
self.assertNotEqual(None,
70
SmartServerResponse(('ok', )))
73
class TestSmartServerRequestFindRepository(tests.TestCaseWithTransport):
74
"""Tests for BzrDir.find_repository."""
76
def test_no_repository(self):
77
"""When there is no repository to be found, ('norepository', ) is returned."""
78
backing = self.get_transport()
79
request = smart.bzrdir.SmartServerRequestFindRepository(backing)
81
self.assertEqual(SmartServerResponse(('norepository', )),
82
request.execute(backing.local_abspath('')))
84
def test_nonshared_repository(self):
85
# nonshared repositorys only allow 'find' to return a handle when the
86
# path the repository is being searched on is the same as that that
87
# the repository is at.
88
backing = self.get_transport()
89
request = smart.bzrdir.SmartServerRequestFindRepository(backing)
90
result = self._make_repository_and_result()
91
self.assertEqual(result, request.execute(backing.local_abspath('')))
92
self.make_bzrdir('subdir')
93
self.assertEqual(SmartServerResponse(('norepository', )),
94
request.execute(backing.local_abspath('subdir')))
96
def _make_repository_and_result(self, shared=False, format=None):
97
"""Convenience function to setup a repository.
99
:result: The SmartServerResponse to expect when opening it.
101
repo = self.make_repository('.', shared=shared, format=format)
102
if repo.supports_rich_root():
106
if repo._format.supports_tree_reference:
110
return SmartServerResponse(('ok', '', rich_root, subtrees))
112
def test_shared_repository(self):
113
"""When there is a shared repository, we get 'ok', 'relpath-to-repo'."""
114
backing = self.get_transport()
115
request = smart.bzrdir.SmartServerRequestFindRepository(backing)
116
result = self._make_repository_and_result(shared=True)
117
self.assertEqual(result, request.execute(backing.local_abspath('')))
118
self.make_bzrdir('subdir')
119
result2 = SmartServerResponse(result.args[0:1] + ('..', ) + result.args[2:])
120
self.assertEqual(result2,
121
request.execute(backing.local_abspath('subdir')))
122
self.make_bzrdir('subdir/deeper')
123
result3 = SmartServerResponse(result.args[0:1] + ('../..', ) + result.args[2:])
124
self.assertEqual(result3,
125
request.execute(backing.local_abspath('subdir/deeper')))
127
def test_rich_root_and_subtree_encoding(self):
128
"""Test for the format attributes for rich root and subtree support."""
129
backing = self.get_transport()
130
request = smart.bzrdir.SmartServerRequestFindRepository(backing)
131
result = self._make_repository_and_result(format='dirstate-with-subtree')
132
# check the test will be valid
133
self.assertEqual('yes', result.args[2])
134
self.assertEqual('yes', result.args[3])
135
self.assertEqual(result, request.execute(backing.local_abspath('')))
138
class TestSmartServerRequestInitializeBzrDir(tests.TestCaseWithTransport):
140
def test_empty_dir(self):
141
"""Initializing an empty dir should succeed and do it."""
142
backing = self.get_transport()
143
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
144
self.assertEqual(SmartServerResponse(('ok', )),
145
request.execute(backing.local_abspath('.')))
146
made_dir = bzrdir.BzrDir.open_from_transport(backing)
147
# no branch, tree or repository is expected with the current
149
self.assertRaises(errors.NoWorkingTree, made_dir.open_workingtree)
150
self.assertRaises(errors.NotBranchError, made_dir.open_branch)
151
self.assertRaises(errors.NoRepositoryPresent, made_dir.open_repository)
153
def test_missing_dir(self):
154
"""Initializing a missing directory should fail like the bzrdir api."""
155
backing = self.get_transport()
156
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
157
self.assertRaises(errors.NoSuchFile,
158
request.execute, backing.local_abspath('subdir'))
160
def test_initialized_dir(self):
161
"""Initializing an extant bzrdir should fail like the bzrdir api."""
162
backing = self.get_transport()
163
request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
164
self.make_bzrdir('subdir')
165
self.assertRaises(errors.FileExists,
166
request.execute, backing.local_abspath('subdir'))
169
class TestSmartServerRequestOpenBranch(tests.TestCaseWithTransport):
171
def test_no_branch(self):
172
"""When there is no branch, ('nobranch', ) is returned."""
173
backing = self.get_transport()
174
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
175
self.make_bzrdir('.')
176
self.assertEqual(SmartServerResponse(('nobranch', )),
177
request.execute(backing.local_abspath('')))
179
def test_branch(self):
180
"""When there is a branch, 'ok' is returned."""
181
backing = self.get_transport()
182
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
183
self.make_branch('.')
184
self.assertEqual(SmartServerResponse(('ok', '')),
185
request.execute(backing.local_abspath('')))
187
def test_branch_reference(self):
188
"""When there is a branch reference, the reference URL is returned."""
189
backing = self.get_transport()
190
request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
191
branch = self.make_branch('branch')
192
checkout = branch.create_checkout('reference',lightweight=True)
193
# TODO: once we have an API to probe for references of any sort, we
195
reference_url = backing.abspath('branch') + '/'
196
self.assertFileEqual(reference_url, 'reference/.bzr/branch/location')
197
self.assertEqual(SmartServerResponse(('ok', reference_url)),
198
request.execute(backing.local_abspath('reference')))
201
class TestSmartServerRequestRevisionHistory(tests.TestCaseWithTransport):
203
def test_empty(self):
204
"""For an empty branch, the body is empty."""
205
backing = self.get_transport()
206
request = smart.branch.SmartServerRequestRevisionHistory(backing)
207
self.make_branch('.')
208
self.assertEqual(SmartServerResponse(('ok', ), ''),
209
request.execute(backing.local_abspath('')))
211
def test_not_empty(self):
212
"""For a non-empty branch, the body is empty."""
213
backing = self.get_transport()
214
request = smart.branch.SmartServerRequestRevisionHistory(backing)
215
tree = self.make_branch_and_memory_tree('.')
218
r1 = tree.commit('1st commit')
219
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
222
SmartServerResponse(('ok', ), ('\x00'.join([r1, r2]))),
223
request.execute(backing.local_abspath('')))
226
class TestSmartServerBranchRequest(tests.TestCaseWithTransport):
228
def test_no_branch(self):
229
"""When there is a bzrdir and no branch, NotBranchError is raised."""
230
backing = self.get_transport()
231
request = smart.branch.SmartServerBranchRequest(backing)
232
self.make_bzrdir('.')
233
self.assertRaises(errors.NotBranchError,
234
request.execute, backing.local_abspath(''))
236
def test_branch_reference(self):
237
"""When there is a branch reference, NotBranchError is raised."""
238
backing = self.get_transport()
239
request = smart.branch.SmartServerBranchRequest(backing)
240
branch = self.make_branch('branch')
241
checkout = branch.create_checkout('reference',lightweight=True)
242
self.assertRaises(errors.NotBranchError,
243
request.execute, backing.local_abspath('checkout'))
246
class TestSmartServerBranchRequestLastRevisionInfo(tests.TestCaseWithTransport):
248
def test_empty(self):
249
"""For an empty branch, the result is ('ok', '0', 'null:')."""
250
backing = self.get_transport()
251
request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
252
self.make_branch('.')
253
self.assertEqual(SmartServerResponse(('ok', '0', 'null:')),
254
request.execute(backing.local_abspath('')))
256
def test_not_empty(self):
257
"""For a non-empty branch, the result is ('ok', 'revno', 'revid')."""
258
backing = self.get_transport()
259
request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
260
tree = self.make_branch_and_memory_tree('.')
263
rev_id_utf8 = u'\xc8'.encode('utf-8')
264
r1 = tree.commit('1st commit')
265
r2 = tree.commit('2nd commit', rev_id=rev_id_utf8)
268
SmartServerResponse(('ok', '2', rev_id_utf8)),
269
request.execute(backing.local_abspath('')))
272
class TestSmartServerBranchRequestGetConfigFile(tests.TestCaseWithTransport):
274
def test_default(self):
275
"""With no file, we get empty content."""
276
backing = self.get_transport()
277
request = smart.branch.SmartServerBranchGetConfigFile(backing)
278
branch = self.make_branch('.')
279
# there should be no file by default
281
self.assertEqual(SmartServerResponse(('ok', ), content),
282
request.execute(backing.local_abspath('')))
284
def test_with_content(self):
285
# SmartServerBranchGetConfigFile should return the content from
286
# branch.control_files.get('branch.conf') for now - in the future it may
287
# perform more complex processing.
288
backing = self.get_transport()
289
request = smart.branch.SmartServerBranchGetConfigFile(backing)
290
branch = self.make_branch('.')
291
branch.control_files.put_utf8('branch.conf', 'foo bar baz')
292
self.assertEqual(SmartServerResponse(('ok', ), 'foo bar baz'),
293
request.execute(backing.local_abspath('')))
296
class TestSmartServerBranchRequestSetLastRevision(tests.TestCaseWithTransport):
298
def test_empty(self):
299
backing = self.get_transport()
300
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
301
b = self.make_branch('.')
302
branch_token = b.lock_write()
303
repo_token = b.repository.lock_write()
304
b.repository.unlock()
306
self.assertEqual(SmartServerResponse(('ok',)),
308
backing.local_abspath(''), branch_token, repo_token,
313
def test_not_present_revision_id(self):
314
backing = self.get_transport()
315
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
316
b = self.make_branch('.')
317
branch_token = b.lock_write()
318
repo_token = b.repository.lock_write()
319
b.repository.unlock()
321
revision_id = 'non-existent revision'
323
SmartServerResponse(('NoSuchRevision', revision_id)),
325
backing.local_abspath(''), branch_token, repo_token,
330
def test_revision_id_present(self):
331
backing = self.get_transport()
332
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
333
tree = self.make_branch_and_memory_tree('.')
336
rev_id_utf8 = u'\xc8'.encode('utf-8')
337
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
338
r2 = tree.commit('2nd commit')
340
branch_token = tree.branch.lock_write()
341
repo_token = tree.branch.repository.lock_write()
342
tree.branch.repository.unlock()
345
SmartServerResponse(('ok',)),
347
backing.local_abspath(''), branch_token, repo_token,
349
self.assertEqual([rev_id_utf8], tree.branch.revision_history())
353
def test_revision_id_present2(self):
354
backing = self.get_transport()
355
request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
356
tree = self.make_branch_and_memory_tree('.')
359
rev_id_utf8 = u'\xc8'.encode('utf-8')
360
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
361
r2 = tree.commit('2nd commit')
363
tree.branch.set_revision_history([])
364
branch_token = tree.branch.lock_write()
365
repo_token = tree.branch.repository.lock_write()
366
tree.branch.repository.unlock()
369
SmartServerResponse(('ok',)),
371
backing.local_abspath(''), branch_token, repo_token,
373
self.assertEqual([rev_id_utf8], tree.branch.revision_history())
378
class TestSmartServerBranchRequestLockWrite(tests.TestCaseWithTransport):
381
tests.TestCaseWithTransport.setUp(self)
382
self.reduceLockdirTimeout()
384
def test_lock_write_on_unlocked_branch(self):
385
backing = self.get_transport()
386
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
387
branch = self.make_branch('.', format='knit')
388
repository = branch.repository
389
response = request.execute(backing.local_abspath(''))
390
branch_nonce = branch.control_files._lock.peek().get('nonce')
391
repository_nonce = repository.control_files._lock.peek().get('nonce')
393
SmartServerResponse(('ok', branch_nonce, repository_nonce)),
395
# The branch (and associated repository) is now locked. Verify that
396
# with a new branch object.
397
new_branch = repository.bzrdir.open_branch()
398
self.assertRaises(errors.LockContention, new_branch.lock_write)
400
def test_lock_write_on_locked_branch(self):
401
backing = self.get_transport()
402
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
403
branch = self.make_branch('.')
405
branch.leave_lock_in_place()
407
response = request.execute(backing.local_abspath(''))
409
SmartServerResponse(('LockContention',)), response)
411
def test_lock_write_with_tokens_on_locked_branch(self):
412
backing = self.get_transport()
413
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
414
branch = self.make_branch('.', format='knit')
415
branch_token = branch.lock_write()
416
repo_token = branch.repository.lock_write()
417
branch.repository.unlock()
418
branch.leave_lock_in_place()
419
branch.repository.leave_lock_in_place()
421
response = request.execute(backing.local_abspath(''),
422
branch_token, repo_token)
424
SmartServerResponse(('ok', branch_token, repo_token)), response)
426
def test_lock_write_with_mismatched_tokens_on_locked_branch(self):
427
backing = self.get_transport()
428
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
429
branch = self.make_branch('.', format='knit')
430
branch_token = branch.lock_write()
431
repo_token = branch.repository.lock_write()
432
branch.repository.unlock()
433
branch.leave_lock_in_place()
434
branch.repository.leave_lock_in_place()
436
response = request.execute(backing.local_abspath(''),
437
branch_token+'xxx', repo_token)
439
SmartServerResponse(('TokenMismatch',)), response)
441
def test_lock_write_on_locked_repo(self):
442
backing = self.get_transport()
443
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
444
branch = self.make_branch('.', format='knit')
445
branch.repository.lock_write()
446
branch.repository.leave_lock_in_place()
447
branch.repository.unlock()
448
response = request.execute(backing.local_abspath(''))
450
SmartServerResponse(('LockContention',)), response)
452
def test_lock_write_on_readonly_transport(self):
453
backing = self.get_readonly_transport()
454
request = smart.branch.SmartServerBranchRequestLockWrite(backing)
455
branch = self.make_branch('.')
456
response = request.execute('')
457
error_name, lock_str, why_str = response.args
458
self.assertFalse(response.is_successful())
459
self.assertEqual('LockFailed', error_name)
462
class TestSmartServerBranchRequestUnlock(tests.TestCaseWithTransport):
465
tests.TestCaseWithTransport.setUp(self)
466
self.reduceLockdirTimeout()
468
def test_unlock_on_locked_branch_and_repo(self):
469
backing = self.get_transport()
470
request = smart.branch.SmartServerBranchRequestUnlock(backing)
471
branch = self.make_branch('.', format='knit')
473
branch_token = branch.lock_write()
474
repo_token = branch.repository.lock_write()
475
branch.repository.unlock()
476
# Unlock the branch (and repo) object, leaving the physical locks
478
branch.leave_lock_in_place()
479
branch.repository.leave_lock_in_place()
481
response = request.execute(backing.local_abspath(''),
482
branch_token, repo_token)
484
SmartServerResponse(('ok',)), response)
485
# The branch is now unlocked. Verify that with a new branch
487
new_branch = branch.bzrdir.open_branch()
488
new_branch.lock_write()
491
def test_unlock_on_unlocked_branch_unlocked_repo(self):
492
backing = self.get_transport()
493
request = smart.branch.SmartServerBranchRequestUnlock(backing)
494
branch = self.make_branch('.', format='knit')
495
response = request.execute(
496
backing.local_abspath(''), 'branch token', 'repo token')
498
SmartServerResponse(('TokenMismatch',)), response)
500
def test_unlock_on_unlocked_branch_locked_repo(self):
501
backing = self.get_transport()
502
request = smart.branch.SmartServerBranchRequestUnlock(backing)
503
branch = self.make_branch('.', format='knit')
504
# Lock the repository.
505
repo_token = branch.repository.lock_write()
506
branch.repository.leave_lock_in_place()
507
branch.repository.unlock()
508
# Issue branch lock_write request on the unlocked branch (with locked
510
response = request.execute(
511
backing.local_abspath(''), 'branch token', repo_token)
513
SmartServerResponse(('TokenMismatch',)), response)
516
class TestSmartServerRepositoryRequest(tests.TestCaseWithTransport):
518
def test_no_repository(self):
519
"""Raise NoRepositoryPresent when there is a bzrdir and no repo."""
520
# we test this using a shared repository above the named path,
521
# thus checking the right search logic is used - that is, that
522
# its the exact path being looked at and the server is not
524
backing = self.get_transport()
525
request = smart.repository.SmartServerRepositoryRequest(backing)
526
self.make_repository('.', shared=True)
527
self.make_bzrdir('subdir')
528
self.assertRaises(errors.NoRepositoryPresent,
529
request.execute, backing.local_abspath('subdir'))
532
class TestSmartServerRepositoryGetRevisionGraph(tests.TestCaseWithTransport):
534
def test_none_argument(self):
535
backing = self.get_transport()
536
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
537
tree = self.make_branch_and_memory_tree('.')
540
r1 = tree.commit('1st commit')
541
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
544
# the lines of revision_id->revision_parent_list has no guaranteed
545
# order coming out of a dict, so sort both our test and response
546
lines = sorted([' '.join([r2, r1]), r1])
547
response = request.execute(backing.local_abspath(''), '')
548
response.body = '\n'.join(sorted(response.body.split('\n')))
551
SmartServerResponse(('ok', ), '\n'.join(lines)), response)
553
def test_specific_revision_argument(self):
554
backing = self.get_transport()
555
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
556
tree = self.make_branch_and_memory_tree('.')
559
rev_id_utf8 = u'\xc9'.encode('utf-8')
560
r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
561
r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
564
self.assertEqual(SmartServerResponse(('ok', ), rev_id_utf8),
565
request.execute(backing.local_abspath(''), rev_id_utf8))
567
def test_no_such_revision(self):
568
backing = self.get_transport()
569
request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
570
tree = self.make_branch_and_memory_tree('.')
573
r1 = tree.commit('1st commit')
576
# Note that it still returns body (of zero bytes).
578
SmartServerResponse(('nosuchrevision', 'missingrevision', ), ''),
579
request.execute(backing.local_abspath(''), 'missingrevision'))
582
class TestSmartServerRequestHasRevision(tests.TestCaseWithTransport):
584
def test_missing_revision(self):
585
"""For a missing revision, ('no', ) is returned."""
586
backing = self.get_transport()
587
request = smart.repository.SmartServerRequestHasRevision(backing)
588
self.make_repository('.')
589
self.assertEqual(SmartServerResponse(('no', )),
590
request.execute(backing.local_abspath(''), 'revid'))
592
def test_present_revision(self):
593
"""For a present revision, ('yes', ) is returned."""
594
backing = self.get_transport()
595
request = smart.repository.SmartServerRequestHasRevision(backing)
596
tree = self.make_branch_and_memory_tree('.')
599
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
600
r1 = tree.commit('a commit', rev_id=rev_id_utf8)
602
self.assertTrue(tree.branch.repository.has_revision(rev_id_utf8))
603
self.assertEqual(SmartServerResponse(('yes', )),
604
request.execute(backing.local_abspath(''), rev_id_utf8))
607
class TestSmartServerRepositoryGatherStats(tests.TestCaseWithTransport):
609
def test_empty_revid(self):
610
"""With an empty revid, we get only size an number and revisions"""
611
backing = self.get_transport()
612
request = smart.repository.SmartServerRepositoryGatherStats(backing)
613
repository = self.make_repository('.')
614
stats = repository.gather_stats()
616
expected_body = 'revisions: 0\nsize: %d\n' % size
617
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
618
request.execute(backing.local_abspath(''), '', 'no'))
620
def test_revid_with_committers(self):
621
"""For a revid we get more infos."""
622
backing = self.get_transport()
623
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
624
request = smart.repository.SmartServerRepositoryGatherStats(backing)
625
tree = self.make_branch_and_memory_tree('.')
628
# Let's build a predictable result
629
tree.commit('a commit', timestamp=123456.2, timezone=3600)
630
tree.commit('a commit', timestamp=654321.4, timezone=0,
634
stats = tree.branch.repository.gather_stats()
636
expected_body = ('firstrev: 123456.200 3600\n'
637
'latestrev: 654321.400 0\n'
640
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
641
request.execute(backing.local_abspath(''),
644
def test_not_empty_repository_with_committers(self):
645
"""For a revid and requesting committers we get the whole thing."""
646
backing = self.get_transport()
647
rev_id_utf8 = u'\xc8abc'.encode('utf-8')
648
request = smart.repository.SmartServerRepositoryGatherStats(backing)
649
tree = self.make_branch_and_memory_tree('.')
652
# Let's build a predictable result
653
tree.commit('a commit', timestamp=123456.2, timezone=3600,
655
tree.commit('a commit', timestamp=654321.4, timezone=0,
656
committer='bar', rev_id=rev_id_utf8)
658
stats = tree.branch.repository.gather_stats()
661
expected_body = ('committers: 2\n'
662
'firstrev: 123456.200 3600\n'
663
'latestrev: 654321.400 0\n'
666
self.assertEqual(SmartServerResponse(('ok', ), expected_body),
667
request.execute(backing.local_abspath(''),
671
class TestSmartServerRepositoryIsShared(tests.TestCaseWithTransport):
673
def test_is_shared(self):
674
"""For a shared repository, ('yes', ) is returned."""
675
backing = self.get_transport()
676
request = smart.repository.SmartServerRepositoryIsShared(backing)
677
self.make_repository('.', shared=True)
678
self.assertEqual(SmartServerResponse(('yes', )),
679
request.execute(backing.local_abspath(''), ))
681
def test_is_not_shared(self):
682
"""For a shared repository, ('no', ) is returned."""
683
backing = self.get_transport()
684
request = smart.repository.SmartServerRepositoryIsShared(backing)
685
self.make_repository('.', shared=False)
686
self.assertEqual(SmartServerResponse(('no', )),
687
request.execute(backing.local_abspath(''), ))
690
class TestSmartServerRepositoryLockWrite(tests.TestCaseWithTransport):
693
tests.TestCaseWithTransport.setUp(self)
694
self.reduceLockdirTimeout()
696
def test_lock_write_on_unlocked_repo(self):
697
backing = self.get_transport()
698
request = smart.repository.SmartServerRepositoryLockWrite(backing)
699
repository = self.make_repository('.', format='knit')
700
response = request.execute(backing.local_abspath(''))
701
nonce = repository.control_files._lock.peek().get('nonce')
702
self.assertEqual(SmartServerResponse(('ok', nonce)), response)
703
# The repository is now locked. Verify that with a new repository
705
new_repo = repository.bzrdir.open_repository()
706
self.assertRaises(errors.LockContention, new_repo.lock_write)
708
def test_lock_write_on_locked_repo(self):
709
backing = self.get_transport()
710
request = smart.repository.SmartServerRepositoryLockWrite(backing)
711
repository = self.make_repository('.', format='knit')
712
repository.lock_write()
713
repository.leave_lock_in_place()
715
response = request.execute(backing.local_abspath(''))
717
SmartServerResponse(('LockContention',)), response)
719
def test_lock_write_on_readonly_transport(self):
720
backing = self.get_readonly_transport()
721
request = smart.repository.SmartServerRepositoryLockWrite(backing)
722
repository = self.make_repository('.', format='knit')
723
response = request.execute('')
724
self.assertFalse(response.is_successful())
725
self.assertEqual('LockFailed', response.args[0])
728
class TestSmartServerRepositoryUnlock(tests.TestCaseWithTransport):
731
tests.TestCaseWithTransport.setUp(self)
732
self.reduceLockdirTimeout()
734
def test_unlock_on_locked_repo(self):
735
backing = self.get_transport()
736
request = smart.repository.SmartServerRepositoryUnlock(backing)
737
repository = self.make_repository('.', format='knit')
738
token = repository.lock_write()
739
repository.leave_lock_in_place()
741
response = request.execute(backing.local_abspath(''), token)
743
SmartServerResponse(('ok',)), response)
744
# The repository is now unlocked. Verify that with a new repository
746
new_repo = repository.bzrdir.open_repository()
747
new_repo.lock_write()
750
def test_unlock_on_unlocked_repo(self):
751
backing = self.get_transport()
752
request = smart.repository.SmartServerRepositoryUnlock(backing)
753
repository = self.make_repository('.', format='knit')
754
response = request.execute(backing.local_abspath(''), 'some token')
756
SmartServerResponse(('TokenMismatch',)), response)
759
class TestSmartServerRepositoryTarball(tests.TestCaseWithTransport):
761
def test_repository_tarball(self):
762
backing = self.get_transport()
763
request = smart.repository.SmartServerRepositoryTarball(backing)
764
repository = self.make_repository('.')
765
# make some extraneous junk in the repository directory which should
767
self.build_tree(['.bzr/repository/extra-junk'])
768
response = request.execute(backing.local_abspath(''), 'bz2')
769
self.assertEqual(('ok',), response.args)
770
# body should be a tbz2
771
body_file = StringIO(response.body)
772
body_tar = tarfile.open('body_tar.tbz2', fileobj=body_file,
774
# let's make sure there are some key repository components inside it.
775
# the tarfile returns directories with trailing slashes...
776
names = set([n.rstrip('/') for n in body_tar.getnames()])
777
self.assertTrue('.bzr/repository/lock' in names)
778
self.assertTrue('.bzr/repository/format' in names)
779
self.assertTrue('.bzr/repository/extra-junk' not in names,
780
"extraneous file present in tar file")
783
class TestSmartServerRepositoryStreamKnitData(tests.TestCaseWithTransport):
785
def test_fetch_revisions(self):
786
backing = self.get_transport()
787
request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
788
tree = self.make_branch_and_memory_tree('.')
791
rev_id1_utf8 = u'\xc8'.encode('utf-8')
792
rev_id2_utf8 = u'\xc9'.encode('utf-8')
793
r1 = tree.commit('1st commit', rev_id=rev_id1_utf8)
794
r1 = tree.commit('2nd commit', rev_id=rev_id2_utf8)
797
response = request.execute(backing.local_abspath(''), rev_id2_utf8)
798
self.assertEqual(('ok',), response.args)
799
from cStringIO import StringIO
800
unpacker = pack.ContainerReader(StringIO(response.body))
802
for [name], read_bytes in unpacker.iter_records():
804
bytes = read_bytes(None)
805
# The bytes should be a valid bencoded string.
806
bencode.bdecode(bytes)
807
# XXX: assert that the bencoded knit records have the right
810
def test_no_such_revision_error(self):
811
backing = self.get_transport()
812
request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
813
repo = self.make_repository('.')
814
rev_id1_utf8 = u'\xc8'.encode('utf-8')
815
response = request.execute(backing.local_abspath(''), rev_id1_utf8)
817
SmartServerResponse(('NoSuchRevision', rev_id1_utf8)),
821
class TestSmartServerRepositoryStreamRevisionsChunked(tests.TestCaseWithTransport):
823
def test_fetch_revisions(self):
824
backing = self.get_transport()
825
request = smart.repository.SmartServerRepositoryStreamRevisionsChunked(
827
tree = self.make_branch_and_memory_tree('.')
830
rev_id1_utf8 = u'\xc8'.encode('utf-8')
831
rev_id2_utf8 = u'\xc9'.encode('utf-8')
832
r1 = tree.commit('1st commit', rev_id=rev_id1_utf8)
833
r1 = tree.commit('2nd commit', rev_id=rev_id2_utf8)
836
response = request.execute(backing.local_abspath(''), rev_id2_utf8)
837
self.assertEqual(('ok',), response.args)
838
from cStringIO import StringIO
839
parser = pack.ContainerPushParser()
841
for stream_bytes in response.body_stream:
842
parser.accept_bytes(stream_bytes)
843
for [name], record_bytes in parser.read_pending_records():
845
# The bytes should be a valid bencoded string.
846
bencode.bdecode(record_bytes)
847
# XXX: assert that the bencoded knit records have the right
850
def test_no_such_revision_error(self):
851
backing = self.get_transport()
852
request = smart.repository.SmartServerRepositoryStreamRevisionsChunked(
854
repo = self.make_repository('.')
855
rev_id1_utf8 = u'\xc8'.encode('utf-8')
856
response = request.execute(backing.local_abspath(''), rev_id1_utf8)
857
# There's no error initially.
858
self.assertTrue(response.is_successful())
859
self.assertEqual(('ok',), response.args)
860
# We only get an error while streaming the body.
861
body = list(response.body_stream)
862
last_chunk = body[-1]
863
self.assertIsInstance(last_chunk, FailedSmartServerResponse)
866
FailedSmartServerResponse(('NoSuchRevision', rev_id1_utf8)))
869
class TestSmartServerIsReadonly(tests.TestCaseWithTransport):
871
def test_is_readonly_no(self):
872
backing = self.get_transport()
873
request = smart.request.SmartServerIsReadonly(backing)
874
response = request.execute()
876
SmartServerResponse(('no',)), response)
878
def test_is_readonly_yes(self):
879
backing = self.get_readonly_transport()
880
request = smart.request.SmartServerIsReadonly(backing)
881
response = request.execute()
883
SmartServerResponse(('yes',)), response)
886
class TestHandlers(tests.TestCase):
887
"""Tests for the request.request_handlers object."""
889
def test_registered_methods(self):
890
"""Test that known methods are registered to the correct object."""
892
smart.request.request_handlers.get('Branch.get_config_file'),
893
smart.branch.SmartServerBranchGetConfigFile)
895
smart.request.request_handlers.get('Branch.lock_write'),
896
smart.branch.SmartServerBranchRequestLockWrite)
898
smart.request.request_handlers.get('Branch.last_revision_info'),
899
smart.branch.SmartServerBranchRequestLastRevisionInfo)
901
smart.request.request_handlers.get('Branch.revision_history'),
902
smart.branch.SmartServerRequestRevisionHistory)
904
smart.request.request_handlers.get('Branch.set_last_revision'),
905
smart.branch.SmartServerBranchRequestSetLastRevision)
907
smart.request.request_handlers.get('Branch.unlock'),
908
smart.branch.SmartServerBranchRequestUnlock)
910
smart.request.request_handlers.get('BzrDir.find_repository'),
911
smart.bzrdir.SmartServerRequestFindRepository)
913
smart.request.request_handlers.get('BzrDirFormat.initialize'),
914
smart.bzrdir.SmartServerRequestInitializeBzrDir)
916
smart.request.request_handlers.get('BzrDir.open_branch'),
917
smart.bzrdir.SmartServerRequestOpenBranch)
919
smart.request.request_handlers.get('Repository.gather_stats'),
920
smart.repository.SmartServerRepositoryGatherStats)
922
smart.request.request_handlers.get('Repository.get_parent_map'),
923
smart.repository.SmartServerRepositoryGetParentMap)
925
smart.request.request_handlers.get(
926
'Repository.get_revision_graph'),
927
smart.repository.SmartServerRepositoryGetRevisionGraph)
929
smart.request.request_handlers.get('Repository.has_revision'),
930
smart.repository.SmartServerRequestHasRevision)
932
smart.request.request_handlers.get('Repository.is_shared'),
933
smart.repository.SmartServerRepositoryIsShared)
935
smart.request.request_handlers.get('Repository.lock_write'),
936
smart.repository.SmartServerRepositoryLockWrite)
938
smart.request.request_handlers.get(
939
'Repository.chunked_stream_knit_data_for_revisions'),
940
smart.repository.SmartServerRepositoryStreamKnitDataForRevisions)
942
smart.request.request_handlers.get('Repository.tarball'),
943
smart.repository.SmartServerRepositoryTarball)
945
smart.request.request_handlers.get('Repository.unlock'),
946
smart.repository.SmartServerRepositoryUnlock)
948
smart.request.request_handlers.get('Transport.is_readonly'),
949
smart.request.SmartServerIsReadonly)