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