/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_lockdir.py

  • Committer: Martin Pool
  • Date: 2006-10-06 02:04:17 UTC
  • mfrom: (1908.10.1 bench_usecases.merge2)
  • mto: This revision was merged to the branch mainline in revision 2068.
  • Revision ID: mbp@sourcefrog.net-20061006020417-4949ca86f4417a4d
merge additional fix from cfbolz

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2006 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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Tests for LockDir"""
18
18
 
19
 
from threading import Thread
 
19
from cStringIO import StringIO
 
20
from threading import Thread, Lock
20
21
import time
21
22
 
 
23
import bzrlib
 
24
from bzrlib import (
 
25
    osutils,
 
26
    )
22
27
from bzrlib.errors import (
23
28
        LockBreakMismatch,
24
29
        LockContention, LockError, UnlockableTransport,
25
30
        LockNotHeld, LockBroken
26
31
        )
27
 
from bzrlib.lockdir import LockDir
 
32
from bzrlib.lockdir import LockDir, _DEFAULT_TIMEOUT_SECONDS
28
33
from bzrlib.tests import TestCaseWithTransport
 
34
from bzrlib.trace import note
29
35
 
30
36
# These tests sometimes use threads to test the behaviour of lock files with
31
37
# concurrent actors.  This is not a typical (or necessarily supported) use;
40
46
class TestLockDir(TestCaseWithTransport):
41
47
    """Test LockDir operations"""
42
48
 
 
49
    def logging_report_function(self, fmt, *args):
 
50
        self._logged_reports.append((fmt, args))
 
51
 
 
52
    def setup_log_reporter(self, lock_dir):
 
53
        self._logged_reports = []
 
54
        lock_dir._report_function = self.logging_report_function
 
55
 
43
56
    def test_00_lock_creation(self):
44
57
        """Creation of lock file on a transport"""
45
58
        t = self.get_transport()
56
69
        lf = LockDir(self.get_transport(), 'test_lock')
57
70
        self.assertEqual(lf.peek(), None)
58
71
 
 
72
    def get_lock(self):
 
73
        return LockDir(self.get_transport(), 'test_lock')
 
74
 
 
75
    def test_unlock_after_break_raises(self):
 
76
        ld = self.get_lock()
 
77
        ld2 = self.get_lock()
 
78
        ld.create()
 
79
        ld.attempt_lock()
 
80
        ld2.force_break(ld2.peek())
 
81
        self.assertRaises(LockBroken, ld.unlock)
 
82
 
59
83
    def test_03_readonly_peek(self):
60
84
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
61
85
        self.assertEqual(lf.peek(), None)
143
167
        lf1 = LockDir(t, 'test_lock')
144
168
        lf1.create()
145
169
        lf2 = LockDir(t, 'test_lock')
 
170
        self.setup_log_reporter(lf2)
146
171
        lf1.attempt_lock()
147
172
        try:
148
173
            before = time.time()
149
174
            self.assertRaises(LockContention, lf2.wait_lock,
150
175
                              timeout=0.4, poll=0.1)
151
176
            after = time.time()
152
 
            self.assertTrue(after - before <= 1.0)
 
177
            # it should only take about 0.4 seconds, but we allow more time in
 
178
            # case the machine is heavily loaded
 
179
            self.assertTrue(after - before <= 8.0, 
 
180
                    "took %f seconds to detect lock contention" % (after - before))
153
181
        finally:
154
182
            lf1.unlock()
 
183
        lock_base = lf2.transport.abspath(lf2.path)
 
184
        self.assertEqual(1, len(self._logged_reports))
 
185
        self.assertEqual('%s %s\n'
 
186
                         '%s\n%s\n'
 
187
                         'Will continue to try until %s\n',
 
188
                         self._logged_reports[0][0])
 
189
        args = self._logged_reports[0][1]
 
190
        self.assertEqual('Unable to obtain', args[0])
 
191
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
192
        self.assertStartsWith(args[2], 'held by ')
 
193
        self.assertStartsWith(args[3], 'locked ')
 
194
        self.assertEndsWith(args[3], ' ago')
 
195
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
155
196
 
156
197
    def test_31_lock_wait_easy(self):
157
198
        """Succeed when waiting on a lock with no contention.
