Skip to content

Commit

Permalink
Adapt module function check for EnvironmentModules
Browse files Browse the repository at this point in the history
Default check_module_function tests that module command is called from
module shell function. With EnvironmentModules v4+, module command is
usually called from the _module_raw shell function.

This commit adds a specific version of check_module_function for
EnvironmentModules class. Module command is first checked within
_module_raw shell function definition. If not found, default test (that
checks module function) is run.

Add new unit test "test_environment_modules_specific" to specifically
check module command definition with EnvironmentModules.

Fixes #4368
  • Loading branch information
xdelaruelle committed Nov 8, 2023
1 parent 6fccffe commit 757b8ee
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 1 deletion.
25 changes: 25 additions & 0 deletions easybuild/tools/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,31 @@ class EnvironmentModules(EnvironmentModulesTcl):
MAX_VERSION = None
VERSION_REGEXP = r'^Modules\s+Release\s+(?P<version>\d\S*)\s'

def check_module_function(self, allow_mismatch=False, regex=None):
"""Check whether selected module tool matches 'module' function definition."""
# Modules 5.1.0+: module command is called from _module_raw shell function
# Modules 4.2.0..5.0.1: module command is called from _module_raw shell function if it has
# been initialized in an interactive shell session (i.e., a session attached to a tty)
if self.testing:
if '_module_raw' in os.environ:
out, ec = os.environ['_module_raw'], 0
else:
out, ec = None, 1
else:
cmd = "type _module_raw"
out, ec = run_cmd(cmd, simple=False, log_ok=False, log_all=False, force_in_dry_run=True, trace=False)

if regex is None:
regex = r".*%s" % os.path.basename(self.cmd)
mod_cmd_re = re.compile(regex, re.M)

if ec == 0 and mod_cmd_re.search(out):
self.log.debug("Found pattern '%s' in defined '_module_raw' function." % mod_cmd_re.pattern)
else:
self.log.debug("Pattern '%s' not found in '_module_raw' function, falling back to 'module' function",
mod_cmd_re.pattern)
super(EnvironmentModules, self).check_module_function(allow_mismatch, regex)

def check_module_output(self, cmd, stdout, stderr):
"""Check output of 'module' command, see if if is potentially invalid."""
if "_mlstatus = False" in stdout:
Expand Down
1 change: 1 addition & 0 deletions easybuild/tools/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"sysctl -n machdep.cpu.brand_string", # used in get_cpu_model (OS X)
"sysctl -n machdep.cpu.vendor", # used in get_cpu_vendor (OS X)
"type module", # used in ModulesTool.check_module_function
"type _module_raw", # used in EnvironmentModules.check_module_function
"ulimit -u", # used in det_parallelism
]

Expand Down
19 changes: 18 additions & 1 deletion test/framework/modulestool.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from easybuild.tools import modules, StrictVersion
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.filetools import read_file, which, write_file
from easybuild.tools.modules import Lmod
from easybuild.tools.modules import EnvironmentModules, Lmod
from test.framework.utilities import init_config


Expand Down Expand Up @@ -192,6 +192,23 @@ def test_lmod_specific(self):
# test updating local spider cache (but don't actually update the local cache file!)
self.assertTrue(lmod.update(), "Updated local Lmod spider cache is non-empty")

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
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}"
mt = EnvironmentModules(testing=True)
self.assertIsInstance(mt.loaded_modules(), list) # dummy usage

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

0 comments on commit 757b8ee

Please sign in to comment.