~bzr-pqm/bzr/bzr.dev

3388.2.1 by Martin Pool
Deprecate LockableFiles.get_utf8
1
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1185.67.4 by Aaron Bentley
Throw if we try to write to a LockableFiles with no write lock
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.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1185.67.4 by Aaron Bentley
Throw if we try to write to a LockableFiles with no write lock
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.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1185.67.4 by Aaron Bentley
Throw if we try to write to a LockableFiles with no write lock
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
1185.70.2 by Martin Pool
Fix funny mistake
17
from StringIO import StringIO
18
1687.1.6 by Robert Collins
Extend LockableFiles to support break_lock() calls.
19
import bzrlib
3107.2.1 by John Arbash Meinel
Fix LockableFiles to not use modes that allow the user to write to things they create.
20
from bzrlib import (
21
    errors,
22
    lockdir,
23
    osutils,
24
    )
1185.65.29 by Robert Collins
Implement final review suggestions.
25
from bzrlib.errors import BzrBadParameterNotString, NoSuchFile, ReadOnlyError
1553.5.63 by Martin Pool
Lock type is now mandatory for LockableFiles constructor
26
from bzrlib.lockable_files import LockableFiles, TransportLock
3388.2.1 by Martin Pool
Deprecate LockableFiles.get_utf8
27
from bzrlib.symbol_versioning import (
28
    deprecated_in,
29
    )
1185.67.4 by Aaron Bentley
Throw if we try to write to a LockableFiles with no write lock
30
from bzrlib.tests import TestCaseInTempDir
2018.5.59 by Robert Collins
Get BranchConfig working somewhat on RemoteBranches (Robert Collins, Vincent Ladeuil).
31
from bzrlib.tests.test_smart import TestCaseWithSmartMedium
1594.2.22 by Robert Collins
Ensure that lockable files calls finish() on transactions.:
32
from bzrlib.tests.test_transactions import DummyWeave
1563.2.34 by Robert Collins
Remove the commit and rollback transaction methods as misleading, and implement a WriteTransaction
33
from bzrlib.transactions import (PassThroughTransaction,
34
                                 ReadOnlyTransaction,
35
                                 WriteTransaction,
36
                                 )
1185.67.4 by Aaron Bentley
Throw if we try to write to a LockableFiles with no write lock
37
from bzrlib.transport import get_transport
1185.65.23 by Robert Collins
update tests somewhat
38
1553.5.42 by Martin Pool
Start to parameterize LockableFiles test cases.
39
40
# these tests are applied in each parameterized suite for LockableFiles
3388.2.1 by Martin Pool
Deprecate LockableFiles.get_utf8
41
#
42
# they use an old style of parameterization, but we want to remove this class
43
# so won't modernize them now. - mbp 20080430
1553.5.42 by Martin Pool
Start to parameterize LockableFiles test cases.
44
class _TestLockableFiles_mixin(object):
1185.67.4 by Aaron Bentley
Throw if we try to write to a LockableFiles with no write lock
45
1185.67.6 by Aaron Bentley
Added tests and fixes for LockableFiles.put_utf8(); imported IterableFile
46
    def test_read_write(self):
1185.65.29 by Robert Collins
Implement final review suggestions.
47
        self.assertRaises(NoSuchFile, self.lockable.get, 'foo')
3388.2.1 by Martin Pool
Deprecate LockableFiles.get_utf8
48
        self.assertRaises(NoSuchFile,
49
            self.applyDeprecated,
50
            deprecated_in((1, 5, 0)),
51
            self.lockable.get_utf8, 'foo')
1185.67.6 by Aaron Bentley
Added tests and fixes for LockableFiles.put_utf8(); imported IterableFile
52
        self.lockable.lock_write()
53
        try:
54
            unicode_string = u'bar\u1234'
55
            self.assertEqual(4, len(unicode_string))
56
            byte_string = unicode_string.encode('utf-8')
57
            self.assertEqual(6, len(byte_string))
