Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/config_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1007,9 +1007,9 @@ The additional properties for the ``filelog`` handler are the following:
{basedir}/
system1/
partition1/
test_name.log
test_short_name.log
partition2/
test_name.log
test_short_name.log
...
system2/
...
Expand Down
54 changes: 37 additions & 17 deletions docs/manpage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ This happens recursively so that if test ``T1`` depends on ``T2`` and ``T2`` dep

If the special notation ``<test_name>@<variant_num>`` is passed as the ``NAME`` argument, then an exact match will be performed selecting the variant ``variant_num`` of the test ``test_name``.

You may also select a test by its hash code using the notation ``/<test-hash>`` for the ``NAME`` argument.

.. note::

Fixtures cannot be selected.
Expand All @@ -133,6 +135,11 @@ This happens recursively so that if test ``T1`` depends on ``T2`` and ``T2`` dep

The option's behaviour was adapted and extended in order to work with the updated test naming scheme.

.. versionchanged:: 4.0.0

Support selecting tests by their hash code.


.. option:: -p, --prgenv=NAME

Filter tests by programming environment.
Expand Down Expand Up @@ -953,26 +960,26 @@ Here is how this test is listed where the various components of the display name

.. code-block:: console

- TestA %x=4 %l.foo=10 %t.p=2
^MyFixture %p=1 ~TestA_4_1
^MyFixture %p=2 ~TestA_4_1
^X %foo=10 ~generic:default+builtin
- TestA %x=3 %l.foo=10 %t.p=2
^MyFixture %p=1 ~TestA_3_1
^MyFixture %p=2 ~TestA_3_1
^X %foo=10 ~generic:default+builtin
- TestA %x=4 %l.foo=10 %t.p=1
^MyFixture %p=2 ~TestA_4_0
^MyFixture %p=1 ~TestA_4_0
^X %foo=10 ~generic:default+builtin
- TestA %x=3 %l.foo=10 %t.p=1
^MyFixture %p=2 ~TestA_3_0
^MyFixture %p=1 ~TestA_3_0
^X %foo=10 ~generic:default+builtin
- TestA %x=4 %l.foo=10 %t.p=2 /1c51609b
^Myfixture %p=1 ~TestA_3 /f027ee75
^MyFixture %p=2 ~TestA_3 /830323a4
^X %foo=10 ~generic:default+builtin /7dae3cc5
- TestA %x=3 %l.foo=10 %t.p=2 /707b752c
^MyFixture %p=1 ~TestA_2 /02368516
^MyFixture %p=2 ~TestA_2 /854b99b5
^X %foo=10 ~generic:default+builtin /7dae3cc5
- TestA %x=4 %l.foo=10 %t.p=1 /c65657d5
^MyFixture %p=2 ~TestA_1 /f0383f7f
^MyFixture %p=1 ~TestA_1 /d07f4281
^X %foo=10 ~generic:default+builtin /7dae3cc5
- TestA %x=3 %l.foo=10 %t.p=1 /1b9f44df
^MyFixture %p=2 ~TestA_0 /b894ab05
^MyFixture %p=1 ~TestA_0 /ca376ca8
^X %foo=10 ~generic:default+builtin /7dae3cc5
Found 4 check(s)

Display names may not always be unique.
In the following example:
Assume the following test:

.. code-block:: python

Expand All @@ -982,6 +989,19 @@ In the following example:
This generates three different tests with different unique names, but their display name is the same for all: ``MyTest %p=1``.
Notice that this example leads to a name conflict with the old naming scheme, since all tests would be named ``MyTest_1``.

Each test is also associated with a hash code that is derived from the test name, its parameters and their values.
As in the example listing above, the hash code of each test is printed with the :option:`-l` option and individual tests can be selected by their hash using the :option:`-n` option, e.g., ``-n /1c51609b``.
The stage and output directories, as well as the performance log file of the ``filelog`` `performance log handler <config_reference.html#the-filelog-log-handler>`__ will use the hash code for the test-specific directories and files.
This might lead to conflicts for tests as the one above when executing them with the asynchronous execution policy, but ensures consistency of performance record files when parameter values are added to or deleted from a test parameter.
More specifically, the test's hash will not change if a new parameter value is added or deleted or even if the parameter values are shuffled.
Test variants on the other side are more volatile and can change with such changes.
Also users should not rely on how the variant numbers are assigned to a test, as this is an implementation detail.


.. versionchanged:: 4.0.0

A hash code is associated with each test.


--------------------------------------
Differences from the old naming scheme
Expand Down
5 changes: 3 additions & 2 deletions reframe/core/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,9 @@ def emit(self, record):
except OSError as e:
raise LoggingError('logging failed') from e

self.baseFilename = os.path.join(dirname,
f'{record.__rfm_check__.name}.log')
self.baseFilename = os.path.join(
dirname, f'{record.__rfm_check__.short_name}.log'
)
self.stream = self._streams.get(self.baseFilename, None)
super().emit(record)
self._streams[self.baseFilename] = self.stream
Expand Down
2 changes: 2 additions & 0 deletions reframe/core/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,8 @@ def __call__(cls, *args, **kwargs):
obj._rfm_unique_name = fixt_name
obj._rfm_fixt_data = fixt_data
obj._rfm_is_fixture = True
else:
obj._rfm_unique_name = cls.variant_name(variant_num)

# Set the variables passed to the constructor
for k, v in fixt_vars.items():
Expand Down
66 changes: 49 additions & 17 deletions reframe/core/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@


import glob
import hashlib
import inspect
import itertools
import numbers
Expand Down Expand Up @@ -281,8 +282,11 @@ def pipeline_hooks(cls):
#: A detailed description of the test.
#:
#: :type: :class:`str`
#: :default: ``self.display_name``
descr = variable(str, loggable=True)
#: :default: ``''``
#:
#: .. versionchanged:: 4.0
#: The default value is now the empty string.
descr = variable(str, value='', loggable=True)

#: The path to the source file or source directory of the test.
#:
Expand Down Expand Up @@ -959,13 +963,6 @@ def __init_subclass__(cls, *, special=False, pin_prefix=False,

@deferrable
def __rfm_init__(self, prefix=None):
if not self.is_fixture() and not hasattr(self, '_rfm_unique_name'):
self._rfm_unique_name = type(self).variant_name(self.variant_num)

# Pass if descr is a required variable.
if not hasattr(self, 'descr'):
self.descr = self.display_name

self._perfvalues = {}

# Static directories of the regression check
Expand Down Expand Up @@ -1089,9 +1086,9 @@ def unique_name(self):
def name(self):
'''The name of the test.

This is an alias of :attr:`unique_name`.
This is an alias of :attr:`display_name`.
'''
return self.unique_name
return self.display_name

@loggable
@property
Expand Down Expand Up @@ -1146,6 +1143,42 @@ def _format_params(cls, info, prefix=' %'):

return self._rfm_display_name

@loggable
@property
def hashcode(self):
if hasattr(self, '_rfm_hashcode'):
return self._rfm_hashcode

m = hashlib.sha256()
if self.is_fixture:
m.update(self.unique_name.encode('utf-8'))
else:
basename, *params = self.display_name.split(' %')
m.update(basename.encode('utf-8'))
for p in sorted(params):
m.update(p.encode('utf-8'))

self._rfm_hashcode = m.hexdigest()[:8]
return self._rfm_hashcode

@loggable
@property
def short_name(self):
'''A short version of the test's display name.

The shortened version coincides with the :attr:`unique_name` for
simple tests and combines the test's class name and a hash code for
parameterised tests.

.. versionadded:: 4.0.0

'''

if self.unique_name != self.display_name:
return f'{type(self).__name__}_{self.hashcode}'
else:
return self.unique_name

@property
def current_environ(self):
'''The programming environment that the regression test is currently
Expand Down Expand Up @@ -1397,7 +1430,7 @@ def info(self):
method may be called at any point of the test's lifetime.
'''

ret = self.display_name
ret = f'{self.display_name} /{self.hashcode}'
if self.current_partition:
ret += f' @{self.current_partition.fullname}'

Expand Down Expand Up @@ -1508,11 +1541,11 @@ def _setup_paths(self):
runtime = rt.runtime()
self._stagedir = runtime.make_stagedir(
self.current_system.name, self._current_partition.name,
self._current_environ.name, self.unique_name
self._current_environ.name, self.short_name
)
self._outputdir = runtime.make_outputdir(
self.current_system.name, self._current_partition.name,
self._current_environ.name, self.unique_name
self._current_environ.name, self.short_name
)
except OSError as e:
raise PipelineError('failed to set up paths') from e
Expand Down Expand Up @@ -1540,13 +1573,12 @@ def _create_job(self, name, force_local=False, **job_opts):
**job_opts)

