~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_smart.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-11-03 23:02:16 UTC
  • mfrom: (2951.1.1 pack)
  • Revision ID: pqm@pqm.ubuntu.com-20071103230216-mnmwuxm413lyhjdv
(robertc) Fix data-refresh logic for packs not to refresh mid-transaction when a names write lock is held. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
"""Tests for the smart wire/domain protocol."""
 
18
 
 
19
from StringIO import StringIO
 
20
import tempfile
 
21
import tarfile
 
22
 
 
23
from bzrlib import bzrdir, errors, pack, smart, tests
 
24
from bzrlib.smart.request import SmartServerResponse
 
25
import bzrlib.smart.bzrdir
 
26
import bzrlib.smart.branch
 
27
import bzrlib.smart.repository
 
28
from bzrlib.util import bencode
 
29
 
 
30
 
 
31
class TestCaseWithSmartMedium(tests.TestCaseWithTransport):
 
32
 
 
33
    def setUp(self):
 
34
        super(TestCaseWithSmartMedium, self).setUp()
 
35
        # We're allowed to set  the transport class here, so that we don't use
 
36
        # the default or a parameterized class, but rather use the
 
37
        # TestCaseWithTransport infrastructure to set up a smart server and
 
38
        # transport.
 
39
        self.transport_server = smart.server.SmartTCPServer_for_testing
 
40
 
 
41
    def get_smart_medium(self):
 
42
        """Get a smart medium to use in tests."""
 
43
        return self.get_transport().get_smart_medium()
 
44
 
 
45
 
 
46
class TestSmartServerResponse(tests.TestCase):
 
47
 
 
48
    def test__eq__(self):
 
49
        self.assertEqual(SmartServerResponse(('ok', )),
 
50
            SmartServerResponse(('ok', )))
 
51
        self.assertEqual(SmartServerResponse(('ok', ), 'body'),
 
52
            SmartServerResponse(('ok', ), 'body'))
 
53
        self.assertNotEqual(SmartServerResponse(('ok', )),
 
54
            SmartServerResponse(('notok', )))
 
55
        self.assertNotEqual(SmartServerResponse(('ok', ), 'body'),
 
56
            SmartServerResponse(('ok', )))
 
57
        self.assertNotEqual(None,
 
58
            SmartServerResponse(('ok', )))
 
59
 
 
60
 
 
61
class TestSmartServerRequestFindRepository(tests.TestCaseWithTransport):
 
62
    """Tests for BzrDir.find_repository."""
 
63
 
 
64
    def test_no_repository(self):
 
65
        """When there is no repository to be found, ('norepository', ) is returned."""
 
66
        backing = self.get_transport()
 
67
        request = smart.bzrdir.SmartServerRequestFindRepository(backing)
 
68
        self.make_bzrdir('.')
 
69
        self.assertEqual(SmartServerResponse(('norepository', )),
 
70
            request.execute(backing.local_abspath('')))
 
71
 
 
72
    def test_nonshared_repository(self):
 
73
        # nonshared repositorys only allow 'find' to return a handle when the 
 
74
        # path the repository is being searched on is the same as that that 
 
75
        # the repository is at.
 
76
        backing = self.get_transport()
 
77
        request = smart.bzrdir.SmartServerRequestFindRepository(backing)
 
78
        result = self._make_repository_and_result()
 
79
        self.assertEqual(result, request.execute(backing.local_abspath('')))
 
80
        self.make_bzrdir('subdir')
 
81
        self.assertEqual(SmartServerResponse(('norepository', )),
 
82
            request.execute(backing.local_abspath('subdir')))
 
83
 
 
84
    def _make_repository_and_result(self, shared=False, format=None):
 
85
        """Convenience function to setup a repository.
 
86
 
 
87
        :result: The SmartServerResponse to expect when opening it.
 
88
        """
 
89
        repo = self.make_repository('.', shared=shared, format=format)
 
90
        if repo.supports_rich_root():
 
91
            rich_root = 'yes'
 
92
        else:
 
93
            rich_root = 'no'
 
94
        if repo._format.supports_tree_reference:
 
95
            subtrees = 'yes'
 
96
        else:
 
97
            subtrees = 'no'
 
98
        return SmartServerResponse(('ok', '', rich_root, subtrees))
 
99
 
 
100
    def test_shared_repository(self):
 
101
        """When there is a shared repository, we get 'ok', 'relpath-to-repo'."""
 
102
        backing = self.get_transport()
 
103
        request = smart.bzrdir.SmartServerRequestFindRepository(backing)
 
104
        result = self._make_repository_and_result(shared=True)
 
105
        self.assertEqual(result, request.execute(backing.local_abspath('')))
 
106
        self.make_bzrdir('subdir')
 
107
        result2 = SmartServerResponse(result.args[0:1] + ('..', ) + result.args[2:])
 
108
        self.assertEqual(result2,
 
109
            request.execute(backing.local_abspath('subdir')))
 
110
        self.make_bzrdir('subdir/deeper')
 
111
        result3 = SmartServerResponse(result.args[0:1] + ('../..', ) + result.args[2:])
 
112
        self.assertEqual(result3,
 
113
            request.execute(backing.local_abspath('subdir/deeper')))
 
114
 
 
115
    def test_rich_root_and_subtree_encoding(self):
 
116
        """Test for the format attributes for rich root and subtree support."""
 
117
        backing = self.get_transport()
 
118
        request = smart.bzrdir.SmartServerRequestFindRepository(backing)
 