159
200
        t = self.get_transport()
160
201
        lf1 = LockDir(t, 'test_lock')
161
202
        lf1.create()
 
203
        self.setup_log_reporter(lf1)
162
204
        try:
163
205
            before = time.time()
164
206
            lf1.wait_lock(timeout=0.4, poll=0.1)
166
208
            self.assertTrue(after - before <= 1.0)
167
209
        finally:
168
210
            lf1.unlock()
 
211
        self.assertEqual([], self._logged_reports)
169
212
 
170
213
    def test_32_lock_wait_succeed(self):
171
214
        """Succeed when trying to acquire a lock that gets released
185
228
        unlocker.start()
186
229
        try:
187
230
            lf2 = LockDir(t, 'test_lock')
 
231
            self.setup_log_reporter(lf2)
188
232
            before = time.time()
189
233
            # wait and then lock
190
234
            lf2.wait_lock(timeout=0.4, poll=0.1)
193
237
        finally:
194
238
            unlocker.join()
195
239
 
 
240
        # There should be only 1 report, even though it should have to
 
241
        # wait for a while
 
242
        lock_base = lf2.transport.abspath(lf2.path)
 
243
        self.assertEqual(1, len(self._logged_reports))
 
244
        self.assertEqual('%s %s\n'
 
245
                         '%s\n%s\n'
 
246
                         'Will continue to try until %s\n',
 
247
                         self._logged_reports[0][0])
 
248
        args = self._logged_reports[0][1]
 
249
        self.assertEqual('Unable to obtain', args[0])
 
250
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
251
        self.assertStartsWith(args[2], 'held by ')
 
252
        self.assertStartsWith(args[3], 'locked ')
 
253
        self.assertEndsWith(args[3], ' ago')
 
254
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
255
 
196
256
    def test_33_wait(self):
197
257
        """Succeed when waiting on a lock that gets released
198
258
 
220
280
        finally:
221
281
            unlocker.join()
222
282
 
 
283
    def test_34_lock_write_waits(self):
 
284
        """LockDir.lock_write() will wait for the lock.""" 
 
285
        t = self.get_transport()
 
286
        lf1 = LockDir(t, 'test_lock')
 
287
        lf1.create()
 
288
        lf1.attempt_lock()
 
289
 
 
290
        def wait_and_unlock():
 
291
            time.sleep(0.1)
 
292
            lf1.unlock()
 
293
        unlocker = Thread(target=wait_and_unlock)
 
294
        unlocker.start()
 
295
        try:
 
296
            lf2 = LockDir(t, 'test_lock')
 
297
            self.setup_log_reporter(lf2)
 
298
            before = time.time()
 
299
            # wait and then lock
 
300
            lf2.lock_write()
 
301
            after = time.time()
 
302
        finally:
 
303
            unlocker.join()
 
304
 
 
305
        # There should be only 1 report, even though it should have to
 
306
        # wait for a while
 
307
        lock_base = lf2.transport.abspath(lf2.path)
 
308
        self.assertEqual(1, len(self._logged_reports))
 
309
        self.assertEqual('%s %s\n'
 
310
                         '%s\n%s\n'
 
311
                         'Will continue to try until %s\n',
 
312
                         self._logged_reports[0][0])
 
313
        args = self._logged_reports[0][1]
 
314
        self.assertEqual('Unable to obtain', args[0])
 
315
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
316
        self.assertStartsWith(args[2], 'held by ')
 
317
        self.assertStartsWith(args[3], 'locked ')
 
318
        self.assertEndsWith(args[3], ' ago')
 
319
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
320
 
 
321
    def test_35_wait_lock_changing(self):
 