2018.5.133 by Andrew Bennetts
All TestLockableFiles_RemoteLockDir tests passing.
58
            self.assertRaises(UnicodeEncodeError, self.lockable.put, 'foo',
1185.67.6 by Aaron Bentley
Added tests and fixes for LockableFiles.put_utf8(); imported IterableFile
59
                              StringIO(unicode_string))
60
            self.lockable.put('foo', StringIO(byte_string))
61
            self.assertEqual(byte_string,
1185.65.29 by Robert Collins
Implement final review suggestions.
62
                             self.lockable.get('foo').read())
3388.2.1 by Martin Pool
Deprecate LockableFiles.get_utf8
63
            unicode_stream = self.applyDeprecated(
64
                deprecated_in((1, 5, 0)),
65
                self.lockable.get_utf8,
66
                'foo')
1185.67.6 by Aaron Bentley
Added tests and fixes for LockableFiles.put_utf8(); imported IterableFile
67
            self.assertEqual(unicode_string,
3388.2.1 by Martin Pool
Deprecate LockableFiles.get_utf8
68
                unicode_stream.read())
1185.65.29 by Robert Collins
Implement final review suggestions.
69
            self.assertRaises(BzrBadParameterNotString,
70
                              self.lockable.put_utf8,
71
                              'bar',
72
                              StringIO(unicode_string)
73
                              )
74
            self.lockable.put_utf8('bar', unicode_string)
3388.2.1 by Martin Pool
Deprecate LockableFiles.get_utf8
75
            unicode_stream = self.applyDeprecated(
76
                deprecated_in((1, 5, 0)),
77
                self.lockable.get_utf8,
78
                'bar')
2249.5.11 by John Arbash Meinel
Audit Branch to ensure utf8 revision ids.
79
            self.assertEqual(unicode_string,
3388.2.1 by Martin Pool
Deprecate LockableFiles.get_utf8
80
                unicode_stream.read())
2249.5.11 by John Arbash Meinel
Audit Branch to ensure utf8 revision ids.
81
            self.assertEqual(byte_string,
1185.65.29 by Robert Collins
Implement final review suggestions.
82
                             self.lockable.get('bar').read())
2249.5.11 by John Arbash Meinel
Audit Branch to ensure utf8 revision ids.
83
            self.lockable.put_bytes('raw', 'raw\xffbytes')
84
            self.assertEqual('raw\xffbytes',
85
                             self.lockable.get('raw').read())
1185.67.6 by Aaron Bentley
Added tests and fixes for LockableFiles.put_utf8(); imported IterableFile
86
        finally:
87
            self.lockable.unlock()
88
1185.67.4 by Aaron Bentley
Throw if we try to write to a LockableFiles with no write lock
89
    def test_locks(self):
1185.67.8 by Aaron Bentley
Test and fix read locks
90
        self.lockable.lock_read()
1185.65.27 by Robert Collins
Tweak storage towards mergability.
91
        try:
92
            self.assertRaises(ReadOnlyError, self.lockable.put, 'foo', 
93
                              StringIO('bar\u1234'))
94
        finally:
95
            self.lockable.unlock()
1185.68.1 by Aaron Bentley
test transactions
96
97
    def test_transactions(self):
98
        self.assertIs(self.lockable.get_transaction().__class__,
99
                      PassThroughTransaction)
100
        self.lockable.lock_read()
101
        try:
102
            self.assertIs(self.lockable.get_transaction().__class__,
103
                          ReadOnlyTransaction)
104
        finally:
105
            self.lockable.unlock()
106
        self.assertIs(self.lockable.get_transaction().__class__,
107
                      PassThroughTransaction)
108
        self.lockable.lock_write()
109
        self.assertIs(self.lockable.get_transaction().__class__,
1563.2.34 by Robert Collins
Remove the commit and rollback transaction methods as misleading, and implement a WriteTransaction
110
                      WriteTransaction)
