~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lockable_files.py

  • Committer: Jelmer Vernooij
  • Date: 2009-01-28 18:42:55 UTC
  • mto: This revision was merged to the branch mainline in revision 3968.
  • Revision ID: jelmer@samba.org-20090128184255-bdmklkvm83ltk191
Update NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
from StringIO import StringIO
18
18
 
19
19
import bzrlib
20
 
from bzrlib.branch import Branch
21
 
import bzrlib.errors as errors
 
20
from bzrlib import (
 
21
    errors,
 
22
    lockdir,
 
23
    osutils,
 
24
    )
22
25
from bzrlib.errors import BzrBadParameterNotString, NoSuchFile, ReadOnlyError
23
26
from bzrlib.lockable_files import LockableFiles, TransportLock
24
 
from bzrlib.lockdir import LockDir
25
 
from bzrlib.tests import TestCaseInTempDir
 
27
from bzrlib.symbol_versioning import (
 
28
    deprecated_in,
 
29
    )
 
30
from bzrlib.tests import (
 
31
    TestCaseInTempDir,
 
32
    TestNotApplicable,
 
33
    )
 
34
from bzrlib.tests.test_smart import TestCaseWithSmartMedium
26
35
from bzrlib.tests.test_transactions import DummyWeave
27
36
from bzrlib.transactions import (PassThroughTransaction,
28
37
                                 ReadOnlyTransaction,
32
41
 
33
42
 
34
43
# these tests are applied in each parameterized suite for LockableFiles
 
44
#
 
45
# they use an old style of parameterization, but we want to remove this class
 
46
# so won't modernize them now. - mbp 20080430
35
47
class _TestLockableFiles_mixin(object):
36
48
 
37
49
    def test_read_write(self):
38
 
        self.assertRaises(NoSuchFile, self.lockable.get, 'foo')
39
 
        self.assertRaises(NoSuchFile, self.lockable.get_utf8, 'foo')
 
50
        self.assertRaises(NoSuchFile,
 
51
            self.applyDeprecated,
 
52
            deprecated_in((1, 5, 0)),
 
53
            self.lockable.get, 'foo')
 
54
        self.assertRaises(NoSuchFile,
 
55
            self.applyDeprecated,
 
56
            deprecated_in((1, 5, 0)),
 
57
            self.lockable.get_utf8, 'foo')
40
58
        self.lockable.lock_write()
41
59
        try:
42
60
            unicode_string = u'bar\u1234'
43
61
            self.assertEqual(4, len(unicode_string))
44
62
            byte_string = unicode_string.encode('utf-8')
45
63
            self.assertEqual(6, len(byte_string))
46
 
            self.assertRaises(UnicodeEncodeError, self.lockable.put, 'foo', 
47
 
                              StringIO(unicode_string))
48
 
            self.lockable.put('foo', StringIO(byte_string))
49
 
            self.assertEqual(byte_string,
50
 
                             self.lockable.get('foo').read())
 
64
            self.assertRaises(UnicodeEncodeError,
 
65
                self.applyDeprecated,
 
66
                deprecated_in((1, 6, 0)),
 
67
                self.lockable.put, 'foo',
 
68
                StringIO(unicode_string))
 
69
            self.applyDeprecated(
 
70
                deprecated_in((1, 6, 0)),
 
71
                self.lockable.put,
 
72
                'foo', StringIO(byte_string))
 
73
            byte_stream = self.applyDeprecated(
 
74
                deprecated_in((1, 5, 0)),
 
75
                self.lockable.get,
 
76
                'foo')
 
77
            self.assertEqual(byte_string, byte_stream.read())
 
78
            unicode_stream = self.applyDeprecated(
 
79
                deprecated_in((1, 5, 0)),
 
80
                self.lockable.get_utf8,
 
81
                'foo')
51
82
            self.assertEqual(unicode_string,
52
 
                             self.lockable.get_utf8('foo').read())
 
83
                unicode_stream.read())
53
84
            self.assertRaises(BzrBadParameterNotString,
54
 
                              self.lockable.put_utf8,
55
 
                              'bar',
56
 
                              StringIO(unicode_string)
57
 
                              )
58
 
            self.lockable.put_utf8('bar', unicode_string)
 
85
                self.applyDeprecated,
 
86
                deprecated_in((1, 6, 0)),
 
87
                self.lockable.put_utf8,
 
88
                'bar',
 
89
                StringIO(unicode_string))
 
90
            self.applyDeprecated(
 
91
                deprecated_in((1, 6, 0)),
 
92
                self.lockable.put_utf8,
 
93
                'bar',
 
94
                unicode_string)
 
