Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions easybuild/tools/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,10 +615,6 @@ def __init__(self, mod_paths=None, testing=False):
else:
env_cmd_path = None

self.mod_paths = None
if mod_paths is not None:
self.set_mod_paths(mod_paths)

if env_cmd_path:
cmd_path = which(self.cmd, log_ok=False, on_error=IGNORE)
# only use command path in environment variable if command in not available in $PATH
Expand All @@ -641,7 +637,6 @@ def __init__(self, mod_paths=None, testing=False):

# some initialisation/verification
self.check_cmd_avail()
self.check_module_path()
self.check_module_function(allow_mismatch=build_option('allow_modules_tool_mismatch'))
self.set_and_check_version()
self.supports_depends_on = False
Expand All @@ -650,6 +645,11 @@ def __init__(self, mod_paths=None, testing=False):
self.supports_safe_auto_load = False
self.supports_extensions = False

self.mod_paths = None
if mod_paths is not None:
self.set_mod_paths(mod_paths)
self.check_module_path()

def __str__(self):
"""String representation of this ModulesTool instance."""
res = self.NAME
Expand Down Expand Up @@ -1924,8 +1924,6 @@ def __init__(self, *args, **kwargs):
setvar('LMOD_QUIET', '1', verbose=False)
# make sure Lmod ignores the spider cache ($LMOD_IGNORE_CACHE supported since Lmod 5.2)
setvar('LMOD_IGNORE_CACHE', '1', verbose=False)
# hard disable output redirection, we expect output messages (list, avail) to always go to stderr
setvar('LMOD_REDIRECT', 'no', verbose=False)
# disable extended defaults within Lmod (introduced and set as default in Lmod 8.0.7)
setvar('LMOD_EXTENDED_DEFAULT', 'no', verbose=False)
# disabled decorations in "ml --terse avail" output
Expand Down
126 changes: 64 additions & 62 deletions test/framework/modulestool.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def test_module_mismatch(self):
# redefine 'module' function (deliberate mismatch with used module command in MockModulesTool)
os.environ['module'] = "() { eval `/tmp/Modules/$MODULE_VERSION/bin/modulecmd bash $*`\n}"
error_regex = ".*pattern .* not found in defined 'module' function"
self.assertErrorRegex(EasyBuildError, error_regex, MockModulesTool, testing=True)
self.assertErrorRegex(EasyBuildError, error_regex, MockModulesTool, mod_paths=["/nonexisting]"], testing=True)

