/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lockable_files.py

  • Committer: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
import bzrlib
 
18
from bzrlib import (
 
19
    errors,
 
20
    lockdir,
 
21
    osutils,
 
22
    transport,
 
23
    )
 
24
from bzrlib.lockable_files import LockableFiles, TransportLock
 
25
from bzrlib.tests import (
 
26
    TestCaseInTempDir,
 
27
    TestNotApplicable,
 
28
    )
 
29
from bzrlib.tests.test_smart import TestCaseWithSmartMedium
 
30
from bzrlib.tests.test_transactions import DummyWeave
 
31
from bzrlib.transactions import (PassThroughTransaction,
 
32
                                 ReadOnlyTransaction,
 
33
                                 WriteTransaction,
 
34
                                 )
 
35
 
 
36
 
 
37
# these tests are applied in each parameterized suite for LockableFiles
 
38
#
 
39
# they use an old style of parameterization, but we want to remove this class
 
40
# so won't modernize them now. - mbp 20080430
 
41
class _TestLockableFiles_mixin(object):
 
42
 
 
43
    def test_transactions(self):
 
44
        self.assertIs(self.lockable.get_transaction().__class__,
 
45
                      PassThroughTransaction)
 
46
        self.lockable.lock_read()
 
47
        try:
 
48
            self.assertIs(self.lockable.get_transaction().__class__,
 
49
                          ReadOnlyTransaction)
 
50
        finally:
 
51
            self.lockable.unlock()
 
52
        self.assertIs(self.lockable.get_transaction().__class__,
 
53
                      PassThroughTransaction)
 
54
        self.lockable.lock_write()
 
55
        self.assertIs(self.lockable.get_transaction().__class__,
 
56
                      WriteTransaction)
 
57
        # check that finish is called:
 
58
        vf = DummyWeave('a')
 
59
        self.lockable.get_transaction().register_dirty(vf)
 
60
        self.lockable.unlock()
 
61
        self.assertTrue(vf.finished)
 
62
 
 
63
    def test__escape(self):
 
64
        self.assertEqual('%25', self.lockable._escape('%'))
 
65
 
 
66
    def test__escape_empty(self):
 
67
        self.assertEqual('', self.lockable._escape(''))
 
68
 
 
69
    def test_break_lock(self):
 
70
        # some locks are not breakable
 
71
        self.lockable.lock_write()
 
72
        try:
 
73
            self.assertRaises(AssertionError, self.lockable.break_lock)
 
74
        except NotImplementedError:
 
75
            # this lock cannot be broken
 
76
            self.lockable.unlock()
 
77
            raise TestNotApplicable("%r is not breakable" % (self.lockable,))
 
78
        l2 = self.get_lockable()
 
79
        orig_factory = bzrlib.ui.ui_factory
 
80
        # silent ui - no need for stdout
 
81
        bzrlib.ui.ui_factory = bzrlib.ui.CannedInputUIFactory([True])
 
82
        try:
 
83
            l2.break_lock()
 
84
        finally:
 
85
            bzrlib.ui.ui_factory = orig_factory
 
86
        try:
 
87
            l2.lock_write()
 
88
            l2.unlock()
 
89
        finally:
 
90
            self.assertRaises(errors.LockBroken, self.lockable.unlock)
 
91
            self.assertFalse(self.lockable.is_locked())
 
92
 
 
93
    def test_lock_write_returns_None_refuses_token(self):
 
94
        token = self.lockable.lock_write()
 
95
        self.addCleanup(self.lockable.unlock)
 
96
        if token is not None:
 
97
            # This test does not apply, because this lockable supports
 
98
            # tokens.
 
99
            raise TestNotApplicable("%r uses tokens" % (self.lockable,))
 
100
        self.assertRaises(errors.TokenLockingNotSupported,
 
101
                          self.lockable.lock_write, token='token')
 
102
 
 
103
    def test_lock_write_returns_token_when_given_token(self):
 
104
        token = self.lockable.lock_write()
 
105
        self.addCleanup(self.lockable.unlock)
 
106
        if token is None:
 
107
            # This test does not apply, because this lockable refuses
 
108
            # tokens.
 
109
            return
 
110
        new_lockable = self.get_lockable()
 
111
        token_from_new_lockable = new_lockable.lock_write(token=token)
 
112
        self.addCleanup(new_lockable.unlock)
 
113
        self.assertEqual(token, token_from_new_lockable)
 
114
 
 
115
    def test_lock_write_raises_on_token_mismatch(self):
 
116
        token = self.lockable.lock_write()
 
117
        self.addCleanup(self.lockable.unlock)
 
118
        if token is None:
 
119
            # This test does not apply, because this lockable refuses
 
120
            # tokens.
 
121
            return
 
122
        different_token = token + 'xxx'
 
123
        # Re-using the same lockable instance with a different token will
 
124
        # raise TokenMismatch.
 
125
        self.assertRaises(errors.TokenMismatch,
 
126
                          self.lockable.lock_write, token=different_token)
 
127
        # A separate instance for the same lockable will also raise
 