1594.2.22 by Robert Collins
Ensure that lockable files calls finish() on transactions.:
111
        # check that finish is called:
112
        vf = DummyWeave('a')
113
        self.lockable.get_transaction().register_dirty(vf)
1185.68.1 by Aaron Bentley
test transactions
114
        self.lockable.unlock()
1594.2.22 by Robert Collins
Ensure that lockable files calls finish() on transactions.:
115
        self.assertTrue(vf.finished)
1185.65.23 by Robert Collins
update tests somewhat
116
117
    def test__escape(self):
118
        self.assertEqual('%25', self.lockable._escape('%'))
119
        
120
    def test__escape_empty(self):
121
        self.assertEqual('', self.lockable._escape(''))
122
1687.1.6 by Robert Collins
Extend LockableFiles to support break_lock() calls.
123
    def test_break_lock(self):
124
        # some locks are not breakable
125
        self.lockable.lock_write()
126
        try:
127
            self.assertRaises(AssertionError, self.lockable.break_lock)
128
        except NotImplementedError:
129
            # this lock cannot be broken
130
            self.lockable.unlock()
131
            return
132
        l2 = self.get_lockable()
133
        orig_factory = bzrlib.ui.ui_factory
134
        # silent ui - no need for stdout
135
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
136
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
137
        try:
138
            l2.break_lock()
139
        finally:
140
            bzrlib.ui.ui_factory = orig_factory
141
        try:
142
            l2.lock_write()
143
            l2.unlock()
144
        finally:
145
            self.assertRaises(errors.LockBroken, self.lockable.unlock)
146
            self.assertFalse(self.lockable.is_locked())
147
2279.7.1 by Andrew Bennetts
``LockableFiles.lock_write()`` now accepts a ``token`` keyword argument, so that
148
    def test_lock_write_returns_None_refuses_token(self):
149
        token = self.lockable.lock_write()
150
        try:
151
            if token is not None:
152
                # This test does not apply, because this lockable supports
153
                # tokens.
154
                return
155
            self.assertRaises(errors.TokenLockingNotSupported,
156
                              self.lockable.lock_write, token='token')
157
        finally:
158
            self.lockable.unlock()
159
2018.5.78 by Andrew Bennetts
Implement RemoteRepository.lock_write/unlock to expect and send tokens over the
160
    def test_lock_write_returns_token_when_given_token(self):
161
        token = self.lockable.lock_write()
162
        try:
163
            if token is None:
164
                # This test does not apply, because this lockable refuses
165
                # tokens.
166
                return
167
            new_lockable = self.get_lockable()
168
            token_from_new_lockable = new_lockable.lock_write(token=token)
169
            try:
170
                self.assertEqual(token, token_from_new_lockable)
171
            finally:
172
                new_lockable.unlock()
173
        finally:
174
            self.lockable.unlock()
175
2279.7.1 by Andrew Bennetts
``LockableFiles.lock_write()`` now accepts a ``token`` keyword argument, so that
176
    def test_lock_write_raises_on_token_mismatch(self):
177
        token = self.lockable.lock_write()
178
        try:
179
            if token is None:
180
                # This test does not apply, because this lockable refuses
181
                # tokens.
182
                return
183
            different_token = token + 'xxx'
184
            # Re-using the same lockable instance with a different token will
185
            # raise TokenMismatch.
186
            self.assertRaises(errors.TokenMismatch,
187
                              self.lockable.lock_write, token=different_token)
188
            # A seperate instance for the same lockable will also raise
189
            # TokenMismatch.
190
            # This detects the case where a caller claims to have a lock (via
191
            # the token) for an external resource, but doesn't (the token is
192
            # different).  Clients need a seperate lock object to make sure the
193
            # external resource is probed, whereas the existing lock object
194
            # might cache.
195
            new_lockable = self.get_lockable()
196
            self.assertRaises(errors.TokenMismatch,
197
                              new_lockable.lock_write, token=different_token)
198
        finally:
199
            self.lockable.unlock()
200
201
    def test_lock_write_with_matching_token(self):
202
        # If the token matches, so no exception is raised by lock_write.
203
        token = self.lockable.lock_write()
204
        try:
205
            if token is None:
206
                # This test does not apply, because this lockable refuses
207
                # tokens.
208
                return
209
            # The same instance will accept a second lock_write if the specified
210
            # token matches.
211
            self.lockable.lock_write(token=token)
212
            self.lockable.unlock()
213
            # Calling lock_write on a new instance for the same lockable will
214
            # also succeed.
215
            new_lockable = self.get_lockable()
216
            new_lockable.lock_write(token=token)
217
            new_lockable.unlock()
218
        finally:
219
            self.lockable.unlock()
220
221
    def test_unlock_after_lock_write_with_token(self):
222
        # If lock_write did not physically acquire the lock (because it was
223
        # passed a token), then unlock should not physically release it.
224
        token = self.lockable.lock_write()
225
        try:
226
            if token is None:
227
                # This test does not apply, because this lockable refuses
228
                # tokens.
229
                return
230
            new_lockable = self.get_lockable()
231
            new_lockable.lock_write(token=token)
232
            new_lockable.unlock()
233
            self.assertTrue(self.lockable.get_physical_lock_status())
234
        finally:
235
            self.lockable.unlock()
236
237
    def test_lock_write_with_token_fails_when_unlocked(self):
238
        # Lock and unlock to get a superficially valid token.  This mimics a
239
        # likely programming error, where a caller accidentally tries to lock
240
        # with a token that is no longer valid (because the original lock was
241
        # released).
242
        token = self.lockable.lock_write()
243
        self.lockable.unlock()
244
        if token is None:
245
            # This test does not apply, because this lockable refuses
246
            # tokens.
247
            return
248
249
        self.assertRaises(errors.TokenMismatch,
250
                          self.lockable.lock_write, token=token)
251
252
    def test_lock_write_reenter_with_token(self):
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
            # Relock with a token.
2018.5.79 by Andrew Bennetts
Implement RemoteBranch.lock_write/unlock as smart operations.
260
            token_from_reentry = self.lockable.lock_write(token=token)
261
            try:
262
                self.assertEqual(token, token_from_reentry)
263
            finally:
264
                self.lockable.unlock()
2279.7.1 by Andrew Bennetts
``LockableFiles.lock_write()`` now accepts a ``token`` keyword argument, so that
265
        finally:
266
            self.lockable.unlock()
267
        # The lock should be unlocked on disk.  Verify that with a new lock
268
        # instance.
269
        new_lockable = self.get_lockable()
270
        # Calling lock_write now should work, rather than raise LockContention.
271
        new_lockable.lock_write()
272
        new_lockable.unlock()
273
2279.7.11 by Andrew Bennetts
Remove some XXXs.
274
    def test_second_lock_write_returns_same_token(self):
275
        first_token = self.lockable.lock_write()
276
        try:
277
            if first_token is None:
278
                # This test does not apply, because this lockable refuses
279
                # tokens.
280
                return
281
            # Relock the already locked lockable.  It should return the same
282
            # token.
283
            second_token = self.lockable.lock_write()
284
            try:
285
                self.assertEqual(first_token, second_token)
286
            finally:
287
                self.lockable.unlock()
288
        finally:
289
            self.lockable.unlock()
290
2018.5.75 by Andrew Bennetts
Add Repository.{dont_,}leave_lock_in_place.
291
    def test_leave_in_place(self):
292
        token = self.lockable.lock_write()
293
        try:
294
            if token is None:
295
                # This test does not apply, because this lockable refuses
296
                # tokens.
297
                return
298
            self.lockable.leave_in_place()
299
        finally:
300
            self.lockable.unlock()
301
        # At this point, the lock is still in place on disk
302
        self.assertRaises(errors.LockContention, self.lockable.lock_write)
303
        # But should be relockable with a token.
304
        self.lockable.lock_write(token=token)
305
        self.lockable.unlock()
306
307
    def test_dont_leave_in_place(self):
308
        token = self.lockable.lock_write()
309
        try:
310
            if token is None:
311
                # This test does not apply, because this lockable refuses
312
                # tokens.
313
                return
314
            self.lockable.leave_in_place()
315
        finally:
316
            self.lockable.unlock()
317
        # At this point, the lock is still in place on disk.
318
        # Acquire the existing lock with the token, and ask that it is removed
319
        # when this object unlocks, and unlock to trigger that removal.
320
        new_lockable = self.get_lockable()
321
        new_lockable.lock_write(token=token)
322
        new_lockable.dont_leave_in_place()
323
        new_lockable.unlock()
324
        # At this point, the lock is no longer on disk, so we can lock it.
325
        third_lockable = self.get_lockable()
326
        third_lockable.lock_write()
327
        third_lockable.unlock()
2279.7.7 by Andrew Bennetts
LockDir, Repository and Branch lock token changes from the hpss branch.
328
329
1553.5.42 by Martin Pool
Start to parameterize LockableFiles test cases.
330
# This method of adapting tests to parameters is different to 
331
# the TestProviderAdapters used elsewhere, but seems simpler for this 
332
# case.  
1553.5.45 by Martin Pool
Clean up Transport-based locks for old branches
333
class TestLockableFiles_TransportLock(TestCaseInTempDir,
334
                                      _TestLockableFiles_mixin):
1553.5.42 by Martin Pool
Start to parameterize LockableFiles test cases.
335
336
    def setUp(self):
2279.7.1 by Andrew Bennetts
``LockableFiles.lock_write()`` now accepts a ``token`` keyword argument, so that
337
        TestCaseInTempDir.setUp(self)
1553.5.42 by Martin Pool
Start to parameterize LockableFiles test cases.
338
        transport = get_transport('.')
339
        transport.mkdir('.bzr')
1687.1.6 by Robert Collins
Extend LockableFiles to support break_lock() calls.
340
        self.sub_transport = transport.clone('.bzr')
341
        self.lockable = self.get_lockable()
1553.5.61 by Martin Pool
Locks protecting LockableFiles must now be explicitly created before use.
342
        self.lockable.create_lock()
1687.1.6 by Robert Collins
Extend LockableFiles to support break_lock() calls.
343
344
    def tearDown(self):
345
        super(TestLockableFiles_TransportLock, self).tearDown()
1687.1.15 by Robert Collins
Review comments.
346
        # free the subtransport so that we do not get a 5 second
347
        # timeout due to the SFTP connection cache.
1687.1.6 by Robert Collins
Extend LockableFiles to support break_lock() calls.
348
        del self.sub_transport
349
350
    def get_lockable(self):
351
        return LockableFiles(self.sub_transport, 'my-lock', TransportLock)
1553.5.43 by Martin Pool
Get LockableFiles tests running against LockDir
352
        
353
354
class TestLockableFiles_LockDir(TestCaseInTempDir,
355
                              _TestLockableFiles_mixin):
356
    """LockableFile tests run with LockDir underneath"""
357
358
    def setUp(self):
2279.7.1 by Andrew Bennetts
``LockableFiles.lock_write()`` now accepts a ``token`` keyword argument, so that
359
        TestCaseInTempDir.setUp(self)
1553.5.61 by Martin Pool
Locks protecting LockableFiles must now be explicitly created before use.
360
        self.transport = get_transport('.')
1687.1.6 by Robert Collins
Extend LockableFiles to support break_lock() calls.
361
        self.lockable = self.get_lockable()
1666.1.4 by Robert Collins
* 'Metadir' is now the default disk format. This improves behaviour in
362
        # the lock creation here sets mode - test_permissions on branch 
363
        # tests that implicitly, but it might be a good idea to factor 
364
        # out the mode checking logic and have it applied to loackable files