# check whether escaping error by allowing mismatch via build options works
build_options = {
Expand Down Expand Up @@ -142,46 +142,47 @@ def test_module_mismatch(self):

def test_lmod_specific(self):
"""Lmod-specific test (skipped unless Lmod is used as modules tool)."""
lmod_abspath = which(Lmod.COMMAND)
# only run this test if 'lmod' is available in $PATH
if lmod_abspath is not None:
build_options = {
'allow_modules_tool_mismatch': True,
'update_modules_tool_cache': True,
}
init_config(build_options=build_options)
build_options = {
'allow_modules_tool_mismatch': True,
'update_modules_tool_cache': True,
}
init_config(build_options=build_options)

lmod_abspath = next((cmd for cmd in (which(Lmod.COMMAND), os.environ.get(Lmod.COMMAND_ENVIRONMENT))
if cmd and os.path.exists(cmd)), None)
if lmod_abspath is not None:
lmod = Lmod(testing=True)
self.assertTrue(os.path.samefile(lmod.cmd, lmod_abspath))

# drop any location where 'lmod' or 'spider' can be found from $PATH
paths = os.environ.get('PATH', '').split(os.pathsep)
new_paths = []
for path in paths:
lmod_cand_path = os.path.join(path, Lmod.COMMAND)
spider_cand_path = os.path.join(path, 'spider')
if not os.path.isfile(lmod_cand_path) and not os.path.isfile(spider_cand_path):
new_paths.append(path)
os.environ['PATH'] = os.pathsep.join(new_paths)

# make sure $MODULEPATH contains path that provides some modules
os.environ['MODULEPATH'] = os.path.abspath(os.path.join(os.path.dirname(__file__), 'modules'))

# initialize Lmod modules tool, pass (fake) full path to 'lmod' via $LMOD_CMD
fake_path = os.path.join(self.test_installpath, 'lmod')
fake_lmod_txt = '\n'.join([
'#!/bin/bash',
'echo "Modules based on Lua: Version %s " >&2' % Lmod.DEPR_VERSION,
'echo "os.environ[\'FOO\'] = \'foo\'"',
])
write_file(fake_path, fake_lmod_txt)
os.chmod(fake_path, stat.S_IRUSR | stat.S_IXUSR)
os.environ['LMOD_CMD'] = fake_path
init_config(build_options=build_options)
lmod = Lmod(testing=True)
self.assertTrue(os.path.samefile(lmod.cmd, fake_path))
# drop any location where 'lmod' or 'spider' can be found from $PATH
paths = os.environ.get('PATH', '').split(os.pathsep)
new_paths = []
for path in paths:
lmod_cand_path = os.path.join(path, Lmod.COMMAND)
spider_cand_path = os.path.join(path, 'spider')
if not os.path.isfile(lmod_cand_path) and not os.path.isfile(spider_cand_path):
new_paths.append(path)
os.environ['PATH'] = os.pathsep.join(new_paths)

# make sure $MODULEPATH contains path that provides some modules
os.environ['MODULEPATH'] = os.path.abspath(os.path.join(os.path.dirname(__file__), 'modules'))

# initialize Lmod modules tool, pass (fake) full path to 'lmod' via $LMOD_CMD
fake_path = os.path.join(self.test_installpath, 'lmod')
fake_lmod_txt = '\n'.join([
'#!/bin/bash',
'echo "Modules based on Lua: Version %s " >&2' % Lmod.DEPR_VERSION,
'echo "os.environ[\'FOO\'] = \'foo\'"',
])
write_file(fake_path, fake_lmod_txt)
os.chmod(fake_path, stat.S_IRUSR | stat.S_IXUSR)
os.environ['LMOD_CMD'] = fake_path
init_config(build_options=build_options)
lmod = Lmod(testing=True)
self.assertTrue(os.path.samefile(lmod.cmd, fake_path))

# use correct full path for 'lmod' via $LMOD_CMD
# use correct full path for 'lmod' via $LMOD_CMD
if lmod_abspath is not None:
os.environ['LMOD_CMD'] = lmod_abspath
init_config(build_options=build_options)
lmod = Lmod(testing=True)
Expand All @@ -194,18 +195,12 @@ def test_lmod_specific(self):

def test_environment_modules_specific(self):
"""Environment Modules-specific test (skipped unless installed)."""
modulecmd_abspath = which(EnvironmentModules.COMMAND)
# only run this test if 'modulecmd.tcl' is installed
modulecmd_abspath = next((cmd for cmd in (which(EnvironmentModules.COMMAND),
os.environ.get(EnvironmentModules.COMMAND_ENVIRONMENT))
if cmd and os.path.exists(cmd)), None)
# redefine '_module_raw' function with correct module command
if modulecmd_abspath is not None:
# redefine 'module' and '_module_raw' function (deliberate mismatch with used module
# command in EnvironmentModules)
os.environ['_module_raw'] = "() { eval `/usr/share/Modules/libexec/foo.tcl' bash $*`;\n}"
os.environ['module'] = "() { _module_raw \"$@\" 2>&1;\n}"
error_regex = ".*pattern .* not found in defined 'module' function"
self.assertErrorRegex(EasyBuildError, error_regex, EnvironmentModules, testing=True)

# redefine '_module_raw' function with correct module command
os.environ['_module_raw'] = "() { eval `/usr/share/Modules/libexec/modulecmd.tcl' bash $*`;\n}"
os.environ['_module_raw'] = "() { eval `%s' bash $*`;\n}" % modulecmd_abspath
mt = EnvironmentModules(testing=True)
self.assertIsInstance(mt.loaded_modules(), list) # dummy usage

Expand All @@ -232,21 +227,28 @@ def test_environment_modules_specific(self):
self.assertTrue(os.path.exists(cache_fp))
os.remove(cache_fp)

# initialize Environment Modules tool with non-official version number
# pass (fake) full path to 'modulecmd.tcl' via $MODULES_CMD
fake_path = os.path.join(self.test_installpath, 'libexec', 'modulecmd.tcl')
fake_modulecmd_txt = '\n'.join([
'#!/bin/bash',
'echo "Modules Release 5.3.1+unload-188-g14b6b59b (2023-10-21)" >&2',
'echo "os.environ[\'FOO\'] = \'foo\'"',
])
write_file(fake_path, fake_modulecmd_txt)
os.chmod(fake_path, stat.S_IRUSR | stat.S_IXUSR)
os.environ['_module_raw'] = "() { eval `%s' bash $*`;\n}" % fake_path
os.environ['MODULES_CMD'] = fake_path
EnvironmentModules.COMMAND = fake_path
mt = EnvironmentModules(testing=True)
self.assertTrue(os.path.samefile(mt.cmd, fake_path), "%s - %s" % (mt.cmd, fake_path))
# initialize Environment Modules tool with non-official version number
# pass (fake) full path to 'modulecmd.tcl' via $MODULES_CMD
fake_path = os.path.join(self.test_installpath, 'libexec', 'modulecmd.tcl')
fake_modulecmd_txt = '\n'.join([
'#!/bin/bash',
'echo "Modules Release 5.3.1+unload-188-g14b6b59b (2023-10-21)" >&2',
'echo "os.environ[\'FOO\'] = \'foo\'"',
])
write_file(fake_path, fake_modulecmd_txt)
os.chmod(fake_path, stat.S_IRUSR | stat.S_IXUSR)
os.environ['_module_raw'] = "() { eval `%s' bash $*`;\n}" % fake_path
os.environ['MODULES_CMD'] = fake_path
EnvironmentModules.COMMAND = fake_path
mt = EnvironmentModules(testing=True)
self.assertTrue(os.path.samefile(mt.cmd, fake_path), "%s - %s" % (mt.cmd, fake_path))

# redefine 'module' and '_module_raw' function (deliberate mismatch with used module
# command in EnvironmentModules)
os.environ['_module_raw'] = "() { eval `/usr/share/Modules/libexec/foo.tcl' bash $*`;\n}"
os.environ['module'] = "() { _module_raw \"$@\" 2>&1;\n}"
error_regex = ".*pattern .* not found in defined 'module' function"
self.assertErrorRegex(EasyBuildError, error_regex, EnvironmentModules, testing=True)

def tearDown(self):
"""Testcase cleanup."""
Expand Down