~bzr-pqm/bzr/bzr.dev

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