128
        # TokenMismatch.
 
129
        # This detects the case where a caller claims to have a lock (via
 
130
        # the token) for an external resource, but doesn't (the token is
 
131
        # different).  Clients need a separate lock object to make sure the
 
132
        # external resource is probed, whereas the existing lock object
 
133
        # might cache.
 
134
        new_lockable = self.get_lockable()
 
135
        self.assertRaises(errors.TokenMismatch,
 
136
                          new_lockable.lock_write, token=different_token)
 
137
 
 
138
    def test_lock_write_with_matching_token(self):
 
139
        # If the token matches, so no exception is raised by lock_write.
 
140
        token = self.lockable.lock_write()
 
141
        self.addCleanup(self.lockable.unlock)
 
142
        if token is None:
 
143
            # This test does not apply, because this lockable refuses
 
144
            # tokens.
 
145
            return
 
146
        # The same instance will accept a second lock_write if the specified
 
147
        # token matches.
 
148
        self.lockable.lock_write(token=token)
 
149
        self.lockable.unlock()
 
150
        # Calling lock_write on a new instance for the same lockable will
 
151
        # also succeed.
 
152
        new_lockable = self.get_lockable()
 
153
        new_lockable.lock_write(token=token)
 
154
        new_lockable.unlock()
 
155
 
 
156
    def test_unlock_after_lock_write_with_token(self):
 
157
        # If lock_write did not physically acquire the lock (because it was
 
158
        # passed a token), then unlock should not physically release it.
 
159
        token = self.lockable.lock_write()
 
160
        self.addCleanup(self.lockable.unlock)
 
161
        if token is None:
 
162
            # This test does not apply, because this lockable refuses
 
163
            # tokens.
 
164
            return
 
165
        new_lockable = self.get_lockable()
 
166
        new_lockable.lock_write(token=token)
 
167
        new_lockable.unlock()
 
168
        self.assertTrue(self.lockable.get_physical_lock_status())
 
169
 
 
170
    def test_lock_write_with_token_fails_when_unlocked(self):
 
171
        # Lock and unlock to get a superficially valid token.  This mimics a
 
172
        # likely programming error, where a caller accidentally tries to lock
 
173
        # with a token that is no longer valid (because the original lock was
 
174
        # released).
 
175
        token = self.lockable.lock_write()
 
176
        self.lockable.unlock()
 
177
        if token is None:
 
178
            # This test does not apply, because this lockable refuses
 
179
            # tokens.
 
180
            return
 
181
 
 
182
        self.assertRaises(errors.TokenMismatch,
 
183
                          self.lockable.lock_write, token=token)
 
184
 
 
185
    def test_lock_write_reenter_with_token(self):
 
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
            # Relock with a token.
 
193
            token_from_reentry = self.lockable.lock_write(token=token)
 
194
            try:
 
195
                self.assertEqual(token, token_from_reentry)
 
196
            finally:
 
197
                self.lockable.unlock()
 
198
        finally:
 
199
            self.lockable.unlock()
 
200
        # The lock should be unlocked on disk.  Verify that with a new lock
 
201
        # instance.
 
202
        new_lockable = self.get_lockable()
 
203
        # Calling lock_write now should work, rather than raise LockContention.
 
204
        new_lockable.lock_write()
 
205
        new_lockable.unlock()
 
206
 
 
207
    def test_second_lock_write_returns_same_token(self):
 
208
        first_token = self.lockable.lock_write()
 
209
        try:
 
210
            if first_token is None:
 
211
                # This test does not apply, because this lockable refuses
 
212
                # tokens.
 
213
                return
 
214
            # Relock the already locked lockable.  It should return the same
 
215
            # token.
 
216
            second_token = self.lockable.lock_write()
 
217
            try:
 
218
                self.assertEqual(first_token, second_token)
 
219
            finally:
 
220
                self.lockable.unlock()
 
221
        finally:
 
222
            self.lockable.unlock()
 
223
 
 
224
    def test_leave_in_place(self):
 
225
        token = self.lockable.lock_write()
 
226
        try:
 
227
            if token is None:
 
228
                # This test does not apply, because this lockable refuses
 
229
                # tokens.
 
230
                return
 
231
            self.lockable.leave_in_place()
 
232
        finally:
 
233
            self.lockable.unlock()
 
234
        # At this point, the lock is still in place on disk
 
235
        self.assertRaises(errors.LockContention, self.lockable.lock_write)
 
236
        # But should be relockable with a token.
 
237
        self.lockable.lock_write(token=token)
 
238
        self.lockable.unlock()
 
239
        # Cleanup: we should still be able to get the lock, but we restore the
 
240
        # behavior to clearing the lock when unlocking.
 
241
        self.lockable.lock_write(token=token)
 
242
        self.lockable.dont_leave_in_place()
 
243
        self.lockable.unlock()
 
244
 
 
245
    def test_dont_leave_in_place(self):
 
246
        token = self.lockable.lock_write()
 
247
        try:
 
248
            if token is None:
 