95
            unicode_stream = self.applyDeprecated(
 
96
                deprecated_in((1, 5, 0)),
 
97
                self.lockable.get_utf8,
 
98
                'bar')
59
99
            self.assertEqual(unicode_string,
60
 
                             self.lockable.get_utf8('bar').read())
61
 
            self.assertEqual(byte_string,
62
 
                             self.lockable.get('bar').read())
63
 
            self.lockable.put_bytes('raw', 'raw\xffbytes')
64
 
            self.assertEqual('raw\xffbytes',
65
 
                             self.lockable.get('raw').read())
 
100
                unicode_stream.read())
 
101
            byte_stream = self.applyDeprecated(
 
102
                deprecated_in((1, 5, 0)),
 
103
                self.lockable.get,
 
104
                'bar')
 
105
            self.assertEqual(byte_string, byte_stream.read())
 
106
            self.applyDeprecated(
 
107
                deprecated_in((1, 6, 0)),
 
108
                self.lockable.put_bytes,
 
109
                'raw', 'raw\xffbytes')
 
110
            byte_stream = self.applyDeprecated(
 
111
                deprecated_in((1, 5, 0)),
 
112
                self.lockable.get,
 
113
                'raw')
 
114
            self.assertEqual('raw\xffbytes', byte_stream.read())
66
115
        finally:
67
116
            self.lockable.unlock()
68
117
 
69
118
    def test_locks(self):
70
119
        self.lockable.lock_read()
71
120
        try:
72
 
            self.assertRaises(ReadOnlyError, self.lockable.put, 'foo', 
 
121
            self.assertRaises(ReadOnlyError, self.lockable.put, 'foo',
73
122
                              StringIO('bar\u1234'))
74
123
        finally:
75
124
            self.lockable.unlock()
108
157
        except NotImplementedError:
109
158
            # this lock cannot be broken
110
159
            self.lockable.unlock()
111
 
            return
 
160
            raise TestNotApplicable("%r is not breakable" % (self.lockable,))
112
161
        l2 = self.get_lockable()
113
162
        orig_factory = bzrlib.ui.ui_factory
114
163
        # silent ui - no need for stdout
125
174
            self.assertRaises(errors.LockBroken, self.lockable.unlock)
126
175
            self.assertFalse(self.lockable.is_locked())
127
176
 
 
177
    def test_lock_write_returns_None_refuses_token(self):
 
178
        token = self.lockable.lock_write()
 
179
        try:
 
180
            if token is not None:
 
181
                # This test does not apply, because this lockable supports
 
182
                # tokens.
 
183
                raise TestNotApplicable("%r uses tokens" % (self.lockable,))
 
184
            self.assertRaises(errors.TokenLockingNotSupported,
 
185
                              self.lockable.lock_write, token='token')
 
186
        finally:
 
187
            self.lockable.unlock()
 
188
 
 
189
    def test_lock_write_returns_token_when_given_token(self):
 
190
        token = self.lockable.lock_write()
 
191
        try:
 
192
            if token is None:
 
193
                # This test does not apply, because this lockable refuses
 
194
                # tokens.
 
195
                return
 
196
            new_lockable = self.get_lockable()
 
197
            token_from_new_lockable = new_lockable.lock_write(token=token)
 
198
            try:
 
199
                self.assertEqual(token, token_from_new_lockable)
 
200
            finally:
 
201
                new_lockable.unlock()
 
202
        finally:
 
203
            self.lockable.unlock()
 
204
 
 
205
    def test_lock_write_raises_on_token_mismatch(self):
 
206
        token = self.lockable.lock_write()
 
207
        try:
 
208
            if token is None:
 
209
                # This test does not apply, because this lockable refuses
 
210
                # tokens.
 
211
                return
 
212
            different_token = token + 'xxx'
 
213
            # Re-using the same lockable instance with a different token will
 
214
            # raise TokenMismatch.
 
215
            self.assertRaises(errors.TokenMismatch,
 
216
                              self.lockable.lock_write, token=different_token)
 
217
            # A seperate instance for the same lockable will also raise
 
218
            # TokenMismatch.
 
219
            # This detects the case where a caller claims to have a lock (via
 
220
            # the token) for an external resource, but doesn't (the token is
 
221
            # different).  Clients need a seperate lock object to make sure the
 
222
            # external resource is probed, whereas the existing lock object
 
223
            # might cache.
 
224
            new_lockable = self.get_lockable()
 
225
            self.assertRaises(errors.TokenMismatch,
 
226
                              new_lockable.lock_write, token=different_token)
 
227
        finally:
 
228
            self.lockable.unlock()
 
229
 
 
230
    def test_lock_write_with_matching_token(self):
 
231
        # If the token matches, so no exception is raised by lock_write.
 
232
        token = self.lockable.lock_write()
 
233
        try:
 
234
            if token is None:
 
235
                # This test does not apply, because this lockable refuses
 
236
                # tokens.
 
237
                return
 
238
            # The same instance will accept a second lock_write if the specified
 
239
            # token matches.
 
240
            self.lockable.lock_write(token=token)
 
241
            self.lockable.unlock()
 
242
            # Calling lock_write on a new instance for the same lockable will
 
243
            # also succeed.
 
244
            new_lockable = self.get_lockable()
 
245
            new_lockable.lock_write(token=token)
 
246
            new_lockable.unlock()
 
247
        finally:
 
248
            self.lockable.unlock()
 
249
 
 
250
    def test_unlock_after_lock_write_with_token(self):
 
251
        # If lock_write did not physically acquire the lock (because it was
 
252
        # passed a token), then unlock should not physically release it.
 
253
        token = self.lockable.lock_write()
 
254
        try:
 
255
            if token is None:
 
256
                # This test does not apply, because this lockable refuses
 
257
                # tokens.
 
258
                return
 
259
            new_lockable = self.get_lockable()
 
260
            new_lockable.lock_write(token=token)
 
261
            new_lockable.unlock()
 
262
            self.assertTrue(self.lockable.get_physical_lock_status())
 
263
        finally:
 
264
            self.lockable.unlock()
 
265
 
 
266
    def test_lock_write_with_token_fails_when_unlocked(self):
 
267
        # Lock and unlock to get a superficially valid token.  This mimics a
 
268
        # likely programming error, where a caller accidentally tries to lock
 
269
        # with a token that is no longer valid (because the original lock was
 
270
        # released).
 
271
        token = self.lockable.lock_write()
 
272
        self.lockable.unlock()
 
273
        if token is None:
 
274
            # This test does not apply, because this lockable refuses
 
275
            # tokens.
 
276
            return
 
277
 
 
278
        self.assertRaises(errors.TokenMismatch,
 
279
                          self.lockable.lock_write, token=token)
 
280
 
 
281
    def test_lock_write_reenter_with_token(self):
 
282
        token = self.lockable.lock_write()
 
283
        try:
 
284
            if token is None:
 
285
                # This test does not apply, because this lockable refuses
 
286
                # tokens.
 
287
                return
 
288
            # Relock with a token.
 
289
            token_from_reentry = self.lockable.lock_write(token=token)
 
290
            try:
 
291
                self.assertEqual(token, token_from_reentry)
 
292
            finally:
 
293
                self.lockable.unlock()
 
294
        finally:
 
295
            self.lockable.unlock()
 
296
        # The lock should be unlocked on disk.  Verify that with a new lock
 
297
        # instance.
 
298
        new_lockable = self.get_lockable()
 
299
        # Calling lock_write now should work, rather than raise LockContention.
 
300
        new_lockable.lock_write()
 
301
        new_lockable.unlock()
 
302
 
 
303
    def test_second_lock_write_returns_same_token(self):
 
304
        first_token = self.lockable.lock_write()
 
305
        try:
 
306
            if first_token is None:
 
307
                # This test does not apply, because this lockable refuses
 
308
                # tokens.
 
309
                return
 
310
            # Relock the already locked lockable.  It should return the same
 
311
            # token.
 
312
            second_token = self.lockable.lock_write()
 
313
            try:
 
314
                self.assertEqual(first_token, second_token)
 
315
            finally:
 
316
                self.lockable.unlock()
 
317
        finally:
 
318
            self.lockable.unlock()
 
319
 
 
320
    def test_leave_in_place(self):
 
321
        token = self.lockable.lock_write()
 
322
        try:
 
323
            if token is None:
 
324
                # This test does not apply, because this lockable refuses
 
325
                # tokens.
 
326
                return
 
327
            self.lockable.leave_in_place()
 
328
        finally:
 
329
            self.lockable.unlock()
 
330
        # At this point, the lock is still in place on disk
 
331
        self.assertRaises(errors.LockContention, self.lockable.lock_write)
 
332
        # But should be relockable with a token.
 
333
        self.lockable.lock_write(token=token)
 
334
        self.lockable.unlock()
 
335
 
 
336
    def test_dont_leave_in_place(self):
 
337
        token = self.lockable.lock_write()
 
338
        try:
 
339
            if token is None:
 
340
                # This test does not apply, because this lockable refuses
 
341
                # tokens.
 
342
                return
 
343
            self.lockable.leave_in_place()
 
344
        finally:
 
345
            self.lockable.unlock()
 
346
        # At this point, the lock is still in place on disk.
 
347
        # Acquire the existing lock with the token, and ask that it is removed
 
348
        # when this object unlocks, and unlock to trigger that removal.
 
349
        new_lockable = self.get_lockable()
 
350
        new_lockable.lock_write(token=token)
 
351
        new_lockable.dont_leave_in_place()
 
352
        new_lockable.unlock()
 
353
        # At this point, the lock is no longer on disk, so we can lock it.
 
354
        third_lockable = self.get_lockable()
 
355
        third_lockable.lock_write()
 
356
        third_lockable.unlock()
 
357
 
128
358
 
129
359
# This method of adapting tests to parameters is different to 
130
360
# the TestProviderAdapters used elsewhere, but seems simpler for this 
133
363
                                      _TestLockableFiles_mixin):
