Skip to content

Commit 2e8a8f4

Browse files
author
Vasileios Karakasis
authored
Merge branch 'master' into bugfix/unittest_module_cleanup
2 parents 75dde7f + 4b35b02 commit 2e8a8f4

File tree

8 files changed

+165
-36
lines changed

8 files changed

+165
-36
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
runs-on: ubuntu-latest
3838
strategy:
3939
matrix:
40-
modules-version: [Lmod, Tmod32, Tmod4]
40+
modules-version: [Lmod, Lmod77, Tmod32, Tmod4]
4141
steps:
4242
- uses: actions/checkout@v2
4343
- name: Build Image for ${{ matrix.modules-version }}

ci-scripts/ci-runner.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ if [ "X${MODULEUSE}" != "X" ]; then
126126
fi
127127

128128
parallel_opts="--workers=auto --forked"
129-
if [[ $(hostname) =~ tsa ]]; then
129+
if [[ $(hostname) =~ tsa|uan ]]; then
130130
parallel_opts=""
131131
fi
132132

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#
2+
# Execute this from the top-level ReFrame source directory
3+
#
4+
5+
#
6+
# LMod versions prior to 8.2 emitted Python commands differently, so we use this
7+
# Dockerfile to test the bindings of older versions
8+
#
9+
10+
11+
FROM ubuntu:20.04
12+
13+
ENV TZ=Europe/Zurich
14+
ENV DEBIAN_FRONTEND=noninteractive
15+
ENV _LMOD_VER=7.7
16+
17+
# ReFrame user
18+
RUN useradd -ms /bin/bash rfmuser
19+
20+
# ReFrame requirements
21+
RUN \
22+
apt-get -y update && \
23+
apt-get -y install ca-certificates && \
24+
update-ca-certificates && \
25+
apt-get -y install gcc && \
26+
apt-get -y install make && \
27+
apt-get -y install git && \
28+
apt-get -y install python3 python3-pip
29+
30+
# Required utilities
31+
RUN apt-get -y install wget
32+
33+
# Install Lmod
34+
RUN \
35+
apt-get -y install lua5.3 lua-bit32:amd64 lua-posix:amd64 lua-posix-dev liblua5.3-0:amd64 liblua5.3-dev:amd64 tcl tcl-dev tcl8.6 tcl8.6-dev:amd64 libtcl8.6:amd64 lua-filesystem:amd64 lua-filesystem-dev:amd64 && \
36+
wget -q https://github.com/TACC/Lmod/archive/${_LMOD_VER}.tar.gz -O lmod.tar.gz && \
37+
tar xzf lmod.tar.gz && \
38+
cd Lmod-${_LMOD_VER} && \
39+
./configure && make install
40+
41+
ENV BASH_ENV=/usr/local/lmod/lmod/init/profile
42+
43+
USER rfmuser
44+
45+
# Install ReFrame from the current directory
46+
COPY --chown=rfmuser . /home/rfmuser/reframe/
47+
48+
WORKDIR /home/rfmuser/reframe
49+
50+
RUN ./bootstrap.sh
51+
52+
CMD ["/bin/bash", "-c", "./test_reframe.py --rfm-user-config=ci-scripts/configs/lmod.py -v"]

cscs-checks/apps/gromacs/gromacs_check.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
# SPDX-License-Identifier: BSD-3-Clause
55

66
import contextlib
7-
import itertools
87
import os
98

109
import reframe as rfm
@@ -100,27 +99,37 @@ def __init__(self, scale, variant):
10099
class GromacsCPUCheck(GromacsBaseCheck):
101100
def __init__(self, scale, variant):
102101
super().__init__('md.log')
103-
self.valid_systems = ['daint:mc']
102+
self.valid_systems = ['daint:mc', 'eiger:mc']
104103
self.descr = 'GROMACS CPU check'
105104
self.executable_opts = ['mdrun', '-dlb yes', '-ntomp 1', '-npme -1',
106105
'-nb cpu', '-s herflat.tpr']
107106

108107
if scale == 'small':
109108
self.valid_systems += ['dom:mc']
110-
self.num_tasks = 216
111-
self.num_tasks_per_node = 36
109+
if (self.current_system.name in ['daint', 'dom']):
110+
self.num_tasks = 216
111+
self.num_tasks_per_node = 36
112+
elif (self.current_system.name in ['eiger']):
113+
self.num_tasks = 768
114+
self.num_tasks_per_node = 128
112115
else:
113-
self.num_tasks = 576
114-
self.num_tasks_per_node = 36
116+
if (self.current_system.name in ['daint', 'dom']):
117+
self.num_tasks = 576
118+
self.num_tasks_per_node = 36
119+
elif (self.current_system.name in ['eiger']):
120+
self.num_tasks = 2048
121+
self.num_tasks_per_node = 128
115122

116123
references = {
117124
'prod': {
118125
'small': {
119126
'dom:mc': {'perf': (40.0, -0.05, None, 'ns/day')},
120-
'daint:mc': {'perf': (38.8, -0.10, None, 'ns/day')}
127+
'daint:mc': {'perf': (38.8, -0.10, None, 'ns/day')},
128+
'eiger:mc': {'perf': (103.00, -0.10, None, 'ns/day')}
121129
},
122130
'large': {
123-
'daint:mc': {'perf': (68.0, -0.20, None, 'ns/day')}
131+
'daint:mc': {'perf': (68.0, -0.20, None, 'ns/day')},
132+
'eiger:mc': {'perf': (146.00, -0.20, None, 'ns/day')}
124133
}
125134
},
126135
}

reframe/core/logging.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,10 @@ def _create_file_handler(site_config, config_prefix):
247247

248248

249249
def _create_filelog_handler(site_config, config_prefix):
250-
basedir = os.path.abspath(site_config.get(f'{config_prefix}/basedir'))
251-
prefix = site_config.get(f'{config_prefix}/prefix')
250+
basedir = os.path.abspath(
251+
osext.expandvars(site_config.get(f'{config_prefix}/basedir'))
252+
)
253+
prefix = osext.expandvars(site_config.get(f'{config_prefix}/prefix'))
252254
filename_patt = os.path.join(basedir, prefix)
253255
append = site_config.get(f'{config_prefix}/append')
254256
return MultiFileHandler(filename_patt, mode='a+' if append else 'w+')

reframe/core/modules.py

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,21 @@ def emit_load_instr(self, module):
492492
def emit_unload_instr(self, module):
493493
'''Emit the instruction that unloads module.'''
494494

495+
def process(self, source):
496+
'''Process the Python source emitted by the Python bindings of the
497+
different backends.
498+
499+
Backends should call this before executing any Python commands.
500+
501+
:arg source: The Python source code to be executed.
502+
:returns: The modified Python source code to be executed. By default
503+
``source`` is returned unchanged.
504+
505+
.. versionadded:: 3.4
506+
507+
'''
508+
return source
509+
495510
def __repr__(self):
496511
return type(self).__name__ + '()'
497512

@@ -562,7 +577,7 @@ def _execute(self, cmd, *args):
562577
completed.stderr,
563578
completed.returncode)
564579

565-
exec(completed.stdout)
580+
exec(self.process(completed.stdout))
566581
return completed.stderr
567582

568583
def available_modules(self, substr):
@@ -692,7 +707,7 @@ def _execute(self, cmd, *args):
692707
with open(exec_match.group(1), 'r') as content_file:
693708
cmd = content_file.read()
694709

695-
exec(cmd)
710+
exec(self.process(cmd))
696711
return completed.stderr
697712

698713

@@ -729,6 +744,7 @@ def __init__(self):
729744
(version, self.MIN_VERSION))
730745

731746
self._version = version
747+
self._extra_module_paths = []
732748

733749
def name(self):
734750
return 'tmod4'
@@ -740,7 +756,7 @@ def _execute(self, cmd, *args):
740756
modulecmd = self.modulecmd(cmd, *args)
741757
completed = osext.run_command(modulecmd, check=False)
742758
namespace = {}
743-
exec(completed.stdout, {}, namespace)
759+
exec(self.process(completed.stdout), {}, namespace)
744760

745761
# _mlstatus is set by the TMod4 only if the command was unsuccessful,
746762
# but Lmod sets it always
@@ -755,6 +771,15 @@ def _execute(self, cmd, *args):
755771
def load_module(self, module):
756772
if module.collection:
757773
self.execute('restore', str(module))
774+
775+
# Here the module search path removal/addition is repeated since
776+
# 'restore' discards previous module path manipulations
777+
for op, mp in self._extra_module_paths:
778+
if op == '+':
779+
super().searchpath_add(mp)
780+
else:
781+
super().searchpath_remove(mp)
782+
758783
return []
759784
else:
760785
return super().load_module(module)
@@ -777,7 +802,15 @@ def conflicted_modules(self, module):
777802

778803
def emit_load_instr(self, module):
779804
if module.collection:
780-
return f'module restore {module}'
805+
cmds = [f'module restore {module}']
806+
807+
# Here we append module searchpath removal/addition commands
808+
# since 'restore' discards previous module path manipulations
809+
for op, mp in self._extra_module_paths:
810+
operation = 'use' if op == '+' else 'unuse'
811+
cmds += [f'module {operation} {mp}']
812+
813+
return '\n'.join(cmds)
781814

782815
return super().emit_load_instr(module)
783816

@@ -787,6 +820,18 @@ def emit_unload_instr(self, module):
787820