119
        result = self._make_repository_and_result(format='dirstate-with-subtree')
 
120
        # check the test will be valid
 
121
        self.assertEqual('yes', result.args[2])
 
122
        self.assertEqual('yes', result.args[3])
 
123
        self.assertEqual(result, request.execute(backing.local_abspath('')))
 
124
 
 
125
 
 
126
class TestSmartServerRequestInitializeBzrDir(tests.TestCaseWithTransport):
 
127
 
 
128
    def test_empty_dir(self):
 
129
        """Initializing an empty dir should succeed and do it."""
 
130
        backing = self.get_transport()
 
131
        request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
 
132
        self.assertEqual(SmartServerResponse(('ok', )),
 
133
            request.execute(backing.local_abspath('.')))
 
134
        made_dir = bzrdir.BzrDir.open_from_transport(backing)
 
135
        # no branch, tree or repository is expected with the current 
 
136
        # default formart.
 
137
        self.assertRaises(errors.NoWorkingTree, made_dir.open_workingtree)
 
138
        self.assertRaises(errors.NotBranchError, made_dir.open_branch)
 
139
        self.assertRaises(errors.NoRepositoryPresent, made_dir.open_repository)
 
140
 
 
141
    def test_missing_dir(self):
 
142
        """Initializing a missing directory should fail like the bzrdir api."""
 
143
        backing = self.get_transport()
 
144
        request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
 
145
        self.assertRaises(errors.NoSuchFile,
 
146
            request.execute, backing.local_abspath('subdir'))
 
147
 
 
148
    def test_initialized_dir(self):
 
149
        """Initializing an extant bzrdir should fail like the bzrdir api."""
 
150
        backing = self.get_transport()
 
151
        request = smart.bzrdir.SmartServerRequestInitializeBzrDir(backing)
 
152
        self.make_bzrdir('subdir')
 
153
        self.assertRaises(errors.FileExists,
 
154
            request.execute, backing.local_abspath('subdir'))
 
155
 
 
156
 
 
157
class TestSmartServerRequestOpenBranch(tests.TestCaseWithTransport):
 
158
 
 
159
    def test_no_branch(self):
 
160
        """When there is no branch, ('nobranch', ) is returned."""
 
161
        backing = self.get_transport()
 
162
        request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
 
163
        self.make_bzrdir('.')
 
164
        self.assertEqual(SmartServerResponse(('nobranch', )),
 
165
            request.execute(backing.local_abspath('')))
 
166
 
 
167
    def test_branch(self):
 
168
        """When there is a branch, 'ok' is returned."""
 
169
        backing = self.get_transport()
 
170
        request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
 
171
        self.make_branch('.')
 
172
        self.assertEqual(SmartServerResponse(('ok', '')),
 
173
            request.execute(backing.local_abspath('')))
 
174
 
 
175
    def test_branch_reference(self):
 
176
        """When there is a branch reference, the reference URL is returned."""
 
177
        backing = self.get_transport()
 
178
        request = smart.bzrdir.SmartServerRequestOpenBranch(backing)
 
179
        branch = self.make_branch('branch')
 
180
        checkout = branch.create_checkout('reference',lightweight=True)
 
181
        # TODO: once we have an API to probe for references of any sort, we
 
182
        # can use it here.
 
183
        reference_url = backing.abspath('branch') + '/'
 
184
        self.assertFileEqual(reference_url, 'reference/.bzr/branch/location')
 
185
        self.assertEqual(SmartServerResponse(('ok', reference_url)),
 
186
            request.execute(backing.local_abspath('reference')))
 
187
 
 
188
 
 
189
class TestSmartServerRequestRevisionHistory(tests.TestCaseWithTransport):
 
190
 
 
191
    def test_empty(self):
 
192
        """For an empty branch, the body is empty."""
 
193
        backing = self.get_transport()
 
194
        request = smart.branch.SmartServerRequestRevisionHistory(backing)
 
195
        self.make_branch('.')
 
196
        self.assertEqual(SmartServerResponse(('ok', ), ''),
 
197
            request.execute(backing.local_abspath('')))
 
198
 
 
199
    def test_not_empty(self):
 
200
        """For a non-empty branch, the body is empty."""
 
201
        backing = self.get_transport()
 
202
        request = smart.branch.SmartServerRequestRevisionHistory(backing)
 
203
        tree = self.make_branch_and_memory_tree('.')
 
204
        tree.lock_write()
 
205
        tree.add('')
 
206
        r1 = tree.commit('1st commit')
 
207
        r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
 
208
        tree.unlock()
 
209
        self.assertEqual(
 
210
            SmartServerResponse(('ok', ), ('\x00'.join([r1, r2]))),
 
211
            request.execute(backing.local_abspath('')))
 
212
 
 
213
 
 
214
class TestSmartServerBranchRequest(tests.TestCaseWithTransport):
 
215
 
 
216
    def test_no_branch(self):
 
217
        """When there is a bzrdir and no branch, NotBranchError is raised."""
 
218
        backing = self.get_transport()
 
219
        request = smart.branch.SmartServerBranchRequest(backing)
 
220
        self.make_bzrdir('.')
 
221
        self.assertRaises(errors.NotBranchError,
 
222
            request.execute, backing.local_abspath(''))
 
223
 
 
224
    def test_branch_reference(self):
 
225
        """When there is a branch reference, NotBranchError is raised."""
 
226
        backing = self.get_transport()
 
