Esempio n. 1
0
    def extend(self, cls):
        '''Extend the parameter space with the local parameter space.'''

        local_param_space = getattr(cls, self.local_namespace_name, dict())
        for name, p in local_param_space.items():
            try:
                filt_vals = p.filter_params(self.params.get(name, ()))
            except Exception:
                raise
            else:
                try:
                    self.params[name] = (tuple(filt_vals) + p.values)
                except TypeError:
                    raise ReframeSyntaxError(
                        f"'filter_param' must return an iterable "
                        f"(parameter {name!r})") from None

        # Clear the local param space
        local_param_space.clear()

        # If any previously declared parameter was defined in the class body
        # by directly assigning it a value, raise an error. Parameters must be
        # changed using the `x = parameter([...])` syntax.
        for key, values in cls.__dict__.items():
            if key in self.params:
                raise ReframeSyntaxError(
                    f'parameter {key!r} must be modified through the built-in '
                    f'parameter type')
Esempio n. 2
0
    def _expand_partitions_envs(self, obj):
        '''Process the partitions and programming environs of the parent.'''

        try:
            part = tuple(obj.valid_systems)
        except AttributeError:
            raise ReframeSyntaxError(
                f"'valid_systems' is undefined in test {obj.name}")
        else:
            rt = runtime.runtime()
            if '*' in part or rt.system.name in part:
                part = tuple(p.fullname for p in rt.system.partitions)

        try:
            prog_envs = tuple(obj.valid_prog_environs)
        except AttributeError:
            raise ReframeSyntaxError(
                f"'valid_prog_environs' is undefined in test {obj.name}")
        else:
            if '*' in prog_envs:
                all_pes = set()
                for p in runtime.runtime().system.partitions:
                    for e in p.environs:
                        all_pes.add(e.name)

                prog_envs = tuple(all_pes)

        return part, prog_envs
Esempio n. 3
0
        def __setitem__(self, key, value):
            if isinstance(value, variables.TestVar):
                # Insert the attribute in the variable namespace
                try:
                    self['_rfm_local_var_space'][key] = value
                    value.__set_name__(self, key)
                except KeyError:
                    raise ReframeSyntaxError(
                        f'variable {key!r} is already declared') from None

                # Override the regular class attribute (if present) and return
                self._namespace.pop(key, None)
                return

            elif isinstance(value, parameters.TestParam):
                # Insert the attribute in the parameter namespace
                try:
                    self['_rfm_local_param_space'][key] = value
                except KeyError:
                    raise ReframeSyntaxError(
                        f'parameter {key!r} is already declared in this class'
                    ) from None

                # Override the regular class attribute (if present) and return
                self._namespace.pop(key, None)
                return

            elif key in self['_rfm_local_param_space']:
                raise ReframeSyntaxError(f'cannot override parameter {key!r}')
            else:
                # Insert the items manually to overide the namespace clash
                # check from the base namespace.
                self._namespace[key] = value

            # Register functions decorated with either @sanity_function or
            # @performance_variables or @performance_function decorators.
            if hasattr(value, '_rfm_sanity_fn'):
                try:
                    super().__setitem__('_rfm_sanity', value)
                except KeyError:
                    raise ReframeSyntaxError(
                        'the @sanity_function decorator can only be used '
                        'once in the class body') from None
            elif hasattr(value, '_rfm_perf_key'):
                try:
                    self['_rfm_perf_fns'][key] = value
                except KeyError:
                    raise ReframeSyntaxError(
                        f'the performance function {key!r} has already been '
                        f'defined in this class') from None

            # Register the final methods
            if hasattr(value, '_rfm_final'):
                self['_rfm_final_methods'].add(key)

            # Register the hooks - if a value does not meet the conditions
            # it will be simply ignored
            self['_rfm_hook_registry'].add(value)
Esempio n. 4
0
    def join(self, other, cls):
        '''Join other parameter space into the current one.

        Join two different parameter spaces into a single one. Both parameter
        spaces must be an instance ot the ParamSpace class. This method will
        raise an error if a parameter is defined in the two parameter spaces
        to be merged.

        :param other: instance of the ParamSpace class.
        :param cls: the target class.
        '''
        for name in other.params:
            # With multiple inheritance, a single parameter
            # could be doubly defined and lead to repeated
            # values
            if self.defines(name) and other.defines(name):
                raise ReframeSyntaxError(
                    f'parameter space conflict: '
                    f'parameter {name!r} is defined in more than '
                    f'one base class of class {cls.__qualname__!r}'
                )

            if not self.defines(name):
                # If we do not define the parameter, take it from other
                self.params[name] = other.params[name]