788821
return super().emit_unload_instr(module)
789822

823+
def searchpath_add(self, *dirs):
824+
if dirs:
825+
self._extra_module_paths += [('+', mp) for mp in dirs]
826+
827+
super().searchpath_add(*dirs)
828+
829+
def searchpath_remove(self, *dirs):
830+
if dirs:
831+
self._extra_module_paths += [('-', mp) for mp in dirs]
832+
833+
super().searchpath_remove(*dirs)
834+
790835

791836
class LModImpl(TMod4Impl):
792837
'''Module system for Lmod (Tcl/Lua).'''
@@ -821,9 +866,22 @@ def __init__(self):
821866
raise ConfigError('Python is not supported by '
822867
'this Lmod installation')
823868

869+
self._extra_module_paths = []
870+
824871
def name(self):
825872
return 'lmod'
826873

874+
def process(self, source):
875+
major, minor, *_ = self.version().split('.')
876+
major, minor = int(major), int(minor)
877+
if (major, minor) < (8, 2):
878+
# Older Lmod versions do not emit an `import os` and emit an
879+
# invalid `false` statement in case of errors; we fix these here
880+
return 'import os\n\n' + source.replace('false',
881+
'_mlstatus = False')
882+
883+
return source
884+
827885
def modulecmd(self, *args):
828886
return ' '.join([self._lmod_cmd, 'python', *args])
829887

@@ -865,20 +923,6 @@ def conflicted_modules(self, module):
865923

866924
return ret
867925

868-
def load_module(self, module):
869-
if module.collection:
870-
self.execute('restore', str(module))
871-
return []
872-
else:
873-
return super().load_module(module)
874-
875-
def unload_module(self, module):
876-
if module.collection:
877-
# Module collection are not unloaded
878-
return
879-
880-
super().unload_module(module)
881-
882926
def unload_all(self):
883927
# Currently, we don't take any provision for sticky modules in Lmod, so
884928
# we forcefully unload everything.

unittests/test_cli.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,8 +364,22 @@ def test_performance_check_failure(run_reframe, tmp_path, perflogdir):
364364
'default' / 'PerformanceFailureCheck.log')
365365

366366

367-
def test_performance_report(run_reframe):
367+
def test_perflogdir_from_env(run_reframe, tmp_path, monkeypatch):
368+
monkeypatch.setenv('FOODIR', str(tmp_path / 'perflogs'))
368369
returncode, stdout, stderr = run_reframe(
370+
checkpath=['unittests/resources/checks/frontend_checks.py'],
371+
more_options=['-t', 'PerformanceFailureCheck'],
372+
perflogdir='$FOODIR'
373+
)
374+
assert returncode == 1
375+
assert 'Traceback' not in stdout
376+
assert 'Traceback' not in stderr
377+
assert os.path.exists(tmp_path / 'perflogs' / 'generic' /
378+
'default' / 'PerformanceFailureCheck.log')
379+
380+
381+
def test_performance_report(run_reframe):
382+
returncode, stdout, _ = run_reframe(
369383
checkpath=['unittests/resources/checks/frontend_checks.py'],
370384
more_options=['-t', 'PerformanceFailureCheck', '--performance-report']
371385
)

unittests/test_modules.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,13 @@
33
#
44
# SPDX-License-Identifier: BSD-3-Clause
55

6-
import abc
76
import os
87
import pytest
98

109
import reframe.core.environments as env
1110
import reframe.core.modules as modules
12-
import reframe.utility as util
13-
import reframe.utility.osext as osext
1411
import unittests.fixtures as fixtures
15-
from reframe.core.exceptions import (ConfigError, EnvironError)
16-
from reframe.core.runtime import runtime
12+
from reframe.core.exceptions import ConfigError, EnvironError
1713

1814

1915
@pytest.fixture(params=['tmod', 'tmod4', 'lmod', 'nomod'])
@@ -130,6 +126,18 @@ def test_module_load_force_collection(modules_system, module_collection):
130126
assert modules_system.is_module_loaded('testmod_foo')
131127

132128

129+
def test_module_load_collection_searchpath(modules_system, tmpdir,
130+
module_collection):
131+
p1 = str(tmpdir.mkdir('path1'))
132+
p2 = str(tmpdir.mkdir('path2'))
133+
modules_system.searchpath_add(p1)
134+
modules_system.searchpath_add(p2)
135+
modules_system.searchpath_remove(p1)
136+
modules_system.load_module(module_collection, collection=True)
137+
assert p1 not in modules_system.searchpath
138+
assert p2 in modules_system.searchpath
139+
140+
133141
def test_module_unload_all(modules_system):
134142
if modules_system.name == 'nomod':
135143
modules_system.unload_all()

0 commit comments

Comments
 (0)