227
        request = smart.branch.SmartServerBranchRequest(backing)
 
228
        branch = self.make_branch('branch')
 
229
        checkout = branch.create_checkout('reference',lightweight=True)
 
230
        self.assertRaises(errors.NotBranchError,
 
231
            request.execute, backing.local_abspath('checkout'))
 
232
 
 
233
 
 
234
class TestSmartServerBranchRequestLastRevisionInfo(tests.TestCaseWithTransport):
 
235
 
 
236
    def test_empty(self):
 
237
        """For an empty branch, the result is ('ok', '0', 'null:')."""
 
238
        backing = self.get_transport()
 
239
        request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
 
240
        self.make_branch('.')
 
241
        self.assertEqual(SmartServerResponse(('ok', '0', 'null:')),
 
242
            request.execute(backing.local_abspath('')))
 
243
 
 
244
    def test_not_empty(self):
 
245
        """For a non-empty branch, the result is ('ok', 'revno', 'revid')."""
 
246
        backing = self.get_transport()
 
247
        request = smart.branch.SmartServerBranchRequestLastRevisionInfo(backing)
 
248
        tree = self.make_branch_and_memory_tree('.')
 
249
        tree.lock_write()
 
250
        tree.add('')
 
251
        rev_id_utf8 = u'\xc8'.encode('utf-8')
 
252
        r1 = tree.commit('1st commit')
 
253
        r2 = tree.commit('2nd commit', rev_id=rev_id_utf8)
 
254
        tree.unlock()
 
255
        self.assertEqual(
 
256
            SmartServerResponse(('ok', '2', rev_id_utf8)),
 
257
            request.execute(backing.local_abspath('')))
 
258
 
 
259
 
 
260
class TestSmartServerBranchRequestGetConfigFile(tests.TestCaseWithTransport):
 
261
 
 
262
    def test_default(self):
 
263
        """With no file, we get empty content."""
 
264
        backing = self.get_transport()
 
265
        request = smart.branch.SmartServerBranchGetConfigFile(backing)
 
266
        branch = self.make_branch('.')
 
267
        # there should be no file by default
 
268
        content = ''
 
269
        self.assertEqual(SmartServerResponse(('ok', ), content),
 
270
            request.execute(backing.local_abspath('')))
 
271
 
 
272
    def test_with_content(self):
 
273
        # SmartServerBranchGetConfigFile should return the content from
 
274
        # branch.control_files.get('branch.conf') for now - in the future it may
 
275
        # perform more complex processing. 
 
276
        backing = self.get_transport()
 
277
        request = smart.branch.SmartServerBranchGetConfigFile(backing)
 
278
        branch = self.make_branch('.')
 
279
        branch.control_files.put_utf8('branch.conf', 'foo bar baz')
 
280
        self.assertEqual(SmartServerResponse(('ok', ), 'foo bar baz'),
 
281
            request.execute(backing.local_abspath('')))
 
282
 
 
283
 
 
284
class TestSmartServerBranchRequestSetLastRevision(tests.TestCaseWithTransport):
 
285
 
 
286
    def test_empty(self):
 
287
        backing = self.get_transport()
 
288
        request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
 
289
        b = self.make_branch('.')
 
290
        branch_token = b.lock_write()
 
291
        repo_token = b.repository.lock_write()
 
292
        b.repository.unlock()
 
293
        try:
 
294
            self.assertEqual(SmartServerResponse(('ok',)),
 
295
                request.execute(
 
296
                    backing.local_abspath(''), branch_token, repo_token,
 
297
                    'null:'))
 
298
        finally:
 
299
            b.unlock()
 
300
 
 
301
    def test_not_present_revision_id(self):
 
302
        backing = self.get_transport()
 
303
        request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
 
304
        b = self.make_branch('.')
 
305
        branch_token = b.lock_write()
 
306
        repo_token = b.repository.lock_write()
 
307
        b.repository.unlock()
 
308
        try:
 
309
            revision_id = 'non-existent revision'
 
310
            self.assertEqual(
 
311
                SmartServerResponse(('NoSuchRevision', revision_id)),
 
312
                request.execute(
 
313
                    backing.local_abspath(''), branch_token, repo_token,
 
314
                    revision_id))
 
315
        finally:
 
316
            b.unlock()
 
317
 
 
318
    def test_revision_id_present(self):
 
319
        backing = self.get_transport()
 
320
        request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
 
321
        tree = self.make_branch_and_memory_tree('.')
 
322
        tree.lock_write()
 
323
        tree.add('')
 
324
        rev_id_utf8 = u'\xc8'.encode('utf-8')
 
325
        r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
 
326
        r2 = tree.commit('2nd commit')
 
327
        tree.unlock()
 
328
        branch_token = tree.branch.lock_write()
 
329
        repo_token = tree.branch.repository.lock_write()
 
330
        tree.branch.repository.unlock()
 
331
        try:
 
332
            self.assertEqual(
 
333
                SmartServerResponse(('ok',)),
 
334
                request.execute(
 
335
                    backing.local_abspath(''), branch_token, repo_token,
 
336
                    rev_id_utf8))
 
337
            self.assertEqual([rev_id_utf8], tree.branch.revision_history())
 
338
        finally:
 
339
            tree.branch.unlock()
 
340
 
 
341
    def test_revision_id_present2(self):
 
342
        backing = self.get_transport()
 
343
        request = smart.branch.SmartServerBranchRequestSetLastRevision(backing)
 
