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

- llnl.util.lock tests are now independent of Spack
This commit is contained in:
Todd Gamblin 2018-06-29 18:33:17 -04:00
parent 7626ec4579
commit b9af52a888
4 changed files with 169 additions and 161 deletions

View file

@ -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:

View file

@ -1,24 +0,0 @@
##############################################################################
# Copyright (c) 2013-2018, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
# LLNL-CODE-647188
#
# For details, see https://github.com/spack/spack
# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License (as
# published by the Free Software Foundation) version 2.1, February 1999.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
# conditions of the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################

View file

@ -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)

View file

@ -0,0 +1,123 @@
##############################################################################
# Copyright (c) 2013-2018, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
# LLNL-CODE-647188
#
# For details, see https://github.com/spack/spack
# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License (as
# published by the Free Software Foundation) version 2.1, February 1999.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
# conditions of the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# 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)