spack/lib/spack/spack/test/modules.py
Todd Gamblin 1d1a14dbe9 Convert Python 2 idioms to Python 2/3-compatible ones.
- convert print, StringIO, except as, octals, izip
- convert print statement to print function
- convert StringIO to six.StringIO
  - remove usage of csv reader in Spec, in favor of simple regex
  - csv reader only does byte strings
- convert 0755 octal literals to 0o755
- convert `except Foo, e` to `except Foo as e`
- fix a few places `str` is used.
  - may need to switch everything to str later.
- convert iteritems usages to use six.iteritems
- fix urllib and HTMLParser
- port metaclasses to use six.with_metaclass
- More octal literal conversions for Python 2/3
- Fix a new octal literal.
- Convert `basestring` to `six.string_types`
- Convert xrange -> range
- Fix various issues with encoding, iteritems, and Python3 semantics.
- Convert contextlib.nested to explicitly nexted context managers.
- Convert use of filter() to list comprehensions.
- Replace reduce() with list comprehensions.
-  Clean up composite: replace inspect.ismethod() with callable()
- Python 3 doesn't have "method" objects; inspect.ismethod returns False.
- Need to use callable in Composite to make it work.
- Update colify to use future division.
- Fix zip() usages that need to be lists.
- Python3: Use line-buffered logging instead of unbuffered.
- Python3 raises an error with unbuffered I/O
  - See https://bugs.python.org/issue17404
2017-03-31 13:40:41 -07:00

514 lines
18 KiB
Python

##############################################################################
# Copyright (c) 2013-2016, 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/llnl/spack
# Please also see the LICENSE file 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
##############################################################################
import collections
import contextlib
from six import StringIO
import pytest
import spack.modules
import spack.spec
# Our "filesystem" for the tests below
FILE_REGISTRY = collections.defaultdict(StringIO)
# Spec strings that will be used throughout the tests
mpich_spec_string = 'mpich@3.0.4'
mpileaks_spec_string = 'mpileaks'
libdwarf_spec_string = 'libdwarf arch=x64-linux'
@pytest.fixture()
def stringio_open(monkeypatch):
"""Overrides the `open` builtin in spack.modules with an implementation
that writes on a StringIO instance.
"""
@contextlib.contextmanager
def _mock(filename, mode):
if not mode == 'w':
raise RuntimeError('unexpected opening mode for stringio_open')
FILE_REGISTRY[filename] = StringIO()
try:
yield FILE_REGISTRY[filename]
finally:
handle = FILE_REGISTRY[filename]
FILE_REGISTRY[filename] = handle.getvalue()
handle.close()
monkeypatch.setattr(spack.modules, 'open', _mock, raising=False)
def get_modulefile_content(factory, spec):
"""Writes the module file and returns the content as a string.
:param factory: module file factory
:param spec: spec of the module file to be written
:return: content of the module file
:rtype: str
"""
spec.concretize()
generator = factory(spec)
generator.write()
content = FILE_REGISTRY[generator.file_name].split('\n')
generator.remove()
return content
def test_update_dictionary_extending_list():
target = {
'foo': {
'a': 1,
'b': 2,
'd': 4
},
'bar': [1, 2, 4],
'baz': 'foobar'
}
update = {
'foo': {
'c': 3,
},
'bar': [3],
'baz': 'foobaz',
'newkey': {
'd': 4
}
}
spack.modules.update_dictionary_extending_lists(target, update)
assert len(target) == 4
assert len(target['foo']) == 4
assert len(target['bar']) == 4
assert target['baz'] == 'foobaz'
def test_inspect_path():
env = spack.modules.inspect_path('/usr')
names = [item.name for item in env]
assert 'PATH' in names
assert 'LIBRARY_PATH' in names
assert 'LD_LIBRARY_PATH' in names
assert 'CPATH' in names
@pytest.fixture()
def tcl_factory(tmpdir, monkeypatch):
"""Returns a factory that writes non-hierarchical TCL module files."""
factory = spack.modules.TclModule
monkeypatch.setattr(factory, 'path', str(tmpdir))
monkeypatch.setattr(spack.modules, 'module_types', {factory.name: factory})
return factory
@pytest.fixture()
def lmod_factory(tmpdir, monkeypatch):
"""Returns a factory that writes hierarchical LUA module files."""
factory = spack.modules.LmodModule
monkeypatch.setattr(factory, 'path', str(tmpdir))
monkeypatch.setattr(spack.modules, 'module_types', {factory.name: factory})
return factory
@pytest.fixture()
def dotkit_factory(tmpdir, monkeypatch):
"""Returns a factory that writes DotKit module files."""
factory = spack.modules.Dotkit
monkeypatch.setattr(factory, 'path', str(tmpdir))
monkeypatch.setattr(spack.modules, 'module_types', {factory.name: factory})
return factory
@pytest.mark.usefixtures('config', 'builtin_mock', 'stringio_open')
class TestTcl(object):
configuration_autoload_direct = {
'enable': ['tcl'],
'tcl': {
'all': {
'autoload': 'direct'
}
}
}
configuration_autoload_all = {
'enable': ['tcl'],
'tcl': {
'all': {
'autoload': 'all'
}
}
}
configuration_prerequisites_direct = {
'enable': ['tcl'],
'tcl': {
'all': {
'prerequisites': 'direct'
}
}
}
configuration_prerequisites_all = {
'enable': ['tcl'],
'tcl': {
'all': {
'prerequisites': 'all'
}
}
}
configuration_alter_environment = {
'enable': ['tcl'],
'tcl': {
'all': {
'filter': {'environment_blacklist': ['CMAKE_PREFIX_PATH']},
'environment': {
'set': {'${PACKAGE}_ROOT': '${PREFIX}'}
}
},
'platform=test target=x86_64': {
'environment': {
'set': {'FOO': 'foo'},
'unset': ['BAR']
}
},
'platform=test target=x86_32': {
'load': ['foo/bar']
}
}
}
configuration_blacklist = {
'enable': ['tcl'],
'tcl': {
'whitelist': ['zmpi'],
'blacklist': ['callpath', 'mpi'],
'all': {
'autoload': 'direct'
}
}
}
configuration_conflicts = {
'enable': ['tcl'],
'tcl': {
'naming_scheme': '${PACKAGE}/${VERSION}-${COMPILERNAME}',
'all': {
'conflict': ['${PACKAGE}', 'intel/14.0.1']
}
}
}
configuration_wrong_conflicts = {
'enable': ['tcl'],
'tcl': {
'naming_scheme': '${PACKAGE}/${VERSION}-${COMPILERNAME}',
'all': {
'conflict': ['${PACKAGE}/${COMPILERNAME}']
}
}
}
configuration_suffix = {
'enable': ['tcl'],
'tcl': {
'mpileaks': {
'suffixes': {
'+debug': 'foo',
'~debug': 'bar'
}
}
}
}
def test_simple_case(self, tcl_factory):
spack.modules._module_config = self.configuration_autoload_direct
spec = spack.spec.Spec(mpich_spec_string)
content = get_modulefile_content(tcl_factory, spec)
assert 'module-whatis "mpich @3.0.4"' in content
with pytest.raises(TypeError):
spack.modules.dependencies(spec, 'non-existing-tag')
def test_autoload(self, tcl_factory):
spack.modules._module_config = self.configuration_autoload_direct
spec = spack.spec.Spec(mpileaks_spec_string)
content = get_modulefile_content(tcl_factory, spec)
assert len([x for x in content if 'is-loaded' in x]) == 2
assert len([x for x in content if 'module load ' in x]) == 2
spack.modules._module_config = self.configuration_autoload_all
spec = spack.spec.Spec(mpileaks_spec_string)
content = get_modulefile_content(tcl_factory, spec)
assert len([x for x in content if 'is-loaded' in x]) == 5
assert len([x for x in content if 'module load ' in x]) == 5
# dtbuild1 has
# - 1 ('run',) dependency
# - 1 ('build','link') dependency
# - 1 ('build',) dependency
# Just make sure the 'build' dependency is not there
spack.modules._module_config = self.configuration_autoload_direct
spec = spack.spec.Spec('dtbuild1')
content = get_modulefile_content(tcl_factory, spec)
assert len([x for x in content if 'is-loaded' in x]) == 2
assert len([x for x in content if 'module load ' in x]) == 2
# dtbuild1 has
# - 1 ('run',) dependency
# - 1 ('build','link') dependency
# - 1 ('build',) dependency
# Just make sure the 'build' dependency is not there
spack.modules._module_config = self.configuration_autoload_all
spec = spack.spec.Spec('dtbuild1')
content = get_modulefile_content(tcl_factory, spec)
assert len([x for x in content if 'is-loaded' in x]) == 2
assert len([x for x in content if 'module load ' in x]) == 2
def test_prerequisites(self, tcl_factory):
spack.modules._module_config = self.configuration_prerequisites_direct
spec = spack.spec.Spec('mpileaks arch=x86-linux')
content = get_modulefile_content(tcl_factory, spec)
assert len([x for x in content if 'prereq' in x]) == 2
spack.modules._module_config = self.configuration_prerequisites_all
spec = spack.spec.Spec('mpileaks arch=x86-linux')
content = get_modulefile_content(tcl_factory, spec)
assert len([x for x in content if 'prereq' in x]) == 5
def test_alter_environment(self, tcl_factory):
spack.modules._module_config = self.configuration_alter_environment
spec = spack.spec.Spec('mpileaks platform=test target=x86_64')
content = get_modulefile_content(tcl_factory, spec)
assert len([x for x in content
if x.startswith('prepend-path CMAKE_PREFIX_PATH')
]) == 0
assert len([x for x in content if 'setenv FOO "foo"' in x]) == 1
assert len([x for x in content if 'unsetenv BAR' in x]) == 1
assert len([x for x in content if 'setenv MPILEAKS_ROOT' in x]) == 1
spec = spack.spec.Spec('libdwarf %clang platform=test target=x86_32')
content = get_modulefile_content(tcl_factory, spec)
assert len(
[x for x in content if x.startswith('prepend-path CMAKE_PREFIX_PATH')] # NOQA: ignore=E501
) == 0
assert len([x for x in content if 'setenv FOO "foo"' in x]) == 0
assert len([x for x in content if 'unsetenv BAR' in x]) == 0
assert len([x for x in content if 'is-loaded foo/bar' in x]) == 1
assert len([x for x in content if 'module load foo/bar' in x]) == 1
assert len([x for x in content if 'setenv LIBDWARF_ROOT' in x]) == 1
def test_blacklist(self, tcl_factory):
spack.modules._module_config = self.configuration_blacklist
spec = spack.spec.Spec('mpileaks ^zmpi')
content = get_modulefile_content(tcl_factory, spec)
assert len([x for x in content if 'is-loaded' in x]) == 1
assert len([x for x in content if 'module load ' in x]) == 1
spec = spack.spec.Spec('callpath arch=x86-linux')
# Returns a StringIO instead of a string as no module file was written
with pytest.raises(AttributeError):
get_modulefile_content(tcl_factory, spec)
spec = spack.spec.Spec('zmpi arch=x86-linux')
content = get_modulefile_content(tcl_factory, spec)
assert len([x for x in content if 'is-loaded' in x]) == 1
assert len([x for x in content if 'module load ' in x]) == 1
def test_conflicts(self, tcl_factory):
spack.modules._module_config = self.configuration_conflicts
spec = spack.spec.Spec('mpileaks')
content = get_modulefile_content(tcl_factory, spec)
assert len([x for x in content if x.startswith('conflict')]) == 2
assert len([x for x in content if x == 'conflict mpileaks']) == 1
assert len([x for x in content if x == 'conflict intel/14.0.1']) == 1
spack.modules._module_config = self.configuration_wrong_conflicts
with pytest.raises(SystemExit):
get_modulefile_content(tcl_factory, spec)
def test_suffixes(self, tcl_factory):
spack.modules._module_config = self.configuration_suffix
spec = spack.spec.Spec('mpileaks+debug arch=x86-linux')
spec.concretize()
generator = tcl_factory(spec)
assert 'foo' in generator.use_name
spec = spack.spec.Spec('mpileaks~debug arch=x86-linux')
spec.concretize()
generator = tcl_factory(spec)
assert 'bar' in generator.use_name
def test_setup_environment(self, tcl_factory):
spec = spack.spec.Spec('mpileaks')
spec.concretize()
content = get_modulefile_content(tcl_factory, spec)
assert len([x for x in content if 'setenv FOOBAR' in x]) == 1
assert len(
[x for x in content if 'setenv FOOBAR "mpileaks"' in x]
) == 1
content = get_modulefile_content(tcl_factory, spec['callpath'])
assert len([x for x in content if 'setenv FOOBAR' in x]) == 1
assert len(
[x for x in content if 'setenv FOOBAR "callpath"' in x]
) == 1
@pytest.mark.usefixtures('config', 'builtin_mock', 'stringio_open')
class TestLmod(object):
configuration_autoload_direct = {
'enable': ['lmod'],
'lmod': {
'all': {
'autoload': 'direct'
}
}
}
configuration_autoload_all = {
'enable': ['lmod'],
'lmod': {
'all': {
'autoload': 'all'
}
}
}
configuration_no_hash = {
'enable': ['lmod'],
'lmod': {
'hash_length': 0
}
}
configuration_alter_environment = {
'enable': ['lmod'],
'lmod': {
'all': {
'filter': {'environment_blacklist': ['CMAKE_PREFIX_PATH']}
},
'platform=test target=x86_64': {
'environment': {
'set': {'FOO': 'foo'},
'unset': ['BAR']
}
},
'platform=test target=x86_32': {
'load': ['foo/bar']
}
}
}
configuration_blacklist = {
'enable': ['lmod'],
'lmod': {
'blacklist': ['callpath'],
'all': {
'autoload': 'direct'
}
}
}
def test_simple_case(self, lmod_factory):
spack.modules._module_config = self.configuration_autoload_direct
spec = spack.spec.Spec(mpich_spec_string)
content = get_modulefile_content(lmod_factory, spec)
assert '-- -*- lua -*-' in content
assert 'whatis([[Name : mpich]])' in content
assert 'whatis([[Version : 3.0.4]])' in content
def test_autoload(self, lmod_factory):
spack.modules._module_config = self.configuration_autoload_direct
spec = spack.spec.Spec(mpileaks_spec_string)
content = get_modulefile_content(lmod_factory, spec)
assert len([x for x in content if 'if not isloaded(' in x]) == 2
assert len([x for x in content if 'load(' in x]) == 2
spack.modules._module_config = self.configuration_autoload_all
spec = spack.spec.Spec(mpileaks_spec_string)
content = get_modulefile_content(lmod_factory, spec)
assert len([x for x in content if 'if not isloaded(' in x]) == 5
assert len([x for x in content if 'load(' in x]) == 5
def test_alter_environment(self, lmod_factory):
spack.modules._module_config = self.configuration_alter_environment
spec = spack.spec.Spec('mpileaks platform=test target=x86_64')
content = get_modulefile_content(lmod_factory, spec)
assert len(
[x for x in content if x.startswith('prepend_path("CMAKE_PREFIX_PATH"')] # NOQA: ignore=E501
) == 0
assert len([x for x in content if 'setenv("FOO", "foo")' in x]) == 1
assert len([x for x in content if 'unsetenv("BAR")' in x]) == 1
spec = spack.spec.Spec('libdwarf %clang platform=test target=x86_32')
content = get_modulefile_content(lmod_factory, spec)
assert len(
[x for x in content if x.startswith('prepend-path("CMAKE_PREFIX_PATH"')] # NOQA: ignore=E501
) == 0
assert len([x for x in content if 'setenv("FOO", "foo")' in x]) == 0
assert len([x for x in content if 'unsetenv("BAR")' in x]) == 0
def test_blacklist(self, lmod_factory):
spack.modules._module_config = self.configuration_blacklist
spec = spack.spec.Spec(mpileaks_spec_string)
content = get_modulefile_content(lmod_factory, spec)
assert len([x for x in content if 'if not isloaded(' in x]) == 1
assert len([x for x in content if 'load(' in x]) == 1
def test_no_hash(self, lmod_factory):
# Make sure that virtual providers (in the hierarchy) always
# include a hash. Make sure that the module file for the spec
# does not include a hash if hash_length is 0.
spack.modules._module_config = self.configuration_no_hash
spec = spack.spec.Spec(mpileaks_spec_string)
spec.concretize()
module = lmod_factory(spec)
path = module.file_name
mpi_spec = spec['mpi']
mpiElement = "{0}/{1}-{2}/".format(
mpi_spec.name, mpi_spec.version, mpi_spec.dag_hash(length=7)
)
assert mpiElement in path
mpileaks_spec = spec
mpileaks_element = "{0}/{1}.lua".format(
mpileaks_spec.name, mpileaks_spec.version)
assert path.endswith(mpileaks_element)
@pytest.mark.usefixtures('config', 'builtin_mock', 'stringio_open')
class TestDotkit(object):
configuration_dotkit = {
'enable': ['dotkit'],
'dotkit': {
'all': {
'prerequisites': 'direct'
}
}
}
def test_dotkit(self, dotkit_factory):
spack.modules._module_config = self.configuration_dotkit
spec = spack.spec.Spec('mpileaks arch=x86-linux')
content = get_modulefile_content(dotkit_factory, spec)
assert '#c spack' in content
assert '#d mpileaks @2.3' in content