Skip to content

Commit d2e32d6

Browse files
author
Theofilos Manitaras
committed
Reframe 2.9 public release
1 parent 92c03e6 commit d2e32d6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1869
-3029
lines changed

docs/advanced.rst

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ In this example, we show how ReFrame can leverage Makefiles to build executables
1515
Compiling a regression test through a Makefile is very straightforward with ReFrame.
1616
If the :attr:`sourcepath <reframe.core.pipeline.RegressionTest.sourcepath>` attribute refers to a directory, then ReFrame will automatically invoke ``make`` there.
1717

18-
.. note:: More specifically, ReFrame constructs the final target source path as ``os.path.join(self.sourcesdir, self.sourcepath)``
18+
.. note:: More specifically, ReFrame will compile the source files found in the directory that is constructed as ``os.path.join(self.sourcesdir, self.sourcepath)`` (given that :attr:`sourcesdir` is defined, as it is usually the case).
19+
20+
If :attr:`sourcesdir` is not defined, ReFrame assumes that the user will make sure that the source files will be in the stagedir (:attr:`_stagedir`) at the moment of compilation (more information on the stagedir directory is found in `"Running ReFrame" <running.html#configuring-reframe-directories>`__ section).
21+
The user may for instance generate the source files or copy them from a git repository by the means of some commands defined in :attr:`prebuild_cmd`.
22+
Thus, ReFrame will in this case compile the sources files found in the directory that is constructed as ``os.path.join(self._stagedir, self.sourcepath)``.
1923

2024
By default, :attr:`sourcepath <reframe.core.pipeline.RegressionTest.sourcepath>` is the empty string and :attr:`sourcesdir <reframe.core.pipeline.RegressionTest.sourcesdir>` is set ``src/``.
2125
As a result, by not specifying a :attr:`sourcepath <reframe.core.pipeline.RegressionTest.sourcepath>` at all, ReFrame will try to invoke ``make`` inside the ``src/`` directory of the test.
@@ -135,13 +139,15 @@ Here is the full regression test (``tutorial/advanced/advanced_example2.py``):
135139
'RunOnlyRegressionTest')
136140
self.valid_systems = ['*']
137141
self.valid_prog_environs = ['*']
142+
self.sourcesdir = None
143+
138144
lower = 90
139145
upper = 100
140-
self.executable = 'echo $((RANDOM%({1}+1-{0})+{0}))'.format(
146+
self.executable = 'echo "Random: $((RANDOM%({1}+1-{0})+{0}))"'.format(
141147
lower, upper)
142148
self.sanity_patterns = sn.assert_bounded(sn.extractsingle(
143-
r'(?P<number>\S+)', self.stdout, 'number', float), lower, upper)
144-
149+
r'Random: (?P<number>\S+)', self.stdout, 'number', float),
150+
lower, upper)
145151
self.maintainers = ['put-your-name-here']
146152
self.tags = {'tutorial'}
147153
@@ -152,6 +158,8 @@ Here is the full regression test (``tutorial/advanced/advanced_example2.py``):
152158
There is nothing special for this test compared to those presented `earlier <tutorial.html>`__ except that it derives from the :class:`RunOnlyRegressionTest <reframe.core.pipeline.RunOnlyRegressionTest>`.
153159
A thing to note about run-only regression tests is that the copying of their resources to the stage directory is performed at the beginning of the run phase.
154160
For standard regression tests, this happens at the beginning of the compilation phase, instead.
161+
Furthermore, in this particular test the :attr:`executable <reframe.core.pipeline.RegressionTest.executable>` consists only of standard Bash shell commands.
162+
For this reason, we can set :attr:`sourcesdir <reframe.core.pipeline.RegressionTest.sourcesdir>` to ``None`` informing ReFrame that the test does not have any resources.
155163