249
                # This test does not apply, because this lockable refuses
 
250
                # tokens.
 
251
                return
 
252
            self.lockable.leave_in_place()
 
253
        finally:
 
254
            self.lockable.unlock()
 
255
        # At this point, the lock is still in place on disk.
 
256
        # Acquire the existing lock with the token, and ask that it is removed
 
257
        # when this object unlocks, and unlock to trigger that removal.
 
258
        new_lockable = self.get_lockable()
 
259
        new_lockable.lock_write(token=token)
 
260
        new_lockable.dont_leave_in_place()
 
261
        new_lockable.unlock()
 
262
        # At this point, the lock is no longer on disk, so we can lock it.
 
263
        third_lockable = self.get_lockable()
 
264
        third_lockable.lock_write()
 
265
        third_lockable.unlock()
 
266
 
 
267
 
 
268
# This method of adapting tests to parameters is different to
 
269
# the TestProviderAdapters used elsewhere, but seems simpler for this
 
270
# case.
 
271
class TestLockableFiles_TransportLock(TestCaseInTempDir,
 
272
                                      _TestLockableFiles_mixin):
 
273
 
 
274
    def setUp(self):
 
275
        TestCaseInTempDir.setUp(self)
 
276
        t = transport.get_transport_from_path('.')
 
277
        t.mkdir('.bzr')
 
278
        self.sub_transport = t.clone('.bzr')
 
279
        self.lockable = self.get_lockable()
 
280
        self.lockable.create_lock()
 
281
 
 
282
    def stop_server(self):
 
283
        super(TestLockableFiles_TransportLock, self).stop_server()
 
284
        # free the subtransport so that we do not get a 5 second
 
285
        # timeout due to the SFTP connection cache.
 
286
        try:
 
287
            del self.sub_transport
 
288
        except AttributeError:
 
289
            pass
 
290
 
 
291
    def get_lockable(self):
 
292
        return LockableFiles(self.sub_transport, 'my-lock', TransportLock)
 
293
 
 
294
 
 
295
class TestLockableFiles_LockDir(TestCaseInTempDir,
 
296
                              _TestLockableFiles_mixin):
 
297
    """LockableFile tests run with LockDir underneath"""
 
298
 
 
299
    def setUp(self):
 
300
        TestCaseInTempDir.setUp(self)
 
301
        self.transport = transport.get_transport_from_path('.')
 
302
        self.lockable = self.get_lockable()
 
303
        # the lock creation here sets mode - test_permissions on branch
 
304
        # tests that implicitly, but it might be a good idea to factor
 
305
        # out the mode checking logic and have it applied to loackable files
 
306
        # directly. RBC 20060418
 
307
        self.lockable.create_lock()
 
308
 
 
309
    def get_lockable(self):
 
310
        return LockableFiles(self.transport, 'my-lock', lockdir.LockDir)
 
311
 
 
312
    def test_lock_created(self):
 
313
        self.assertTrue(self.transport.has('my-lock'))
 
314
        self.lockable.lock_write()
 
315
        self.assertTrue(self.transport.has('my-lock/held/info'))
 
316
        self.lockable.unlock()
 
317
        self.assertFalse(self.transport.has('my-lock/held/info'))
 
318
        self.assertTrue(self.transport.has('my-lock'))
 
319
 
 
320
    def test__file_modes(self):
 
321
        self.transport.mkdir('readonly')
 
322
        osutils.make_readonly('readonly')
 
323
        lockable = LockableFiles(self.transport.clone('readonly'), 'test-lock',
 
324
                                 lockdir.LockDir)
 
325
        # The directory mode should be read-write-execute for the current user
 
326
        self.assertEqual(00700, lockable._dir_mode & 00700)
 
327
        # Files should be read-write for the current user
 
328
        self.assertEqual(00600, lockable._file_mode & 00700)
 
329
 
 
330
 
 
331
class TestLockableFiles_RemoteLockDir(TestCaseWithSmartMedium,
 
332
                              _TestLockableFiles_mixin):
 
333
    """LockableFile tests run with RemoteLockDir on a branch."""
 
334
 
 
335
    def setUp(self):
 
336
        TestCaseWithSmartMedium.setUp(self)
 
337
        # can only get a RemoteLockDir with some RemoteObject...
 
338
        # use a branch as thats what we want. These mixin tests test the end
 
339
        # to end behaviour, so stubbing out the backend and simulating would
 
340
        # defeat the purpose. We test the protocol implementation separately
 
341
        # in test_remote and test_smart as usual.
 
342
        b = self.make_branch('foo')
 
343
        self.addCleanup(b.bzrdir.transport.disconnect)
 
344
        self.transport = transport.get_transport_from_path('.')
 
345
        self.lockable = self.get_lockable()
 
346
 
 
347
    def get_lockable(self):
 
348
        # getting a new lockable involves opening a new instance of the branch
 
349
        branch = bzrlib.branch.Branch.open(self.get_url('foo'))
 
350
        self.addCleanup(branch.bzrdir.transport.disconnect)
 
351
        return branch.control_files