Esempio n. 5
0
    def __setattr__(cls, name, value):
        '''Handle the special treatment required for variables and parameters.

        A variable's default value can be updated when accessed as a regular
        class attribute. This behavior does not apply when the assigned value
        is a descriptor object. In that case, the task of setting the value is
        delegated to the base :func:`__setattr__` (this is to comply with
        standard Python behavior). However, since the variables are already
        descriptors which are injected during class instantiation, we disallow
        any attempt to override this descriptor (since it would be silently
        re-overridden in any case).

        Altering the value of a parameter when accessed as a class attribute
        is not allowed. This would break the parameter space internals.
        '''

        # Try to treat `name` as variable
        if cls.setvar(name, value):
            return

        # Try to treat `name` as a parameter
        try:
            # Catch attempts to override a test parameter
            param_space = super().__getattribute__('_rfm_param_space')
            if name in param_space.params:
                raise ReframeSyntaxError(f'cannot override parameter {name!r}')
        except AttributeError:
            '''Catch early access attempt to the parameter space.'''

        # Treat `name` as normal class attribute
        super().__setattr__(name, value)
Esempio n. 6
0
def run_after(stage):
    '''Decorator for attaching a test method to a pipeline stage.

    .. deprecated:: 3.7.0
       Please use the :func:`~reframe.core.pipeline.RegressionMixin.run_after`
       built-in function.

    '''
    warn.user_deprecation_warning(
        'using the @rfm.run_after decorator from the rfm module is '
        'deprecated; please use the built-in decorator @run_after instead.',
        from_version='3.7.0'
    )
    if stage not in _USER_PIPELINE_STAGES:
        raise ReframeSyntaxError(
            f'invalid pipeline stage specified: {stage!r}')

    # Map user stage names to the actual pipeline functions if needed
    if stage == 'init':
        stage = '__init__'
    elif stage == 'compile':
        stage = 'compile_wait'
    elif stage == 'run':
        stage = 'run_wait'

    return hooks.attach_to('post_' + stage)
Esempio n. 7
0
    def setvar(cls, name, value):
        '''Set the value of a variable.

        :param name: The name of the variable.
        :param value: The value of the variable.

        :returns: :class:`True` if the variable was set.
            A variable will *not* be set, if it does not exist or when an
            attempt is made to set it with its underlying descriptor.
            This happens during the variable injection time and it should be
            delegated to the class' :func:`__setattr__` method.

        :raises ReframeSyntaxError: If an attempt is made to override a
            variable with a descriptor other than its underlying one.

        '''

        try:
            var_space = super().__getattribute__('_rfm_var_space')
            if name in var_space:
                if not hasattr(value, '__get__'):
                    var_space[name].define(value)
                    return True
                elif var_space[name].field is not value:
                    desc = '.'.join([cls.__qualname__, name])
                    raise ReframeSyntaxError(
                        f'cannot override variable descriptor {desc!r}')
                else:
                    # Variable is being injected
                    return False
        except AttributeError:
            '''Catch early access attempt to the variable space.'''
            return False
Esempio n. 8
0
    def join(self, other, cls):
        '''Join other parameter space into the current one.

        Join two different parameter spaces into a single one. Both parameter
        spaces must be an instance ot the ParamSpace class. This method will
        raise an error if a parameter is defined in the two parameter spaces
        to be merged.

        :param other: instance of the ParamSpace class.
        :param cls: the target class.
        '''
        for key in other.params:
            # With multiple inheritance, a single parameter
            # could be doubly defined and lead to repeated
            # values
            if (key in self.params and self.params[key] != ()
                    and other.params[key] != ()):

                raise ReframeSyntaxError(
                    f'parameter space conflict: '
                    f'parameter {key!r} is defined in more than '
                    f'one base class of class {cls.__qualname__!r}')

            self.params[key] = (other.params.get(key, ()) +
                                self.params.get(key, ()))
Esempio n. 9
0
def _validate_test(cls):
    if not issubclass(cls, RegressionTest):
        raise ReframeSyntaxError('the decorated class must be a '
                                 'subclass of RegressionTest')

    if (cls.is_abstract()):
        raise ValueError(f'decorated test ({cls.__qualname__!r}) has one or '
                         f'more undefined parameters')
