Skip to content
25 changes: 25 additions & 0 deletions docs/manpage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,16 @@ It does so by leveraging the selected system's environment modules system.
This option can also be set using the :envvar:`RFM_UNLOAD_MODULES` environment variable or the :js:attr:`unload_modules` general configuration parameter.


.. option:: --module-path=PATH

Manipulate the ``MODULEPATH`` environment variable before acting on any tests.
If ``PATH`` starts with the `-` character, it will be removed from the ``MODULEPATH``, whereas if it starts with the `+` character, it will be added to it.
In all other cases, ``PATH`` will completely override MODULEPATH.
This option may be specified multiple times, in which case all the paths specified will be added or removed in order.

.. versionadded:: 3.3


.. option:: --purge-env

Unload all environment modules before acting on any tests.
Expand Down Expand Up @@ -883,6 +893,21 @@ Here is an alphabetical list of the environment variables recognized by ReFrame:
================================== ==================


.. versionadded:: 3.3

.. envvar:: RFM_UNUSE_MODULE_PATHS

A colon-separated list of module paths to be unused before acting on any tests.

.. table::
:align: left

================================== ==================
Associated command line option :option:`--unuse-module-path`
Associated configuration parameter :js:attr:`unuse_module_paths` general configuration parameter
================================== ==================


.. envvar:: RFM_USE_LOGIN_SHELL

Use a login shell for the generated job scripts.
Expand Down
24 changes: 19 additions & 5 deletions reframe/core/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,22 @@ class ModulesSystemImpl(abc.ABC):
:meta private:
'''

@abc.abstractmethod
def execute(self, cmd, *args):
'''Execute an arbitrary module command using the modules backend.