365
        # directly. RBC 20060418
1553.5.61 by Martin Pool
Locks protecting LockableFiles must now be explicitly created before use.
366
        self.lockable.create_lock()
1553.5.43 by Martin Pool
Get LockableFiles tests running against LockDir
367
1687.1.6 by Robert Collins
Extend LockableFiles to support break_lock() calls.
368
    def get_lockable(self):
3107.2.1 by John Arbash Meinel
Fix LockableFiles to not use modes that allow the user to write to things they create.
369
        return LockableFiles(self.transport, 'my-lock', lockdir.LockDir)
1553.5.60 by Martin Pool
New LockableFiles.create_lock() method
370
371
    def test_lock_created(self):
1553.5.61 by Martin Pool
Locks protecting LockableFiles must now be explicitly created before use.
372
        self.assertTrue(self.transport.has('my-lock'))
373
        self.lockable.lock_write()
374
        self.assertTrue(self.transport.has('my-lock/held/info'))
375
        self.lockable.unlock()
376
        self.assertFalse(self.transport.has('my-lock/held/info'))
377
        self.assertTrue(self.transport.has('my-lock'))
378
3107.2.1 by John Arbash Meinel
Fix LockableFiles to not use modes that allow the user to write to things they create.
379
    def test__file_modes(self):
380
        self.transport.mkdir('readonly')
381
        osutils.make_readonly('readonly')
382
        lockable = LockableFiles(self.transport.clone('readonly'), 'test-lock',
383
                                 lockdir.LockDir)
384
        # The directory mode should be read-write-execute for the current user
385
        self.assertEqual(00700, lockable._dir_mode & 00700)
386
        # Files should be read-write for the current user
387
        self.assertEqual(00600, lockable._file_mode & 00700)
1553.5.61 by Martin Pool
Locks protecting LockableFiles must now be explicitly created before use.
388
2018.5.59 by Robert Collins
Get BranchConfig working somewhat on RemoteBranches (Robert Collins, Vincent Ladeuil).
389
390
class TestLockableFiles_RemoteLockDir(TestCaseWithSmartMedium,
391
                              _TestLockableFiles_mixin):
392
    """LockableFile tests run with RemoteLockDir on a branch."""
393
394
    def setUp(self):
2018.5.75 by Andrew Bennetts
Add Repository.{dont_,}leave_lock_in_place.
395
        TestCaseWithSmartMedium.setUp(self)
2018.5.59 by Robert Collins
Get BranchConfig working somewhat on RemoteBranches (Robert Collins, Vincent Ladeuil).
396
        # can only get a RemoteLockDir with some RemoteObject...
397
        # use a branch as thats what we want. These mixin tests test the end
398
        # to end behaviour, so stubbing out the backend and simulating would
399
        # defeat the purpose. We test the protocol implementation separately
400
        # in test_remote and test_smart as usual.
2018.5.171 by Andrew Bennetts
Disconnect RemoteTransports in some tests to avoid tripping up test_strace with leftover threads from previous tests.
401
        b = self.make_branch('foo')
402
        self.addCleanup(b.bzrdir.transport.disconnect)
2018.5.59 by Robert Collins
Get BranchConfig working somewhat on RemoteBranches (Robert Collins, Vincent Ladeuil).
403
        self.transport = get_transport('.')
404
        self.lockable = self.get_lockable()
405
406
    def get_lockable(self):
407
        # getting a new lockable involves opening a new instance of the branch
408
        branch = bzrlib.branch.Branch.open(self.get_url('foo'))
2018.5.171 by Andrew Bennetts
Disconnect RemoteTransports in some tests to avoid tripping up test_strace with leftover threads from previous tests.
409
        self.addCleanup(branch.bzrdir.transport.disconnect)
2018.5.59 by Robert Collins
Get BranchConfig working somewhat on RemoteBranches (Robert Collins, Vincent Ladeuil).
410
        return branch.control_files