Esempio n. 10
0
def _validate_test(cls):
    if not issubclass(cls, RegressionTest):
        raise ReframeSyntaxError('the decorated class must be a '
                                 'subclass of RegressionTest')

    if (cls.is_abstract()):
        raise ValueError(f'decorated test ({cls.__qualname__!r}) is an'
                         f' abstract test')
Esempio n. 11
0
    def extend(self, cls):
        '''Extend the VarSpace with the content in the LocalVarSpace.

        Merge the VarSpace inherited from the base classes with the
        LocalVarSpace. Note that the LocalVarSpace can also contain
        define and undefine actions on existing vars. Thus, since it
        does not make sense to define and undefine a var in the same
        class, the order on which the define and undefine functions
        are called is not preserved. In fact, applying more than one
        of these actions on the same var for the same local var space
        is disallowed.
        '''
        local_varspace = getattr(cls, self.local_namespace_name)
        for key, var in local_varspace.items():
            if isinstance(var, TestVar):
                # Disable redeclaring a variable
                if key in self.vars:
                    raise ReframeSyntaxError(
                        f'cannot redeclare the variable {key!r}'
                    )

                # Add a new var
                self.vars[key] = var

        # If any previously declared variable was defined in the class body
        # by directly assigning it a value, retrieve this value from the class
        # namespace and update it into the variable space.
        _assigned_vars = set()
        for key, value in cls.__dict__.items():
            if key in self.vars:
                self.vars[key].define(value)
                _assigned_vars.add(key)
            elif value is Undefined:
                # Cannot be set as Undefined if not a variable
                raise ReframeSyntaxError(
                    f'{key!r} has not been declared as a variable'
                )

        # Delete the vars from the class __dict__.
        for key in _assigned_vars:
            delattr(cls, key)

        # Clear the local var space
        local_varspace.clear()
Esempio n. 12
0
    def _do_register(cls):
        if _validate_test(cls):
            if not cls.param_space.is_empty():
                raise ReframeSyntaxError(
                    f'{cls.__qualname__!r} is already a parameterized test')

            for args in inst:
                _register_parameterized_test(cls, args)

        return cls
Esempio n. 13
0
def run_before(stage):
    '''Decorator for attaching a test method to a pipeline stage.

    .. deprecated:: 3.7.0
       Please use the :func:`~reframe.core.pipeline.RegressionMixin.run_before`
       built-in function.

    '''
    warn.user_deprecation_warning(
        'using the @rfm.run_before decorator from the rfm module is '
        'deprecated; please use the built-in decorator @run_before instead.',
        from_version='3.7.0')
    if stage not in _USER_PIPELINE_STAGES:
        raise ReframeSyntaxError(
            f'invalid pipeline stage specified: {stage!r}')

    if stage == 'init':
        raise ReframeSyntaxError('pre-init hooks are not allowed')

    return hooks.attach_to('pre_' + stage)
Esempio n. 14
0
        def __getitem__(self, key):
            '''Expose and control access to the local namespaces.

            Variables may only be retrieved if their value has been previously
            set. Accessing a parameter in the class body is disallowed (the
            actual test parameter is set during the class instantiation).
            '''

            try:
                return super().__getitem__(key)
            except KeyError as err:
                try:
                    # Handle variable access
                    return self['_rfm_local_var_space'][key]
                except KeyError:
                    # Handle parameter access
                    if key in self['_rfm_local_param_space']:
                        raise ReframeSyntaxError(
                            'accessing a test parameter from the class '
                            'body is disallowed'
                        ) from None
                    elif key in self['_rfm_local_fixture_space']:
                        raise ReframeSyntaxError(
                            'accessing a fixture from the class body is '
                            'disallowed'
                        ) from None
                    else:
                        # As the last resource, look if key is a variable in
                        # any of the base classes. If so, make its value
                        # available in the current class' namespace.
                        for b in self['_rfm_bases']:
                            if key in b._rfm_var_space:
                                # Store a deep-copy of the variable's
                                # value and return.
                                v = b._rfm_var_space[key].default_value
                                self._namespace[key] = v
                                return self._namespace[key]

                        # If 'key' is neither a variable nor a parameter,
                        # raise the exception from the base __getitem__.
                        raise err from None
Esempio n. 15
0
    def __init__(cls, name, bases, namespace, **kwargs):
        super().__init__(name, bases, namespace, **kwargs)

        # Create a set with the attribute names already in use.
        cls._rfm_dir = set()
        for base in bases:
            if hasattr(base, '_rfm_dir'):
                cls._rfm_dir.update(base._rfm_dir)

        used_attribute_names = set(cls._rfm_dir)

        # Build the var space and extend the target namespace
        variables.VarSpace(cls, used_attribute_names)
        used_attribute_names.update(cls._rfm_var_space.vars)

        # Build the parameter space
        parameters.ParamSpace(cls, used_attribute_names)

        # Update used names set with the local __dict__
        cls._rfm_dir.update(cls.__dict__)

        # Set up the hooks for the pipeline stages based on the _rfm_attach
        # attribute; all dependencies will be resolved first in the post-setup
        # phase if not assigned elsewhere
        hooks = HookRegistry.create(namespace)
        for b in bases:
            if hasattr(b, '_rfm_pipeline_hooks'):
                hooks.update(getattr(b, '_rfm_pipeline_hooks'))

        cls._rfm_pipeline_hooks = hooks  # HookRegistry(local_hooks)
        cls._final_methods = {
            v.__name__
            for v in namespace.values() if hasattr(v, '_rfm_final')
        }

        # Add the final functions from its parents
        cls._final_methods.update(*(b._final_methods for b in bases
                                    if hasattr(b, '_final_methods')))

        if hasattr(cls, '_rfm_special_test') and cls._rfm_special_test:
            return

        for v in namespace.values():
            for b in bases:
                if not hasattr(b, '_final_methods'):
                    continue

                if callable(v) and v.__name__ in b._final_methods:
                    msg = (f"'{cls.__qualname__}.{v.__name__}' attempts to "
                           f"override final method "
                           f"'{b.__qualname__}.{v.__name__}'; "
                           f"you should use the pipeline hooks instead")
                    raise ReframeSyntaxError(msg)
Esempio n. 16
0
    def sanity(self, cls, illegal_names=None):
        '''Sanity checks post-creation of the namespace.

        By default, we make illegal to have any item in the namespace
        that clashes with a member of the target class.
        '''
        if illegal_names is None:
            illegal_names = set(dir(cls))

        for key in self._namespace:
            if key in illegal_names:
                raise ReframeSyntaxError(f'{key!r} already defined in class '
                                         f'{cls.__qualname__!r}')
Esempio n. 17
0
    def join(self, other, cls):
        '''Join other fixture spaces into the current one.

        :param other: instance of the FixtureSpace class.
        :param cls: the target class.
        '''
        for key, value in other.fixtures.items():
            if key in self.fixtures:
                raise ReframeSyntaxError(
                    f'fixture space conflict: '
                    f'fixture {key!r} is defined in more than '
                    f'one base class of {cls.__qualname__!r}')

            self.fixtures[key] = value
Esempio n. 18
0
    def extend(self, cls):
        '''Extend the inherited fixture space with the local fixture space.'''
        local_fixture_space = getattr(cls, self.local_namespace_name, False)
        while local_fixture_space:
            name, fixture = local_fixture_space.popitem()
            self.fixtures[name] = fixture

        # If any previously declared fixture was defined in the class body
        # by directly assigning it a value, raise an error. Fixtures must be
        # changed using the `x = fixture(...)` syntax.
        for key in self.fixtures:
            if key in cls.__dict__:
                raise ReframeSyntaxError(
                    f'fixture {key!r} can only be redefined through the '
                    f'fixture built-in')
Esempio n. 19
0
    def join(self, other, cls):
        '''Join an existing VarSpace into the current one.

        :param other: instance of the VarSpace class.
        :param cls: the target class.
        '''
        for key, var in other.items():
            # Make doubly declared vars illegal. Note that this will be
            # triggered when inheriting from multiple RegressionTest classes.
            if key in self.vars:
                raise ReframeSyntaxError(
                    f'variable {key!r} is declared in more than one of the '
                    f'parent classes of class {cls.__qualname__!r}')

            self.vars[key] = copy.deepcopy(var)

        # Carry over the set of injected variables
        self._injected_vars.update(other._injected_vars)
Esempio n. 20
0
    def update(self, other):
        '''Update this parameter from another one.

        The values from the other parameter will be filtered according to the
        filter function of this one and prepended to this parameter's values.
        '''

        try:
            filt_vals = self.filter_params(other.values)
        except Exception:
            raise
        else:
            try:
                self.values = tuple(filt_vals) + self.values
            except TypeError:
                raise ReframeSyntaxError(
                    f"'filter_param' must return an iterable"
                ) from None
Esempio n. 21
0
def _validate_test(cls):
    if not issubclass(cls, RegressionTest):
        raise ReframeSyntaxError('the decorated class must be a '
                                 'subclass of RegressionTest')

    if (cls.is_abstract()):
        getlogger().warning(f'skipping test {cls.__qualname__!r}: '
                            f'test has one or more undefined parameters')
        return False

    conditions = [VersionValidator(v) for v in cls._rfm_required_version]
    if (cls._rfm_required_version and
            not any(c.validate(osext.reframe_version()) for c in conditions)):

        getlogger().warning(f"skipping incompatible test "
                            f"'{cls.__qualname__}': not valid for ReFrame "
                            f"version {osext.reframe_version().split('-')[0]}")
        return False

    return True
Esempio n. 22
0
    def extend(self, cls):
        '''Extend the parameter space with the local parameter space.'''

        local_param_space = getattr(cls, self.local_namespace_name, dict())
        for name, p in local_param_space.items():
            if name in self.params:
                p.update(self.params[name])

            self.params[name] = p

        # Clear the local param space
        local_param_space.clear()

        # If any previously declared parameter was defined in the class body
        # by directly assigning it a value, raise an error. Parameters must be
        # changed using the `x = parameter([...])` syntax.
        for key, values in cls.__dict__.items():
            if key in self.params:
                raise ReframeSyntaxError(
                    f'parameter {key!r} must be modified through the built-in '
                    f'parameter type')
Esempio n. 23
0
def _validate_test(cls):
    if not issubclass(cls, RegressionTest):
        raise ReframeSyntaxError('the decorated class must be a '
                                 'subclass of RegressionTest')
Esempio n. 24
0
    def __init__(cls, name, bases, namespace, **kwargs):
        super().__init__(name, bases, namespace, **kwargs)

        # Create a set with the attribute names already in use.
        cls._rfm_dir = set()
        for base in (b for b in bases if hasattr(b, '_rfm_dir')):
            cls._rfm_dir.update(base._rfm_dir)

        used_attribute_names = set(cls._rfm_dir)

        # Build the var space and extend the target namespace
        variables.VarSpace(cls, used_attribute_names)
        used_attribute_names.update(cls._rfm_var_space.vars)

        # Build the parameter space
        parameters.ParamSpace(cls, used_attribute_names)

        # Update used names set with the local __dict__
        cls._rfm_dir.update(cls.__dict__)

        # Update the hook registry with the bases
        for base in cls._rfm_bases:
            cls._rfm_hook_registry.update(base._rfm_hook_registry,
                                          denied_hooks=namespace)

        # Search the bases if no local sanity functions exist.
        if '_rfm_sanity' not in namespace:
            for base in cls._rfm_bases:
                if hasattr(base, '_rfm_sanity'):
                    cls._rfm_sanity = getattr(base, '_rfm_sanity')
                    if cls._rfm_sanity.__name__ in namespace:
                        raise ReframeSyntaxError(
                            f'{cls.__qualname__!r} overrides the candidate '
                            f'sanity function '
                            f'{cls._rfm_sanity.__qualname__!r} without '
                            f'defining an alternative')

                    break

        # Update the performance function dict with the bases.
        for base in cls._rfm_bases:
            for k, v in base._rfm_perf_fns.items():
                if k not in namespace:
                    try:
                        cls._rfm_perf_fns[k] = v
                    except KeyError:
                        '''Performance function overridden by other class'''

        # Add the final functions from its parents
        cls._rfm_final_methods.update(*(b._rfm_final_methods
                                        for b in cls._rfm_bases))

        if getattr(cls, '_rfm_override_final', None):
            return

        for b in cls._rfm_bases:
            for key in b._rfm_final_methods:
                if key in namespace and callable(namespace[key]):
                    msg = (f"'{cls.__qualname__}.{key}' attempts to "
                           f"override final method "
                           f"'{b.__qualname__}.{key}'; "
                           f"you should use the pipeline hooks instead")
                    raise ReframeSyntaxError(msg)
Esempio n. 25
0
def required_version(*versions):
    '''Class decorator for specifying the required ReFrame versions for the
    following test.

    If the test is not compatible with the current ReFrame version it will be
    skipped.

    :arg versions: A list of ReFrame version specifications that this test is
      allowed to run. A version specification string can have one of the
      following formats:

      1. ``VERSION``: Specifies a single version.
      2. ``{OP}VERSION``, where ``{OP}`` can be any of ``>``, ``>=``, ``<``,
         ``<=``, ``==`` and ``!=``. For example, the version specification
         string ``'>=3.5.0'`` will allow the following test to be loaded only
         by ReFrame 3.5.0 and higher. The ``==VERSION`` specification is the
         equivalent of ``VERSION``.
      3. ``V1..V2``: Specifies a range of versions.

      You can specify multiple versions with this decorator, such as
      ``@required_version('3.5.1', '>=3.5.6')``, in which case the test will be
      selected if *any* of the versions is satisfied, even if the versions
      specifications are conflicting.

    .. versionadded:: 2.13

    .. versionchanged:: 3.5.0

       Passing ReFrame version numbers that do not comply with the `semantic
       versioning <https://semver.org/>`__ specification is deprecated.
       Examples of non-compliant version numbers are ``3.5`` and ``3.5-dev0``.
       These should be written as ``3.5.0`` and ``3.5.0-dev.0``.

    .. deprecated:: 3.5.0
       Please set the ``require_version`` parameter in the class definition
       instead.

    '''
    warn.user_deprecation_warning(
        "the '@required_version' decorator is deprecated; please set "
        "the 'require_version' parameter in the class definition instead",
        from_version='3.7.0'
    )

    if not versions:
        raise ReframeSyntaxError('no versions specified')

    conditions = [VersionValidator(v) for v in versions]

    def _skip_tests(cls):
        mod = inspect.getmodule(cls)
        if not hasattr(mod, '__rfm_skip_tests'):
            mod.__rfm_skip_tests = set()

        if not hasattr(mod, '_rfm_test_registry'):
            mod._rfm_test_registry = TestRegistry()

        if not any(c.validate(osext.reframe_version()) for c in conditions):
            getlogger().warning(
                f"skipping incompatible test '{cls.__qualname__}': not valid "
                f"for ReFrame version {osext.reframe_version().split('-')[0]}"
            )
            if cls in mod._rfm_test_registry:
                mod._rfm_test_registry.skip(cls)
            else:
                mod.__rfm_skip_tests.add(cls)

        return cls

    return _skip_tests
Esempio n. 26
0
 def __setitem__(self, key, value):
     raise ReframeSyntaxError(
         f'cannot set item {key!r} into a {type(self).__qualname__} object')
