~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/branch_implementations/test_locking.py

  • Committer: Robert Collins
  • Date: 2008-08-20 02:07:36 UTC
  • mfrom: (3640 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3682.
  • Revision ID: robertc@robertcollins.net-20080820020736-g2xe4921zzxtymle
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006 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
"""Test locks across all branch implemenations"""
 
18
 
 
19
from bzrlib import errors
 
20
from bzrlib.branch import BzrBranchFormat4
 
21
from bzrlib.bzrdir import RemoteBzrDirFormat
 
22
from bzrlib.tests import TestSkipped
 
23
from bzrlib.tests.branch_implementations.test_branch import TestCaseWithBranch
 
24
from bzrlib.tests.lock_helpers import TestPreventLocking, LockWrapper
 
25
 
 
26
 
 
27
class TestBranchLocking(TestCaseWithBranch):
 
28
 
 
29
    def setUp(self):
 
30
        TestCaseWithBranch.setUp(self)
 
31
        self.reduceLockdirTimeout()
 
32
 
 
33
    def get_instrumented_branch(self):
 
34
        """Get a Branch object which has been instrumented"""
 
35
        # TODO: jam 20060630 It may be that not all formats have a 
 
36
        # 'control_files' member. So we should fail gracefully if
 
37
        # not there. But assuming it has them lets us test the exact 
 
38
        # lock/unlock order.
 
39
        self.locks = []
 
40
        b = LockWrapper(self.locks, self.get_branch(), 'b')
 
41
        b.repository = LockWrapper(self.locks, b.repository, 'r')
 
42
        bcf = b.control_files
 
43
        rcf = getattr(b.repository, 'control_files', None)
 
44
        if rcf is None:
 
45
            self.combined_branch = False
 
46
        else:
 
47
            # Look out for branch types that reuse their control files
 
48
            self.combined_control = bcf is rcf
 
49
        try:
 
50
            b.control_files = LockWrapper(self.locks, b.control_files, 'bc')
 
51
        except AttributeError:
 
52
            # RemoteBranch seems to trigger this.
 
53
            raise TestSkipped("Could not instrument branch control files.")
 
54
        if self.combined_control:
 
55
            # instrument the repository control files too to ensure its worked
 
56
            # with correctly. When they are not shared, we trust the repository
 
57
            # API and only instrument the repository itself. 
 
58
            b.repository.control_files = \
 
59
                LockWrapper(self.locks, b.repository.control_files, 'rc')
 
60
        return b
 
61
 
 
62
    def test_01_lock_read(self):
 
63
        # Test that locking occurs in the correct order
 
64
        b = self.get_instrumented_branch()
 
65
 
 
66
        self.assertFalse(b.is_locked())
 
67
        self.assertFalse(b.repository.is_locked())
 
68
        b.lock_read()
 
69
        try:
 
70
            self.assertTrue(b.is_locked())
 
71
            self.assertTrue(b.repository.is_locked())
 
72
        finally:
 
73
            b.unlock()
 
74
        self.assertFalse(b.is_locked())
 
75
        self.assertFalse(b.repository.is_locked())
 
76
 
 
77
        if self.combined_control:
 
78
            self.assertEqual([('b', 'lr', True),
 
79
                              ('r', 'lr', True),
 
80
                              ('rc', 'lr', True),
 
81
                              ('bc', 'lr', True),
 
82
                              ('b', 'ul', True),
 
83
                              ('bc', 'ul', True),
 
84
                              ('r', 'ul', True),
 
85
                              ('rc', 'ul', True),
 
86
                             ], self.locks)
 
87
        else:
 
88
            self.assertEqual([('b', 'lr', True),
 
89
                              ('r', 'lr', True),
 
90
                              ('bc', 'lr', True),
 
91
                              ('b', 'ul', True),
 
92
                              ('bc', 'ul', True),
 
93
                              ('r', 'ul', True),
 
94
                             ], self.locks)
 
95
 
 
96
    def test_02_lock_write(self):
 
97
        # Test that locking occurs in the correct order
 
98
        b = self.get_instrumented_branch()
 
99
 
 
100
        self.assertFalse(b.is_locked())
 
101
        self.assertFalse(b.repository.is_locked())
 
102
        b.lock_write()
 
103
        try:
 
104
            self.assertTrue(b.is_locked())
 
105
            self.assertTrue(b.repository.is_locked())
 
106
        finally:
 
107
            b.unlock()
 
108
        self.assertFalse(b.is_locked())
 
109
        self.assertFalse(b.repository.is_locked())
 
110
 
 
111
        if self.combined_control:
 
112
            self.assertEqual([('b', 'lw', True),
 
113
                              ('r', 'lw', True),
 
114
                              ('rc', 'lw', True),
 
115
                              ('bc', 'lw', True),
 
116
                              ('b', 'ul', True),
 
117
                              ('bc', 'ul', True),
 
118
                              ('r', 'ul', True),
 
119
                              ('rc', 'ul', True),
 
120
                             ], self.locks)
 
121
        else:
 
122
            self.assertEqual([('b', 'lw', True),
 
123
                              ('r', 'lw', True),
 
124
                              ('bc', 'lw', True),
 
125
                              ('b', 'ul', True),
 
126
                              ('bc', 'ul', True),
 
127
                              ('r', 'ul', True),
 
128
                             ], self.locks)
 
129
 
 
130
    def test_03_lock_fail_unlock_repo(self):
 
131
        # Make sure branch.unlock() is called, even if there is a
 
132
        # failure while unlocking the repository.
 
133
        b = self.get_instrumented_branch()
 
134
        b.repository.disable_unlock()
 
135
 
 
136
        self.assertFalse(b.is_locked())
 
137
        self.assertFalse(b.repository.is_locked())
 
138
        b.lock_write()
 
139
        try:
 
140
            self.assertTrue(b.is_locked())
 
141
            self.assertTrue(b.repository.is_locked())
 
142
            self.assertRaises(TestPreventLocking, b.unlock)
 
143
            if self.combined_control:
 
144
                self.assertTrue(b.is_locked())
 
145
            else:
 
146
                self.assertFalse(b.is_locked())
 
147
            self.assertTrue(b.repository.is_locked())
 
148
 
 
149
            # We unlock the branch control files, even if 
 
150
            # we fail to unlock the repository
 
151
            if self.combined_control:
 
152
                self.assertEqual([('b', 'lw', True),
 
153
                                  ('r', 'lw', True),
 
154
                                  ('rc', 'lw', True),
 
155
                                  ('bc', 'lw', True),
 
156
                                  ('b', 'ul', True),
 
157
                                  ('bc', 'ul', True),
 
158
                                  ('r', 'ul', False),
 
159
                                 ], self.locks)
 
160
            else:
 
161
                self.assertEqual([('b', 'lw', True),
 
162
                                  ('r', 'lw', True),
 
163
                                  ('bc', 'lw', True),
 
164
                                  ('b', 'ul', True),
 
165
                                  ('bc', 'ul', True),
 
166
                                  ('r', 'ul', False),
 
167
                                 ], self.locks)
 
168
 
 
169
        finally:
 
170
            # For cleanup purposes, make sure we are unlocked
 
171
            b.repository._other.unlock()
 
172
 
 
173
    def test_04_lock_fail_unlock_control(self):
 
174
        # Make sure repository.unlock() is called, if we fail to unlock self
 
175
        b = self.get_instrumented_branch()
 
176
        b.control_files.disable_unlock()
 
177
 
 
178
        self.assertFalse(b.is_locked())
 
179
        self.assertFalse(b.repository.is_locked())
 
180
        b.lock_write()
 
181
        try:
 
182
            self.assertTrue(b.is_locked())
 
183
            self.assertTrue(b.repository.is_locked())
 
184
            self.assertRaises(TestPreventLocking, b.unlock)
 
185
            self.assertTrue(b.is_locked())
 
186
            if self.combined_control:
 
187
                self.assertTrue(b.repository.is_locked())
 
188
            else:
 
189
                self.assertFalse(b.repository.is_locked())
 
190
 
 
191
            # We unlock the repository even if 
 
192
            # we fail to unlock the control files
 
193
            if self.combined_control:
 
194
                self.assertEqual([('b', 'lw', True),
 
195
                                  ('r', 'lw', True),
 
196
                                  ('rc', 'lw', True),
 
197
                                  ('bc', 'lw', True),
 
198
                                  ('b', 'ul', True),
 
199
                                  ('bc', 'ul', False),
 
200
                                  ('r', 'ul', True),
 
201
                                  ('rc', 'ul', True),
 
202
                                 ], self.locks)
 
203
            else:
 
204
                self.assertEqual([('b', 'lw', True),
 
205
                                  ('r', 'lw', True),
 
206
                                  ('bc', 'lw', True),
 
207
                                  ('b', 'ul', True),
 
208
                                  ('bc', 'ul', False),
 
209
                                  ('r', 'ul', True),
 
210
                                 ], self.locks)
 
211
 
 
212
        finally:
 
213
            # For cleanup purposes, make sure we are unlocked
 
214
            b.control_files._other.unlock()
 
215
 
 
216
    def test_05_lock_read_fail_repo(self):
 
217
        # Test that the branch is not locked if it cannot lock the repository
 
218
        b = self.get_instrumented_branch()
 
219
        b.repository.disable_lock_read()
 
220
 
 
221
        self.assertRaises(TestPreventLocking, b.lock_read)
 
222
        self.assertFalse(b.is_locked())
 
223
        self.assertFalse(b.repository.is_locked())
 
224
 
 
225
        self.assertEqual([('b', 'lr', True),
 
226
                          ('r', 'lr', False), 
 
227
                         ], self.locks)
 
228
 
 
229
    def test_06_lock_write_fail_repo(self):
 
230
        # Test that the branch is not locked if it cannot lock the repository
 
231
        b = self.get_instrumented_branch()
 
232
        b.repository.disable_lock_write()
 
233
 
 
234
        self.assertRaises(TestPreventLocking, b.lock_write)
 
235
        self.assertFalse(b.is_locked())
 
236
        self.assertFalse(b.repository.is_locked())
 
237
 
 
238
        self.assertEqual([('b', 'lw', True),
 
239
                          ('r', 'lw', False), 
 
240
                         ], self.locks)
 
241
 
 
242
    def test_07_lock_read_fail_control(self):
 
243
        # Test the repository is unlocked if we can't lock self
 
244
        b = self.get_instrumented_branch()
 
245
        b.control_files.disable_lock_read()
 
246
 
 
247
        self.assertRaises(TestPreventLocking, b.lock_read)
 
248
        self.assertFalse(b.is_locked())
 
249
        self.assertFalse(b.repository.is_locked())
 
250
 
 
251
        if self.combined_control:
 
252
            self.assertEqual([('b', 'lr', True),
 
253
                              ('r', 'lr', True),
 
254
                              ('rc', 'lr', True),
 
255
                              ('bc', 'lr', False),
 
256
                              ('r', 'ul', True),
 
257
                              ('rc', 'ul', True),
 
258
                             ], self.locks)
 
259
        else:
 
260
            self.assertEqual([('b', 'lr', True),
 
261
                              ('r', 'lr', True),
 
262
                              ('bc', 'lr', False),
 
263
                              ('r', 'ul', True),
 
264
                             ], self.locks)
 
265
 
 
266
    def test_08_lock_write_fail_control(self):
 
267
        # Test the repository is unlocked if we can't lock self
 
268
        b = self.get_instrumented_branch()
 
269
        b.control_files.disable_lock_write()
 
270
 
 
271
        self.assertRaises(TestPreventLocking, b.lock_write)
 
272
        self.assertFalse(b.is_locked())
 
273
        self.assertFalse(b.repository.is_locked())
 
274
        if self.combined_control:
 
275
            self.assertEqual([('b', 'lw', True),
 
276
                              ('r', 'lw', True),
 
277
                              ('rc', 'lw', True),
 
278
                              ('bc', 'lw', False),
 
279
                              ('r', 'ul', True),
 
280
                              ('rc', 'ul', True),
 
281
                             ], self.locks)
 
282
        else:
 
283
            self.assertEqual([('b', 'lw', True),
 
284
                              ('r', 'lw', True),
 
285
                              ('bc', 'lw', False),
 
286
                              ('r', 'ul', True),
 
287
                             ], self.locks)
 
288
 
 
289
    def test_lock_write_returns_None_refuses_token(self):
 
290
        branch = self.make_branch('b')
 
291
        token = branch.lock_write()
 
292
        try:
 
293
            if token is not None:
 
294
                # This test does not apply, because this lockable supports
 
295
                # tokens.
 
296
                return
 
297
            self.assertRaises(errors.TokenLockingNotSupported,
 
298
                              branch.lock_write, token='token')
 
299
        finally:
 
300
            branch.unlock()
 
301
 
 
302
    def test_reentering_lock_write_raises_on_token_mismatch(self):
 
303
        branch = self.make_branch('b')
 
304
        token = branch.lock_write()
 
305
        try:
 
306
            if token is None:
 
307
                # This test does not apply, because this lockable refuses
 
308
                # tokens.
 
309
                return
 
310
            different_branch_token = token + 'xxx'
 
311
            # Re-using the same lockable instance with a different branch token
 
312
            # will raise TokenMismatch.
 
313
            self.assertRaises(errors.TokenMismatch,
 
314
                              branch.lock_write,
 
315
                              token=different_branch_token)
 
316
        finally:
 
317
            branch.unlock()
 
318
 
 
319
    def test_lock_write_with_nonmatching_token(self):
 
320
        branch = self.make_branch('b')
 
321
        token = branch.lock_write()
 
322
        try:
 
323
            if token is None:
 
324
                # This test does not apply, because this branch refuses
 
325
                # tokens.
 
326
                return
 
327
            different_branch_token = token + 'xxx'
 
328
 
 
329
            new_branch = branch.bzrdir.open_branch()
 
330
            # We only want to test the relocking abilities of branch, so use the
 
331
            # existing repository object which is already locked.
 
332
            new_branch.repository = branch.repository
 
333
            self.assertRaises(errors.TokenMismatch,
 
334
                              new_branch.lock_write,
 
335
                              token=different_branch_token)
 
336
        finally:
 
337
            branch.unlock()
 
338
 
 
339
 
 
340
    def test_lock_write_with_matching_token(self):
 
341
        """Test that a branch can be locked with a token, if it is already
 
342
        locked by that token."""
 
343
        branch = self.make_branch('b')
 
344
        token = branch.lock_write()
 
345
        try:
 
346
            if token is None:
 
347
                # This test does not apply, because this branch refuses tokens.
 
348
                return
 
349
            # The same instance will accept a second lock_write if the specified
 
350
            # token matches.
 
351
            branch.lock_write(token=token)
 
352
            branch.unlock()
 
353
            # Calling lock_write on a new instance for the same lockable will
 
354
            # also succeed.
 
355
            new_branch = branch.bzrdir.open_branch()
 
356
            # We only want to test the relocking abilities of branch, so use the
 
357
            # existing repository object which is already locked.
 
358
            new_branch.repository = branch.repository
 
359
            new_branch.lock_write(token=token)
 
360
            new_branch.unlock()
 
361
        finally:
 
362
            branch.unlock()
 
363
 
 
364
    def test_unlock_after_lock_write_with_token(self):
 
365
        # If lock_write did not physically acquire the lock (because it was
 
366
        # passed some tokens), then unlock should not physically release it.
 
367
        branch = self.make_branch('b')
 
368
        token = branch.lock_write()
 
369
        try:
 
370
            if token is None:
 
371
                # This test does not apply, because this lockable refuses
 
372
                # tokens.
 
373
                return
 
374
            new_branch = branch.bzrdir.open_branch()
 
375
            # We only want to test the relocking abilities of branch, so use the
 
376
            # existing repository object which is already locked.
 
377
            new_branch.repository = branch.repository
 
378
            new_branch.lock_write(token=token)
 
379
            new_branch.unlock()
 
380
            self.assertTrue(branch.get_physical_lock_status()) #XXX
 
381
        finally:
 
382
            branch.unlock()
 
383
 
 
384
    def test_lock_write_with_token_fails_when_unlocked(self):
 
385
        # First, lock and then unlock to get superficially valid tokens.  This
 
386
        # mimics a likely programming error, where a caller accidentally tries
 
387
        # to lock with a token that is no longer valid (because the original
 
388
        # lock was released).
 
389
        branch = self.make_branch('b')
 
390
        token = branch.lock_write()
 
391
        branch.unlock()
 
392
        if token is None:
 
393
            # This test does not apply, because this lockable refuses
 
394
            # tokens.
 
395
            return
 
396
 
 
397
        self.assertRaises(errors.TokenMismatch,
 
398
                          branch.lock_write, token=token)
 
399
 
 
400
    def test_lock_write_reenter_with_token(self):
 
401
        branch = self.make_branch('b')
 
402
        token = branch.lock_write()
 
403
        try:
 
404
            if token is None:
 
405
                # This test does not apply, because this lockable refuses
 
406
                # tokens.
 
407
                return
 
408
            # Relock with a token.
 
409
            branch.lock_write(token=token)
 
410
            branch.unlock()
 
411
        finally:
 
412
            branch.unlock()
 
413
        # The lock should be unlocked on disk.  Verify that with a new lock
 
414
        # instance.
 
415
        new_branch = branch.bzrdir.open_branch()
 
416
        # Calling lock_write now should work, rather than raise LockContention.
 
417
        new_branch.lock_write()
 
418
        new_branch.unlock()
 
419
 
 
420
    def test_leave_lock_in_place(self):
 
421
        branch = self.make_branch('b')
 
422
        # Lock the branch, then use leave_lock_in_place so that when we
 
423
        # unlock the branch the lock is still held on disk.
 
424
        token = branch.lock_write()
 
425
        try:
 
426
            if token is None:
 
427
                # This test does not apply, because this repository refuses lock
 
428
                # tokens.
 
429
                self.assertRaises(NotImplementedError,
 
430
                                  branch.leave_lock_in_place)
 
431
                return
 
432
            branch.leave_lock_in_place()
 
433
        finally:
 
434
            branch.unlock()
 
435
        # We should be unable to relock the repo.
 
436
        self.assertRaises(errors.LockContention, branch.lock_write)
 
437
 
 
438
    def test_dont_leave_lock_in_place(self):
 
439
        branch = self.make_branch('b')
 
440
        # Create a lock on disk.
 
441
        token = branch.lock_write()
 
442
        try:
 
443
            if token is None:
 
444
                # This test does not apply, because this branch refuses lock
 
445
                # tokens.
 
446
                self.assertRaises(NotImplementedError,
 
447
                                  branch.dont_leave_lock_in_place)
 
448
                return
 
449
            try:
 
450
                branch.leave_lock_in_place()
 
451
            except NotImplementedError:
 
452
                # This branch doesn't support this API.
 
453
                return
 
454
            try:
 
455
                branch.repository.leave_lock_in_place()
 
456
            except NotImplementedError:
 
457
                # This repo doesn't support leaving locks around,
 
458
                # assume it is essentially lock-free.
 
459
                repo_token = None
 
460
            else:
 
461
                repo_token = branch.repository.lock_write()
 
462
                branch.repository.unlock()
 
463
        finally:
 
464
            branch.unlock()
 
465
        # Reacquire the lock (with a different branch object) by using the
 
466
        # tokens.
 
467
        new_branch = branch.bzrdir.open_branch()
 
468
        if repo_token is not None:
 
469
            # We have to explicitly lock the repository first.
 
470
            new_branch.repository.lock_write(token=repo_token)
 
471
        new_branch.lock_write(token=token)
 
472
        if repo_token is not None:
 
473
            # Now we don't need our own repository lock anymore (the branch is
 
474
            # holding it for us).
 
475
            new_branch.repository.unlock()
 
476
        # Call dont_leave_lock_in_place, so that the lock will be released by
 
477
        # this instance, even though the lock wasn't originally acquired by it.
 
478
        new_branch.dont_leave_lock_in_place()
 
479
        if repo_token is not None:
 
480
            new_branch.repository.dont_leave_lock_in_place()
 
481
        new_branch.unlock()
 
482
        # Now the branch (and repository) is unlocked.  Test this by locking it
 
483
        # without tokens.
 
484
        branch.lock_write()
 
485
        branch.unlock()
 
486
 
 
487
    def test_lock_read_then_unlock(self):
 
488
        # Calling lock_read then unlocking should work without errors.
 
489
        branch = self.make_branch('b')
 
490
        branch.lock_read()
 
491
        branch.unlock()
 
492
 
 
493
    def test_lock_write_locks_repo_too(self):
 
494
        if isinstance(self.branch_format, BzrBranchFormat4):
 
495
            # Branch format 4 is combined with the repository, so this test
 
496
            # doesn't apply.
 
497
            return
 
498
        branch = self.make_branch('b')
 
499
        branch = branch.bzrdir.open_branch()
 
500
        branch.lock_write()
 
501
        try:
 
502
            # The branch should have asked the repository to lock.
 
503
            self.assertTrue(branch.repository.is_write_locked())
 
504
            # Does the repository type actually lock?
 
505
            if not branch.repository.get_physical_lock_status():
 
506
                # The test was successfully applied, so it was applicable.
 
507
                return
 
508
            # Now the branch.repository is physically locked, so we can't lock
 
509
            # it with a new repository instance.
 
510
            new_repo = branch.bzrdir.open_repository()
 
511
            self.assertRaises(errors.LockContention, new_repo.lock_write)
 
512
            # We can call lock_write on the original repository object though,
 
513
            # because it is already locked.
 
514
            branch.repository.lock_write()
 
515
            branch.repository.unlock()
 
516
        finally:
 
517
            branch.unlock()