156164
Implementing a Compile-Only Regression Test
157165
-------------------------------------------
@@ -348,3 +356,114 @@ The sanity condition for this test verifies that associated job has been cancele
348356
.. code-block:: python
349357
350358
self.sanity_patterns = sn.assert_found('CANCELLED.*TIME LIMIT', self.stderr)
359+
360+
Applying a sanity function iteratively
361+
--------------------------------------
362+
363+
It is often the case that a common sanity pattern has to be applied many times.
364+
In this example we will demonstrate how the above situation can be easily tackled using the :mod:`sanity <reframe.utility.sanity>` functions offered by ReFrame.
365+
Specifically, we would like to execute the following shell script and check that its output is correct:
366+
367+
.. code-block:: bash
368+
369+
#!/usr/bin/env bash
370+
371+
for i in {1..100}
372+
do
373+
echo Random: $((RANDOM%($UPPER+1-$LOWER)+$LOWER))
374+
done
375+
376+
The above script simply prints 100 random integers between the limits given by the variables ``LOWER`` and ``UPPER``.
377+
For this example the above limits are exported as environment variables by the ``limits.sh`` script as follows:
378+
379+
.. code-block:: bash
380+
381+
#!/usr/bin/env bash
382+
383+
export LOWER=90
384+
export UPPER=100
385+
386+
In the corresponding regression test we want to check that all the random numbers printed lie between 90 and 100 ensuring that the script executed correctly.
387+
Hence, a common sanity check has to be applied to all the printed random numbers.
388+
In ReFrame this can achieved by the use of :func:`map <reframe.utility.sanity.map>` sanity function accepting a function and an iterable as arguments.
389+
Through :func:`map <reframe.utility.sanity.map>` the given function will be applied to all the members of the iterable object.
390+
Note that since :func:`map <reframe.utility.sanity.map>` is a sanity function, its execution will be deferred.
391+
The contents of the ReFrame regression test contained in ``advanced_example6.py`` are the following:
392+
393+
.. code-block:: python
394+
395+
import os
396+
397+
import reframe.utility.sanity as sn
398+
from reframe.core.pipeline import RunOnlyRegressionTest
399+
400+
401+
class DeferredIterationTest(RunOnlyRegressionTest):
402+
def __init__(self, **kwargs):
403+
super().__init__('deferred_iteration_check',
404+
os.path.dirname(__file__), **kwargs)
405+
406+
self.descr = ('ReFrame tutorial demonstrating the use of deferred '
407+
'iteration via the `map` sanity function.')
408+
409+
self.valid_systems = ['*']
410+
self.valid_prog_environs = ['*']
411+
412+
self.executable = './advanced_example6.sh'
413+
numbers = sn.extractall(r'Random: (?P<number>\S+)', self.stdout,
414+
'number', float)
415+
416+
self.sanity_patterns = sn.and_(
417+
sn.assert_eq(sn.count(numbers), 100),
418+
sn.all(sn.map(lambda x: sn.assert_bounded(x, 90, 100), numbers)))
419+
420+
self.maintainers = ['put-your-name-here']
421+
self.tags = {'tutorial'}
422+
423+
def setup(self, partition, environ, **job_opts):
424+
super().setup(partition, environ, **job_opts)
425+
self.job.pre_run = ['source %s/limits.sh' % self.stagedir]
426+
427+
428+
def _get_checks(**kwargs):
429+
return [DeferredIterationTest(**kwargs)]
430+
431+
First the random numbers are extracted through the :func:`extractall <reframe.utility.sanity.extractall>` function as follows:
432+
433+
.. code-block:: python
434+
435+
numbers = sn.extractall(r'Random: (?P<number>\S+)', self.stdout,
436+
'number', float)
437+
438+
The ``numbers`` variable is a deferred iterable, which upon evaluation will return all the extracted numbers.
439+
In order to check that the extracted numbers lie within the specified limits, we make use of the :func:`map <reframe.utility.sanity.map>` sanity function, which will apply the :func:`assert_bounded <reframe.utility.sanity.assert_bounded>` to all the elements of ``numbers``.
440+
Additionally, our requirement is that all the numbers satisfy the above constraint and we therefore use :func:`all <reframe.utility.sanity.all>`.
441+
442+
There is still a small complication that needs to be addressed.
443+
The :func:`all <reframe.utility.sanity.all>` function returns ``True`` for empty iterables, which is not what we want.
444+
So we must ensure that all the numbers are extracted as well.
445+
To achieve this, we make use of :func:`count <reframe.utility.sanity.count>` to get the number of elements contained in ``numbers`` combined with :func:`assert_eq <reframe.utility.sanity.assert_eq>` to check that the number is indeed 100.
446+
Finally, both of the above conditions have to be satisfied for the program execution to be considered successful, hence the use of the :func:`and_ <reframe.utility.sanity.and_>` function.
447+
Note that the ``and`` operator is not deferrable and will trigger the evaluation of any deferrable argument passed to it.
448+
449+
The full syntax for the :attr:`sanity_patterns` is the following:
450+
451+
.. code-block:: python
452+
453+
self.sanity_patterns = sn.and_(
454+
sn.assert_eq(sn.count(numbers), 100),
455+
sn.all(sn.map(lambda x: sn.assert_bounded(x, 90, 100), numbers)))
456+
457+
Note that the environment variables ``LOWER`` and ``UPPER`` have to be exported before execution of the ``advanced_example6.sh`` script.
458+
Within ReFrame it is possible to define commands that will be run before execution of the actual :attr:`executable <reframe.core.pipeline.RegressionTest.executable>`.
459+
To achieve this, the :func:`setup <reframe.core.pipeline.RegressionTest.setup>` method has to be overriden to access the :attr:`pre_run <reframe.core.schedulers.Job.pre_run>` field of the corresponding job.
460+
In this particular case, the setup implementation is written as:
461+
462+
.. code-block:: python
463+
464+
def setup(self, partition, environ, **job_opts):
465+
super().setup(partition, environ, **job_opts)
466+
self.job.pre_run = ['source %s/limits.sh' % self.stagedir]
467+
468+
The :attr:`pre_run <reframe.core.schedulers.Job.pre_run>` attribute is a list of shell commands to be emitted verbatim in the generated job script before the executable.
469+
In this case, we make sure that the ``limits.sh`` file is sourced before executing the ``advanced_example6.sh`` script.

