~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_smart.py

  • Committer: John Arbash Meinel
  • Date: 2007-12-10 16:46:00 UTC
  • mto: (3112.1.1 bzr_access)
  • mto: This revision was merged to the branch mainline in revision 3165.
  • Revision ID: john@arbash-meinel.com-20071210164600-xcvl9fto3gn5aqtj
Change the indentation to 4 spaces according to Bazaar style guidelines.

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
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
 
22
properties.
 
23
 
 
24
Tests for low-level protocol encoding are found in test_smart_transport.
 
25
"""
 
26
 
 
27
from StringIO import StringIO
 
28
import tempfile
 
29
import tarfile
 
30
 
 
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
 
37
 
 
38
 
 
39
class TestCaseWithSmartMedium(tests.TestCaseWithTransport):
 
40
 
 
41
    def setUp(self):
 
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
 
46
        # transport.
 
47
        self.transport_server = smart.server.SmartTCPServer_for_testing
 
48
 
 
49
    def get_smart_medium(self):
 
50
        """Get a smart medium to use in tests."""
 
51
        return self.get_transport().get_smart_medium()
 
52
 
 
53
 
 
54
class TestSmartServerResponse(tests.TestCase):
 
55
 
 
56
    def test__eq__(self):
 
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', )))
 
67
 
 
68
 
 
69
class TestSmartServerRequestFindRepository(tests.TestCaseWithTransport):
 
70
    """Tests for BzrDir.find_repository."""
 
71
 
 
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)
 
76
        self.make_bzrdir('.')
 
77
        self.assertEqual(SmartServerResponse(('norepository', )),
 
78
            request.execute(backing.local_abspath('')))
 
79
 
 
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')))
 
91
 
 
92
    def _make_repository_and_result(self, shared=False, format=None):
 
93
        """Convenience function to setup a repository.
 
94
 
 
95
        :result: The SmartServerResponse to expect when opening it.
 
96
        """
 
97
        repo = self.make_repository('.', shared=shared, format=format)
 
98
        if repo.supports_rich_root():
 
99
            rich_root = 'yes'
 
100
        else:
 
101
            rich_root = 'no'
 
102
        if repo._format.supports_tree_reference:
 
103
            subtrees = 'yes'
 
104
        else:
 
105
            subtrees = 'no'
 
106
        return SmartServerResponse(('ok', '', rich_root, subtrees))
 
107
 
 
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')))
 
122
 
 
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('')))
 
132
 
 
133
 
 
134
class TestSmartServerRequestInitializeBzrDir(tests.TestCaseWithTransport):
 
135
 
 
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 
 
144
        # default formart.
 
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)
 
148
 
 
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'))
 
155
 
 
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'))
 
163
 
 
164
 
 
165
class TestSmartServerRequestOpenBranch(tests.TestCaseWithTransport):
 
166
 
 
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('')))
 
174
 
 
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('')))
 
182
 
 
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
 
190
        # can use it here.
 
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')))
 
195
 
 
196
 
 
197
class TestSmartServerRequestRevisionHistory(tests.TestCaseWithTransport):
 
198
 
 
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('')))
 
206
 
 
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('.')
 
212
        tree.lock_write()
 
213
        tree.add('')
 
214
        r1 = tree.commit('1st commit')
 
215
        r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
 
216
        tree.unlock()
 
217
        self.assertEqual(
 
218
            SmartServerResponse(('ok', ), ('\x00'.join([r1, r2]))),
 
219
            request.execute(backing.local_abspath('')))
 
220
 
 
221
 
 
222
class TestSmartServerBranchRequest(tests.TestCaseWithTransport):
 
223
 
 
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(''))
 
231
 
 
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'))
 
240
 
 
241
 
 
242
class TestSmartServerBranchRequestLastRevisionInfo(tests.TestCaseWithTransport):
 
243
 
 
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('')))
 
251
 
 
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('.')
 
257
        tree.lock_write()
 
258
        tree.add('')
 
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)
 
262
        tree.unlock()
 
263
        self.assertEqual(
 
264
            SmartServerResponse(('ok', '2', rev_id_utf8)),
 
265
            request.execute(backing.local_abspath('')))
 
266
 
 
267
 
 
268
class TestSmartServerBranchRequestGetConfigFile(tests.TestCaseWithTransport):
 
269
 
 
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
 
276
        content = ''
 
277
        self.assertEqual(SmartServerResponse(('ok', ), content),
 
278
            request.execute(backing.local_abspath('')))
 
279
 
 
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('')))
 
290
 
 
291
 
 
292
class TestSmartServerBranchRequestSetLastRevision(tests.TestCaseWithTransport):
 
293
 
 
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()
 
301
        try:
 
302
            self.assertEqual(SmartServerResponse(('ok',)),
 
303
                request.execute(
 
304
                    backing.local_abspath(''), branch_token, repo_token,
 
305
                    'null:'))
 
306
        finally:
 
307
            b.unlock()
 
308
 
 
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()
 
316
        try:
 
317
            revision_id = 'non-existent revision'
 
318
            self.assertEqual(
 
319
                SmartServerResponse(('NoSuchRevision', revision_id)),
 
320
                request.execute(
 
321
                    backing.local_abspath(''), branch_token, repo_token,
 
322
                    revision_id))
 
323
        finally:
 
324
            b.unlock()
 
325
 
 
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('.')
 
330
        tree.lock_write()
 
331
        tree.add('')
 
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')
 
335
        tree.unlock()
 
336
        branch_token = tree.branch.lock_write()
 
337
        repo_token = tree.branch.repository.lock_write()
 
338
        tree.branch.repository.unlock()
 
339
        try:
 
340
            self.assertEqual(
 
341
                SmartServerResponse(('ok',)),
 
342
                request.execute(
 
343
                    backing.local_abspath(''), branch_token, repo_token,
 
344
                    rev_id_utf8))
 
345
            self.assertEqual([rev_id_utf8], tree.branch.revision_history())
 
346
        finally:
 
347
            tree.branch.unlock()
 
348
 
 
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('.')
 
353
        tree.lock_write()
 
354
        tree.add('')
 
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')
 
358
        tree.unlock()
 
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()
 
363
        try:
 
364
            self.assertEqual(
 
365
                SmartServerResponse(('ok',)),
 
366
                request.execute(
 
367
                    backing.local_abspath(''), branch_token, repo_token,
 
368
                    rev_id_utf8))
 
369
            self.assertEqual([rev_id_utf8], tree.branch.revision_history())
 
370
        finally:
 
371
            tree.branch.unlock()
 
372
 
 
373
 
 
374
class TestSmartServerBranchRequestLockWrite(tests.TestCaseWithTransport):
 
375
 
 
376
    def setUp(self):
 
377
        tests.TestCaseWithTransport.setUp(self)
 
378
        self.reduceLockdirTimeout()
 
379
 
 
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')
 
388
        self.assertEqual(
 
389
            SmartServerResponse(('ok', branch_nonce, repository_nonce)),
 
390
            response)
 
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)
 
395
 
 
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('.')
 
400
        branch.lock_write()
 
401
        branch.leave_lock_in_place()
 
402
        branch.unlock()
 
403
        response = request.execute(backing.local_abspath(''))
 
404
        self.assertEqual(
 
405
            SmartServerResponse(('LockContention',)), response)
 
406
 
 
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()
 
416
        branch.unlock()
 
417
        response = request.execute(backing.local_abspath(''),
 
418
                                   branch_token, repo_token)
 
419
        self.assertEqual(
 
420
            SmartServerResponse(('ok', branch_token, repo_token)), response)
 
421
 
 
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()
 
431
        branch.unlock()
 
432
        response = request.execute(backing.local_abspath(''),
 
433
                                   branch_token+'xxx', repo_token)
 
434
        self.assertEqual(
 
435
            SmartServerResponse(('TokenMismatch',)), response)
 
436
 
 
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(''))
 
445
        self.assertEqual(
 
446
            SmartServerResponse(('LockContention',)), response)
 
447
 
 
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)
 
456
 
 
457
 
 
458
class TestSmartServerBranchRequestUnlock(tests.TestCaseWithTransport):
 
459
 
 
460
    def setUp(self):
 
461
        tests.TestCaseWithTransport.setUp(self)
 
462
        self.reduceLockdirTimeout()
 
463
 
 
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')
 
468
        # Lock the branch
 
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
 
473
        # in place.
 
474
        branch.leave_lock_in_place()
 
475
        branch.repository.leave_lock_in_place()
 
476
        branch.unlock()
 
477
        response = request.execute(backing.local_abspath(''),
 
478
                                   branch_token, repo_token)
 
479
        self.assertEqual(
 
480
            SmartServerResponse(('ok',)), response)
 
481
        # The branch is now unlocked.  Verify that with a new branch
 
482
        # object.
 
483
        new_branch = branch.bzrdir.open_branch()
 
484
        new_branch.lock_write()
 
485
        new_branch.unlock()
 
486
 
 
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')
 
493
        self.assertEqual(
 
494
            SmartServerResponse(('TokenMismatch',)), response)
 
495
 
 
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
 
505
        # repo).
 
506
        response = request.execute(
 
507
            backing.local_abspath(''), 'branch token', repo_token)
 
508
        self.assertEqual(
 
509
            SmartServerResponse(('TokenMismatch',)), response)
 
510
 
 
511
 
 
512
class TestSmartServerRepositoryRequest(tests.TestCaseWithTransport):
 
513
 
 
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
 
519
        # searching.
 
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'))
 
526
 
 
527
 
 
528
class TestSmartServerRepositoryGetRevisionGraph(tests.TestCaseWithTransport):
 
529
 
 
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('.')
 
534
        tree.lock_write()
 
535
        tree.add('')
 
536
        r1 = tree.commit('1st commit')
 
537
        r2 = tree.commit('2nd commit', rev_id=u'\xc8'.encode('utf-8'))
 
538
        tree.unlock()
 
539
 
 
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')))
 
545
 
 
546
        self.assertEqual(
 
547
            SmartServerResponse(('ok', ), '\n'.join(lines)), response)
 
548
 
 
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('.')
 
553
        tree.lock_write()
 
554
        tree.add('')
 
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'))
 
558
        tree.unlock()
 
559
 
 
560
        self.assertEqual(SmartServerResponse(('ok', ), rev_id_utf8),
 
561
            request.execute(backing.local_abspath(''), rev_id_utf8))
 
562
    
 
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('.')
 
567
        tree.lock_write()
 
568
        tree.add('')
 
569
        r1 = tree.commit('1st commit')
 
570
        tree.unlock()
 
571
 
 
572
        # Note that it still returns body (of zero bytes).
 
573
        self.assertEqual(
 
574
            SmartServerResponse(('nosuchrevision', 'missingrevision', ), ''),
 
575
            request.execute(backing.local_abspath(''), 'missingrevision'))
 
576
 
 
577
 
 
578
class TestSmartServerRequestHasRevision(tests.TestCaseWithTransport):
 
579
 
 
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'))
 
587
 
 
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('.')
 
593
        tree.lock_write()
 
594
        tree.add('')
 
595
        rev_id_utf8 = u'\xc8abc'.encode('utf-8')
 
596
        r1 = tree.commit('a commit', rev_id=rev_id_utf8)
 
597
        tree.unlock()
 
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))
 
601
 
 
602
 
 
603
class TestSmartServerRepositoryGatherStats(tests.TestCaseWithTransport):
 
604
 
 
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()
 
611
        size = stats['size']
 
612
        expected_body = 'revisions: 0\nsize: %d\n' % size
 
613
        self.assertEqual(SmartServerResponse(('ok', ), expected_body),
 
614
                         request.execute(backing.local_abspath(''), '', 'no'))
 
615
 
 
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('.')
 
622
        tree.lock_write()
 
623
        tree.add('')
 
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,
 
627
                    rev_id=rev_id_utf8)
 
628
        tree.unlock()
 
629
 
 
630
        stats = tree.branch.repository.gather_stats()
 
631
        size = stats['size']
 
632
        expected_body = ('firstrev: 123456.200 3600\n'
 
633
                         'latestrev: 654321.400 0\n'
 
634
                         'revisions: 2\n'
 
635
                         'size: %d\n' % size)
 
636
        self.assertEqual(SmartServerResponse(('ok', ), expected_body),
 
637
                         request.execute(backing.local_abspath(''),
 
638
                                         rev_id_utf8, 'no'))
 
639
 
 
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('.')
 
646
        tree.lock_write()
 
647
        tree.add('')
 
648
        # Let's build a predictable result
 
649
        tree.commit('a commit', timestamp=123456.2, timezone=3600,
 
650
                    committer='foo')
 
651
        tree.commit('a commit', timestamp=654321.4, timezone=0,
 
652
                    committer='bar', rev_id=rev_id_utf8)
 
653
        tree.unlock()
 
654
        stats = tree.branch.repository.gather_stats()
 
655
 
 
656
        size = stats['size']
 
657
        expected_body = ('committers: 2\n'
 
658
                         'firstrev: 123456.200 3600\n'
 
659
                         'latestrev: 654321.400 0\n'
 
660
                         'revisions: 2\n'
 
661
                         'size: %d\n' % size)
 
662
        self.assertEqual(SmartServerResponse(('ok', ), expected_body),
 
663
                         request.execute(backing.local_abspath(''),
 
664
                                         rev_id_utf8, 'yes'))
 
665
 
 
666
 
 
667
class TestSmartServerRepositoryIsShared(tests.TestCaseWithTransport):
 
668
 
 
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(''), ))
 
676
 
 
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(''), ))
 
684
 
 
685
 
 
686
class TestSmartServerRepositoryLockWrite(tests.TestCaseWithTransport):
 
687
 
 
688
    def setUp(self):
 
689
        tests.TestCaseWithTransport.setUp(self)
 
690
        self.reduceLockdirTimeout()
 
691
 
 
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
 
700
        # object.
 
701
        new_repo = repository.bzrdir.open_repository()
 
702
        self.assertRaises(errors.LockContention, new_repo.lock_write)
 
703
 
 
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()
 
710
        repository.unlock()
 
711
        response = request.execute(backing.local_abspath(''))
 
712
        self.assertEqual(
 
713
            SmartServerResponse(('LockContention',)), response)
 
714
 
 
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])
 
722
 
 
723
 
 
724
class TestSmartServerRepositoryUnlock(tests.TestCaseWithTransport):
 
725
 
 
726
    def setUp(self):
 
727
        tests.TestCaseWithTransport.setUp(self)
 
728
        self.reduceLockdirTimeout()
 
729
 
 
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()
 
736
        repository.unlock()
 
737
        response = request.execute(backing.local_abspath(''), token)
 
738
        self.assertEqual(
 
739
            SmartServerResponse(('ok',)), response)
 
740
        # The repository is now unlocked.  Verify that with a new repository
 
741
        # object.
 
742
        new_repo = repository.bzrdir.open_repository()
 
743
        new_repo.lock_write()
 
744
        new_repo.unlock()
 
745
 
 
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')
 
751
        self.assertEqual(
 
752
            SmartServerResponse(('TokenMismatch',)), response)
 
753
 
 
754
 
 
755
class TestSmartServerRepositoryTarball(tests.TestCaseWithTransport):
 
756
 
 
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
 
762
        # not be copied
 
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,
 
769
            mode='r|bz2')
 
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")
 
777
 
 
778
 
 
779
class TestSmartServerRepositoryStreamKnitData(tests.TestCaseWithTransport):
 
780
 
 
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('.')
 
785
        tree.lock_write()
 
786
        tree.add('')
 
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)
 
791
        tree.unlock()
 
792
 
 
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))
 
797
        names = []
 
798
        for [name], read_bytes in unpacker.iter_records():
 
799
            names.append(name)
 
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
 
804
            # contents?
 
805
        
 
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)
 
812
        self.assertEqual(
 
813
            SmartServerResponse(('NoSuchRevision', rev_id1_utf8)),
 
814
            response)
 
815
 
 
816
 
 
817
class TestSmartServerIsReadonly(tests.TestCaseWithTransport):
 
818
 
 
819
    def test_is_readonly_no(self):
 
820
        backing = self.get_transport()
 
821
        request = smart.request.SmartServerIsReadonly(backing)
 
822
        response = request.execute()
 
823
        self.assertEqual(
 
824
            SmartServerResponse(('no',)), response)
 
825
 
 
826
    def test_is_readonly_yes(self):
 
827
        backing = self.get_readonly_transport()
 
828
        request = smart.request.SmartServerIsReadonly(backing)
 
829
        response = request.execute()
 
830
        self.assertEqual(
 
831
            SmartServerResponse(('yes',)), response)
 
832
 
 
833
 
 
834
class TestHandlers(tests.TestCase):
 
835
    """Tests for the request.request_handlers object."""
 
836
 
 
837
    def test_registered_methods(self):
 
838
        """Test that known methods are registered to the correct object."""
 
839
        self.assertEqual(
 
840
            smart.request.request_handlers.get('Branch.get_config_file'),
 
841
            smart.branch.SmartServerBranchGetConfigFile)
 
842
        self.assertEqual(
 
843
            smart.request.request_handlers.get('Branch.lock_write'),
 
844
            smart.branch.SmartServerBranchRequestLockWrite)
 
845
        self.assertEqual(
 
846
            smart.request.request_handlers.get('Branch.last_revision_info'),
 
847
            smart.branch.SmartServerBranchRequestLastRevisionInfo)
 
848
        self.assertEqual(
 
849
            smart.request.request_handlers.get('Branch.revision_history'),
 
850
            smart.branch.SmartServerRequestRevisionHistory)
 
851
        self.assertEqual(
 
852
            smart.request.request_handlers.get('Branch.set_last_revision'),
 
853
            smart.branch.SmartServerBranchRequestSetLastRevision)
 
854
        self.assertEqual(
 
855
            smart.request.request_handlers.get('Branch.unlock'),
 
856
            smart.branch.SmartServerBranchRequestUnlock)
 
857
        self.assertEqual(
 
858
            smart.request.request_handlers.get('BzrDir.find_repository'),
 
859
            smart.bzrdir.SmartServerRequestFindRepository)
 
860
        self.assertEqual(
 
861
            smart.request.request_handlers.get('BzrDirFormat.initialize'),
 
862
            smart.bzrdir.SmartServerRequestInitializeBzrDir)
 
863
        self.assertEqual(
 
864
            smart.request.request_handlers.get('BzrDir.open_branch'),
 
865
            smart.bzrdir.SmartServerRequestOpenBranch)
 
866
        self.assertEqual(
 
867
            smart.request.request_handlers.get('Repository.gather_stats'),
 
868
            smart.repository.SmartServerRepositoryGatherStats)
 
869
        self.assertEqual(
 
870
            smart.request.request_handlers.get(
 
871
                'Repository.get_revision_graph'),
 
872
            smart.repository.SmartServerRepositoryGetRevisionGraph)
 
873
        self.assertEqual(
 
874
            smart.request.request_handlers.get('Repository.has_revision'),
 
875
            smart.repository.SmartServerRequestHasRevision)
 
876
        self.assertEqual(
 
877
            smart.request.request_handlers.get('Repository.is_shared'),
 
878
            smart.repository.SmartServerRepositoryIsShared)
 
879
        self.assertEqual(
 
880
            smart.request.request_handlers.get('Repository.lock_write'),
 
881
            smart.repository.SmartServerRepositoryLockWrite)
 
882
        self.assertEqual(
 
883
            smart.request.request_handlers.get(
 
884
                'Repository.stream_knit_data_for_revisions'),
 
885
            smart.repository.SmartServerRepositoryStreamKnitDataForRevisions)
 
886
        self.assertEqual(
 
887
            smart.request.request_handlers.get('Repository.tarball'),
 
888
            smart.repository.SmartServerRepositoryTarball)
 
889
        self.assertEqual(
 
890
            smart.request.request_handlers.get('Repository.unlock'),
 
891
            smart.repository.SmartServerRepositoryUnlock)
 
892
        self.assertEqual(
 
893
            smart.request.request_handlers.get('Transport.is_readonly'),
 
894
            smart.request.SmartServerIsReadonly)