Esempio n. 27
0
    def add(self, fixture, variant_num, parent_test):
        '''Register a fixture.

        This method mangles the fixture name, ensuring that different fixture
        variants (see above for the definition of a fixture variant in the
        context of a fixture registry) have a different name. This method
        is aware of the public members from the :class:`TestFixture`.
        Fixtures `steal` the valid environments and valid partitions from
        their parent test, where the number of combinations that trickle
        down into the fixture varies depending on the fixture's scope.
        The rationale is as follows for the different scopes:
         - session: Only one environment+partition combination per fixture.
               This kind of fixture may be shared across all tests. The name
               for fixtures with this scope is mangled with the system name.
               This is necesssary to avoid name conflicts with classes that
               are used both as regular tests and fixtures with session scope.
         - partition: Only one environment per partition. This kind of fixture
               may be shared amongst all the tests running on the same
               partition. The name is mangled to include the partition where
               fixture will run.
         - environment: One fixture per available environment+partition
               combination. This kind of fixture may be shared only with
               other tests that execute on the same environment+partition
               combination. The name is mangled to contain the partition and
               the environment where the fixture will run.
         - test: Use the environments and partitions from the parent test
               without any modifications. Fixtures using this scope are
               private to the parent test and they will not be shared with
               any other test in the session. The fixture name is mangled to
               contain the name of the parent test the fixture belongs to.

        If a fixture modifies the default value of any of its attributes,
        these modified variables are always mangled into the fixture name
        regardless of the scope used. These variables are sorted by their
        name before they're accounted for into the name mangling, so the
        order on which they're specified is irrelevant.

        This method returns a list with the mangled names of the newly
        registered fixtures.

        :param fixture: An instance of :class:`TestFixture`.
        :param variant_num: The variant index for the given ``fixture``.
        :param parent_test: The parent test.
        '''

        cls = fixture.cls
        scope = fixture.scope
        fname = fixture.cls.variant_name(variant_num)
        variables = fixture.variables
        reg_names = []
        self._registry.setdefault(cls, dict())

        # Mangle the fixture name with the modified variables (sorted by keys)
        if variables:
            vname = ''.join((f'%{k}={utils.toalphanum(str(v))}'
                             for k, v in sorted(variables.items())))
            if self._hash:
                vname = '_' + sha256(vname.encode('utf-8')).hexdigest()[:8]

            fname += vname

        # Select only the valid partitions
        try:
            valid_sysenv = runtime.valid_sysenv_comb(
                parent_test.valid_systems, parent_test.valid_prog_environs)
        except AttributeError as e:
            msg = e.args[0] + f' in test {parent_test.display_name!r}'
            raise ReframeSyntaxError(msg) from None

        # Return if there are no valid system/environment combinations
        if not valid_sysenv:
            return []

        # Register the fixture
        if scope == 'session':
            # The name is mangled with the system name
            # Pick the first valid system/environment combination
            pname, ename = None, None
            for part, environs in valid_sysenv.items():
                pname = part.fullname
                for env in environs:
                    ename = env.name
                    break

            if ename is None:
                # No valid environments found
                return []

            # Register the fixture
            fixt_data = FixtureData(variant_num, [ename], [pname], variables,
                                    scope, self._sys_name)
            name = f'{cls.__name__}_{fixt_data.mashup()}'
            self._registry[cls][name] = fixt_data
            reg_names.append(name)
        elif scope == 'partition':
            for part, environs in valid_sysenv.items():
                # The mangled name contains the full partition name
                # Select an environment supported by the partition
                pname = part.fullname
                try:
                    ename = environs[0].name
                except IndexError:
                    continue

                # Register the fixture
                fixt_data = FixtureData(variant_num, [ename], [pname],
                                        variables, scope, pname)
                name = f'{cls.__name__}_{fixt_data.mashup()}'
                self._registry[cls][name] = fixt_data
                reg_names.append(name)
        elif scope == 'environment':
            for part, environs in valid_sysenv.items():
                for env in environs:
                    # The mangled name contains the full part and env names
                    # Register the fixture
                    pname, ename = part.fullname, env.name
                    fixt_data = FixtureData(variant_num, [ename], [pname],
                                            variables, scope,
                                            f'{pname}+{ename}')
                    name = f'{cls.__name__}_{fixt_data.mashup()}'
                    self._registry[cls][name] = fixt_data
                    reg_names.append(name)
        elif scope == 'test':
            # The mangled name contains the parent test name.

            # Register the fixture
            fixt_data = FixtureData(variant_num,
                                    list(parent_test.valid_prog_environs),
                                    list(parent_test.valid_systems), variables,
                                    scope, parent_test.unique_name)
            name = f'{cls.__name__}_{fixt_data.mashup()}'
            self._registry[cls][name] = fixt_data
            reg_names.append(name)

        return reg_names
Esempio n. 28
0
 def _check_is_defined(self):
     if not self.is_defined():
         raise ReframeSyntaxError(
             f'variable {self._name} is not assigned a value')
