This part of the Rose user guide explains how to use the rose stem testing system, and the motivation behind it.
rose stem requires the use of FCM as it requires some of the version control information.
Why do we test code?
Most people would answer something along the lines of "so we know it works".
However, this is really asking two related but separate questions.
Answering the first question may involve writing a bespoke test and checking the results. The second question can at least partially be answered by using an automated testing system which runs predefined tasks and presents the answers. rose stem is a system for doing this.
N.B. When writing tests for new code, they should be added to the testing system so that future developers can be confident that they haven't broken the new functionality.
There are two components in rose stem:
We will describe each in turn. It is intended that a test suite lives alongside the code in the same version-controlled project, which should encourage developers to update the test suite when they update the code. This means that the test suite will always be a valid test of the code it is accompanying.
The rose stem
command is essentially
a wrapper to rose suite-run
, which
accepts some additional arguments and converts them
to Jinja2 variables which the suite can
interpret.
These arguments are:
--source
- specifies a source tree
to include in a suite.--group
- specifies a group of
tasks to run.A group is a set of Rose tasks which together test a certain configuration of a program.
--source
argumentThe source argument provides a set of Jinja2
variables which can then be included in any
compilation tasks in a suite. You can specify
multiple --source
arguments on the
command line. For example:
rose stem --source=/path/to/workingcopy --source=fcm:other_project_tr@head
Each source tree is associated with a project (via an
fcm command) when rose stem
is run
on the command line. This project name is then used in the
construction of the Jinja2 variable names.
Each project has a Jinja2 variable SOURCE_FOO
where FOO is the project name. This contains a space-separated
list of all sourcetrees belonging to that project, which can
then be given to an appropriate build task in the suite so it
builds those source trees.
Similarly, a HOST_SOURCE_FOO
variable is also
provided. This is identical to SOURCE_FOO
except
any working copies have the local hostname prepended. This is
to assist building on remote machines.
The first source specified must be a working copy which
contains the rose stem suite. The suite is
expected to be in a subdirectory named rose-stem
off the top of the working copy. This source is used to
generate three additional variables:
SOURCE_FOO_BASE
- the base directory of
the projectHOST_SOURCE_FOO_BASE
- the base directory
of the project with the hostname prepended if it is a
working copySOURCE_FOO_REV
- the revision of the
project (if any)These settings override the variables in the
rose-suite.conf
file.
These should allow the use of configuration files which control the build process inside the working copy, e.g. you can refer to:
{{HOST_SOURCE_FOO_BASE}}/fcm-make/configs/machine.cfg{{SOURCE_FOO_REV}}
If you omit the source argument, rose stem defaults to assuming
rose stem --source=.
viz. that the current directory is part of the working copy which should be added as a source tree.
The project to which a source tree belongs is
normally automatically determined using FCM commands.
However, in the case where the source tree is not a
valid FCM URL, or where you wish to assign it to
another project, you can specify this using the
--source
argument:
rose stem --source=foo=/path/to/source
assigns the URL /path/to/source
to
the foo project, so the variables
SOURCE_FOO and
SOURCE_FOO_BASE will be set to
/path/to/source
.
--group
argumentThe group argument is used to provide a Pythonic list of groups in the variable RUN_NAMES which can then be looped over in a suite to switch sets of tasks on and off.
Each --group
argument adds another
group to the list. For example:
rose stem --group=mygroup --group=myothergroup
runs two groups named mygroup and myothergroup with the current working copy. The suite will then interpret these into a set of tasks which build with the given source tree(s), run the program, and compare the output.
--task
argumentThe --task
argument is provided as a
synonym for --group
. Depending on how
exactly the rose stem suite works users
may find one of these arguments more intuitive to use
than the other.
Any task beginning with rose_ana_ will be interpreted by Rose as a rose ana task, and run through the rose ana task app.
A rose ana rose-app.conf
file contains a series of blocks; each one describing
a different analysis task to perform. A common task
which rose ana is used for is to compare
output contained in different files (e.g. from a new test
versus previous output from a control). The analysis
modules which provide these tasks are flexible and able
to be provided by the user; however there is one built-in
module inside rose ana itself.
An example based on the built-in grepper
module:
[ana:grepper.FilePattern(Compare data from myfile)] pattern='data value:(\d+)' files=/data/kgo/myfile =../run.1/myfile
This tells rose ana to scan the
contents of the file ../run.1/myfile
(which is relative to the rose ana
task's work
directory) and the contents
of /data/kgo/myfile
for the specified regular
expression. Since the pattern contains a group (in parentheses)
so it is the contents of this group which will be compared
between the two files. The grepper.FilePattern
analysis task can optionally be given a "tolerance" option for
matching numeric values, but without it the matching is expected
to be exact. If the pattern or group contents do not match the
task will return a failure.
As well as sections defining analysis tasks, rose_ana apps allow for one additional section for storing global configuration settings for the app. Just like the tasks themselves these options and their effects are dependent on which analysis tasks are used by the app.
Therefore we will here present an example using the built-in
grepper
class. An app may begin with a section like
this:
[ana:config] grepper-report-limit=5 skip-if-all-files-missing=.true.
Each of these modifies the behaviour of grepper
.
The first option suppresses printed output for each analysis task
once the specified number of lines have been printed (in this case
5 lines). The second option causes rose_ana to skip
any grepper
tasks which compare files in the case that
both files do not exist.
Note that any options given to this section may instead be
specified in the rose-ana
section of the user or site
configuration. In the case that the same configuration option
appears in both locations the one contained in the app file will
take precedence.
It is possible to add additional analysis modules to rose ana by placing an appropriately formatted python file in one of the following places (in order of precedence):
ana
sub-directory of the
rose ana
appana
sub-directory of the
suiterose ana
and is specified in the
method-path
variable in the
rose ana
section of the
rose.conf
file.The only module provided with Rose can be found at
lib/python/rose/apps/ana_builtin/grepper.py
and it
provides the following analysis tasks and options:
%
it will be interpreted
as a relative tolerance (otherwise absolute).The grepper
analysis module also supports some
configuration options (specified in the [ana:config]
section of the app or in the [rose-ana]
section of
the Rose site/user configuration:
.true.
or .false.
; if active, any
comparison done on files by grepper
will be
skipped if all of those files are non-existent. In
this case the task will return as "skipped" rather than
passed/failed.The format for analysis modules themselves is relatively
simple; the easiest route to understanding how they should be
arranged is likely to look at the built-in grepper
module. But the key concepts are as follows. To be recognised
as a valid analysis module, the Python file must contain at least
one class which inherits and extends
rose.apps.rose_ana.AnalysisTask
, which will
automatically populate itself with certain attributes; e.g:
from rose.apps.rose_ana import AnalysisTask class CustomAnalysisTask(AnalysisTask): """My new custom analysis task.""" def run_analysis(self): print self.options # Dictionary of options (see next slide) if self.options["option1"] == "5": self.passed = True
Assuming the above was saved in a file called
custom.py
and placed into a folder suitable for
analysis modules this would allow a rose ana
app
to specify:
[ana:custom.CustomAnalysisTask(Example rose-ana test)] option1 = 5 option2 = test of rose ana option3 = .true.
Note that the custom
part of the filename appears
at the start of the ana
entry, followed by the name
of the desired class (in the style of Python's own namespacing).
All options specified by the app-task will be processed by
rose ana
into a dictionary and attached to the
running analysis class instance as the options
attribute. Hopefully you can see that in this case the task would
pass because option1
is set to 5
as required by the class.
The parent AnalysisTask
class will also set
several other attributes which may be of use when designing a
new method:
rose.reporter.Reporter
instance used by the parent
app (for printing to stderr/stdout)rose.popen.RosePopener
instance used by the parent
app (for spawning subprocesses)The parent class also provides the self.passed and
self.skipped methods, which will default to
False
but should be set to True
by the
task to indicate how rose ana should proceed. (Note
that the skipped state is only checked if the passed
state is False
.
In addition to performing the comparisons each of
the rose ana
tasks in the suite can be configured
to append some key details about any comparisons performed to an
sqlite
database kept in the suite's
log
directory (at
log/rose-ana-comparisons.db
).
This is intended to provide a quick means to interrogate the suite for information about the status of any comparisons it has performed. There are 2 tables present in the suite which contain the following:
rose ana
task, using the
following columns:
rose ana
task.rose ana
tasks have failed
unexpectedly (or are still running).
rose
ana
task, using the following columns:
The database is entirely optional; by default is will
not be produced; if it is required it can be activated by
setting the kgo-database
option in the
rose-ana
section of the Rose configuration
to .true.
.
Note that the system does not provide any direct methods for working with or interrogating the database - since there could be various reasons for doing so, and there may be other suite-design factors to consider. Users are therefore expected to provide this functionality separately based on their specific needs.
From within a working copy, running rose stem is simple. Just run
rose stem --group=groupnamereplacing the groupname with the desired task. rose stem should then automatically pick up the working copy and run the requested tests on it.
For further information about writing your own rose stem test suite, please see the advanced tutorial on the subject.