344
        tree = self.make_branch_and_memory_tree('.')
 
345
        tree.lock_write()
 
346
        tree.add('')
 
347
        rev_id_utf8 = u'\xc8'.encode('utf-8')
 
348
        r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
 
349
        r2 = tree.commit('2nd commit')
 
350
        tree.unlock()
 
351
        tree.branch.set_revision_history([])
 
352
        branch_token = tree.branch.lock_write()
 
353
        repo_token = tree.branch.repository.lock_write()
 
354
        tree.branch.repository.unlock()
 
355
        try:
 
356
            self.assertEqual(
 
357
                SmartServerResponse(('ok',)),
 
358
                request.execute(
 
359
                    backing.local_abspath(''), branch_token, repo_token,
 
360
                    rev_id_utf8))
 
361
            self.assertEqual([rev_id_utf8], tree.branch.revision_history())
 
362
        finally:
 
363
            tree.branch.unlock()
 
364
 
 
365
 
 
366
class TestSmartServerBranchRequestLockWrite(tests.TestCaseWithTransport):
 
367
 
 
368
    def setUp(self):
 
369
        tests.TestCaseWithTransport.setUp(self)
 
370
        self.reduceLockdirTimeout()
 
371
 
 
372
    def test_lock_write_on_unlocked_branch(self):
 
373
        backing = self.get_transport()
 
374
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
375
        branch = self.make_branch('.')
 
376
        repository = branch.repository
 
377
        response = request.execute(backing.local_abspath(''))
 
378
        branch_nonce = branch.control_files._lock.peek().get('nonce')
 
379
        repository_nonce = repository.control_files._lock.peek().get('nonce')
 
380
        self.assertEqual(
 
381
            SmartServerResponse(('ok', branch_nonce, repository_nonce)),
 
382
            response)
 
383
        # The branch (and associated repository) is now locked.  Verify that
 
384
        # with a new branch object.
 
385
        new_branch = repository.bzrdir.open_branch()
 
386
        self.assertRaises(errors.LockContention, new_branch.lock_write)
 
387
 
 
388
    def test_lock_write_on_locked_branch(self):
 
389
        backing = self.get_transport()
 
390
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
391
        branch = self.make_branch('.')
 
392
        branch.lock_write()
 
393
        branch.leave_lock_in_place()
 
394
        branch.unlock()
 
395
        response = request.execute(backing.local_abspath(''))
 
396
        self.assertEqual(
 
397
            SmartServerResponse(('LockContention',)), response)
 
398
 
 
399
    def test_lock_write_with_tokens_on_locked_branch(self):
 
400
        backing = self.get_transport()
 
401
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
402
        branch = self.make_branch('.')
 
403
        branch_token = branch.lock_write()
 
404
        repo_token = branch.repository.lock_write()
 
405
        branch.repository.unlock()
 
406
        branch.leave_lock_in_place()
 
407
        branch.repository.leave_lock_in_place()
 
408
        branch.unlock()
 
409
        response = request.execute(backing.local_abspath(''),
 
410
                                   branch_token, repo_token)
 
411
        self.assertEqual(
 
412
            SmartServerResponse(('ok', branch_token, repo_token)), response)
 
413
 
 
414
    def test_lock_write_with_mismatched_tokens_on_locked_branch(self):
 
415
        backing = self.get_transport()
 
416
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
417
        branch = self.make_branch('.')
 
418
        branch_token = branch.lock_write()
 
419
        repo_token = branch.repository.lock_write()
 
420
        branch.repository.unlock()
 
421
        branch.leave_lock_in_place()
 
422
        branch.repository.leave_lock_in_place()
 
423
        branch.unlock()
 
424
        response = request.execute(backing.local_abspath(''),
 
425
                                   branch_token+'xxx', repo_token)
 
426
        self.assertEqual(
 
427
            SmartServerResponse(('TokenMismatch',)), response)
 
428
 
 
429
    def test_lock_write_on_locked_repo(self):
 
430
        backing = self.get_transport()
 
431
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
432
        branch = self.make_branch('.')
 
433
        branch.repository.lock_write()
 
434
        branch.repository.leave_lock_in_place()
 
435
        branch.repository.unlock()
 
436
        response = request.execute(backing.local_abspath(''))
 
437
        self.assertEqual(
 
438
            SmartServerResponse(('LockContention',)), response)
 
439
 
 
440
    def test_lock_write_on_readonly_transport(self):
 
441
        backing = self.get_readonly_transport()
 
442
        request = smart.branch.SmartServerBranchRequestLockWrite(backing)
 
443
        branch = self.make_branch('.')
 
444
        response = request.execute('')
 
445
        error_name, lock_str, why_str = response.args
 
446
        self.assertFalse(response.is_successful())
 
447
        self.assertEqual('LockFailed', error_name)
 
448
 
 
449
 
 
450
class TestSmartServerBranchRequestUnlock(tests.TestCaseWithTransport):
 
451
 
 
452
    def setUp(self):
 
453
        tests.TestCaseWithTransport.setUp(self)
 
454
        self.reduceLockdirTimeout()
 
455
 
 
456
    def test_unlock_on_locked_branch_and_repo(self):
 
457
        backing = self.get_transport()
 
458
        request = smart.branch.SmartServerBranchRequestUnlock(backing)
 
459
        branch = self.make_branch('.')
 
460
        # Lock the branch
 
461
        branch_token = branch.lock_write()
 
