Skip to content
Snippets Groups Projects
Commit b9af52a8 authored by Todd Gamblin's avatar Todd Gamblin
Browse files

tests: Separate tests for llnl.util.lock and spack.util.lock

- llnl.util.lock tests are now independent of Spack
parent 7626ec45
Branches
Tags
No related merge requests found
......@@ -96,7 +96,7 @@ def test(parser, args, unknown_args):
pytest.main(['-h'])
return
# pytest.ini lives in the root of the spack repository.
# pytest.ini lives in lib/spack/spack/test
with working_dir(spack.paths.test_path):
# --list and --long-list print the test output better.
if args.list or args.long_list:
......
......@@ -72,11 +72,9 @@
import pytest
import llnl.util.lock as lk
import llnl.util.multiproc as mp
from llnl.util.filesystem import touch, group_ids
import spack.util.lock
from spack.util.lock import Lock, WriteTransaction, ReadTransaction, LockError
from llnl.util.filesystem import touch
#
......@@ -267,7 +265,7 @@ def wait(self):
#
def acquire_write(lock_path, start=0, length=0):
def fn(barrier):
lock = Lock(lock_path, start, length)
lock = lk.Lock(lock_path, start, length)
lock.acquire_write() # grab exclusive lock
barrier.wait()
barrier.wait() # hold the lock until timeout in other procs.
......@@ -276,7 +274,7 @@ def fn(barrier):
def acquire_read(lock_path, start=0, length=0):
def fn(barrier):
lock = Lock(lock_path, start, length)
lock = lk.Lock(lock_path, start, length)
lock.acquire_read() # grab shared lock
barrier.wait()
barrier.wait() # hold the lock until timeout in other procs.
......@@ -285,9 +283,9 @@ def fn(barrier):
def timeout_write(lock_path, start=0, length=0):
def fn(barrier):
lock = Lock(lock_path, start, length)
lock = lk.Lock(lock_path, start, length)
barrier.wait() # wait for lock acquire in first process
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_write(lock_fail_timeout)
barrier.wait()
return fn
......@@ -295,9 +293,9 @@ def fn(barrier):
def timeout_read(lock_path, start=0, length=0):
def fn(barrier):
lock = Lock(lock_path, start, length)
lock = lk.Lock(lock_path, start, length)
barrier.wait() # wait for lock acquire in first process
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_read(lock_fail_timeout)
barrier.wait()
return fn
......@@ -552,7 +550,7 @@ def test_upgrade_read_to_write(private_lock_path):
# to begin wtih.
touch(private_lock_path)
lock = Lock(private_lock_path)
lock = lk.Lock(private_lock_path)
assert lock._reads == 0
assert lock._writes == 0
......@@ -577,16 +575,14 @@ def test_upgrade_read_to_write(private_lock_path):
assert lock._file is None
#
# Test that read-only file can be read-locked but not write-locked.
#
def test_upgrade_read_to_write_fails_with_readonly_file(private_lock_path):
# ensure lock file exists the first time, so we open it read-only
# to begin wtih.
"""Test that read-only file can be read-locked but not write-locked."""
# ensure lock file exists the first time
touch(private_lock_path)
# open it read-only to begin wtih.
with read_only(private_lock_path):
lock = Lock(private_lock_path)
lock = lk.Lock(private_lock_path)
assert lock._reads == 0
assert lock._writes == 0
......@@ -595,7 +591,8 @@ def test_upgrade_read_to_write_fails_with_readonly_file(private_lock_path):
assert lock._writes == 0
assert lock._file.mode == 'r'
with pytest.raises(LockError):
# upgrade to writ here
with pytest.raises(lk.LockError):
lock.acquire_write()
......@@ -605,7 +602,7 @@ def test_upgrade_read_to_write_fails_with_readonly_file(private_lock_path):
#
def test_complex_acquire_and_release_chain(lock_path):
def p1(barrier):
lock = Lock(lock_path)
lock = lk.Lock(lock_path)
lock.acquire_write()
barrier.wait() # ---------------------------------------- 1
......@@ -613,7 +610,7 @@ def p1(barrier):
barrier.wait() # ---------------------------------------- 2
lock.release_write() # release and others acquire read
barrier.wait() # ---------------------------------------- 3
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_write(lock_fail_timeout)
lock.acquire_read()
barrier.wait() # ---------------------------------------- 4
......@@ -622,9 +619,9 @@ def p1(barrier):
# p2 upgrades read to write
barrier.wait() # ---------------------------------------- 6
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_write(lock_fail_timeout)
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_read(lock_fail_timeout)
barrier.wait() # ---------------------------------------- 7
# p2 releases write and read
......@@ -634,9 +631,9 @@ def p1(barrier):
barrier.wait() # ---------------------------------------- 9
# p3 upgrades read to write
barrier.wait() # ---------------------------------------- 10
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_write(lock_fail_timeout)
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_read(lock_fail_timeout)
barrier.wait() # ---------------------------------------- 11
# p3 releases locks
......@@ -646,13 +643,13 @@ def p1(barrier):
lock.release_read()
def p2(barrier):
lock = Lock(lock_path)
lock = lk.Lock(lock_path)
# p1 acquires write
barrier.wait() # ---------------------------------------- 1
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_write(lock_fail_timeout)
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_read(lock_fail_timeout)
barrier.wait() # ---------------------------------------- 2
lock.acquire_read()
......@@ -674,9 +671,9 @@ def p2(barrier):
barrier.wait() # ---------------------------------------- 9
# p3 upgrades read to write
barrier.wait() # ---------------------------------------- 10
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_write(lock_fail_timeout)
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_read(lock_fail_timeout)
barrier.wait() # ---------------------------------------- 11
# p3 releases locks
......@@ -686,13 +683,13 @@ def p2(barrier):
lock.release_read()
def p3(barrier):
lock = Lock(lock_path)
lock = lk.Lock(lock_path)
# p1 acquires write
barrier.wait() # ---------------------------------------- 1
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_write(lock_fail_timeout)
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_read(lock_fail_timeout)
barrier.wait() # ---------------------------------------- 2
lock.acquire_read()
......@@ -704,9 +701,9 @@ def p3(barrier):
# p2 upgrades read to write
barrier.wait() # ---------------------------------------- 6
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_write(lock_fail_timeout)
with pytest.raises(LockError):
with pytest.raises(lk.LockError):
lock.acquire_read(lock_fail_timeout)
barrier.wait() # ---------------------------------------- 7
# p2 releases write & read
......@@ -736,9 +733,9 @@ def exit_fn(t, v, tb):
vals['exited'] = True
vals['exception'] = (t or v or tb)
lock = Lock(lock_path)
lock = lk.Lock(lock_path)
vals = {'entered': False, 'exited': False, 'exception': False}
with ReadTransaction(lock, enter_fn, exit_fn):
with lk.ReadTransaction(lock, enter_fn, exit_fn):
pass
assert vals['entered']
......@@ -746,7 +743,7 @@ def exit_fn(t, v, tb):
assert not vals['exception']
vals = {'entered': False, 'exited': False, 'exception': False}
with WriteTransaction(lock, enter_fn, exit_fn):
with lk.WriteTransaction(lock, enter_fn, exit_fn):
pass
assert vals['entered']
......@@ -762,14 +759,14 @@ def exit_fn(t, v, tb):
vals['exited'] = True
vals['exception'] = (t or v or tb)
lock = Lock(lock_path)
lock = lk.Lock(lock_path)
def do_read_with_exception():
with ReadTransaction(lock, enter_fn, exit_fn):
with lk.ReadTransaction(lock, enter_fn, exit_fn):
raise Exception()
def do_write_with_exception():
with WriteTransaction(lock, enter_fn, exit_fn):
with lk.WriteTransaction(lock, enter_fn, exit_fn):
raise Exception()
vals = {'entered': False, 'exited': False, 'exception': False}
......@@ -801,11 +798,11 @@ def exit_fn(t, v, tb):
vals['exited_fn'] = True
vals['exception_fn'] = (t or v or tb)
lock = Lock(lock_path)
lock = lk.Lock(lock_path)
vals = {'entered': False, 'exited': False, 'exited_fn': False,
'exception': False, 'exception_fn': False}
with ReadTransaction(lock, TestContextManager, exit_fn):
with lk.ReadTransaction(lock, TestContextManager, exit_fn):
pass
assert vals['entered']
......@@ -816,7 +813,7 @@ def exit_fn(t, v, tb):
vals = {'entered': False, 'exited': False, 'exited_fn': False,
'exception': False, 'exception_fn': False}
with ReadTransaction(lock, TestContextManager):
with lk.ReadTransaction(lock, TestContextManager):
pass
assert vals['entered']
......@@ -827,7 +824,7 @@ def exit_fn(t, v, tb):
vals = {'entered': False, 'exited': False, 'exited_fn': False,
'exception': False, 'exception_fn': False}
with WriteTransaction(lock, TestContextManager, exit_fn):
with lk.WriteTransaction(lock, TestContextManager, exit_fn):
pass
assert vals['entered']
......@@ -838,7 +835,7 @@ def exit_fn(t, v, tb):
vals = {'entered': False, 'exited': False, 'exited_fn': False,
'exception': False, 'exception_fn': False}
with WriteTransaction(lock, TestContextManager):
with lk.WriteTransaction(lock, TestContextManager):
pass
assert vals['entered']
......@@ -861,14 +858,14 @@ def exit_fn(t, v, tb):
vals['exited_fn'] = True
vals['exception_fn'] = (t or v or tb)
lock = Lock(lock_path)
lock = lk.Lock(lock_path)
def do_read_with_exception(exit_fn):
with ReadTransaction(lock, TestContextManager, exit_fn):
with lk.ReadTransaction(lock, TestContextManager, exit_fn):
raise Exception()
def do_write_with_exception(exit_fn):
with WriteTransaction(lock, TestContextManager, exit_fn):
with lk.WriteTransaction(lock, TestContextManager, exit_fn):
raise Exception()
vals = {'entered': False, 'exited': False, 'exited_fn': False,
......@@ -910,91 +907,3 @@ def do_write_with_exception(exit_fn):
assert vals['exception']
assert not vals['exited_fn']
assert not vals['exception_fn']
def test_disable_locking(private_lock_path):
"""Ensure that locks do no real locking when disabled."""
old_value = spack.config.get('config:locks')
with spack.config.override('config:locks', False):
lock = Lock(private_lock_path)
lock.acquire_read()
assert not os.path.exists(private_lock_path)
lock.acquire_write()
assert not os.path.exists(private_lock_path)
lock.release_write()
assert not os.path.exists(private_lock_path)
lock.release_read()
assert not os.path.exists(private_lock_path)
assert old_value == spack.config.get('config:locks')
def test_lock_checks_user(tmpdir):
"""Ensure lock checks work with a self-owned, self-group repo."""
uid = os.getuid()
if uid not in group_ids():
pytest.skip("user has no group with gid == uid")
# self-owned, own group
tmpdir.chown(uid, uid)
# safe
path = str(tmpdir)
tmpdir.chmod(0o744)
spack.util.lock.check_lock_safety(path)
# safe
tmpdir.chmod(0o774)
spack.util.lock.check_lock_safety(path)
# unsafe
tmpdir.chmod(0o777)
with pytest.raises(spack.error.SpackError):
spack.util.lock.check_lock_safety(path)
# safe
tmpdir.chmod(0o474)
spack.util.lock.check_lock_safety(path)
# safe
tmpdir.chmod(0o477)
spack.util.lock.check_lock_safety(path)
def test_lock_checks_group(tmpdir):
"""Ensure lock checks work with a self-owned, non-self-group repo."""
uid = os.getuid()
gid = next((g for g in group_ids() if g != uid), None)
if not gid:
pytest.skip("user has no group with gid != uid")
# self-owned, another group
tmpdir.chown(uid, gid)
# safe
path = str(tmpdir)
tmpdir.chmod(0o744)
spack.util.lock.check_lock_safety(path)
# unsafe
tmpdir.chmod(0o774)
with pytest.raises(spack.error.SpackError):
spack.util.lock.check_lock_safety(path)
# unsafe
tmpdir.chmod(0o777)
with pytest.raises(spack.error.SpackError):
spack.util.lock.check_lock_safety(path)
# safe
tmpdir.chmod(0o474)
spack.util.lock.check_lock_safety(path)
# safe
tmpdir.chmod(0o477)
spack.util.lock.check_lock_safety(path)
......@@ -22,3 +22,102 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
"""Tests for Spack's wrapper module around llnl.util.lock."""
import os
import pytest
from llnl.util.filesystem import group_ids
import spack.config
import spack.util.lock as lk
def test_disable_locking(tmpdir):
"""Ensure that locks do no real locking when disabled."""
lock_path = str(tmpdir.join('lockfile'))
old_value = spack.config.get('config:locks')
with spack.config.override('config:locks', False):
lock = lk.Lock(lock_path)
lock.acquire_read()
assert not os.path.exists(lock_path)
lock.acquire_write()
assert not os.path.exists(lock_path)
lock.release_write()
assert not os.path.exists(lock_path)
lock.release_read()
assert not os.path.exists(lock_path)
assert old_value == spack.config.get('config:locks')
def test_lock_checks_user(tmpdir):
"""Ensure lock checks work with a self-owned, self-group repo."""
uid = os.getuid()
if uid not in group_ids():
pytest.skip("user has no group with gid == uid")
# self-owned, own group
tmpdir.chown(uid, uid)
# safe
path = str(tmpdir)
tmpdir.chmod(0o744)
lk.check_lock_safety(path)
# safe
tmpdir.chmod(0o774)
lk.check_lock_safety(path)
# unsafe
tmpdir.chmod(0o777)
with pytest.raises(spack.error.SpackError):
lk.check_lock_safety(path)
# safe
tmpdir.chmod(0o474)
lk.check_lock_safety(path)
# safe
tmpdir.chmod(0o477)
lk.check_lock_safety(path)
def test_lock_checks_group(tmpdir):
"""Ensure lock checks work with a self-owned, non-self-group repo."""
uid = os.getuid()
gid = next((g for g in group_ids() if g != uid), None)
if not gid:
pytest.skip("user has no group with gid != uid")
# self-owned, another group
tmpdir.chown(uid, gid)
# safe
path = str(tmpdir)
tmpdir.chmod(0o744)
lk.check_lock_safety(path)
# unsafe
tmpdir.chmod(0o774)
with pytest.raises(spack.error.SpackError):
lk.check_lock_safety(path)
# unsafe
tmpdir.chmod(0o777)
with pytest.raises(spack.error.SpackError):
lk.check_lock_safety(path)
# safe
tmpdir.chmod(0o474)
lk.check_lock_safety(path)
# safe
tmpdir.chmod(0o477)
lk.check_lock_safety(path)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment