spack/lib/spack/spack/test/modules/lmod.py
Massimiliano Culpo 8ecf5ae2ee Spack can guess lmod core compilers, if none is already present
closes #8916

Currently Spack ends with an error if asked to write lmod modules files
and the 'core_compilers' entry is not found in `modules.yaml`. After
this PR an attempt will be made to guess that entry and the site
configuration file will be updated accordingly.

This is similar to what Spack already does to guess compilers on first
run.
2018-08-27 14:49:50 -07:00

284 lines
10 KiB
Python

##############################################################################
# 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
##############################################################################
import re
import pytest
import spack.modules.lmod
mpich_spec_string = 'mpich@3.0.4'
mpileaks_spec_string = 'mpileaks'
libdwarf_spec_string = 'libdwarf arch=x64-linux'
#: Class of the writer tested in this module
writer_cls = spack.modules.lmod.LmodModulefileWriter
@pytest.fixture(params=[
'clang@3.3',
'gcc@4.5.0'
])
def compiler(request):
return request.param
@pytest.fixture(params=[
('mpich@3.0.4', ('mpi',)),
('openblas@0.2.15', ('blas',)),
('openblas-with-lapack@0.2.15', ('blas', 'lapack'))
])
def provider(request):
return request.param
@pytest.mark.usefixtures('config', 'mock_packages',)
class TestLmod(object):
def test_file_layout(
self, compiler, provider, factory, module_configuration
):
"""Tests the layout of files in the hierarchy is the one expected."""
module_configuration('complex_hierarchy')
spec_string, services = provider
module, spec = factory(spec_string + '%' + compiler)
layout = module.layout
# Check that the services provided are in the hierarchy
for s in services:
assert s in layout.conf.hierarchy_tokens
# Check that the compiler part of the path has no hash and that it
# is transformed to r"Core" if the compiler is listed among core
# compilers
if compiler == 'clang@3.3':
assert 'Core' in layout.available_path_parts
else:
assert compiler.replace('@', '/') in layout.available_path_parts
# Check that the provider part instead has always an hash even if
# hash has been disallowed in the configuration file
path_parts = layout.available_path_parts
service_part = spec_string.replace('@', '/')
service_part = '-'.join([service_part, layout.spec.dag_hash(length=7)])
assert service_part in path_parts
# Check that multi-providers have repetitions in path parts
repetitions = len([x for x in path_parts if service_part == x])
if spec_string == 'openblas-with-lapack@0.2.15':
assert repetitions == 2
else:
assert repetitions == 1
def test_simple_case(self, modulefile_content, module_configuration):
"""Tests the generation of a simple TCL module file."""
module_configuration('autoload_direct')
content = modulefile_content(mpich_spec_string)
assert '-- -*- lua -*-' in content
assert 'whatis([[Name : mpich]])' in content
assert 'whatis([[Version : 3.0.4]])' in content
assert 'family("mpi")' in content
def test_autoload_direct(self, modulefile_content, module_configuration):
"""Tests the automatic loading of direct dependencies."""
module_configuration('autoload_direct')
content = modulefile_content(mpileaks_spec_string)
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
# The configuration file doesn't set the verbose keyword
# that defaults to False
messages = [x for x in content if 'LmodMessage("Autoloading' in x]
assert len(messages) == 0
def test_autoload_all(self, modulefile_content, module_configuration):
"""Tests the automatic loading of all dependencies."""
module_configuration('autoload_all')
content = modulefile_content(mpileaks_spec_string)
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
# The configuration file sets the verbose keyword to True
messages = [x for x in content if 'LmodMessage("Autoloading' in x]
assert len(messages) == 5
def test_alter_environment(self, modulefile_content, module_configuration):
"""Tests modifications to run-time environment."""
module_configuration('alter_environment')
content = modulefile_content('mpileaks platform=test target=x86_64')
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
content = modulefile_content(
'libdwarf %clang platform=test target=x86_32'
)
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_prepend_path_separator(self, modulefile_content,
module_configuration):
"""Tests modifications to run-time environment."""
module_configuration('module_path_separator')
content = modulefile_content('module-path-separator')
for line in content:
if re.match(r'[a-z]+_path\("COLON"', line):
assert line.endswith('"foo", ":")')
elif re.match(r'[a-z]+_path\("SEMICOLON"', line):
assert line.endswith('"bar", ";")')
def test_blacklist(self, modulefile_content, module_configuration):
"""Tests blacklisting the generation of selected modules."""
module_configuration('blacklist')
content = modulefile_content(mpileaks_spec_string)
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, factory, module_configuration):
"""Makes 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.
"""
module_configuration('no_hash')
module, spec = factory(mpileaks_spec_string)
path = module.layout.filename
mpi_spec = spec['mpi']
mpi_element = "{0}/{1}-{2}/".format(
mpi_spec.name, mpi_spec.version, mpi_spec.dag_hash(length=7)
)
assert mpi_element in path
mpileaks_spec = spec
mpileaks_element = "{0}/{1}.lua".format(
mpileaks_spec.name, mpileaks_spec.version
)
assert path.endswith(mpileaks_element)
def test_no_core_compilers(self, factory, module_configuration):
"""Ensures that missing 'core_compilers' in the configuration file
raises the right exception.
"""
# In this case we miss the entry completely
module_configuration('missing_core_compilers')
module, spec = factory(mpileaks_spec_string)
with pytest.raises(spack.modules.lmod.CoreCompilersNotFoundError):
module.write()
# Here we have an empty list
module_configuration('core_compilers_empty')
module, spec = factory(mpileaks_spec_string)
with pytest.raises(spack.modules.lmod.CoreCompilersNotFoundError):
module.write()
def test_non_virtual_in_hierarchy(self, factory, module_configuration):
"""Ensures that if a non-virtual is in hierarchy, an exception will
be raised.
"""
module_configuration('non_virtual_in_hierarchy')
module, spec = factory(mpileaks_spec_string)
with pytest.raises(spack.modules.lmod.NonVirtualInHierarchyError):
module.write()
def test_override_template_in_package(
self, modulefile_content, module_configuration
):
"""Tests overriding a template from and attribute in the package."""
module_configuration('autoload_direct')
content = modulefile_content('override-module-templates')
assert 'Override successful!' in content
def test_override_template_in_modules_yaml(
self, modulefile_content, module_configuration
):
"""Tests overriding a template from `modules.yaml`"""
module_configuration('override_template')
content = modulefile_content('override-module-templates')
assert 'Override even better!' in content
content = modulefile_content('mpileaks arch=x86-linux')
assert 'Override even better!' in content
@pytest.mark.usefixtures('config')
def test_external_configure_args(
self, factory
):
# If this package is detected as an external, its configure option line
# in the module file starts with 'unknown'
writer, spec = factory('externaltool')
assert 'unknown' in writer.context.configure_options
def test_guess_core_compilers(
self, factory, module_configuration, monkeypatch
):
"""Check that we can guess core compilers."""
# In this case we miss the entry completely
module_configuration('missing_core_compilers')
# Our mock paths must be detected as system paths
monkeypatch.setattr(
spack.util.environment, 'system_dirs', ['/path/to']
)
# We don't want to really write into user configuration
# when running tests
def no_op_set(*args, **kwargs):
pass
monkeypatch.setattr(spack.config, 'set', no_op_set)
# Assert we have core compilers now
writer, _ = factory(mpileaks_spec_string)
assert writer.conf.core_compilers