~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_smart.py

  • Committer: Robert Collins
  • Date: 2007-07-13 16:35:54 UTC
  • mto: (2592.3.3 repository)
  • mto: This revision was merged to the branch mainline in revision 2624.
  • Revision ID: robertc@robertcollins.net-20070713163554-ok2qtnzv6rcbpt3z
Change the missing key interface in index operations to not raise, allowing callers to set policy.

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