Esempio n. 29
0
    def __init__(cls, name, bases, namespace, **kwargs):
        super().__init__(name, bases, namespace, **kwargs)

        # Create a set with the attribute names already in use.
        cls._rfm_dir = set()
        for base in (b for b in bases if hasattr(b, '_rfm_dir')):
            cls._rfm_dir.update(base._rfm_dir)

        used_attribute_names = set(cls._rfm_dir)

        # Build the var space and extend the target namespace
        variables.VarSpace(cls, used_attribute_names)
        used_attribute_names.update(cls._rfm_var_space.vars)

        # Build the parameter space
        parameters.ParamSpace(cls, used_attribute_names)

        # Update used names set with the local __dict__
        cls._rfm_dir.update(cls.__dict__)

        # Set up the hooks for the pipeline stages based on the _rfm_attach
        # attribute; all dependencies will be resolved first in the post-setup
        # phase if not assigned elsewhere
        hook_reg = hooks.HookRegistry.create(namespace)
        for base in (b for b in bases if hasattr(b, '_rfm_pipeline_hooks')):
            hook_reg.update(getattr(base, '_rfm_pipeline_hooks'),
                            denied_hooks=namespace)

        cls._rfm_pipeline_hooks = hook_reg

        # Gather all the locally defined sanity functions based on the
        # _rfm_sanity_fn attribute.

        sn_fn = [v for v in namespace.values() if hasattr(v, '_rfm_sanity_fn')]
        if sn_fn:
            cls._rfm_sanity = sn_fn[0]
            if len(sn_fn) > 1:
                raise ReframeSyntaxError(
                    f'{cls.__qualname__!r} defines more than one sanity '
                    'function in the class body.')

        else:
            # Search the bases if no local sanity functions exist.
            for base in (b for b in bases if hasattr(b, '_rfm_sanity')):
                cls._rfm_sanity = getattr(base, '_rfm_sanity')
                if cls._rfm_sanity.__name__ in namespace:
                    raise ReframeSyntaxError(
                        f'{cls.__qualname__!r} overrides the candidate '
                        f'sanity function '
                        f'{cls._rfm_sanity.__qualname__!r} without '
                        f'defining an alternative')

                break

        cls._final_methods = {
            v.__name__
            for v in namespace.values() if hasattr(v, '_rfm_final')
        }

        # Add the final functions from its parents
        cls._final_methods.update(*(b._final_methods for b in bases
                                    if hasattr(b, '_final_methods')))

        if getattr(cls, '_rfm_special_test', None):
            return

        bases_w_final = [b for b in bases if hasattr(b, '_final_methods')]
        for v in namespace.values():
            for b in bases_w_final:
                if callable(v) and v.__name__ in b._final_methods:
                    msg = (f"'{cls.__qualname__}.{v.__name__}' attempts to "
                           f"override final method "
                           f"'{b.__qualname__}.{v.__name__}'; "
                           f"you should use the pipeline hooks instead")
                    raise ReframeSyntaxError(msg)
Esempio n. 30
0
    def __init__(cls, name, bases, namespace, **kwargs):
        super().__init__(name, bases, namespace, **kwargs)

        # Create a set with the attribute names already in use.
        cls._rfm_dir = set()
        for base in (b for b in bases if hasattr(b, '_rfm_dir')):
            cls._rfm_dir.update(base._rfm_dir)

        used_attribute_names = set(cls._rfm_dir).union(
            {h.__name__
             for h in cls._rfm_local_hook_registry})

        # Build the different global class namespaces
        namespace_types = (variables.VarSpace, parameters.ParamSpace,
                           fixtures.FixtureSpace)
        for ns_type in namespace_types:
            ns = ns_type(cls, used_attribute_names)
            setattr(cls, ns.namespace_name, ns)
            used_attribute_names.update(ns.data())

        # Update used names set with the local __dict__
        cls._rfm_dir.update(cls.__dict__)

        # Populate the global hook registry with the hook registries of the
        # parent classes in reverse MRO order
        for c in list(reversed(cls.mro()))[:-1]:
            if hasattr(c, '_rfm_local_hook_registry'):
                cls._rfm_hook_registry.update(c._rfm_local_hook_registry,
                                              denied_hooks=namespace)

        cls._rfm_hook_registry.update(cls._rfm_local_hook_registry)

        # Search the bases if no local sanity functions exist.
        if '_rfm_sanity' not in namespace:
            for base in cls._rfm_bases:
                if hasattr(base, '_rfm_sanity'):
                    cls._rfm_sanity = getattr(base, '_rfm_sanity')
                    if cls._rfm_sanity.__name__ in namespace:
                        raise ReframeSyntaxError(
                            f'{cls.__qualname__!r} overrides the candidate '
                            f'sanity function '
                            f'{cls._rfm_sanity.__qualname__!r} without '
                            f'defining an alternative')

                    break

        # Update the performance function dict with the bases.
        for base in cls._rfm_bases:
            for k, v in base._rfm_perf_fns.items():
                if k not in namespace:
                    try:
                        cls._rfm_perf_fns[k] = v
                    except KeyError:
                        '''Performance function overridden by other class'''

        # Add the final functions from its parents
        cls._rfm_final_methods.update(*(b._rfm_final_methods
                                        for b in cls._rfm_bases))

        if getattr(cls, '_rfm_override_final', None):
            return

        for b in cls._rfm_bases:
            for key in b._rfm_final_methods:
                if key in namespace and callable(namespace[key]):
                    msg = (f"'{cls.__qualname__}.{key}' attempts to "
                           f"override final method "
                           f"'{b.__qualname__}.{key}'; "
                           f"you should use the pipeline hooks instead")
                    raise ReframeSyntaxError(msg)