462
        repo_token = branch.repository.lock_write()
 
463
        branch.repository.unlock()
 
464
        # Unlock the branch (and repo) object, leaving the physical locks
 
465
        # in place.
 
466
        branch.leave_lock_in_place()
 
467
        branch.repository.leave_lock_in_place()
 
468
        branch.unlock()
 
469
        response = request.execute(backing.local_abspath(''),
 
470
                                   branch_token, repo_token)
 
471
        self.assertEqual(
 
472
            SmartServerResponse(('ok',)), response)
 
473
        # The branch is now unlocked.  Verify that with a new branch
 
474
        # object.
 
475
        new_branch = branch.bzrdir.open_branch()
 
476
        new_branch.lock_write()
 
477
        new_branch.unlock()
 
478
 
 
479
    def test_unlock_on_unlocked_branch_unlocked_repo(self):
 
480
        backing = self.get_transport()
 
481
        request = smart.branch.SmartServerBranchRequestUnlock(backing)
 
482
        branch = self.make_branch('.')
 
483
        response = request.execute(
 
484
            backing.local_abspath(''), 'branch token', 'repo token')
 
485
        self.assertEqual(
 
486
            SmartServerResponse(('TokenMismatch',)), response)
 
487
 
 
488
    def test_unlock_on_unlocked_branch_locked_repo(self):
 
489
        backing = self.get_transport()
 
490
        request = smart.branch.SmartServerBranchRequestUnlock(backing)
 
491
        branch = self.make_branch('.')
 
492
        # Lock the repository.
 
493
        repo_token = branch.repository.lock_write()
 
494
        branch.repository.leave_lock_in_place()
 
495
        branch.repository.unlock()
 
496
        # Issue branch lock_write request on the unlocked branch (with locked
 
497
        # repo).
 
498
        response = request.execute(
 
499
            backing.local_abspath(''), 'branch token', repo_token)
 
500
        self.assertEqual(
 
501
            SmartServerResponse(('TokenMismatch',)), response)
 
502
 
 
503
 
 
504
class TestSmartServerRepositoryRequest(tests.TestCaseWithTransport):
 
505
 
 
506
    def test_no_repository(self):
 
507
        """Raise NoRepositoryPresent when there is a bzrdir and no repo."""
 
508
        # we test this using a shared repository above the named path,
 
509
        # thus checking the right search logic is used - that is, that
 
510
        # its the exact path being looked at and the server is not
 
511
        # searching.
 
512
        backing = self.get_transport()
 
513
        request = smart.repository.SmartServerRepositoryRequest(backing)
 
514
        self.make_repository('.', shared=True)
 
515
        self.make_bzrdir('subdir')
 
516
        self.assertRaises(errors.NoRepositoryPresent,
 
517
            request.execute, backing.local_abspath('subdir'))
 
518
 
 
519
 
 
520
class TestSmartServerRepositoryGetRevisionGraph(tests.TestCaseWithTransport):
 
521
 
 
522
    def test_none_argument(self):
 
523
        backing = self.get_transport()
 
524
        request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
 
525
        tree = self.make_branch_and_memory_tree('.')
 
526
        tree.lock_write()
 
527
        tree.add('')
 
528
        r1 = tree.commit('1st commit')
 
529
        r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
 
530
        tree.unlock()
 
531
 
 
532
        # the lines of revision_id->revision_parent_list has no guaranteed
 
533
        # order coming out of a dict, so sort both our test and response
 
534
        lines = sorted([' '.join([r2, r1]), r1])
 
535
        response = request.execute(backing.local_abspath(''), '')
 
536
        response.body = '\n'.join(sorted(response.body.split('\n')))
 
537
 
 
538
        self.assertEqual(
 
539
            SmartServerResponse(('ok', ), '\n'.join(lines)), response)
 
540
 
 
541
    def test_specific_revision_argument(self):
 
542
        backing = self.get_transport()
 
543
        request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
 
544
        tree = self.make_branch_and_memory_tree('.')
 
545
        tree.lock_write()
 
546
        tree.add('')
 
547
        rev_id_utf8 = u'\xc9'.encode('utf-8')
 
548
        r1 = tree.commit('1st commit', rev_id=rev_id_utf8)
 
549
        r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
 
550
        tree.unlock()
 
551
 
 
552
        self.assertEqual(SmartServerResponse(('ok', ), rev_id_utf8),
 
553
            request.execute(backing.local_abspath(''), rev_id_utf8))
 
554
    
 
555
    def test_no_such_revision(self):
 
556
        backing = self.get_transport()
 
557
        request = smart.repository.SmartServerRepositoryGetRevisionGraph(backing)
 
558
        tree = self.make_branch_and_memory_tree('.')
 
559
        tree.lock_write()
 
560
        tree.add('')
 
561
        r1 = tree.commit('1st commit')
 
562
        tree.unlock()
 
563
 
 
564
        # Note that it still returns body (of zero bytes).
 
565
        self.assertEqual(
 
566
            SmartServerResponse(('nosuchrevision', 'missingrevision', ), ''),
 
567
            request.execute(backing.local_abspath(''), 'missingrevision'))
 
568
 
 
569
 
 
570
class TestSmartServerRequestHasRevision(tests.TestCaseWithTransport):
 
571
 
 
572
    def test_missing_revision(self):
 
573
        """For a missing revision, ('no', ) is returned."""
 
574
        backing = self.get_transport()
 
575
        request = smart.repository.SmartServerRequestHasRevision(backing)
 
