~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_lockdir.py

  • Committer: John Arbash Meinel
  • Date: 2006-10-11 23:08:27 UTC
  • mto: This revision was merged to the branch mainline in revision 2080.
  • Revision ID: john@arbash-meinel.com-20061011230827-2bdfc45020695281
Change Copyright .. by Canonical to Copyright ... Canonical

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Tests for LockDir"""
 
18
 
 
19
from cStringIO import StringIO
 
20
from threading import Thread, Lock
 
21
import time
 
22
 
 
23
import bzrlib
 
24
from bzrlib import (
 
25
    osutils,
 
26
    )
 
27
from bzrlib.errors import (
 
28
        LockBreakMismatch,
 
29
        LockContention, LockError, UnlockableTransport,
 
30
        LockNotHeld, LockBroken
 
31
        )
 
32
from bzrlib.lockdir import LockDir, _DEFAULT_TIMEOUT_SECONDS
 
33
from bzrlib.tests import TestCaseWithTransport
 
34
from bzrlib.trace import note
 
35
 
 
36
# These tests sometimes use threads to test the behaviour of lock files with
 
37
# concurrent actors.  This is not a typical (or necessarily supported) use;
 
38
# they're really meant for guarding between processes.
 
39
 
 
40
# These tests are run on the default transport provided by the test framework
 
41
# (typically a local disk transport).  That can be changed by the --transport
 
42
# option to bzr selftest.  The required properties of the transport
 
43
# implementation are tested separately.  (The main requirement is just that
 
44
# they don't allow overwriting nonempty directories.)
 
45
 
 
46
class TestLockDir(TestCaseWithTransport):
 
47
    """Test LockDir operations"""
 
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
 
 
56
    def test_00_lock_creation(self):
 
57
        """Creation of lock file on a transport"""
 
58
        t = self.get_transport()
 
59
        lf = LockDir(t, 'test_lock')
 
60
        self.assertFalse(lf.is_held)
 
61
 
 
62
    def test_01_lock_repr(self):
 
63
        """Lock string representation"""
 
64
        lf = LockDir(self.get_transport(), 'test_lock')
 
65
        r = repr(lf)
 
66
        self.assertContainsRe(r, r'^LockDir\(.*/test_lock\)$')
 
67
 
 
68
    def test_02_unlocked_peek(self):
 
69
        lf = LockDir(self.get_transport(), 'test_lock')
 
70
        self.assertEqual(lf.peek(), None)
 
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
 
 
83
    def test_03_readonly_peek(self):
 
84
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
 
85
        self.assertEqual(lf.peek(), None)
 
86
 
 
87
    def test_10_lock_uncontested(self):
 
88
        """Acquire and release a lock"""
 
89
        t = self.get_transport()
 
90
        lf = LockDir(t, 'test_lock')
 
91
        lf.create()
 
92
        lf.attempt_lock()
 
93
        try:
 
94
            self.assertTrue(lf.is_held)
 
95
        finally:
 
96
            lf.unlock()
 
97
            self.assertFalse(lf.is_held)
 
98
 
 
99
    def test_11_create_readonly_transport(self):
 
100
        """Fail to create lock on readonly transport"""
 
101
        t = self.get_readonly_transport()
 
102
        lf = LockDir(t, 'test_lock')
 
103
        self.assertRaises(UnlockableTransport, lf.create)
 
104
 
 
105
    def test_12_lock_readonly_transport(self):
 
106
        """Fail to lock on readonly transport"""
 
107
        lf = LockDir(self.get_transport(), 'test_lock')
 
108
        lf.create()
 
109
        lf = LockDir(self.get_readonly_transport(), 'test_lock')
 
110
        self.assertRaises(UnlockableTransport, lf.attempt_lock)
 
111
 
 
112
    def test_20_lock_contested(self):
 
113
        """Contention to get a lock"""
 
114
        t = self.get_transport()
 
115
        lf1 = LockDir(t, 'test_lock')
 
116
        lf1.create()
 
117
        lf1.attempt_lock()
 
118
        lf2 = LockDir(t, 'test_lock')
 
119
        try:
 
120
            # locking is between LockDir instances; aliases within 
 
121
            # a single process are not detected
 
122
            lf2.attempt_lock()
 
123
            self.fail('Failed to detect lock collision')
 
124
        except LockContention, e:
 
125
            self.assertEqual(e.lock, lf2)
 
126
            self.assertContainsRe(str(e),
 
127
                    r'^Could not acquire.*test_lock.*$')
 
128
        lf1.unlock()
 
129
 
 
130
    def test_20_lock_peek(self):
 
131
        """Peek at the state of a lock"""
 
132
        t = self.get_transport()
 
133
        lf1 = LockDir(t, 'test_lock')
 
134
        lf1.create()
 
135
        lf1.attempt_lock()
 
136
        # lock is held, should get some info on it
 
137
        info1 = lf1.peek()
 
138
        self.assertEqual(set(info1.keys()),
 
139
                         set(['user', 'nonce', 'hostname', 'pid', 'start_time']))
 
140
        # should get the same info if we look at it through a different
 
141
        # instance
 
142
        info2 = LockDir(t, 'test_lock').peek()
 
143
        self.assertEqual(info1, info2)
 
144
        # locks which are never used should be not-held
 
145
        self.assertEqual(LockDir(t, 'other_lock').peek(), None)
 
146
 
 
147
    def test_21_peek_readonly(self):
 
148
        """Peek over a readonly transport"""
 
149
        t = self.get_transport()
 
150
        lf1 = LockDir(t, 'test_lock')
 
151
        lf1.create()
 
152
        lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
 
153
        self.assertEqual(lf2.peek(), None)
 
154
        lf1.attempt_lock()
 
155
        info2 = lf2.peek()
 
156
        self.assertTrue(info2)
 
157
        self.assertEqual(info2['nonce'], lf1.nonce)
 
158
 
 
159
    def test_30_lock_wait_fail(self):
 
160
        """Wait on a lock, then fail
 