docs/conf.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import sphinx_rtd_theme
2525

2626
sys.path.insert(0, os.path.abspath('..'))
27+
import reframe
28+
2729
# -- General configuration ------------------------------------------------
2830

2931
# If your documentation needs a minimal Sphinx version, state it here.
@@ -69,9 +71,9 @@
6971
# built documents.
7072
#
7173
# The short X.Y version.
72-
version = '2.8.1'
74+
version = reframe.VERSION
7375
# The full version, including alpha/beta/rc tags.
74-
release = '2.8.1'
76+
release = reframe.VERSION
7577

7678
# The language for content autogenerated by Sphinx. Refer to documentation
7779
# for a list of supported languages.

docs/configure.rst

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -170,41 +170,73 @@ The available partition attributes are the following:
170170
This option is relevant only when ReFrame executes with the `asynchronous execution policy <running.html#asynchronous-execution-of-regression-checks>`__.
171171

172172
* ``resources``: A set of custom resource specifications and how these can be requested from the partition's scheduler (default ``{}``).
173+
173174
This variable is a set of key/value pairs with the key being the resource name and the value being a list of options to be passed to the partition's job scheduler.
174-
The option strings can contain "references" to the resource being required using the syntax ``{resource_name}``.
175-
In such cases, the ``{resource_name}`` will be replaced by the value of that resource defined in the regression test that is being run. For example, one could define a :attr:`num_gpus_per_node reframe.core.pipeline.RegressionTest.num_gpus_per_node` resource for a multi-GPU system that uses Slurm as follows:
175+
The option strings can contain *placeholders* of the form ``{placeholder_name}``.
176+
These placeholders may be replaced with concrete values by a regression tests through the :attr:`extra_resources` attribute.
177+
178+
For example, one could define a ``gpu`` resource for a multi-GPU system that uses Slurm as follows:
176179