576
        self.make_repository('.')
 
577
        self.assertEqual(SmartServerResponse(('no', )),
 
578
            request.execute(backing.local_abspath(''), 'revid'))
 
579
 
 
580
    def test_present_revision(self):
 
581
        """For a present revision, ('yes', ) is returned."""
 
582
        backing = self.get_transport()
 
583
        request = smart.repository.SmartServerRequestHasRevision(backing)
 
584
        tree = self.make_branch_and_memory_tree('.')
 
585
        tree.lock_write()
 
586
        tree.add('')
 
587
        rev_id_utf8 = u'\xc8abc'.encode('utf-8')
 
588
        r1 = tree.commit('a commit', rev_id=rev_id_utf8)
 
589
        tree.unlock()
 
590
        self.assertTrue(tree.branch.repository.has_revision(rev_id_utf8))
 
591
        self.assertEqual(SmartServerResponse(('yes', )),
 
592
            request.execute(backing.local_abspath(''), rev_id_utf8))
 
593
 
 
594
 
 
595
class TestSmartServerRepositoryGatherStats(tests.TestCaseWithTransport):
 
596
 
 
597
    def test_empty_revid(self):
 
598
        """With an empty revid, we get only size an number and revisions"""
 
599
        backing = self.get_transport()
 
600
        request = smart.repository.SmartServerRepositoryGatherStats(backing)
 
601
        repository = self.make_repository('.')
 
602
        stats = repository.gather_stats()
 
603
        size = stats['size']
 
604
        expected_body = 'revisions: 0\nsize: %d\n' % size
 
605
        self.assertEqual(SmartServerResponse(('ok', ), expected_body),
 
606
                         request.execute(backing.local_abspath(''), '', 'no'))
 
607
 
 
608
    def test_revid_with_committers(self):
 
609
        """For a revid we get more infos."""
 
610
        backing = self.get_transport()
 
611
        rev_id_utf8 = u'\xc8abc'.encode('utf-8')
 
612
        request = smart.repository.SmartServerRepositoryGatherStats(backing)
 
613
        tree = self.make_branch_and_memory_tree('.')
 
614
        tree.lock_write()
 
615
        tree.add('')
 
616
        # Let's build a predictable result
 
617
        tree.commit('a commit', timestamp=123456.2, timezone=3600)
 
618
        tree.commit('a commit', timestamp=654321.4, timezone=0,
 
619
                    rev_id=rev_id_utf8)
 
620
        tree.unlock()
 
621
 
 
622
        stats = tree.branch.repository.gather_stats()
 
623
        size = stats['size']
 
624
        expected_body = ('firstrev: 123456.200 3600\n'
 
625
                         'latestrev: 654321.400 0\n'
 
626
                         'revisions: 2\n'
 
627
                         'size: %d\n' % size)
 
628
        self.assertEqual(SmartServerResponse(('ok', ), expected_body),
 
629
                         request.execute(backing.local_abspath(''),
 
630
                                         rev_id_utf8, 'no'))
 
631
 
 
632
    def test_not_empty_repository_with_committers(self):
 
633
        """For a revid and requesting committers we get the whole thing."""
 
634
        backing = self.get_transport()
 
635
        rev_id_utf8 = u'\xc8abc'.encode('utf-8')
 
636
        request = smart.repository.SmartServerRepositoryGatherStats(backing)
 
637
        tree = self.make_branch_and_memory_tree('.')
 
638
        tree.lock_write()
 
639
        tree.add('')
 
640
        # Let's build a predictable result
 
641
        tree.commit('a commit', timestamp=123456.2, timezone=3600,
 
642
                    committer='foo')
 
643
        tree.commit('a commit', timestamp=654321.4, timezone=0,
 
644
                    committer='bar', rev_id=rev_id_utf8)
 
645
        tree.unlock()
 
646
        stats = tree.branch.repository.gather_stats()
 
647
 
 
648
        size = stats['size']
 
649
        expected_body = ('committers: 2\n'
 
650
                         'firstrev: 123456.200 3600\n'
 
651
                         'latestrev: 654321.400 0\n'
 
652
                         'revisions: 2\n'
 
653
                         'size: %d\n' % size)
 
654
        self.assertEqual(SmartServerResponse(('ok', ), expected_body),
 
655
                         request.execute(backing.local_abspath(''),
 
656
                                         rev_id_utf8, 'yes'))
 
657
 
 
658
 
 
659
class TestSmartServerRepositoryIsShared(tests.TestCaseWithTransport):
 
660
 
 
661
    def test_is_shared(self):
 
662
        """For a shared repository, ('yes', ) is returned."""
 
663
        backing = self.get_transport()
 
664
        request = smart.repository.SmartServerRepositoryIsShared(backing)
 
665
        self.make_repository('.', shared=True)
 
666
        self.assertEqual(SmartServerResponse(('yes', )),
 
667
            request.execute(backing.local_abspath(''), ))
 
668
 
 
669
    def test_is_not_shared(self):
 
670
        """For a shared repository, ('no', ) is returned."""
 
671
        backing = self.get_transport()
 
672
        request = smart.repository.SmartServerRepositoryIsShared(backing)
 
673
        self.make_repository('.', shared=False)
 
674
        self.assertEqual(SmartServerResponse(('no', )),
 
675
            request.execute(backing.local_abspath(''), ))
 
676
 
 
677
 
 
678
class TestSmartServerRepositoryLockWrite(tests.TestCaseWithTransport):
 