322
        """LockDir.wait_lock() will report if the lock changes underneath.
 
323
        
 
324
        This is the stages we want to happen:
 
325
 
 
326
        0) Synchronization locks are created and locked.
 
327
        1) Lock1 obtains the lockdir, and releases the 'check' lock.
 
328
        2) Lock2 grabs the 'check' lock, and checks the lockdir.
 
329
           It sees the lockdir is already acquired, reports the fact, 
 
330
           and unsets the 'checked' lock.
 
331
        3) Thread1 blocks on acquiring the 'checked' lock, and then tells
 
332
           Lock1 to release and acquire the lockdir. This resets the 'check'
 
333
           lock.
 
334
        4) Lock2 acquires the 'check' lock, and checks again. It notices
 
335
           that the holder of the lock has changed, and so reports a new 
 
336
           lock holder.
 
337
        5) Thread1 blocks on the 'checked' lock, this time, it completely
 
338
           unlocks the lockdir, allowing Lock2 to acquire the lock.
 
339
        """
 
340
 
 
341
        wait_to_check_lock = Lock()
 
342
        wait_until_checked_lock = Lock()
 
343
 
 
344
        wait_to_check_lock.acquire()
 
345
        wait_until_checked_lock.acquire()
 
346
        note('locked check and checked locks')
 
347
 
 
348
        class LockDir1(LockDir):
 
349
            """Use the synchronization points for the first lock."""
 
350
 
 
351
            def attempt_lock(self):
 
352
                # Once we have acquired the lock, it is okay for
 
353
                # the other lock to check it
 
354
                try:
 
355
                    return super(LockDir1, self).attempt_lock()
 
356
                finally:
 
357
                    note('lock1: releasing check lock')
 
358
                    wait_to_check_lock.release()
 
359
 
 
360
        class LockDir2(LockDir):
 
361
            """Use the synchronization points for the second lock."""
 
362
 
 
363
            def attempt_lock(self):
 
364
                note('lock2: waiting for check lock')
 
365
                wait_to_check_lock.acquire()
 
366
                note('lock2: acquired check lock')
 
367
                try:
 
368
                    return super(LockDir2, self).attempt_lock()
 
369
                finally:
 
370
                    note('lock2: releasing checked lock')
 
371
                    wait_until_checked_lock.release()
 
372
 
 
373
        t = self.get_transport()
 
374
        lf1 = LockDir1(t, 'test_lock')
 
375
        lf1.create()
 
376
 
 
377
        lf2 = LockDir2(t, 'test_lock')
 
378
        self.setup_log_reporter(lf2)
 
379
 
 
380
        def wait_and_switch():
 
381
            lf1.attempt_lock()
 
382
            # Block until lock2 has had a chance to check
 
383
            note('lock1: waiting 1 for checked lock')
 
384
            wait_until_checked_lock.acquire()
 
385
            note('lock1: acquired for checked lock')
 
386
            note('lock1: released lockdir')
 
387
            lf1.unlock()
 
388
            note('lock1: acquiring lockdir')
 
389
            # Create a new nonce, so the lock looks different.
 
390
            lf1.nonce = osutils.rand_chars(20)
 
391
            lf1.lock_write()
 
392
            note('lock1: acquired lockdir')
 
393
 
 
394
            # Block until lock2 has peeked again
 
395
            note('lock1: waiting 2 for checked lock')
 
396
            wait_until_checked_lock.acquire()
 
397
            note('lock1: acquired for checked lock')
 
398
            # Now unlock, and let lock 2 grab the lock
 
399
            lf1.unlock()
 
400
            wait_to_check_lock.release()
 
401
 
 
402
        unlocker = Thread(target=wait_and_switch)
 
403
        unlocker.start()
 
404
        try:
 
405
            # Wait and play against the other thread
 
406
            lf2.wait_lock(timeout=1.0, poll=0.01)
 
407
        finally:
 
408
            unlocker.join()
 
409
        lf2.unlock()
 
410
 
 
411
        # There should be 2 reports, because the lock changed
 
412
        lock_base = lf2.transport.abspath(lf2.path)
 
413
        self.assertEqual(2, len(self._logged_reports))
 
414
 
 
415
        self.assertEqual('%s %s\n'
 
416
                         '%s\n%s\n'
 
417
                         'Will continue to try until %s\n',
 
418
                         self._logged_reports[0][0])
 