177180
.. code-block:: python
178181
179-
'resources' : {
180-
'num_gpus_per_node' : [
181-
'--gres=gpu:{num_gpus_per_node}'
182-
]
182+
'resources': {
183+
'gpu': ['--gres=gpu:{num_gpus_per_node}']
183184
}
184185
185186
A regression test then may request this resource as follows:
186187

187188
.. code-block:: python
188189
189-
self.extra_resources = {'num_gpus_per_node': '8'}
190+
self.extra_resources = {'gpu': {'num_gpus_per_node': '8'}}
190191
191-
and the generated job script will have the following line in its preamble:
192+
And the generated job script will have the following line in its preamble:
192193

193194
.. code-block:: bash
194195
195196
#SBATCH --gres=gpu:8
196197
197-
Refer to the `reference guide <reference.html#reframe.core.pipeline.RegressionTest.extra_resources>`__ for more information on the use of the ``extra_resources`` regression test attribute.
198+
A resource specification may also start with ``#PREFIX``, in which case ``#PREFIX`` will replace the standard job script prefix of the backend scheduler of this partition.
199+
This is useful in cases of job schedulers like Slurm, that allow alternative prefixes for certain features.
200+
An example is the `DataWarp <https://www.cray.com/datawarp>`__ functionality of Slurm which is supported by the ``#DW`` prefix.
201+
One could then define DataWarp related resources as follows:
198202

199-
.. note::
200-
.. versionadded:: 2.8.1
201-
The ``squeue`` backend scheduler was added.
203+
.. code-block:: python
204+
205+
'resources': {
206+
'datawarp': [
207+
'#DW jobdw capacity={capacity} access_mode={mode} type=scratch'
208+
'#DW stage_out source={out_src} destination={out_dst} type={stage_filetype}'
209+
]
210+
}
211+
212+
A regression test that wants to make use of that resource, it can set its :attr:`extra_resources` as follows:
213+
214+
.. code-block:: python
215+
216+
self.extra_resources = {
217+
'datawarp': {
218+
'capacity': '100GB',
219+
'mode': 'striped',
220+
'out_src': '$DW_JOB_STRIPED/name',
221+
'out_dst': '/my/file',
222+
'stage_filetype': 'file'
223+
}
224+
}
202225
203226
.. note::
204227
.. versionchanged:: 2.8
205228
A new syntax for the ``scheduler`` values was introduced as well as more parallel program launchers.
206229
The old values for the ``scheduler`` key will continue to be supported.
207230

231+
.. note::
232+
.. versionadded:: 2.8.1
233+
The ``squeue`` backend scheduler was added.
234+
235+
.. note::
236+
.. versionchanged:: 2.9
237+
Better support for custom job resources.
238+
239+
208240
Environments Configuration
209241
--------------------------
210242

docs/running.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,12 @@ ReFrame uses three basic directories during the execution of tests:
340340
1. The stage directory
341341

342342
* Each regression test is executed in a "sandbox";
343-
all of its resources (source files, resources) are copied over to a stage directory and executed from there.
343+
all of its resources (source files, resources) are copied over to a stage directory (if the directory preexists, it will be wiped out) and executed from there.
344344
This will also be the working directory for the test.
345345

346346
2. The output directory
347347

348-
* After a regression test finishes some important files will be copied from the stage directory to the output directory.
348+
* After a regression test finishes some important files will be copied from the stage directory to the output directory (if the directory preexists, it will be wiped out).
349349
By default these are the standard output, standard error and the generated job script file.
350350
A regression test may also specify to keep additional files.
351351

reframe/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
import subprocess
88
import sys
99

10-
required_version = (3, 5, 0)
10+
11+
VERSION = '2.9'
12+
_required_pyver = (3, 5, 0)
1113

1214
# Check python version
13-
if sys.version_info[:3] < required_version:
15+
if sys.version_info[:3] < _required_pyver:
1416
sys.stderr.write('Unsupported Python version: '
15-
'Python >= %d.%d.%d is required\n' % required_version)
17+
'Python >= %d.%d.%d is required\n' % _required_pyver)
1618
sys.exit(1)

0 commit comments

Comments
 (0)