679
 
 
680
    def setUp(self):
 
681
        tests.TestCaseWithTransport.setUp(self)
 
682
        self.reduceLockdirTimeout()
 
683
 
 
684
    def test_lock_write_on_unlocked_repo(self):
 
685
        backing = self.get_transport()
 
686
        request = smart.repository.SmartServerRepositoryLockWrite(backing)
 
687
        repository = self.make_repository('.')
 
688
        response = request.execute(backing.local_abspath(''))
 
689
        nonce = repository.control_files._lock.peek().get('nonce')
 
690
        self.assertEqual(SmartServerResponse(('ok', nonce)), response)
 
691
        # The repository is now locked.  Verify that with a new repository
 
692
        # object.
 
693
        new_repo = repository.bzrdir.open_repository()
 
694
        self.assertRaises(errors.LockContention, new_repo.lock_write)
 
695
 
 
696
    def test_lock_write_on_locked_repo(self):
 
697
        backing = self.get_transport()
 
698
        request = smart.repository.SmartServerRepositoryLockWrite(backing)
 
699
        repository = self.make_repository('.')
 
700
        repository.lock_write()
 
701
        repository.leave_lock_in_place()
 
702
        repository.unlock()
 
703
        response = request.execute(backing.local_abspath(''))
 
704
        self.assertEqual(
 
705
            SmartServerResponse(('LockContention',)), response)
 
706
 
 
707
    def test_lock_write_on_readonly_transport(self):
 
708
        backing = self.get_readonly_transport()
 
709
        request = smart.repository.SmartServerRepositoryLockWrite(backing)
 
710
        repository = self.make_repository('.')
 
711
        response = request.execute('')
 
712
        self.assertFalse(response.is_successful())
 
713
        self.assertEqual('LockFailed', response.args[0])
 
714
 
 
715
 
 
716
class TestSmartServerRepositoryUnlock(tests.TestCaseWithTransport):
 
717
 
 
718
    def setUp(self):
 
719
        tests.TestCaseWithTransport.setUp(self)
 
720
        self.reduceLockdirTimeout()
 
721
 
 
722
    def test_unlock_on_locked_repo(self):
 
723
        backing = self.get_transport()
 
724
        request = smart.repository.SmartServerRepositoryUnlock(backing)
 
725
        repository = self.make_repository('.')
 
726
        token = repository.lock_write()
 
727
        repository.leave_lock_in_place()
 
728
        repository.unlock()
 
729
        response = request.execute(backing.local_abspath(''), token)
 
730
        self.assertEqual(
 
731
            SmartServerResponse(('ok',)), response)
 
732
        # The repository is now unlocked.  Verify that with a new repository
 
733
        # object.
 
734
        new_repo = repository.bzrdir.open_repository()
 
735
        new_repo.lock_write()
 
736
        new_repo.unlock()
 
737
 
 
738
    def test_unlock_on_unlocked_repo(self):
 
739
        backing = self.get_transport()
 
740
        request = smart.repository.SmartServerRepositoryUnlock(backing)
 
741
        repository = self.make_repository('.')
 
742
        response = request.execute(backing.local_abspath(''), 'some token')
 
743
        self.assertEqual(
 
744
            SmartServerResponse(('TokenMismatch',)), response)
 
745
 
 
746
 
 
747
class TestSmartServerRepositoryTarball(tests.TestCaseWithTransport):
 
748
 
 
749
    def test_repository_tarball(self):
 
750
        backing = self.get_transport()
 
751
        request = smart.repository.SmartServerRepositoryTarball(backing)
 
752
        repository = self.make_repository('.')
 
753
        # make some extraneous junk in the repository directory which should
 
754
        # not be copied
 
755
        self.build_tree(['.bzr/repository/extra-junk'])
 
756
        response = request.execute(backing.local_abspath(''), 'bz2')
 
757
        self.assertEqual(('ok',), response.args)
 
758
        # body should be a tbz2
 
759
        body_file = StringIO(response.body)
 
760
        body_tar = tarfile.open('body_tar.tbz2', fileobj=body_file,
 
761
            mode='r|bz2')
 
762
        # let's make sure there are some key repository components inside it.
 
763
        # the tarfile returns directories with trailing slashes...
 
764
        names = set([n.rstrip('/') for n in body_tar.getnames()])
 
765
        self.assertTrue('.bzr/repository/lock' in names)
 
766
        self.assertTrue('.bzr/repository/format' in names)
 
767
        self.assertTrue('.bzr/repository/extra-junk' not in names,
 
768
            "extraneous file present in tar file")
 
769
 
 
770
 
 
771
class TestSmartServerRepositoryStreamKnitData(tests.TestCaseWithTransport):
 
772
 
 
773
    def test_fetch_revisions(self):
 
774
        backing = self.get_transport()
 
775
        request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
 
776
        tree = self.make_branch_and_memory_tree('.')
 
777
        tree.lock_write()
 
778
        tree.add('')
 
779
        rev_id1_utf8 = u'\xc8'.encode('utf-8')
 
780
        rev_id2_utf8 = u'\xc9'.encode('utf-8')
 
781
        r1 = tree.commit('1st commit', rev_id=rev_id1_utf8)
 
782
        r1 = tree.commit('2nd commit', rev_id=rev_id2_utf8)
 
783
        tree.unlock()
 
784
 
 
785
        response = request.execute(backing.local_abspath(''), rev_id2_utf8)
 