134
364
 
135
365
    def setUp(self):
136
 
        super(TestLockableFiles_TransportLock, self).setUp()
 
366
        TestCaseInTempDir.setUp(self)
137
367
        transport = get_transport('.')
138
368
        transport.mkdir('.bzr')
139
369
        self.sub_transport = transport.clone('.bzr')
144
374
        super(TestLockableFiles_TransportLock, self).tearDown()
145
375
        # free the subtransport so that we do not get a 5 second
146
376
        # timeout due to the SFTP connection cache.
147
 
        del self.sub_transport
 
377
        try:
 
378
            del self.sub_transport
 
379
        except AttributeError:
 
380
            pass
148
381
 
149
382
    def get_lockable(self):
150
383
        return LockableFiles(self.sub_transport, 'my-lock', TransportLock)
155
388
    """LockableFile tests run with LockDir underneath"""
156
389
 
157
390
    def setUp(self):
158
 
        super(TestLockableFiles_LockDir, self).setUp()
 
391
        TestCaseInTempDir.setUp(self)
159
392
        self.transport = get_transport('.')
160
393
        self.lockable = self.get_lockable()
161
394
        # the lock creation here sets mode - test_permissions on branch 
165
398
        self.lockable.create_lock()
166
399
 
167
400
    def get_lockable(self):
168
 
        return LockableFiles(self.transport, 'my-lock', LockDir)
 
401
        return LockableFiles(self.transport, 'my-lock', lockdir.LockDir)
169
402
 
170
403
    def test_lock_created(self):
171
404
        self.assertTrue(self.transport.has('my-lock'))
175
408
        self.assertFalse(self.transport.has('my-lock/held/info'))
176
409
        self.assertTrue(self.transport.has('my-lock'))
177
410
 
178
 
 
179
 
    # TODO: Test the lockdir inherits the right file and directory permissions
180
 
    # from the LockableFiles.
 
411
    def test__file_modes(self):
 
412
        self.transport.mkdir('readonly')
 
413
        osutils.make_readonly('readonly')
 
414
        lockable = LockableFiles(self.transport.clone('readonly'), 'test-lock',
 
415
                                 lockdir.LockDir)
 
416
        # The directory mode should be read-write-execute for the current user
 
417
        self.assertEqual(00700, lockable._dir_mode & 00700)
 
418
        # Files should be read-write for the current user
 
419
        self.assertEqual(00600, lockable._file_mode & 00700)
 
420
 
 
421
 
 
422
class TestLockableFiles_RemoteLockDir(TestCaseWithSmartMedium,
 
423
                              _TestLockableFiles_mixin):
 
424
    """LockableFile tests run with RemoteLockDir on a branch."""
 
425
 
 
426
    def setUp(self):
 
427
        TestCaseWithSmartMedium.setUp(self)
 
428
        # can only get a RemoteLockDir with some RemoteObject...
 
429
        # use a branch as thats what we want. These mixin tests test the end
 
430
        # to end behaviour, so stubbing out the backend and simulating would
 
431
        # defeat the purpose. We test the protocol implementation separately
 
432
        # in test_remote and test_smart as usual.
 
433
        b = self.make_branch('foo')
 
434
        self.addCleanup(b.bzrdir.transport.disconnect)
 
435
        self.transport = get_transport('.')
 
436
        self.lockable = self.get_lockable()
 
437
 
 
438
    def get_lockable(self):
 
439
        # getting a new lockable involves opening a new instance of the branch
 
440
        branch = bzrlib.branch.Branch.open(self.get_url('foo'))
 
441
        self.addCleanup(branch.bzrdir.transport.disconnect)
 
442
        return branch.control_files