:arg cmd: The command to execute, e.g., ``load``, ``restore`` etc.
:arg args: The arguments to pass to the command.
:returns: The command output.
'''
try:
exec_output = self._execute(cmd, *args)
except SpawnedProcessError as e:
raise EnvironError('could not execute module operation') from e

return exec_output

@abc.abstractmethod
def _execute(self, cmd, *args):
'''Execute an arbitrary command of the module system.'''

@abc.abstractmethod
Expand Down Expand Up @@ -539,7 +553,7 @@ def version(self):
def modulecmd(self, *args):
return ' '.join(['modulecmd', 'python', *args])

def execute(self, cmd, *args):
def _execute(self, cmd, *args):
modulecmd = self.modulecmd(cmd, *args)
completed = osext.run_command(modulecmd)
if re.search(r'ERROR', completed.stderr) is not None:
Expand Down Expand Up @@ -661,7 +675,7 @@ def name(self):
def modulecmd(self, *args):
return ' '.join([self._command, *args])

def execute(self, cmd, *args):
def _execute(self, cmd, *args):
modulecmd = self.modulecmd(cmd, *args)
completed = osext.run_command(modulecmd)
if re.search(r'ERROR', completed.stderr) is not None:
Expand Down Expand Up @@ -722,7 +736,7 @@ def name(self):
def modulecmd(self, *args):
return ' '.join(['modulecmd', 'python', *args])

def execute(self, cmd, *args):
def _execute(self, cmd, *args):
modulecmd = self.modulecmd(cmd, *args)
completed = osext.run_command(modulecmd, check=False)
namespace = {}
Expand Down Expand Up @@ -883,7 +897,7 @@ def loaded_modules(self):
def conflicted_modules(self, module):
return []

def execute(self, cmd, *args):
def _execute(self, cmd, *args):
return ''

def load_module(self, module):
Expand Down
34 changes: 34 additions & 0 deletions reframe/frontend/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ def main():
help='Unload module MOD before running any regression check',
envvar='RFM_UNLOAD_MODULES ,', configvar='general/unload_modules'
)
env_options.add_argument(
'--module-path', action='append', metavar='PATH',
dest='module_paths', default=[],
help='(Un)use module path PATH before running any regression check',
)
env_options.add_argument(
'--purge-env', action='store_true', dest='purge_env', default=False,
help='Unload all modules before running any regression check',
Expand Down Expand Up @@ -733,6 +738,35 @@ def print_infoline(param, value):
printer.debug(str(e))
raise

printer.debug('(Un)using module paths from command line')
for d in options.module_paths:
if d.startswith('-'):
try:
rt.modules_system.searchpath_remove(d[1:])
except errors.EnvironError as e:
printer.warning(
f'could not remove module path {d} correctly; '
f'skipping...'
)
printer.verbose(str(e))
elif d.startswith('+'):
try:
rt.modules_system.searchpath_add(d[1:])
except errors.EnvironError as e:
printer.warning(
f'could not add module path {d} correctly; '
f'skipping...'
)
printer.verbose(str(e))
else:
# Here we make sure that we don't try to remove an empty path
# from the searchpath
searchpath = [p for p in rt.modules_system.searchpath if p]
if searchpath:
rt.modules_system.searchpath_remove(*searchpath)

rt.modules_system.searchpath_add(d)

printer.debug('Loading user modules from command line')
for m in site_config.get('general/0/user_modules'):
try:
Expand Down
53 changes: 53 additions & 0 deletions unittests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,59 @@ def test_unload_module(run_reframe, user_exec_ctx):
assert returncode == 0


def test_unuse_module_path(run_reframe, user_exec_ctx, monkeypatch):
ms = rt.runtime().modules_system
if ms.name == 'nomod':
pytest.skip('no modules system found')

module_path = 'unittests/modules'
monkeypatch.setenv('MODULEPATH', module_path)
returncode, stdout, stderr = run_reframe(
more_options=[f'--module-path=-{module_path}', '--module=testmod_foo'],
config_file=fixtures.USER_CONFIG_FILE, action='run',
system=rt.runtime().system.name
)
assert "could not load module 'testmod_foo' correctly" in stdout
assert 'Traceback' not in stderr
assert returncode == 0


def test_use_module_path(run_reframe, user_exec_ctx):
ms = rt.runtime().modules_system
if ms.name == 'nomod':
pytest.skip('no modules system found')

module_path = 'unittests/modules'
returncode, stdout, stderr = run_reframe(
more_options=[f'--module-path=+{module_path}', '--module=testmod_foo'],
config_file=fixtures.USER_CONFIG_FILE, action='run',
system=rt.runtime().system.name
)

assert 'Traceback' not in stdout
assert 'Traceback' not in stderr
assert "could not load module 'testmod_foo' correctly" not in stdout
assert returncode == 0


def test_overwrite_module_path(run_reframe, user_exec_ctx):
ms = rt.runtime().modules_system
if ms.name == 'nomod':
pytest.skip('no modules system found')

module_path = 'unittests/modules'
returncode, stdout, stderr = run_reframe(
more_options=[f'--module-path={module_path}', '--module=testmod_foo'],
config_file=fixtures.USER_CONFIG_FILE, action='run',
system=rt.runtime().system.name
)

assert 'Traceback' not in stdout
assert 'Traceback' not in stderr
assert "could not load module 'testmod_foo' correctly" not in stdout
assert returncode == 0


def test_failure_stats(run_reframe):
returncode, stdout, stderr = run_reframe(
checkpath=['unittests/resources/checks/frontend_checks.py'],
Expand Down
4 changes: 2 additions & 2 deletions unittests/test_environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import reframe.core.environments as env
import reframe.core.runtime as rt
import unittests.fixtures as fixtures
from reframe.core.exceptions import (EnvironError, SpawnedProcessError)
from reframe.core.exceptions import EnvironError


@pytest.fixture
Expand Down Expand Up @@ -275,7 +275,7 @@ def test_emit_loadenv_failure(user_runtime):

# Suppress the module load error and verify that the original environment
# is preserved
with contextlib.suppress(SpawnedProcessError):
with contextlib.suppress(EnvironError):
rt.emit_loadenv_commands(environ)

assert rt.snapshot() == snap
8 changes: 3 additions & 5 deletions unittests/test_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
import reframe.utility as util
import reframe.utility.osext as osext
import unittests.fixtures as fixtures
from reframe.core.exceptions import (ConfigError,
EnvironError,
SpawnedProcessError)
from reframe.core.exceptions import (ConfigError, EnvironError)
from reframe.core.runtime import runtime


Expand Down Expand Up @@ -80,7 +78,7 @@ def test_module_load(modules_system):
modules_system.load_module('foo')
modules_system.unload_module('foo')
else:
with pytest.raises(SpawnedProcessError):
with pytest.raises(EnvironError):
modules_system.load_module('foo')

assert not modules_system.is_module_loaded('foo')
Expand Down Expand Up @@ -307,7 +305,7 @@ def loaded_modules(self):
def conflicted_modules(self, module):
return []

def execute(self, cmd, *args):
def _execute(self, cmd, *args):
return ''

def load_module(self, module):
Expand Down