161
        
 
162
        We ask to wait up to 400ms; this should fail within at most one
 
163
        second.  (Longer times are more realistic but we don't want the test
 
164
        suite to take too long, and this should do for now.)
 
165
        """
 
166
        t = self.get_transport()
 
167
        lf1 = LockDir(t, 'test_lock')
 
168
        lf1.create()
 
169
        lf2 = LockDir(t, 'test_lock')
 
170
        self.setup_log_reporter(lf2)
 
171
        lf1.attempt_lock()
 
172
        try:
 
173
            before = time.time()
 
174
            self.assertRaises(LockContention, lf2.wait_lock,
 
175
                              timeout=0.4, poll=0.1)
 
176
            after = time.time()
 
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))
 
181
        finally:
 
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')
 
196
 
 
197
    def test_31_lock_wait_easy(self):
 
198
        """Succeed when waiting on a lock with no contention.
 
199
        """
 
200
        t = self.get_transport()
 
201
        lf1 = LockDir(t, 'test_lock')
 
202
        lf1.create()
 
203
        self.setup_log_reporter(lf1)
 
204
        try:
 
205
            before = time.time()
 
206
            lf1.wait_lock(timeout=0.4, poll=0.1)
 
207
            after = time.time()
 
208
            self.assertTrue(after - before <= 1.0)
 
209
        finally:
 
210
            lf1.unlock()
 
211
        self.assertEqual([], self._logged_reports)
 
212
 
 
213
    def test_32_lock_wait_succeed(self):
 
214
        """Succeed when trying to acquire a lock that gets released
 
215
 
 
216
        One thread holds on a lock and then releases it; another 
 
217
        tries to lock it.
 
218
        """
 
219
        t = self.get_transport()
 
220
        lf1 = LockDir(t, 'test_lock')
 
221
        lf1.create()
 
222
        lf1.attempt_lock()
 
223
 
 
224
        def wait_and_unlock():
 
225
            time.sleep(0.1)
 
226
            lf1.unlock()
 
227
        unlocker = Thread(target=wait_and_unlock)
 
228
        unlocker.start()
 
229
        try:
 
230
            lf2 = LockDir(t, 'test_lock')
 
231
            self.setup_log_reporter(lf2)
 
232
            before = time.time()
 
233
            # wait and then lock
 
234
            lf2.wait_lock(timeout=0.4, poll=0.1)
 
235
            after = time.time()
 
236
            self.assertTrue(after - before <= 1.0)
 
237
        finally:
 
238
            unlocker.join()
 
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
 
 
256
    def test_33_wait(self):
 
257
        """Succeed when waiting on a lock that gets released
 
258
 
 
259
        The difference from test_32_lock_wait_succeed is that the second 
 
260
        caller does not actually acquire the lock, but just waits for it
 
261
        to be released.  This is done over a readonly transport.
 