419
        args = self._logged_reports[0][1]
 
420
        self.assertEqual('Unable to obtain', args[0])
 
421
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
422
        self.assertStartsWith(args[2], 'held by ')
 
423
        self.assertStartsWith(args[3], 'locked ')
 
424
        self.assertEndsWith(args[3], ' ago')
 
425
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
426
 
 
427
        self.assertEqual('%s %s\n'
 
428
                         '%s\n%s\n'
 
429
                         'Will continue to try until %s\n',
 
430
                         self._logged_reports[1][0])
 
431
        args = self._logged_reports[1][1]
 
432
        self.assertEqual('Lock owner changed for', args[0])
 
433
        self.assertEqual('lock %s' % (lock_base,), args[1])
 
434
        self.assertStartsWith(args[2], 'held by ')
 
435
        self.assertStartsWith(args[3], 'locked ')
 
436
        self.assertEndsWith(args[3], ' ago')
 
437
        self.assertContainsRe(args[4], r'\d\d:\d\d:\d\d')
 
438
 
223
439
    def test_40_confirm_easy(self):
224
440
        """Confirm a lock that's already held"""
225
441
        t = self.get_transport()
318
534
        self.assertTrue(t.has('test_lock/held/info'))
319
535
        lf1.unlock()
320
536
        self.assertFalse(t.has('test_lock/held/info'))
 
537
 
 
538
    def test_break_lock(self):
 
539
        # the ui based break_lock routine should Just Work (tm)
 
540
        ld1 = self.get_lock()
 
541
        ld2 = self.get_lock()
 
542
        ld1.create()
 
543
        ld1.lock_write()
 
544
        # do this without IO redirection to ensure it doesn't prompt.
 
545
        self.assertRaises(AssertionError, ld1.break_lock)
 
546
        orig_factory = bzrlib.ui.ui_factory
 
547
        # silent ui - no need for stdout
 
548
        bzrlib.ui.ui_factory = bzrlib.ui.SilentUIFactory()
 
549
        bzrlib.ui.ui_factory.stdin = StringIO("y\n")
 
550
        try:
 
551
            ld2.break_lock()
 
552
            self.assertRaises(LockBroken, ld1.unlock)
 
553
        finally:
 
554
            bzrlib.ui.ui_factory = orig_factory
 
555
 
 
556
    def test_create_missing_base_directory(self):
 
557
        """If LockDir.path doesn't exist, it can be created
 
558
 
 
559
        Some people manually remove the entire lock/ directory trying
 
560
        to unlock a stuck repository/branch/etc. Rather than failing
 
561
        after that, just create the lock directory when needed.
 
562
        """
 
563
        t = self.get_transport()
 
564
        lf1 = LockDir(t, 'test_lock')
 
565
 
 
566
        lf1.create()
 
567
        self.failUnless(t.has('test_lock'))
 
568
 
 
569
        t.rmdir('test_lock')
 
570
        self.failIf(t.has('test_lock'))
 
571
 
 
572
        # This will create 'test_lock' if it needs to
 
573
        lf1.lock_write()
 
574
        self.failUnless(t.has('test_lock'))
 
575
        self.failUnless(t.has('test_lock/held/info'))
 
576
 
 
577
        lf1.unlock()
 
578
        self.failIf(t.has('test_lock/held/info'))
 
579
 
 
580
    def test__format_lock_info(self):
 
581
        ld1 = self.get_lock()
 
582
        ld1.create()
 
583
        ld1.lock_write()
 
584
        try:
 
585
            info_list = ld1._format_lock_info(ld1.peek())
 
586
        finally:
 
587
            ld1.unlock()
 
588
        self.assertEqual('lock %s' % (ld1.transport.abspath(ld1.path),),
 
589
                         info_list[0])
 
590
        self.assertContainsRe(info_list[1],
 
591
                              r'^held by .* on host .* \[process #\d*\]$')
 
592
        self.assertContainsRe(info_list[2], r'locked \d+ seconds? ago$')