786
        self.assertEqual(('ok',), response.args)
 
787
        from cStringIO import StringIO
 
788
        unpacker = pack.ContainerReader(StringIO(response.body))
 
789
        names = []
 
790
        for [name], read_bytes in unpacker.iter_records():
 
791
            names.append(name)
 
792
            bytes = read_bytes(None)
 
793
            # The bytes should be a valid bencoded string.
 
794
            bencode.bdecode(bytes)
 
795
            # XXX: assert that the bencoded knit records have the right
 
796
            # contents?
 
797
        
 
798
    def test_no_such_revision_error(self):
 
799
        backing = self.get_transport()
 
800
        request = smart.repository.SmartServerRepositoryStreamKnitDataForRevisions(backing)
 
801
        repo = self.make_repository('.')
 
802
        rev_id1_utf8 = u'\xc8'.encode('utf-8')
 
803
        response = request.execute(backing.local_abspath(''), rev_id1_utf8)
 
804
        self.assertEqual(
 
805
            SmartServerResponse(('NoSuchRevision', rev_id1_utf8)),
 
806
            response)
 
807
 
 
808
 
 
809
class TestSmartServerIsReadonly(tests.TestCaseWithTransport):
 
810
 
 
811
    def test_is_readonly_no(self):
 
812
        backing = self.get_transport()
 
813
        request = smart.request.SmartServerIsReadonly(backing)
 
814
        response = request.execute()
 
815
        self.assertEqual(
 
816
            SmartServerResponse(('no',)), response)
 
817
 
 
818
    def test_is_readonly_yes(self):
 
819
        backing = self.get_readonly_transport()
 
820
        request = smart.request.SmartServerIsReadonly(backing)
 
821
        response = request.execute()
 
822
        self.assertEqual(
 
823
            SmartServerResponse(('yes',)), response)
 
824
 
 
825
 
 
826
class TestHandlers(tests.TestCase):
 
827
    """Tests for the request.request_handlers object."""
 
828
 
 
829
    def test_registered_methods(self):
 
830
        """Test that known methods are registered to the correct object."""
 
831
        self.assertEqual(
 
832
            smart.request.request_handlers.get('Branch.get_config_file'),
 
833
            smart.branch.SmartServerBranchGetConfigFile)
 
834
        self.assertEqual(
 
835
            smart.request.request_handlers.get('Branch.lock_write'),
 
836
            smart.branch.SmartServerBranchRequestLockWrite)
 
837
        self.assertEqual(
 
838
            smart.request.request_handlers.get('Branch.last_revision_info'),
 
839
            smart.branch.SmartServerBranchRequestLastRevisionInfo)
 
840
        self.assertEqual(
 
841
            smart.request.request_handlers.get('Branch.revision_history'),
 
842
            smart.branch.SmartServerRequestRevisionHistory)
 
843
        self.assertEqual(
 
844
            smart.request.request_handlers.get('Branch.set_last_revision'),
 
845
            smart.branch.SmartServerBranchRequestSetLastRevision)
 
846
        self.assertEqual(
 
847
            smart.request.request_handlers.get('Branch.unlock'),
 
848
            smart.branch.SmartServerBranchRequestUnlock)
 
849
        self.assertEqual(
 
850
            smart.request.request_handlers.get('BzrDir.find_repository'),
 
851
            smart.bzrdir.SmartServerRequestFindRepository)
 
852
        self.assertEqual(
 
853
            smart.request.request_handlers.get('BzrDirFormat.initialize'),
 
854
            smart.bzrdir.SmartServerRequestInitializeBzrDir)
 
855
        self.assertEqual(
 
856
            smart.request.request_handlers.get('BzrDir.open_branch'),
 
857
            smart.bzrdir.SmartServerRequestOpenBranch)
 
858
        self.assertEqual(
 
859
            smart.request.request_handlers.get('Repository.gather_stats'),
 
860
            smart.repository.SmartServerRepositoryGatherStats)
 
861
        self.assertEqual(
 
862
            smart.request.request_handlers.get(
 
863
                'Repository.get_revision_graph'),
 
864
            smart.repository.SmartServerRepositoryGetRevisionGraph)
 
865
        self.assertEqual(
 
866
            smart.request.request_handlers.get('Repository.has_revision'),
 
867
            smart.repository.SmartServerRequestHasRevision)
 
868
        self.assertEqual(
 
869
            smart.request.request_handlers.get('Repository.is_shared'),
 
870
            smart.repository.SmartServerRepositoryIsShared)
 
871
        self.assertEqual(
 
872
            smart.request.request_handlers.get('Repository.lock_write'),
 
873
            smart.repository.SmartServerRepositoryLockWrite)
 
874
        self.assertEqual(
 
875
            smart.request.request_handlers.get(
 
876
                'Repository.stream_knit_data_for_revisions'),
 
877
            smart.repository.SmartServerRepositoryStreamKnitDataForRevisions)
 
878
        self.assertEqual(
 
879
            smart.request.request_handlers.get('Repository.tarball'),
 
880
            smart.repository.SmartServerRepositoryTarball)
 
881
        self.assertEqual(
 
882
            smart.request.request_handlers.get('Repository.unlock'),
 
883
            smart.repository.SmartServerRepositoryUnlock)
 
884
        self.assertEqual(
 
885
            smart.request.request_handlers.get('Transport.is_readonly'),
 
886
            smart.request.SmartServerIsReadonly)