def _setup_build_job(self, **job_opts):
self._build_job = self._create_job(f'rfm_{self.unique_name}_build',
self._build_job = self._create_job(f'rfm_build',
self.local or self.build_locally,
**job_opts)

def _setup_run_job(self, **job_opts):
self._job = self._create_job(f'rfm_{self.unique_name}_job',
self.local, **job_opts)
self._job = self._create_job(f'rfm_job', self.local, **job_opts)

def _setup_perf_logging(self):
self._perf_logger = logging.getperflogger(self)
Expand Down
12 changes: 6 additions & 6 deletions reframe/frontend/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,37 +73,37 @@ def dep_lines(u, *, prefix, depth=0, lines=None, printed=None):
unique_checks.add(v.check.unique_name)

if depth:
name_info = f'{u.check.display_name} /{u.check.hashcode}'
tc_info = ''
details = ''
if concretized:
tc_info = f' @{u.partition.fullname}+{u.environ.name}'

location = inspect.getfile(type(u.check))
if detailed:
details = f' [id: {u.check.unique_name}, file: {location!r}]'
details = f' [variant: {u.check.variant_num}, file: {location!r}]'

lines.append(
f'{prefix}^{u.check.display_name}{tc_info}{details}'
)
lines.append(f'{prefix}^{name_info}{tc_info}{details}')

return lines

# We need the leaf test cases to be printed at the leftmost
leaf_testcases = list(t for t in testcases if t.in_degree == 0)
for t in leaf_testcases:
name_info = f'{t.check.display_name} /{t.check.hashcode}'
tc_info = ''
details = ''
if concretized:
tc_info = f' @{t.partition.fullname}+{t.environ.name}'

location = inspect.getfile(type(t.check))
if detailed:
details = f' [id: {t.check.unique_name}, file: {location!r}]'
details = f' [variant: {t.check.variant_num}, file: {location!r}]'

# if not concretized and t.check.name not in unique_checks:
if concretized or (not concretized and
t.check.unique_name not in unique_checks):
printer.info(f'- {t.check.display_name}{tc_info}{details}')
printer.info(f'- {name_info}{tc_info}{details}')

if not t.check.is_fixture():
unique_checks.add(t.check.unique_name)
Expand Down
3 changes: 2 additions & 1 deletion reframe/frontend/executors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,9 +416,10 @@ def abort(self, cause=None):
def info(self):
'''Return an info string about this task.'''
name = self.check.display_name
hashcode = self.check.hashcode
part = self.testcase.partition.fullname
env = self.testcase.environ.name
return f'{name} @{part}+{env}'
return f'{name} /{hashcode} @{part}+{env}'


class TaskEventListener(abc.ABC):
Expand Down
16 changes: 12 additions & 4 deletions reframe/frontend/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@ def _fn(case):

def have_any_name(names):
rt = runtime()
exact_matches = []
variant_matches = []
hash_matches = []
regex_matches = []
for n in names:
if '@' in n:
test, _, variant = n.rpartition('@')
if variant.isdigit():
exact_matches.append((test, int(variant)))
variant_matches.append((test, int(variant)))
elif n.startswith('/'):
hash_matches.append(n[1:])
else:
regex_matches.append(n)

Expand All @@ -57,12 +60,17 @@ def have_any_name(names):
regex = None

def _fn(case):
# Check if we have an exact match
for m in exact_matches:
# Check the variant matches
for m in variant_matches:
cls_name = type(case.check).__name__
if (cls_name, case.check.variant_num) == m:
return True

# Check hash matches
for m in hash_matches:
if m == case.check.hashcode:
return True

display_name = case.check.display_name.replace(' ', '')
if regex:
return regex.match(display_name)
Expand Down
Loading