Ported lock test to pytest.
This commit is contained in:
parent
81d696cc66
commit
326e2f7f66
@ -27,29 +27,32 @@
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import functools
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import traceback
|
||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
|
|
||||||
from llnl.util.filesystem import join_path, touch
|
import pytest
|
||||||
|
|
||||||
|
from llnl.util.filesystem import join_path, touch, mkdirp
|
||||||
from llnl.util.lock import *
|
from llnl.util.lock import *
|
||||||
from spack.util.multiproc import Barrier
|
from spack.util.multiproc import Barrier
|
||||||
|
|
||||||
|
|
||||||
# This is the longest a failed test will take, as the barriers will
|
# This is the longest a failed test will take, as the barriers will
|
||||||
# time out and raise an exception.
|
# time out and raise an exception.
|
||||||
barrier_timeout = 5
|
barrier_timeout = 5
|
||||||
|
|
||||||
|
|
||||||
class LockTest(unittest.TestCase):
|
@pytest.fixture()
|
||||||
|
def lock_path():
|
||||||
|
tempdir = tempfile.mkdtemp()
|
||||||
|
lock_file = join_path(tempdir, 'lockfile')
|
||||||
|
yield lock_file
|
||||||
|
shutil.rmtree(tempdir)
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.tempdir = tempfile.mkdtemp()
|
|
||||||
self.lock_path = join_path(self.tempdir, 'lockfile')
|
|
||||||
|
|
||||||
def tearDown(self):
|
def multiproc_test(*functions):
|
||||||
shutil.rmtree(self.tempdir, ignore_errors=True)
|
|
||||||
|
|
||||||
def multiproc_test(self, *functions):
|
|
||||||
"""Order some processes using simple barrier synchronization."""
|
"""Order some processes using simple barrier synchronization."""
|
||||||
b = Barrier(len(functions), timeout=barrier_timeout)
|
b = Barrier(len(functions), timeout=barrier_timeout)
|
||||||
procs = [Process(target=f, args=(b,)) for f in functions]
|
procs = [Process(target=f, args=(b,)) for f in functions]
|
||||||
@ -59,250 +62,340 @@ def multiproc_test(self, *functions):
|
|||||||
|
|
||||||
for p in procs:
|
for p in procs:
|
||||||
p.join()
|
p.join()
|
||||||
self.assertEqual(p.exitcode, 0)
|
assert p.exitcode == 0
|
||||||
|
|
||||||
#
|
|
||||||
# Process snippets below can be composed into tests.
|
#
|
||||||
#
|
# Process snippets below can be composed into tests.
|
||||||
def acquire_write(self, start=0, length=0):
|
#
|
||||||
|
def acquire_write(lock_path, start=0, length=0):
|
||||||
def fn(barrier):
|
def fn(barrier):
|
||||||
lock = Lock(self.lock_path, start, length)
|
lock = Lock(lock_path, start, length)
|
||||||
lock.acquire_write() # grab exclusive lock
|
lock.acquire_write() # grab exclusive lock
|
||||||
barrier.wait()
|
barrier.wait()
|
||||||
barrier.wait() # hold the lock until timeout in other procs.
|
barrier.wait() # hold the lock until timeout in other procs.
|
||||||
return fn
|
return fn
|
||||||
|
|
||||||
def acquire_read(self, start=0, length=0):
|
|
||||||
|
def acquire_read(lock_path, start=0, length=0):
|
||||||
def fn(barrier):
|
def fn(barrier):
|
||||||
lock = Lock(self.lock_path, start, length)
|
lock = Lock(lock_path, start, length)
|
||||||
lock.acquire_read() # grab shared lock
|
lock.acquire_read() # grab shared lock
|
||||||
barrier.wait()
|
barrier.wait()
|
||||||
barrier.wait() # hold the lock until timeout in other procs.
|
barrier.wait() # hold the lock until timeout in other procs.
|
||||||
return fn
|
return fn
|
||||||
|
|
||||||
def timeout_write(self, start=0, length=0):
|
|
||||||
|
def timeout_write(lock_path, start=0, length=0):
|
||||||
def fn(barrier):
|
def fn(barrier):
|
||||||
lock = Lock(self.lock_path, start, length)
|
lock = Lock(lock_path, start, length)
|
||||||
barrier.wait() # wait for lock acquire in first process
|
barrier.wait() # wait for lock acquire in first process
|
||||||
self.assertRaises(LockError, lock.acquire_write, 0.1)
|
with pytest.raises(LockError):
|
||||||
|
lock.acquire_write(0.1)
|
||||||
barrier.wait()
|
barrier.wait()
|
||||||
return fn
|
return fn
|
||||||
|
|
||||||
def timeout_read(self, start=0, length=0):
|
|
||||||
|
def timeout_read(lock_path, start=0, length=0):
|
||||||
def fn(barrier):
|
def fn(barrier):
|
||||||
lock = Lock(self.lock_path, start, length)
|
lock = Lock(lock_path, start, length)
|
||||||
barrier.wait() # wait for lock acquire in first process
|
barrier.wait() # wait for lock acquire in first process
|
||||||
self.assertRaises(LockError, lock.acquire_read, 0.1)
|
with pytest.raises(LockError):
|
||||||
|
lock.acquire_read(0.1)
|
||||||
barrier.wait()
|
barrier.wait()
|
||||||
return fn
|
return fn
|
||||||
|
|
||||||
#
|
|
||||||
# Test that exclusive locks on other processes time out when an
|
|
||||||
# exclusive lock is held.
|
|
||||||
#
|
|
||||||
def test_write_lock_timeout_on_write(self):
|
|
||||||
self.multiproc_test(self.acquire_write(), self.timeout_write())
|
|
||||||
|
|
||||||
def test_write_lock_timeout_on_write_2(self):
|
#
|
||||||
self.multiproc_test(
|
# Test that exclusive locks on other processes time out when an
|
||||||
self.acquire_write(), self.timeout_write(), self.timeout_write())
|
# exclusive lock is held.
|
||||||
|
#
|
||||||
|
def test_write_lock_timeout_on_write(lock_path):
|
||||||
|
multiproc_test(acquire_write(lock_path), timeout_write(lock_path))
|
||||||
|
|
||||||
def test_write_lock_timeout_on_write_3(self):
|
|
||||||
self.multiproc_test(
|
|
||||||
self.acquire_write(), self.timeout_write(), self.timeout_write(),
|
|
||||||
self.timeout_write())
|
|
||||||
|
|
||||||
def test_write_lock_timeout_on_write_ranges(self):
|
def test_write_lock_timeout_on_write_2(lock_path):
|
||||||
self.multiproc_test(
|
multiproc_test(
|
||||||
self.acquire_write(0, 1), self.timeout_write(0, 1))
|
acquire_write(lock_path),
|
||||||
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path))
|
||||||
|
|
||||||
def test_write_lock_timeout_on_write_ranges_2(self):
|
|
||||||
self.multiproc_test(
|
|
||||||
self.acquire_write(0, 64), self.acquire_write(65, 1),
|
|
||||||
self.timeout_write(0, 1), self.timeout_write(63, 1))
|
|
||||||
|
|
||||||
def test_write_lock_timeout_on_write_ranges_3(self):
|
def test_write_lock_timeout_on_write_3(lock_path):
|
||||||
self.multiproc_test(
|
multiproc_test(
|
||||||
self.acquire_write(0, 1), self.acquire_write(1, 1),
|
acquire_write(lock_path),
|
||||||
self.timeout_write(), self.timeout_write(), self.timeout_write())
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path))
|
||||||
|
|
||||||
def test_write_lock_timeout_on_write_ranges_4(self):
|
|
||||||
self.multiproc_test(
|
|
||||||
self.acquire_write(0, 1), self.acquire_write(1, 1),
|
|
||||||
self.acquire_write(2, 456), self.acquire_write(500, 64),
|
|
||||||
self.timeout_write(), self.timeout_write(), self.timeout_write())
|
|
||||||
|
|
||||||
#
|
def test_write_lock_timeout_on_write_ranges(lock_path):
|
||||||
# Test that shared locks on other processes time out when an
|
multiproc_test(
|
||||||
# exclusive lock is held.
|
acquire_write(lock_path, 0, 1),
|
||||||
#
|
timeout_write(lock_path, 0, 1))
|
||||||
def test_read_lock_timeout_on_write(self):
|
|
||||||
self.multiproc_test(self.acquire_write(), self.timeout_read())
|
|
||||||
|
|
||||||
def test_read_lock_timeout_on_write_2(self):
|
|
||||||
self.multiproc_test(
|
|
||||||
self.acquire_write(), self.timeout_read(), self.timeout_read())
|
|
||||||
|
|
||||||
def test_read_lock_timeout_on_write_3(self):
|
def test_write_lock_timeout_on_write_ranges_2(lock_path):
|
||||||
self.multiproc_test(
|
multiproc_test(
|
||||||
self.acquire_write(), self.timeout_read(), self.timeout_read(),
|
acquire_write(lock_path, 0, 64),
|
||||||
self.timeout_read())
|
acquire_write(lock_path, 65, 1),
|
||||||
|
timeout_write(lock_path, 0, 1),
|
||||||
|
timeout_write(lock_path, 63, 1))
|
||||||
|
|
||||||
def test_read_lock_timeout_on_write_ranges(self):
|
|
||||||
|
def test_write_lock_timeout_on_write_ranges_3(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_write(lock_path, 0, 1),
|
||||||
|
acquire_write(lock_path, 1, 1),
|
||||||
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_lock_timeout_on_write_ranges_4(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_write(lock_path, 0, 1),
|
||||||
|
acquire_write(lock_path, 1, 1),
|
||||||
|
acquire_write(lock_path, 2, 456),
|
||||||
|
acquire_write(lock_path, 500, 64),
|
||||||
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path))
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test that shared locks on other processes time out when an
|
||||||
|
# exclusive lock is held.
|
||||||
|
#
|
||||||
|
def test_read_lock_timeout_on_write(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_write(lock_path),
|
||||||
|
timeout_read(lock_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_lock_timeout_on_write_2(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_write(lock_path),
|
||||||
|
timeout_read(lock_path),
|
||||||
|
timeout_read(lock_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_lock_timeout_on_write_3(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_write(lock_path),
|
||||||
|
timeout_read(lock_path),
|
||||||
|
timeout_read(lock_path),
|
||||||
|
timeout_read(lock_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_lock_timeout_on_write_ranges(lock_path):
|
||||||
"""small write lock, read whole file."""
|
"""small write lock, read whole file."""
|
||||||
self.multiproc_test(self.acquire_write(0, 1), self.timeout_read())
|
multiproc_test(
|
||||||
|
acquire_write(lock_path, 0, 1),
|
||||||
|
timeout_read(lock_path))
|
||||||
|
|
||||||
def test_read_lock_timeout_on_write_ranges_2(self):
|
|
||||||
|
def test_read_lock_timeout_on_write_ranges_2(lock_path):
|
||||||
"""small write lock, small read lock"""
|
"""small write lock, small read lock"""
|
||||||
self.multiproc_test(self.acquire_write(0, 1), self.timeout_read(0, 1))
|
multiproc_test(
|
||||||
|
acquire_write(lock_path, 0, 1),
|
||||||
|
timeout_read(lock_path, 0, 1))
|
||||||
|
|
||||||
def test_read_lock_timeout_on_write_ranges_3(self):
|
|
||||||
|
def test_read_lock_timeout_on_write_ranges_3(lock_path):
|
||||||
"""two write locks, overlapping read locks"""
|
"""two write locks, overlapping read locks"""
|
||||||
self.multiproc_test(
|
multiproc_test(
|
||||||
self.acquire_write(0, 1), self.acquire_write(64, 128),
|
acquire_write(lock_path, 0, 1),
|
||||||
self.timeout_read(0, 1), self.timeout_read(128, 256))
|
acquire_write(lock_path, 64, 128),
|
||||||
|
timeout_read(lock_path, 0, 1),
|
||||||
|
timeout_read(lock_path, 128, 256))
|
||||||
|
|
||||||
#
|
|
||||||
# Test that exclusive locks time out when shared locks are held.
|
|
||||||
#
|
|
||||||
def test_write_lock_timeout_on_read(self):
|
|
||||||
self.multiproc_test(self.acquire_read(), self.timeout_write())
|
|
||||||
|
|
||||||
def test_write_lock_timeout_on_read_2(self):
|
#
|
||||||
self.multiproc_test(
|
# Test that exclusive locks time out when shared locks are held.
|
||||||
self.acquire_read(), self.timeout_write(), self.timeout_write())
|
#
|
||||||
|
def test_write_lock_timeout_on_read(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_read(lock_path),
|
||||||
|
timeout_write(lock_path))
|
||||||
|
|
||||||
def test_write_lock_timeout_on_read_3(self):
|
|
||||||
self.multiproc_test(
|
|
||||||
self.acquire_read(), self.timeout_write(), self.timeout_write(),
|
|
||||||
self.timeout_write())
|
|
||||||
|
|
||||||
def test_write_lock_timeout_on_read_ranges(self):
|
def test_write_lock_timeout_on_read_2(lock_path):
|
||||||
self.multiproc_test(self.acquire_read(0, 1), self.timeout_write())
|
multiproc_test(
|
||||||
|
acquire_read(lock_path),
|
||||||
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path))
|
||||||
|
|
||||||
def test_write_lock_timeout_on_read_ranges_2(self):
|
|
||||||
self.multiproc_test(self.acquire_read(0, 1), self.timeout_write(0, 1))
|
|
||||||
|
|
||||||
def test_write_lock_timeout_on_read_ranges_3(self):
|
def test_write_lock_timeout_on_read_3(lock_path):
|
||||||
self.multiproc_test(
|
multiproc_test(
|
||||||
self.acquire_read(0, 1), self.acquire_read(10, 1),
|
acquire_read(lock_path),
|
||||||
self.timeout_write(0, 1), self.timeout_write(10, 1))
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path))
|
||||||
|
|
||||||
def test_write_lock_timeout_on_read_ranges_4(self):
|
|
||||||
self.multiproc_test(
|
|
||||||
self.acquire_read(0, 64),
|
|
||||||
self.timeout_write(10, 1), self.timeout_write(32, 1))
|
|
||||||
|
|
||||||
def test_write_lock_timeout_on_read_ranges_5(self):
|
def test_write_lock_timeout_on_read_ranges(lock_path):
|
||||||
self.multiproc_test(
|
multiproc_test(
|
||||||
self.acquire_read(64, 128),
|
acquire_read(lock_path, 0, 1),
|
||||||
self.timeout_write(65, 1), self.timeout_write(127, 1),
|
timeout_write(lock_path))
|
||||||
self.timeout_write(90, 10))
|
|
||||||
|
|
||||||
#
|
|
||||||
# Test that exclusive locks time while lots of shared locks are held.
|
|
||||||
#
|
|
||||||
def test_write_lock_timeout_with_multiple_readers_2_1(self):
|
|
||||||
self.multiproc_test(
|
|
||||||
self.acquire_read(), self.acquire_read(), self.timeout_write())
|
|
||||||
|
|
||||||
def test_write_lock_timeout_with_multiple_readers_2_2(self):
|
def test_write_lock_timeout_on_read_ranges_2(lock_path):
|
||||||
self.multiproc_test(
|
multiproc_test(
|
||||||
self.acquire_read(), self.acquire_read(), self.timeout_write(),
|
acquire_read(lock_path, 0, 1),
|
||||||
self.timeout_write())
|
timeout_write(lock_path, 0, 1))
|
||||||
|
|
||||||
def test_write_lock_timeout_with_multiple_readers_3_1(self):
|
|
||||||
self.multiproc_test(
|
|
||||||
self.acquire_read(), self.acquire_read(), self.acquire_read(),
|
|
||||||
self.timeout_write())
|
|
||||||
|
|
||||||
def test_write_lock_timeout_with_multiple_readers_3_2(self):
|
def test_write_lock_timeout_on_read_ranges_3(lock_path):
|
||||||
self.multiproc_test(
|
multiproc_test(
|
||||||
self.acquire_read(), self.acquire_read(), self.acquire_read(),
|
acquire_read(lock_path, 0, 1),
|
||||||
self.timeout_write(), self.timeout_write())
|
acquire_read(lock_path, 10, 1),
|
||||||
|
timeout_write(lock_path, 0, 1),
|
||||||
|
timeout_write(lock_path, 10, 1))
|
||||||
|
|
||||||
def test_write_lock_timeout_with_multiple_readers_2_1_ranges(self):
|
|
||||||
self.multiproc_test(
|
|
||||||
self.acquire_read(0, 10), self.acquire_read(5, 10),
|
|
||||||
self.timeout_write(5, 5))
|
|
||||||
|
|
||||||
def test_write_lock_timeout_with_multiple_readers_2_3_ranges(self):
|
def test_write_lock_timeout_on_read_ranges_4(lock_path):
|
||||||
self.multiproc_test(
|
multiproc_test(
|
||||||
self.acquire_read(0, 10), self.acquire_read(5, 15),
|
acquire_read(lock_path, 0, 64),
|
||||||
self.timeout_write(0, 1), self.timeout_write(11, 3),
|
timeout_write(lock_path, 10, 1), timeout_write(lock_path, 32, 1))
|
||||||
self.timeout_write(7, 1))
|
|
||||||
|
|
||||||
def test_write_lock_timeout_with_multiple_readers_3_1_ranges(self):
|
|
||||||
self.multiproc_test(
|
|
||||||
self.acquire_read(0, 5), self.acquire_read(5, 5),
|
|
||||||
self.acquire_read(10, 5),
|
|
||||||
self.timeout_write(0, 15))
|
|
||||||
|
|
||||||
def test_write_lock_timeout_with_multiple_readers_3_2_ranges(self):
|
def test_write_lock_timeout_on_read_ranges_5(lock_path):
|
||||||
self.multiproc_test(
|
multiproc_test(
|
||||||
self.acquire_read(0, 5), self.acquire_read(5, 5),
|
acquire_read(lock_path, 64, 128),
|
||||||
self.acquire_read(10, 5),
|
timeout_write(lock_path, 65, 1),
|
||||||
self.timeout_write(3, 10), self.timeout_write(5, 1))
|
timeout_write(lock_path, 127, 1),
|
||||||
|
timeout_write(lock_path, 90, 10))
|
||||||
|
|
||||||
#
|
#
|
||||||
# Test that read can be upgraded to write.
|
# Test that exclusive locks time while lots of shared locks are held.
|
||||||
#
|
#
|
||||||
def test_upgrade_read_to_write(self):
|
def test_write_lock_timeout_with_multiple_readers_2_1(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_read(lock_path),
|
||||||
|
acquire_read(lock_path),
|
||||||
|
timeout_write(lock_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_lock_timeout_with_multiple_readers_2_2(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_read(lock_path),
|
||||||
|
acquire_read(lock_path),
|
||||||
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_lock_timeout_with_multiple_readers_3_1(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_read(lock_path),
|
||||||
|
acquire_read(lock_path),
|
||||||
|
acquire_read(lock_path),
|
||||||
|
timeout_write(lock_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_lock_timeout_with_multiple_readers_3_2(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_read(lock_path),
|
||||||
|
acquire_read(lock_path),
|
||||||
|
acquire_read(lock_path),
|
||||||
|
timeout_write(lock_path),
|
||||||
|
timeout_write(lock_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_lock_timeout_with_multiple_readers_2_1_ranges(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_read(lock_path, 0, 10),
|
||||||
|
acquire_read(lock_path, 0.5, 10),
|
||||||
|
timeout_write(lock_path, 5, 5))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_lock_timeout_with_multiple_readers_2_3_ranges(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_read(lock_path, 0, 10),
|
||||||
|
acquire_read(lock_path, 5, 15),
|
||||||
|
timeout_write(lock_path, 0, 1),
|
||||||
|
timeout_write(lock_path, 11, 3),
|
||||||
|
timeout_write(lock_path, 7, 1))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_lock_timeout_with_multiple_readers_3_1_ranges(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_read(lock_path, 0, 5),
|
||||||
|
acquire_read(lock_path, 5, 5),
|
||||||
|
acquire_read(lock_path, 10, 5),
|
||||||
|
timeout_write(lock_path, 0, 15))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_lock_timeout_with_multiple_readers_3_2_ranges(lock_path):
|
||||||
|
multiproc_test(
|
||||||
|
acquire_read(lock_path, 0, 5),
|
||||||
|
acquire_read(lock_path, 5, 5),
|
||||||
|
acquire_read(lock_path, 10, 5),
|
||||||
|
timeout_write(lock_path, 3, 10),
|
||||||
|
timeout_write(lock_path, 5, 1))
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test that read can be upgraded to write.
|
||||||
|
#
|
||||||
|
def test_upgrade_read_to_write(lock_path):
|
||||||
# ensure lock file exists the first time, so we open it read-only
|
# ensure lock file exists the first time, so we open it read-only
|
||||||
# to begin wtih.
|
# to begin wtih.
|
||||||
touch(self.lock_path)
|
touch(lock_path)
|
||||||
|
|
||||||
lock = Lock(self.lock_path)
|
lock = Lock(lock_path)
|
||||||
self.assertTrue(lock._reads == 0)
|
assert lock._reads == 0
|
||||||
self.assertTrue(lock._writes == 0)
|
assert lock._writes == 0
|
||||||
|
|
||||||
lock.acquire_read()
|
lock.acquire_read()
|
||||||
self.assertTrue(lock._reads == 1)
|
assert lock._reads == 1
|
||||||
self.assertTrue(lock._writes == 0)
|
assert lock._writes == 0
|
||||||
self.assertTrue(lock._file.mode == 'r+')
|
assert lock._file.mode == 'r+'
|
||||||
|
|
||||||
lock.acquire_write()
|
lock.acquire_write()
|
||||||
self.assertTrue(lock._reads == 1)
|
assert lock._reads == 1
|
||||||
self.assertTrue(lock._writes == 1)
|
assert lock._writes == 1
|
||||||
self.assertTrue(lock._file.mode == 'r+')
|
assert lock._file.mode == 'r+'
|
||||||
|
|
||||||
lock.release_write()
|
lock.release_write()
|
||||||
self.assertTrue(lock._reads == 1)
|
assert lock._reads == 1
|
||||||
self.assertTrue(lock._writes == 0)
|
assert lock._writes == 0
|
||||||
self.assertTrue(lock._file.mode == 'r+')
|
assert lock._file.mode == 'r+'
|
||||||
|
|
||||||
lock.release_read()
|
lock.release_read()
|
||||||
self.assertTrue(lock._reads == 0)
|
assert lock._reads == 0
|
||||||
self.assertTrue(lock._writes == 0)
|
assert lock._writes == 0
|
||||||
self.assertTrue(lock._file is None)
|
assert lock._file is None
|
||||||
|
|
||||||
#
|
#
|
||||||
# Test that read-only file can be read-locked but not write-locked.
|
# Test that read-only file can be read-locked but not write-locked.
|
||||||
#
|
#
|
||||||
def test_upgrade_read_to_write_fails_with_readonly_file(self):
|
def test_upgrade_read_to_write_fails_with_readonly_file(lock_path):
|
||||||
# ensure lock file exists the first time, so we open it read-only
|
# ensure lock file exists the first time, so we open it read-only
|
||||||
# to begin wtih.
|
# to begin wtih.
|
||||||
touch(self.lock_path)
|
touch(lock_path)
|
||||||
os.chmod(self.lock_path, 0o444)
|
os.chmod(lock_path, 0o444)
|
||||||
|
|
||||||
lock = Lock(self.lock_path)
|
lock = Lock(lock_path)
|
||||||
self.assertTrue(lock._reads == 0)
|
assert lock._reads == 0
|
||||||
self.assertTrue(lock._writes == 0)
|
assert lock._writes == 0
|
||||||
|
|
||||||
lock.acquire_read()
|
lock.acquire_read()
|
||||||
self.assertTrue(lock._reads == 1)
|
assert lock._reads == 1
|
||||||
self.assertTrue(lock._writes == 0)
|
assert lock._writes == 0
|
||||||
self.assertTrue(lock._file.mode == 'r')
|
assert lock._file.mode == 'r'
|
||||||
|
|
||||||
self.assertRaises(LockError, lock.acquire_write)
|
with pytest.raises(LockError):
|
||||||
|
lock.acquire_write()
|
||||||
|
|
||||||
#
|
#
|
||||||
# Longer test case that ensures locks are reusable. Ordering is
|
# Longer test case that ensures locks are reusable. Ordering is
|
||||||
# enforced by barriers throughout -- steps are shown with numbers.
|
# enforced by barriers throughout -- steps are shown with numbers.
|
||||||
#
|
#
|
||||||
def test_complex_acquire_and_release_chain(self):
|
def test_complex_acquire_and_release_chain(lock_path):
|
||||||
def p1(barrier):
|
def p1(barrier):
|
||||||
lock = Lock(self.lock_path)
|
lock = Lock(lock_path)
|
||||||
|
|
||||||
lock.acquire_write()
|
lock.acquire_write()
|
||||||
barrier.wait() # ---------------------------------------- 1
|
barrier.wait() # ---------------------------------------- 1
|
||||||
@ -310,7 +403,8 @@ def p1(barrier):
|
|||||||
barrier.wait() # ---------------------------------------- 2
|
barrier.wait() # ---------------------------------------- 2
|
||||||
lock.release_write() # release and others acquire read
|
lock.release_write() # release and others acquire read
|
||||||
barrier.wait() # ---------------------------------------- 3
|
barrier.wait() # ---------------------------------------- 3
|
||||||
self.assertRaises(LockError, lock.acquire_write, 0.1)
|
with pytest.raises(LockError):
|
||||||
|
lock.acquire_write(0.1)
|
||||||
lock.acquire_read()
|
lock.acquire_read()
|
||||||
barrier.wait() # ---------------------------------------- 4
|
barrier.wait() # ---------------------------------------- 4
|
||||||
lock.release_read()
|
lock.release_read()
|
||||||
@ -318,8 +412,10 @@ def p1(barrier):
|
|||||||
|
|
||||||
# p2 upgrades read to write
|
# p2 upgrades read to write
|
||||||
barrier.wait() # ---------------------------------------- 6
|
barrier.wait() # ---------------------------------------- 6
|
||||||
self.assertRaises(LockError, lock.acquire_write, 0.1)
|
with pytest.raises(LockError):
|
||||||
self.assertRaises(LockError, lock.acquire_read, 0.1)
|
lock.acquire_write(0.1)
|
||||||
|
with pytest.raises(LockError):
|
||||||
|
lock.acquire_read(0.1)
|
||||||
barrier.wait() # ---------------------------------------- 7
|
barrier.wait() # ---------------------------------------- 7
|
||||||
# p2 releases write and read
|
# p2 releases write and read
|
||||||
barrier.wait() # ---------------------------------------- 8
|
barrier.wait() # ---------------------------------------- 8
|
||||||
@ -328,8 +424,10 @@ def p1(barrier):
|
|||||||
barrier.wait() # ---------------------------------------- 9
|
barrier.wait() # ---------------------------------------- 9
|
||||||
# p3 upgrades read to write
|
# p3 upgrades read to write
|
||||||
barrier.wait() # ---------------------------------------- 10
|
barrier.wait() # ---------------------------------------- 10
|
||||||
self.assertRaises(LockError, lock.acquire_write, 0.1)
|
with pytest.raises(LockError):
|
||||||
self.assertRaises(LockError, lock.acquire_read, 0.1)
|
lock.acquire_write(0.1)
|
||||||
|
with pytest.raises(LockError):
|
||||||
|
lock.acquire_read(0.1)
|
||||||
barrier.wait() # ---------------------------------------- 11
|
barrier.wait() # ---------------------------------------- 11
|
||||||
# p3 releases locks
|
# p3 releases locks
|
||||||
barrier.wait() # ---------------------------------------- 12
|
barrier.wait() # ---------------------------------------- 12
|
||||||
@ -338,12 +436,14 @@ def p1(barrier):
|
|||||||
lock.release_read()
|
lock.release_read()
|
||||||
|
|
||||||
def p2(barrier):
|
def p2(barrier):
|
||||||
lock = Lock(self.lock_path)
|
lock = Lock(lock_path)
|
||||||
|
|
||||||
# p1 acquires write
|
# p1 acquires write
|
||||||
barrier.wait() # ---------------------------------------- 1
|
barrier.wait() # ---------------------------------------- 1
|
||||||
self.assertRaises(LockError, lock.acquire_write, 0.1)
|
with pytest.raises(LockError):
|
||||||
self.assertRaises(LockError, lock.acquire_read, 0.1)
|
lock.acquire_write(0.1)
|
||||||
|
with pytest.raises(LockError):
|
||||||
|
lock.acquire_read(0.1)
|
||||||
barrier.wait() # ---------------------------------------- 2
|
barrier.wait() # ---------------------------------------- 2
|
||||||
lock.acquire_read()
|
lock.acquire_read()
|
||||||
barrier.wait() # ---------------------------------------- 3
|
barrier.wait() # ---------------------------------------- 3
|
||||||
@ -364,8 +464,10 @@ def p2(barrier):
|
|||||||
barrier.wait() # ---------------------------------------- 9
|
barrier.wait() # ---------------------------------------- 9
|
||||||
# p3 upgrades read to write
|
# p3 upgrades read to write
|
||||||
barrier.wait() # ---------------------------------------- 10
|
barrier.wait() # ---------------------------------------- 10
|
||||||
self.assertRaises(LockError, lock.acquire_write, 0.1)
|
with pytest.raises(LockError):
|
||||||
self.assertRaises(LockError, lock.acquire_read, 0.1)
|
lock.acquire_write(0.1)
|
||||||
|
with pytest.raises(LockError):
|
||||||
|
lock.acquire_read(0.1)
|
||||||
barrier.wait() # ---------------------------------------- 11
|
barrier.wait() # ---------------------------------------- 11
|
||||||
# p3 releases locks
|
# p3 releases locks
|
||||||
barrier.wait() # ---------------------------------------- 12
|
barrier.wait() # ---------------------------------------- 12
|
||||||
@ -374,12 +476,14 @@ def p2(barrier):
|
|||||||
lock.release_read()
|
lock.release_read()
|
||||||
|
|
||||||
def p3(barrier):
|
def p3(barrier):
|
||||||
lock = Lock(self.lock_path)
|
lock = Lock(lock_path)
|
||||||
|
|
||||||
# p1 acquires write
|
# p1 acquires write
|
||||||
barrier.wait() # ---------------------------------------- 1
|
barrier.wait() # ---------------------------------------- 1
|
||||||
self.assertRaises(LockError, lock.acquire_write, 0.1)
|
with pytest.raises(LockError):
|
||||||
self.assertRaises(LockError, lock.acquire_read, 0.1)
|
lock.acquire_write(0.1)
|
||||||
|
with pytest.raises(LockError):
|
||||||
|
lock.acquire_read(0.1)
|
||||||
barrier.wait() # ---------------------------------------- 2
|
barrier.wait() # ---------------------------------------- 2
|
||||||
lock.acquire_read()
|
lock.acquire_read()
|
||||||
barrier.wait() # ---------------------------------------- 3
|
barrier.wait() # ---------------------------------------- 3
|
||||||
@ -390,8 +494,10 @@ def p3(barrier):
|
|||||||
|
|
||||||
# p2 upgrades read to write
|
# p2 upgrades read to write
|
||||||
barrier.wait() # ---------------------------------------- 6
|
barrier.wait() # ---------------------------------------- 6
|
||||||
self.assertRaises(LockError, lock.acquire_write, 0.1)
|
with pytest.raises(LockError):
|
||||||
self.assertRaises(LockError, lock.acquire_read, 0.1)
|
lock.acquire_write(0.1)
|
||||||
|
with pytest.raises(LockError):
|
||||||
|
lock.acquire_read(0.1)
|
||||||
barrier.wait() # ---------------------------------------- 7
|
barrier.wait() # ---------------------------------------- 7
|
||||||
# p2 releases write & read
|
# p2 releases write & read
|
||||||
barrier.wait() # ---------------------------------------- 8
|
barrier.wait() # ---------------------------------------- 8
|
||||||
@ -409,9 +515,9 @@ def p3(barrier):
|
|||||||
barrier.wait() # ---------------------------------------- 13
|
barrier.wait() # ---------------------------------------- 13
|
||||||
lock.release_read()
|
lock.release_read()
|
||||||
|
|
||||||
self.multiproc_test(p1, p2, p3)
|
multiproc_test(p1, p2, p3)
|
||||||
|
|
||||||
def test_transaction(self):
|
def test_transaction(lock_path):
|
||||||
def enter_fn():
|
def enter_fn():
|
||||||
vals['entered'] = True
|
vals['entered'] = True
|
||||||
|
|
||||||
@ -419,24 +525,24 @@ def exit_fn(t, v, tb):
|
|||||||
vals['exited'] = True
|
vals['exited'] = True
|
||||||
vals['exception'] = (t or v or tb)
|
vals['exception'] = (t or v or tb)
|
||||||
|
|
||||||
lock = Lock(self.lock_path)
|
lock = Lock(lock_path)
|
||||||
vals = {'entered': False, 'exited': False, 'exception': False}
|
vals = {'entered': False, 'exited': False, 'exception': False}
|
||||||
with ReadTransaction(lock, enter_fn, exit_fn):
|
with ReadTransaction(lock, enter_fn, exit_fn):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.assertTrue(vals['entered'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['exited']
|
||||||
self.assertFalse(vals['exception'])
|
assert not vals['exception']
|
||||||
|
|
||||||
vals = {'entered': False, 'exited': False, 'exception': False}
|
vals = {'entered': False, 'exited': False, 'exception': False}
|
||||||
with WriteTransaction(lock, enter_fn, exit_fn):
|
with WriteTransaction(lock, enter_fn, exit_fn):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.assertTrue(vals['entered'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['exited']
|
||||||
self.assertFalse(vals['exception'])
|
assert not vals['exception']
|
||||||
|
|
||||||
def test_transaction_with_exception(self):
|
def test_transaction_with_exception(lock_path):
|
||||||
def enter_fn():
|
def enter_fn():
|
||||||
vals['entered'] = True
|
vals['entered'] = True
|
||||||
|
|
||||||
@ -444,7 +550,7 @@ def exit_fn(t, v, tb):
|
|||||||
vals['exited'] = True
|
vals['exited'] = True
|
||||||
vals['exception'] = (t or v or tb)
|
vals['exception'] = (t or v or tb)
|
||||||
|
|
||||||
lock = Lock(self.lock_path)
|
lock = Lock(lock_path)
|
||||||
|
|
||||||
def do_read_with_exception():
|
def do_read_with_exception():
|
||||||
with ReadTransaction(lock, enter_fn, exit_fn):
|
with ReadTransaction(lock, enter_fn, exit_fn):
|
||||||
@ -455,18 +561,20 @@ def do_write_with_exception():
|
|||||||
raise Exception()
|
raise Exception()
|
||||||
|
|
||||||
vals = {'entered': False, 'exited': False, 'exception': False}
|
vals = {'entered': False, 'exited': False, 'exception': False}
|
||||||
self.assertRaises(Exception, do_read_with_exception)
|
with pytest.raises(Exception):
|
||||||
self.assertTrue(vals['entered'])
|
do_read_with_exception()
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exception'])
|
assert vals['exited']
|
||||||
|
assert vals['exception']
|
||||||
|
|
||||||
vals = {'entered': False, 'exited': False, 'exception': False}
|
vals = {'entered': False, 'exited': False, 'exception': False}
|
||||||
self.assertRaises(Exception, do_write_with_exception)
|
with pytest.raises(Exception):
|
||||||
self.assertTrue(vals['entered'])
|
do_write_with_exception()
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exception'])
|
assert vals['exited']
|
||||||
|
assert vals['exception']
|
||||||
|
|
||||||
def test_transaction_with_context_manager(self):
|
def test_transaction_with_context_manager(lock_path):
|
||||||
class TestContextManager(object):
|
class TestContextManager(object):
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
@ -480,55 +588,54 @@ def exit_fn(t, v, tb):
|
|||||||
vals['exited_fn'] = True
|
vals['exited_fn'] = True
|
||||||
vals['exception_fn'] = (t or v or tb)
|
vals['exception_fn'] = (t or v or tb)
|
||||||
|
|
||||||
lock = Lock(self.lock_path)
|
lock = Lock(lock_path)
|
||||||
|
|
||||||
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
||||||
'exception': False, 'exception_fn': False}
|
'exception': False, 'exception_fn': False}
|
||||||
with ReadTransaction(lock, TestContextManager, exit_fn):
|
with ReadTransaction(lock, TestContextManager, exit_fn):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.assertTrue(vals['entered'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['exited']
|
||||||
self.assertFalse(vals['exception'])
|
assert not vals['exception']
|
||||||
self.assertTrue(vals['exited_fn'])
|
assert vals['exited_fn']
|
||||||
self.assertFalse(vals['exception_fn'])
|
assert not vals['exception_fn']
|
||||||
|
|
||||||
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
||||||
'exception': False, 'exception_fn': False}
|
'exception': False, 'exception_fn': False}
|
||||||
with ReadTransaction(lock, TestContextManager):
|
with ReadTransaction(lock, TestContextManager):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.assertTrue(vals['entered'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['exited']
|
||||||
self.assertFalse(vals['exception'])
|
assert not vals['exception']
|
||||||
self.assertFalse(vals['exited_fn'])
|
assert not vals['exited_fn']
|
||||||
self.assertFalse(vals['exception_fn'])
|
assert not vals['exception_fn']
|
||||||
|
|
||||||
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
||||||
'exception': False, 'exception_fn': False}
|
'exception': False, 'exception_fn': False}
|
||||||
with WriteTransaction(lock, TestContextManager, exit_fn):
|
with WriteTransaction(lock, TestContextManager, exit_fn):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.assertTrue(vals['entered'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['exited']
|
||||||
self.assertFalse(vals['exception'])
|
assert not vals['exception']
|
||||||
self.assertTrue(vals['exited_fn'])
|
assert vals['exited_fn']
|
||||||
self.assertFalse(vals['exception_fn'])
|
assert not vals['exception_fn']
|
||||||
|
|
||||||
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
||||||
'exception': False, 'exception_fn': False}
|
'exception': False, 'exception_fn': False}
|
||||||
with WriteTransaction(lock, TestContextManager):
|
with WriteTransaction(lock, TestContextManager):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.assertTrue(vals['entered'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['exited']
|
||||||
self.assertFalse(vals['exception'])
|
assert not vals['exception']
|
||||||
self.assertFalse(vals['exited_fn'])
|
assert not vals['exited_fn']
|
||||||
self.assertFalse(vals['exception_fn'])
|
assert not vals['exception_fn']
|
||||||
|
|
||||||
def test_transaction_with_context_manager_and_exception(self):
|
def test_transaction_with_context_manager_and_exception(lock_path):
|
||||||
class TestContextManager(object):
|
class TestContextManager(object):
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
vals['entered'] = True
|
vals['entered'] = True
|
||||||
|
|
||||||
@ -540,7 +647,7 @@ def exit_fn(t, v, tb):
|
|||||||
vals['exited_fn'] = True
|
vals['exited_fn'] = True
|
||||||
vals['exception_fn'] = (t or v or tb)
|
vals['exception_fn'] = (t or v or tb)
|
||||||
|
|
||||||
lock = Lock(self.lock_path)
|
lock = Lock(lock_path)
|
||||||
|
|
||||||
def do_read_with_exception(exit_fn):
|
def do_read_with_exception(exit_fn):
|
||||||
with ReadTransaction(lock, TestContextManager, exit_fn):
|
with ReadTransaction(lock, TestContextManager, exit_fn):
|
||||||
@ -552,36 +659,40 @@ def do_write_with_exception(exit_fn):
|
|||||||
|
|
||||||
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
||||||
'exception': False, 'exception_fn': False}
|
'exception': False, 'exception_fn': False}
|
||||||
self.assertRaises(Exception, do_read_with_exception, exit_fn)
|
with pytest.raises(Exception):
|
||||||
self.assertTrue(vals['entered'])
|
do_read_with_exception(exit_fn)
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exception'])
|
assert vals['exited']
|
||||||
self.assertTrue(vals['exited_fn'])
|
assert vals['exception']
|
||||||
self.assertTrue(vals['exception_fn'])
|
assert vals['exited_fn']
|
||||||
|
assert vals['exception_fn']
|
||||||
|
|
||||||
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
||||||
'exception': False, 'exception_fn': False}
|
'exception': False, 'exception_fn': False}
|
||||||
self.assertRaises(Exception, do_read_with_exception, None)
|
with pytest.raises(Exception):
|
||||||
self.assertTrue(vals['entered'])
|
do_read_with_exception(None)
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exception'])
|
assert vals['exited']
|
||||||
self.assertFalse(vals['exited_fn'])
|
assert vals['exception']
|
||||||
self.assertFalse(vals['exception_fn'])
|
assert not vals['exited_fn']
|
||||||
|
assert not vals['exception_fn']
|
||||||
|
|
||||||
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
||||||
'exception': False, 'exception_fn': False}
|
'exception': False, 'exception_fn': False}
|
||||||
self.assertRaises(Exception, do_write_with_exception, exit_fn)
|
with pytest.raises(Exception):
|
||||||
self.assertTrue(vals['entered'])
|
do_write_with_exception(exit_fn)
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exception'])
|
assert vals['exited']
|
||||||
self.assertTrue(vals['exited_fn'])
|
assert vals['exception']
|
||||||
self.assertTrue(vals['exception_fn'])
|
assert vals['exited_fn']
|
||||||
|
assert vals['exception_fn']
|
||||||
|
|
||||||
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
vals = {'entered': False, 'exited': False, 'exited_fn': False,
|
||||||
'exception': False, 'exception_fn': False}
|
'exception': False, 'exception_fn': False}
|
||||||
self.assertRaises(Exception, do_write_with_exception, None)
|
with pytest.raises(Exception):
|
||||||
self.assertTrue(vals['entered'])
|
do_write_with_exception(None)
|
||||||
self.assertTrue(vals['exited'])
|
assert vals['entered']
|
||||||
self.assertTrue(vals['exception'])
|
assert vals['exited']
|
||||||
self.assertFalse(vals['exited_fn'])
|
assert vals['exception']
|
||||||
self.assertFalse(vals['exception_fn'])
|
assert not vals['exited_fn']
|
||||||
|
assert not vals['exception_fn']
|
||||||
|
Loading…
Reference in New Issue
Block a user