262
        """
 
263
        t = self.get_transport()
 
264
        lf1 = LockDir(t, 'test_lock')
 
265
        lf1.create()
 
266
        lf1.attempt_lock()
 
267
 
 
268
        def wait_and_unlock():
 
269
            time.sleep(0.1)
 
270
            lf1.unlock()
 
271
        unlocker = Thread(target=wait_and_unlock)
 
272
        unlocker.start()
 
273
        try:
 
274
            lf2 = LockDir(self.get_readonly_transport(), 'test_lock')
 
275
            before = time.time()
 
276
            # wait but don't lock
 
277
            lf2.wait(timeout=0.4, poll=0.1)
 
278
            after = time.time()
 
279
            self.assertTrue(after - before <= 1.0)
 
280
        finally:
 
281
            unlocker.join()
 
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
 
 
439
    def test_40_confirm_easy(self):
 
440
        """Confirm a lock that's already held"""
 
441
        t = self.get_transport()
 
442
        lf1 = LockDir(t, 'test_lock')
 
443
        lf1.create()
 
444
        lf1.attempt_lock()
 
445
        lf1.confirm()
 
446
 
 
447
    def test_41_confirm_not_held(self):
 
448
        """Confirm a lock that's already held"""
 
449
        t = self.get_transport()
 
450
        lf1 = LockDir(t, 'test_lock')
 
451
        lf1.create()
 
452
        self.assertRaises(LockNotHeld, lf1.confirm)
 
453
 
 
454
    def test_42_confirm_broken_manually(self):
 
455
        """Confirm a lock broken by hand"""
 
456
        t = self.get_transport()
 
457
        lf1 = LockDir(t, 'test_lock')
 
458
        lf1.create()
 
459
        lf1.attempt_lock()
 
460
        t.move('test_lock', 'lock_gone_now')
 
461
        self.assertRaises(LockBroken, lf1.confirm)
 
462
 
 
463
    def test_43_break(self):
 
464
        """Break a lock whose caller has forgotten it"""
 
465
        t = self.get_transport()
 
466
        lf1 = LockDir(t, 'test_lock')
 
467
        lf1.create()
 
468
        lf1.attempt_lock()
 
469
        # we incorrectly discard the lock object without unlocking it
 
470
        del lf1
 
471
        # someone else sees it's still locked
 
472
        lf2 = LockDir(t, 'test_lock')
 
473
        holder_info = lf2.peek()
 
474
        self.assertTrue(holder_info)
 
475
        lf2.force_break(holder_info)
 
476
        # now we should be able to take it
 
477
        lf2.attempt_lock()
 
478
        lf2.confirm()
 
479
 
 
480
    def test_44_break_already_released(self):
 
481
        """Lock break races with regular release"""
 
482
        t = self.get_transport()
 
483
        lf1 = LockDir(t, 'test_lock')
 
484
        lf1.create()
 
485
        lf1.attempt_lock()
 
486
        # someone else sees it's still locked
 
487
        lf2 = LockDir(t, 'test_lock')
 
488
        holder_info = lf2.peek()
 
489
        # in the interim the lock is released
 
490
        lf1.unlock()
 
491
        # break should succeed
 
492
        lf2.force_break(holder_info)
 
493
        # now we should be able to take it
 
494
        lf2.attempt_lock()
 
495
        lf2.confirm()
 
496
 
 
497
    def test_45_break_mismatch(self):
 
498
        """Lock break races with someone else acquiring it"""
 
499
        t = self.get_transport()
 
500
        lf1 = LockDir(t, 'test_lock')
 
501
        lf1.create()
 
502
        lf1.attempt_lock()
 
503
        # someone else sees it's still locked
 
504
        lf2 = LockDir(t, 'test_lock')
 
505
        holder_info = lf2.peek()
 
506
        # in the interim the lock is released
 
507
        lf1.unlock()
 
508
        lf3 = LockDir(t, 'test_lock')
 
509
        lf3.attempt_lock()
 
510
        # break should now *fail*
 
511
        self.assertRaises(LockBreakMismatch, lf2.force_break,
 
512
                          holder_info)
 
513
        lf3.unlock()
 
514
 
 
515
    def test_46_fake_read_lock(self):
 
516
        t = self.get_transport()
 
517
        lf1 = LockDir(t, 'test_lock')
 
518
        lf1.create()
 
519
        lf1.lock_read()
 
520
        lf1.unlock()
 
521
 
 
522
    def test_50_lockdir_representation(self):
 
523
        """Check the on-disk representation of LockDirs is as expected.
 
524
 
 
525
        There should always be a top-level directory named by the lock.
 
526
        When the lock is held, there should be a lockname/held directory 
 
527
        containing an info file.
 
528
        """
 
529
        t = self.get_transport()
 
530
        lf1 = LockDir(t, 'test_lock')
 
531
        lf1.create()
 
532
        self.assertTrue(t.has('test_lock'))
 
533
        lf1.lock_write()
 
534
        self.assertTrue(t.has('test_lock/held/info'))
 
535
        lf1.unlock()
 
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$')