Blame tests/system_tests.py

Packit Service 21b5d1
# -*- coding: utf-8 -*-
Packit Service 21b5d1
Packit Service 21b5d1
import configparser
Packit Service 21b5d1
import os
Packit Service 21b5d1
import inspect
Packit Service 21b5d1
import subprocess
Packit Service 21b5d1
import threading
Packit Service 21b5d1
import shlex
Packit Service 21b5d1
import sys
Packit Service 21b5d1
import shutil
Packit Service 21b5d1
import string
Packit Service 21b5d1
import unittest
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
if sys.platform == 'win32':
Packit Service 21b5d1
    #: invoke subprocess.Popen with shell=True on Windows
Packit Service 21b5d1
    _SUBPROCESS_SHELL = True
Packit Service 21b5d1
Packit Service 21b5d1
    def _cmd_splitter(cmd):
Packit Service 21b5d1
        return cmd
Packit Service 21b5d1
Packit Service 21b5d1
    def _process_output_post(output):
Packit Service 21b5d1
        return output.replace('\r\n', '\n')
Packit Service 21b5d1
Packit Service 21b5d1
else:
Packit Service 21b5d1
    #: invoke subprocess.Popen with shell=False on Unix
Packit Service 21b5d1
    _SUBPROCESS_SHELL = False
Packit Service 21b5d1
Packit Service 21b5d1
    def _cmd_splitter(cmd):
Packit Service 21b5d1
        return shlex.split(cmd)
Packit Service 21b5d1
Packit Service 21b5d1
    def _process_output_post(output):
Packit Service 21b5d1
        return output
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
def _disjoint_dict_merge(d1, d2):
Packit Service 21b5d1
    """
Packit Service 21b5d1
    Merges two dictionaries whose keys are disjoint sets and returns the
Packit Service 21b5d1
    resulting dictionary:
Packit Service 21b5d1
Packit Service 21b5d1
    >>> d1 = {"a": 1}
Packit Service 21b5d1
    >>> d2 = {"b": 2, "c": 3}
Packit Service 21b5d1
    >>> _disjoint_dict_merge(d1, d2) == {"a": 1, "b": 2, "c": 3}
Packit Service 21b5d1
    True
Packit Service 21b5d1
Packit Service 21b5d1
    Passing dictionaries that share keys raises a ValueError:
Packit Service 21b5d1
    >>> _disjoint_dict_merge({"a": 1, "b": 6}, {"b": 2, "a": 3})
Packit Service 21b5d1
    Traceback (most recent call last):
Packit Service 21b5d1
     ..
Packit Service 21b5d1
    ValueError: Dictionaries have common keys.
Packit Service 21b5d1
Packit Service 21b5d1
    """
Packit Service 21b5d1
    inter = set(d1.keys()).intersection(set(d2.keys()))
Packit Service 21b5d1
    if len(inter) > 0:
Packit Service 21b5d1
        raise ValueError("Dictionaries have common keys.")
Packit Service 21b5d1
    res = d1.copy()
Packit Service 21b5d1
    res.update(d2)
Packit Service 21b5d1
    return res
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
class CasePreservingConfigParser(configparser.ConfigParser):
Packit Service 21b5d1
    r""" ConfigParser where the keys are case sensitive.
Packit Service 21b5d1
Packit Service 21b5d1
    The default ConfigParser converts all options in the config file with their
Packit Service 21b5d1
    lowercase version. This class overrides the respective functions and
Packit Service 21b5d1
    preserves the case of keys.
Packit Service 21b5d1
Packit Service 21b5d1
    The default behavior of ConfigParser:
Packit Service 21b5d1
    >>> conf_string = "[Section1]\nKey = Value"
Packit Service 21b5d1
    >>> default_conf = configparser.ConfigParser()
Packit Service 21b5d1
    >>> default_conf.read_string(conf_string)
Packit Service 21b5d1
    >>> list(default_conf['Section1'].keys())
Packit Service 21b5d1
    ['key']
Packit Service 21b5d1
Packit Service 21b5d1
    This classes' behavior:
Packit Service 21b5d1
    >>> case_preserve = CasePreservingConfigParser()
Packit Service 21b5d1
    >>> case_preserve.read_string(conf_string)
Packit Service 21b5d1
    >>> list(case_preserve['Section1'].keys())
Packit Service 21b5d1
    ['Key']
Packit Service 21b5d1
    """
Packit Service 21b5d1
Packit Service 21b5d1
    def optionxform(self, option):
Packit Service 21b5d1
        return option
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
#: global parameters extracted from the test suite's configuration file
Packit Service 21b5d1
_parameters = {}
Packit Service 21b5d1
Packit Service 21b5d1
#: variables extracted from the test suite's configuration file
Packit Service 21b5d1
_config_variables = {}
Packit Service 21b5d1
Packit Service 21b5d1
#: setting whether debug mode is enabled or not
Packit Service 21b5d1
_debug_mode = False
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
def set_debug_mode(debug):
Packit Service 21b5d1
    """ Enable or disable debug mode
Packit Service 21b5d1
Packit Service 21b5d1
    In debug mode the test suite will print out all commands that it runs, the
Packit Service 21b5d1
    expected output and the actually obtained output
Packit Service 21b5d1
    """
Packit Service 21b5d1
    global _debug_mode
Packit Service 21b5d1
    _debug_mode = debug
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
def configure_suite(config_file):
Packit Service 21b5d1
    """
Packit Service 21b5d1
    Populates a global datastructure with the parameters from the suite's
Packit Service 21b5d1
    configuration file.
Packit Service 21b5d1
Packit Service 21b5d1
    This function performs the following steps:
Packit Service 21b5d1
    1. read in the file ``config_file`` via the ConfigParser module using
Packit Service 21b5d1
       extended interpolation
Packit Service 21b5d1
    2. check that the sections ``variables`` and ``paths`` are disjoint
Packit Service 21b5d1
    3. extract the environment variables given in the ``ENV`` section
Packit Service 21b5d1
    4. save all entries from the ``variables`` section in the global
Packit Service 21b5d1
       datastructure
Packit Service 21b5d1
    5. interpret all entries in the ``paths`` section as relative paths from
Packit Service 21b5d1
       the configuration file, expand them to absolute paths and save them in
Packit Service 21b5d1
       the global datastructure
Packit Service 21b5d1
Packit Service 21b5d1
    For further information concerning the rationale behind this, please
Packit Service 21b5d1
    consult the documentation in ``doc.md``.
Packit Service 21b5d1
    """
Packit Service 21b5d1
Packit Service 21b5d1
    if not os.path.exists(config_file):
Packit Service 21b5d1
        raise ValueError(
Packit Service 21b5d1
            "Test suite config file {:s} does not exist"
Packit Service 21b5d1
            .format(os.path.abspath(config_file))
Packit Service 21b5d1
        )
Packit Service 21b5d1
Packit Service 21b5d1
    config = CasePreservingConfigParser(
Packit Service 21b5d1
        interpolation=configparser.ExtendedInterpolation(),
Packit Service 21b5d1
        delimiters=(':'),
Packit Service 21b5d1
        comment_prefixes=('#')
Packit Service 21b5d1
    )
Packit Service 21b5d1
    config.read(config_file)
Packit Service 21b5d1
Packit Service 21b5d1
    _parameters["suite_root"] = os.path.split(os.path.abspath(config_file))[0]
Packit Service 21b5d1
Packit Service 21b5d1
    if 'variables' in config and 'paths' in config:
Packit Service 21b5d1
        intersecting_keys = set(config["paths"].keys()) \
Packit Service 21b5d1
            .intersection(set(config["variables"].keys()))
Packit Service 21b5d1
        if len(intersecting_keys) > 0:
Packit Service 21b5d1
            raise ValueError(
Packit Service 21b5d1
                "The sections 'paths' and 'variables' must not share keys, "
Packit Service 21b5d1
                "but they have the following common key{:s}: {:s}"
Packit Service 21b5d1
                .format(
Packit Service 21b5d1
                    's' if len(intersecting_keys) > 1 else '',
Packit Service 21b5d1
                    ', '.join(k for k in intersecting_keys)
Packit Service 21b5d1
                )
Packit Service 21b5d1
            )
Packit Service 21b5d1
Packit Service 21b5d1
    # extract variables from the environment
Packit Service 21b5d1
    for key in config['ENV']:
Packit Service 21b5d1
        if key in config['ENV fallback']:
Packit Service 21b5d1
            fallback = config['ENV fallback'][key]
Packit Service 21b5d1
        else:
Packit Service 21b5d1
            fallback = ""
Packit Service 21b5d1
        config['ENV'][key] = os.getenv(config['ENV'][key]) or fallback
Packit Service 21b5d1
Packit Service 21b5d1
    if 'variables' in config:
Packit Service 21b5d1
        for key in config['variables']:
Packit Service 21b5d1
            _config_variables[key] = config['variables'][key]
Packit Service 21b5d1
Packit Service 21b5d1
    if 'paths' in config:
Packit Service 21b5d1
        for key in config['paths']:
Packit Service 21b5d1
            rel_path = config['paths'][key]
Packit Service 21b5d1
            abs_path = os.path.abspath(
Packit Service 21b5d1
                os.path.join(_parameters["suite_root"], rel_path)
Packit Service 21b5d1
            )
Packit Service 21b5d1
            if not os.path.exists(abs_path):
Packit Service 21b5d1
                raise ValueError(
Packit Service 21b5d1
                    "Path replacement for {short}: {abspath} does not exist"
Packit Service 21b5d1
                    " (was expanded from {rel})".format(
Packit Service 21b5d1
                        short=key,
Packit Service 21b5d1
                        abspath=abs_path,
Packit Service 21b5d1
                        rel=rel_path)
Packit Service 21b5d1
                )
Packit Service 21b5d1
            _config_variables[key] = abs_path
Packit Service 21b5d1
Packit Service 21b5d1
    for key in _config_variables:
Packit Service 21b5d1
        if key in globals():
Packit Service 21b5d1
            raise ValueError("Variable name {!s} already used.")
Packit Service 21b5d1
Packit Service 21b5d1
        globals()[key] = _config_variables[key]
Packit Service 21b5d1
Packit Service 21b5d1
    _parameters["timeout"] = config.getfloat(
Packit Service 21b5d1
        "General", "timeout", fallback=1.0
Packit Service 21b5d1
    )
Packit Service 21b5d1
Packit Service 21b5d1
    if 'memcheck' in config['General']:
Packit Service 21b5d1
        if config['General']['memcheck'] != '':
Packit Service 21b5d1
            _parameters['memcheck'] = config['General']['memcheck']
Packit Service 21b5d1
            _parameters["timeout"] *= config.getfloat(
Packit Service 21b5d1
                "General", "memcheck_timeout_penalty", fallback=20.0
Packit Service 21b5d1
            )
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
class FileDecoratorBase(object):
Packit Service 21b5d1
    """
Packit Service 21b5d1
    Base class for decorators that manipulate files for test cases.
Packit Service 21b5d1
Packit Service 21b5d1
    The decorator expects to be provided with at least one file path
Packit Service 21b5d1
    on construction. When called, it replaces the setUp() and
Packit Service 21b5d1
    tearDown() functions of the type it is called on with custom ones.
Packit Service 21b5d1
Packit Service 21b5d1
    The new setUp() function performs the following steps:
Packit Service 21b5d1
    - create a file list in the decorated class with the name stored in
Packit Service 21b5d1
      FILE_LIST_NAME (defaults to _files)
Packit Service 21b5d1
    - iterate over all files, performing:
Packit Service 21b5d1
        - expand the file's path via expand_variables (member function
Packit Service 21b5d1
          of the decorated class)
Packit Service 21b5d1
        - call self.setUp_file_action(expanded file name)
Packit Service 21b5d1
        - append the result to the file list in the decorated class
Packit Service 21b5d1
    - call the old setUp()
Packit Service 21b5d1
Packit Service 21b5d1
    The function self.setUp_file_action is provided by this class and
Packit Service 21b5d1
    is intended to be overridden by child classes to provide some
Packit Service 21b5d1
    functionality, like file copies.
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
    The new tearDown() function performs the following steps:
Packit Service 21b5d1
    - call the old tearDown() function
Packit Service 21b5d1
    - iterate over all files in the file list:
Packit Service 21b5d1
         - call self.tearDown_file_action(filename)
Packit Service 21b5d1
Packit Service 21b5d1
    The function self.tearDown_file_action can be overridden by child
Packit Service 21b5d1
    classes. The default version provided by this class simply deletes
Packit Service 21b5d1
    all files that are passed to it.
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
    Example
Packit Service 21b5d1
    -------
Packit Service 21b5d1
Packit Service 21b5d1
    We'll inherit from FileDecoratorBase and override the member
Packit Service 21b5d1
    functions setUp_file_action and tearDown_file_action:
Packit Service 21b5d1
Packit Service 21b5d1
    >>> class TestDecorator(FileDecoratorBase):
Packit Service 21b5d1
    ...     def setUp_file_action(self, f):
Packit Service 21b5d1
    ...         print("setUp_file_action with", f)
Packit Service 21b5d1
    ...         return f.capitalize()
Packit Service 21b5d1
    ...
Packit Service 21b5d1
    ...     def tearDown_file_action(self, f):
Packit Service 21b5d1
    ...         print("tearDown_file_action with", f)
Packit Service 21b5d1
Packit Service 21b5d1
    Then, we use that decorator to wrap a class mocking
Packit Service 21b5d1
    system_tests.Case:
Packit Service 21b5d1
Packit Service 21b5d1
    >>> @TestDecorator("one", "two", "three")
Packit Service 21b5d1
    ... class MockCase(object):
Packit Service 21b5d1
    ...     def setUp(self):
Packit Service 21b5d1
    ...         print("calling MockCase.setUp()")
Packit Service 21b5d1
    ...
Packit Service 21b5d1
    ...     def tearDown(self):
Packit Service 21b5d1
    ...         print("calling MockCase.tearDown()")
Packit Service 21b5d1
    ...
Packit Service 21b5d1
    ...     def expand_variables(self, var):
Packit Service 21b5d1
    ...         return var + "_file"
Packit Service 21b5d1
Packit Service 21b5d1
    >>> M = MockCase()
Packit Service 21b5d1
Packit Service 21b5d1
    setUp has been replaced by a the new version, but the old one is
Packit Service 21b5d1
    still called. The new setUp iterates over all parameters passed to
Packit Service 21b5d1
    the constructor of the decorator, passes them to expand_variables
Packit Service 21b5d1
    and then to setUp_file_action:
Packit Service 21b5d1
    >>> M.setUp()
Packit Service 21b5d1
    setUp_file_action with one_file
Packit Service 21b5d1
    setUp_file_action with two_file
Packit Service 21b5d1
    setUp_file_action with three_file
Packit Service 21b5d1
    calling MockCase.setUp()
Packit Service 21b5d1
Packit Service 21b5d1
    The tearDown() function works accordingly:
Packit Service 21b5d1
    >>> M.tearDown()
Packit Service 21b5d1
    calling MockCase.tearDown()
Packit Service 21b5d1
    tearDown_file_action with One_file
Packit Service 21b5d1
    tearDown_file_action with Two_file
Packit Service 21b5d1
    tearDown_file_action with Three_file
Packit Service 21b5d1
Packit Service 21b5d1
    Please note the capitalized "file" names (this is due to
Packit Service 21b5d1
    setUp_file_action returning f.capitalized()) and that the old
Packit Service 21b5d1
    tearDown is called after the new one runs.
Packit Service 21b5d1
    """
Packit Service 21b5d1
Packit Service 21b5d1
    #: Name of the attribute in the decorated child class where the list of
Packit Service 21b5d1
    #: files is stored
Packit Service 21b5d1
    FILE_LIST_NAME = '_files'
Packit Service 21b5d1
Packit Service 21b5d1
    def __init__(self, *files):
Packit Service 21b5d1
        """
Packit Service 21b5d1
        Constructor of FileDecoratorBase.
Packit Service 21b5d1
Packit Service 21b5d1
        To prevent accidental wrong usage, it raises an exception if
Packit Service 21b5d1
        it is not called as a decorator with parameters.
Packit Service 21b5d1
Packit Service 21b5d1
        Only the following syntax works for this decorator:
Packit Service 21b5d1
        >>> @FileDecoratorBase("test")
Packit Service 21b5d1
        ... class Test(unittest.TestCase):
Packit Service 21b5d1
        ...     pass
Packit Service 21b5d1
Packit Service 21b5d1
        Calling it without parameters or without parenthesis raises an
Packit Service 21b5d1
        exception:
Packit Service 21b5d1
        >>> @FileDecoratorBase()
Packit Service 21b5d1
        ... class Test(unittest.TestCase):
Packit Service 21b5d1
        ...     pass
Packit Service 21b5d1
        Traceback (most recent call last):
Packit Service 21b5d1
         ..
Packit Service 21b5d1
        ValueError: No files supplied.
Packit Service 21b5d1
Packit Service 21b5d1
        >>> @FileDecoratorBase
Packit Service 21b5d1
        ... class Test(unittest.TestCase):
Packit Service 21b5d1
        ...     pass
Packit Service 21b5d1
        Traceback (most recent call last):
Packit Service 21b5d1
         ..
Packit Service 21b5d1
        UserWarning: Decorator used wrongly, must be called with filenames in parenthesis
Packit Service 21b5d1
        """
Packit Service 21b5d1
        if len(files) == 0:
Packit Service 21b5d1
            raise ValueError("No files supplied.")
Packit Service 21b5d1
        elif len(files) == 1:
Packit Service 21b5d1
            if isinstance(files[0], type):
Packit Service 21b5d1
                raise UserWarning(
Packit Service 21b5d1
                    "Decorator used wrongly, must be called with "
Packit Service 21b5d1
                    "filenames in parenthesis"
Packit Service 21b5d1
                )
Packit Service 21b5d1
Packit Service 21b5d1
        self.files = files
Packit Service 21b5d1
Packit Service 21b5d1
    def new_setUp(self, old_setUp):
Packit Service 21b5d1
        """
Packit Service 21b5d1
        Returns a new setUp() function that can be used as a class
Packit Service 21b5d1
        member function (i.e. invoked via self.setUp()).
Packit Service 21b5d1
Packit Service 21b5d1
        Its functionality is described in this classes' docstring.
Packit Service 21b5d1
        """
Packit Service 21b5d1
Packit Service 21b5d1
        def setUp(other):
Packit Service 21b5d1
            if hasattr(other, self.FILE_LIST_NAME):
Packit Service 21b5d1
                raise TypeError(
Packit Service 21b5d1
                    "{!s} already has an attribute with the name {!s} which "
Packit Service 21b5d1
                    "would be overwritten by setUp()"
Packit Service 21b5d1
                    .format(other, self.FILE_LIST_NAME)
Packit Service 21b5d1
                )
Packit Service 21b5d1
            setattr(other, self.FILE_LIST_NAME, [])
Packit Service 21b5d1
            for f in self.files:
Packit Service 21b5d1
                expanded_fname = other.expand_variables(f)
Packit Service 21b5d1
                getattr(other, self.FILE_LIST_NAME).append(
Packit Service 21b5d1
                    self.setUp_file_action(expanded_fname)
Packit Service 21b5d1
                )
Packit Service 21b5d1
            old_setUp(other)
Packit Service 21b5d1
        return setUp
Packit Service 21b5d1
Packit Service 21b5d1
    def setUp_file_action(self, expanded_file_name):
Packit Service 21b5d1
        """
Packit Service 21b5d1
        This function is called on each file that is passed to the
Packit Service 21b5d1
        constructor during the call of the decorated class' setUp().
Packit Service 21b5d1
Packit Service 21b5d1
        Parameters:
Packit Service 21b5d1
        - expanded_file_name: the file's path expanded via
Packit Service 21b5d1
                              expand_variables from system_tests.Case
Packit Service 21b5d1
Packit Service 21b5d1
        Returns:
Packit Service 21b5d1
        This function should return a path that will be stored in the decorated
Packit Service 21b5d1
        class' file list (the name is given by the attribute
Packit Service 21b5d1
        FILE_LIST_NAME). The custom tearDown() function (that is returned by
Packit Service 21b5d1
        self.new_tearDown()) iterates over this list and invokes
Packit Service 21b5d1
        self.tearDown_file_action on each element in that list.
Packit Service 21b5d1
        E.g. if a child class creates file copies, that should be deleted after
Packit Service 21b5d1
        the test ran, then one would have to return the path of the copy, so
Packit Service 21b5d1
        that tearDown() can delete the copies.
Packit Service 21b5d1
Packit Service 21b5d1
        The default implementation does nothing.
Packit Service 21b5d1
        """
Packit Service 21b5d1
        pass
Packit Service 21b5d1
Packit Service 21b5d1
    def new_tearDown(self, old_tearDown):
Packit Service 21b5d1
        """
Packit Service 21b5d1
        Returns a new tearDown() function that can be used as a class
Packit Service 21b5d1
        member function.
Packit Service 21b5d1
Packit Service 21b5d1
        It's functionality is described in this classes' docstring.
Packit Service 21b5d1
        """
Packit Service 21b5d1
Packit Service 21b5d1
        def tearDown(other):
Packit Service 21b5d1
            old_tearDown(other)
Packit Service 21b5d1
            for f in getattr(other, self.FILE_LIST_NAME):
Packit Service 21b5d1
                self.tearDown_file_action(f)
Packit Service 21b5d1
Packit Service 21b5d1
        return tearDown
Packit Service 21b5d1
Packit Service 21b5d1
    def tearDown_file_action(self, f):
Packit Service 21b5d1
        """
Packit Service 21b5d1
        This function is called on each file in the decorated class'
Packit Service 21b5d1
        file list (that list is populated during setUp()).
Packit Service 21b5d1
Packit Service 21b5d1
        It can be used to perform cleanup operations after a test run.
Packit Service 21b5d1
Packit Service 21b5d1
        Parameters:
Packit Service 21b5d1
        - f: An element of the file list
Packit Service 21b5d1
Packit Service 21b5d1
        Returns:
Packit Service 21b5d1
        The return value is ignored
Packit Service 21b5d1
Packit Service 21b5d1
        The default implementation removes f.
Packit Service 21b5d1
        """
Packit Service 21b5d1
        os.remove(f)
Packit Service 21b5d1
Packit Service 21b5d1
    def __call__(self, cls):
Packit Service 21b5d1
        """
Packit Service 21b5d1
        Call operator for the usage as a decorator. It is
Packit Service 21b5d1
        automatically used by Python when this class is used as a
Packit Service 21b5d1
        decorator.
Packit Service 21b5d1
Packit Service 21b5d1
        Parameters:
Packit Service 21b5d1
        - cls: The decorated type. Must be a type
Packit Service 21b5d1
Packit Service 21b5d1
        Returns:
Packit Service 21b5d1
        - cls where the setUp and tearDown functions have been
Packit Service 21b5d1
          replaced by the functions that are returned by
Packit Service 21b5d1
          self.new_setUp() and self.new_tearDown()
Packit Service 21b5d1
        """
Packit Service 21b5d1
        if not isinstance(cls, type):
Packit Service 21b5d1
            raise ValueError("The decorator must be called on a type")
Packit Service 21b5d1
        old_setUp = cls.setUp
Packit Service 21b5d1
        cls.setUp = self.new_setUp(old_setUp)
Packit Service 21b5d1
Packit Service 21b5d1
        old_tearDown = cls.tearDown
Packit Service 21b5d1
        cls.tearDown = self.new_tearDown(old_tearDown)
Packit Service 21b5d1
Packit Service 21b5d1
        return cls
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
class CopyFiles(FileDecoratorBase):
Packit Service 21b5d1
    """
Packit Service 21b5d1
    Decorator for subclasses of system_test.Case that automatically creates a
Packit Service 21b5d1
    copy of the files specified as the parameters passed to the decorator.
Packit Service 21b5d1
Packit Service 21b5d1
    Example:
Packit Service 21b5d1
    >>> @CopyFiles("$some_var/file.txt", "$another_var/other_file.png")
Packit Service 21b5d1
    ... class Foo(Case):
Packit Service 21b5d1
    ...     pass
Packit Service 21b5d1
Packit Service 21b5d1
    The decorator will inject a new setUp method that at first calls the
Packit Service 21b5d1
    already defined setUp(), then expands all supplied file names using
Packit Service 21b5d1
    Case.expand_variables and then creates copies by appending '_copy' before
Packit Service 21b5d1
    the file extension. The paths to the copies are stored in
Packit Service 21b5d1
    self._copied_files.
Packit Service 21b5d1
Packit Service 21b5d1
    The decorator also injects a new tearDown method that deletes all files in
Packit Service 21b5d1
    self._files and then calls the original tearDown method.
Packit Service 21b5d1
Packit Service 21b5d1
    This function will also complain if it is called without arguments or
Packit Service 21b5d1
    without parenthesis, which is valid decorator syntax but is obviously a bug
Packit Service 21b5d1
    in this case as it can result in tests not being run without a warning.
Packit Service 21b5d1
    """
Packit Service 21b5d1
Packit Service 21b5d1
    #: override the name of the file list
Packit Service 21b5d1
    FILE_LIST_NAME = '_copied_files'
Packit Service 21b5d1
Packit Service 21b5d1
    def setUp_file_action(self, expanded_file_name):
Packit Service 21b5d1
        fname, ext = os.path.splitext(expanded_file_name)
Packit Service 21b5d1
        new_name = fname + '_copy' + ext
Packit Service 21b5d1
        return shutil.copyfile(expanded_file_name, new_name)
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
class DeleteFiles(FileDecoratorBase):
Packit Service 21b5d1
    """
Packit Service 21b5d1
    Decorator for subclasses of system_test.Case that automatically deletes all
Packit Service 21b5d1
    files specified as the parameters passed to the decorator after the test
Packit Service 21b5d1
    were run.
Packit Service 21b5d1
Packit Service 21b5d1
    Example:
Packit Service 21b5d1
    >>> @DeleteFiles("$some_var/an_output_file", "auxiliary_output.bin")
Packit Service 21b5d1
    ... class Foo(Case):
Packit Service 21b5d1
    ...     pass
Packit Service 21b5d1
Packit Service 21b5d1
    The decorator injects new setUp() and tearDown() functions. The new setUp()
Packit Service 21b5d1
    at first calls the old setUp() and then saves all files that should be
Packit Service 21b5d1
    deleted later in self._files_to_delete. The new tearDown() actually deletes
Packit Service 21b5d1
    all files supplied to the decorator and then runs the original tearDown()
Packit Service 21b5d1
    function.
Packit Service 21b5d1
    """
Packit Service 21b5d1
Packit Service 21b5d1
    #: override the name of the file list
Packit Service 21b5d1
    FILE_LIST_NAME = '_files_to_delete'
Packit Service 21b5d1
Packit Service 21b5d1
    def setUp_file_action(self, expanded_file_name):
Packit Service 21b5d1
        return expanded_file_name
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
def path(path_string):
Packit Service 21b5d1
    r"""
Packit Service 21b5d1
    Converts a path which uses ``/`` as a separator into a path which uses the
Packit Service 21b5d1
    path separator of the current operating system.
Packit Service 21b5d1
Packit Service 21b5d1
    Example
Packit Service 21b5d1
    -------
Packit Service 21b5d1
Packit Service 21b5d1
    >>> import platform
Packit Service 21b5d1
    >>> sep = "\\" if platform.system() == "Windows" else "/"
Packit Service 21b5d1
    >>> path("a/b") == "a" + sep + "b"
Packit Service 21b5d1
    True
Packit Service 21b5d1
    >>> path("a/more/complex/path") == sep.join(['a', 'more', 'complex', 'path'])
Packit Service 21b5d1
    True
Packit Service 21b5d1
    """
Packit Service 21b5d1
    return os.path.join(*path_string.split('/'))
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
def test_run(self):
Packit Service 21b5d1
    """
Packit Service 21b5d1
    This function reads in the attributes commands, retval, stdout, stderr,
Packit Service 21b5d1
    stdin and runs the `expand_variables` function on each. The resulting
Packit Service 21b5d1
    commands are then run using the subprocess module and compared against the
Packit Service 21b5d1
    expected values that were provided in the attributes via `compare_stdout`
Packit Service 21b5d1
    and `compare_stderr`. Furthermore a threading.Timer is used to abort the
Packit Service 21b5d1
    execution if a configured timeout is reached.
Packit Service 21b5d1
Packit Service 21b5d1
    This function is automatically added as a member function to each system
Packit Service 21b5d1
    test by the CaseMeta metaclass. This ensures that it is run by each system
Packit Service 21b5d1
    test **after** setUp() and setUpClass() were run.
Packit Service 21b5d1
    """
Packit Service 21b5d1
    if not (len(self.commands) == len(self.retval)
Packit Service 21b5d1
            == len(self.stdout) == len(self.stderr) == len(self.stdin)):
Packit Service 21b5d1
        raise ValueError(
Packit Service 21b5d1
            "commands, retval, stdout, stderr and stdin don't have the same "
Packit Service 21b5d1
            "length"
Packit Service 21b5d1
        )
Packit Service 21b5d1
Packit Service 21b5d1
    for i, command, retval, stdout, stderr, stdin in \
Packit Service 21b5d1
        zip(range(len(self.commands)),
Packit Service 21b5d1
            self.commands,
Packit Service 21b5d1
            self.retval,
Packit Service 21b5d1
            self.stdout,
Packit Service 21b5d1
            self.stderr,
Packit Service 21b5d1
            self.stdin):
Packit Service 21b5d1
        command, retval, stdout, stderr, stdin = [
Packit Service 21b5d1
            self.expand_variables(var) for var in
Packit Service 21b5d1
            (command, retval, stdout, stderr, stdin)
Packit Service 21b5d1
        ]
Packit Service 21b5d1
Packit Service 21b5d1
        retval = int(retval)
Packit Service 21b5d1
Packit Service 21b5d1
        if "memcheck" in _parameters:
Packit Service 21b5d1
            command = _parameters["memcheck"] + " " + command
Packit Service 21b5d1
Packit Service 21b5d1
        if _debug_mode:
Packit Service 21b5d1
            print(
Packit Service 21b5d1
                '', "="*80, "will run: " + command, "expected stdout:", stdout,
Packit Service 21b5d1
                "expected stderr:", stderr,
Packit Service 21b5d1
                "expected return value: {:d}".format(retval),
Packit Service 21b5d1
                sep='\n'
Packit Service 21b5d1
            )
Packit Service 21b5d1
Packit Service 21b5d1
        proc = subprocess.Popen(
Packit Service 21b5d1
            _cmd_splitter(command),
Packit Service 21b5d1
            stdout=subprocess.PIPE,
Packit Service 21b5d1
            stderr=subprocess.PIPE,
Packit Service 21b5d1
            stdin=subprocess.PIPE if stdin is not None else None,
Packit Service 21b5d1
            env=self._get_env(),
Packit Service 21b5d1
            cwd=self.work_dir,
Packit Service 21b5d1
            shell=_SUBPROCESS_SHELL
Packit Service 21b5d1
        )
Packit Service 21b5d1
Packit Service 21b5d1
        # Setup a threading.Timer which will terminate the command if it takes
Packit Service 21b5d1
        # too long. Don't use the timeout parameter in subprocess.Popen, since
Packit Service 21b5d1
        # that is not available for all Python 3 versions.
Packit Service 21b5d1
        # Use a dictionary to indicate a timeout, as booleans get passed by
Packit Service 21b5d1
        # value and the changes made timeout_reached function will not be
Packit Service 21b5d1
        # visible once it exits (the command will still be terminated once the
Packit Service 21b5d1
        # timeout expires).
Packit Service 21b5d1
        timeout = {"flag": False}
Packit Service 21b5d1
Packit Service 21b5d1
        def timeout_reached(tmout):
Packit Service 21b5d1
            tmout["flag"] = True
Packit Service 21b5d1
            proc.kill()
Packit Service 21b5d1
Packit Service 21b5d1
        t = threading.Timer(
Packit Service 21b5d1
            _parameters["timeout"], timeout_reached, args=[timeout]
Packit Service 21b5d1
        )
Packit Service 21b5d1
Packit Service 21b5d1
        def get_encode_err():
Packit Service 21b5d1
            """ Return an error message indicating that the encoding of stdin
Packit Service 21b5d1
            failed.
Packit Service 21b5d1
            """
Packit Service 21b5d1
            return "Could not encode stdin {!s} for the command {!s} with the"\
Packit Service 21b5d1
                " following encodings: {!s}"\
Packit Service 21b5d1
                .format(stdin, command, ','.join(self.encodings))
Packit Service 21b5d1
Packit Service 21b5d1
        # Prepare stdin: try to encode it or keep it at None if it was not
Packit Service 21b5d1
        # provided
Packit Service 21b5d1
        encoded_stdin = None
Packit Service 21b5d1
        if stdin is not None:
Packit Service 21b5d1
            encoded_stdin = self._encode(
Packit Service 21b5d1
                stdin, lambda data_in, encoding: data_in.encode(encoding),
Packit Service 21b5d1
                get_encode_err
Packit Service 21b5d1
            )
Packit Service 21b5d1
Packit Service 21b5d1
        if _debug_mode:
Packit Service 21b5d1
            print('', "stdin:", stdin or "", sep='\n')
Packit Service 21b5d1
Packit Service 21b5d1
        t.start()
Packit Service 21b5d1
        got_stdout, got_stderr = proc.communicate(input=encoded_stdin)
Packit Service 21b5d1
        t.cancel()
Packit Service 21b5d1
Packit Service 21b5d1
        def get_decode_error():
Packit Service 21b5d1
            """ Return an error indicating the the decoding of stdout/stderr
Packit Service 21b5d1
            failed.
Packit Service 21b5d1
            """
Packit Service 21b5d1
            return "Could not decode the output of the command '{!s}' with "\
Packit Service 21b5d1
                "the following encodings: {!s}"\
Packit Service 21b5d1
                .format(command, ','.join(self.encodings))
Packit Service 21b5d1
Packit Service 21b5d1
        def decode_output(data_in, encoding):
Packit Service 21b5d1
            """ Decode stdout/stderr, consider platform dependent line
Packit Service 21b5d1
            endings.
Packit Service 21b5d1
            """
Packit Service 21b5d1
            return _process_output_post(data_in.decode(encoding))
Packit Service 21b5d1
Packit Service 21b5d1
        processed_stdout, processed_stderr = [
Packit Service 21b5d1
            self._encode(output, decode_output, get_decode_error)
Packit Service 21b5d1
            for output in (got_stdout, got_stderr)
Packit Service 21b5d1
        ]
Packit Service 21b5d1
Packit Service 21b5d1
        if _debug_mode:
Packit Service 21b5d1
            print(
Packit Service 21b5d1
                "got stdout:", processed_stdout, "got stderr:",
Packit Service 21b5d1
                processed_stderr, "got return value: {:d}"
Packit Service 21b5d1
                .format(proc.returncode),
Packit Service 21b5d1
                sep='\n'
Packit Service 21b5d1
            )
Packit Service 21b5d1
Packit Service 21b5d1
        self.assertFalse(timeout["flag"], msg="Timeout reached")
Packit Service 21b5d1
        self.compare_stderr(i, command, processed_stderr, stderr)
Packit Service 21b5d1
        self.compare_stdout(i, command, processed_stdout, stdout)
Packit Service 21b5d1
        self.assertEqual(
Packit Service 21b5d1
            retval, proc.returncode, msg="Return value does not match"
Packit Service 21b5d1
        )
Packit Service 21b5d1
Packit Service 21b5d1
        self.post_command_hook(i, command)
Packit Service 21b5d1
Packit Service 21b5d1
    self.post_tests_hook()
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
class Case(unittest.TestCase):
Packit Service 21b5d1
    """
Packit Service 21b5d1
    System test case base class, provides the functionality to interpret static
Packit Service 21b5d1
    class members as system tests.
Packit Service 21b5d1
Packit Service 21b5d1
    The class itself only provides utility functions and system tests need not
Packit Service 21b5d1
    inherit from it, as it is automatically added via the CaseMeta metaclass.
Packit Service 21b5d1
    """
Packit Service 21b5d1
Packit Service 21b5d1
    #: maxDiff set so that arbitrarily large diffs will be shown
Packit Service 21b5d1
    maxDiff = None
Packit Service 21b5d1
Packit Service 21b5d1
    #: list of encodings that are used to decode the test program's output
Packit Service 21b5d1
    #: the first encoding that does not raise a UnicodeError is used
Packit Service 21b5d1
    encodings = ['utf-8', 'iso-8859-1']
Packit Service 21b5d1
Packit Service 21b5d1
    inherit_env = True
Packit Service 21b5d1
Packit Service 21b5d1
    @classmethod
Packit Service 21b5d1
    def setUpClass(cls):
Packit Service 21b5d1
        """
Packit Service 21b5d1
        This function adds the variable work_dir to the class, which is the
Packit Service 21b5d1
        path to the directory where the python source file is located.
Packit Service 21b5d1
        """
Packit Service 21b5d1
        cls.work_dir = os.path.dirname(inspect.getfile(cls))
Packit Service 21b5d1
Packit Service 21b5d1
    def _get_env(self):
Packit Service 21b5d1
        """ Return an appropriate env value for subprocess.Popen.
Packit Service 21b5d1
Packit Service 21b5d1
        This function returns either an appropriately populated dictionary or
Packit Service 21b5d1
        None (the latter if this class has no attribute env). If a dictionary
Packit Service 21b5d1
        is returned, then it will be either exactly self.env (when inherit_env
Packit Service 21b5d1
        is False) or a copy of the current environment merged with self.env
Packit Service 21b5d1
        (the values from self.env take precedence).
Packit Service 21b5d1
        """
Packit Service 21b5d1
        if not hasattr(self, "env"):
Packit Service 21b5d1
            return None
Packit Service 21b5d1
Packit Service 21b5d1
        if not self.inherit_env:
Packit Service 21b5d1
            return self.env
Packit Service 21b5d1
Packit Service 21b5d1
        env_copy = os.environ.copy()
Packit Service 21b5d1
        for key in self.env:
Packit Service 21b5d1
            env_copy[key] = self.env[key]
Packit Service 21b5d1
Packit Service 21b5d1
        return env_copy
Packit Service 21b5d1
Packit Service 21b5d1
    def _encode(self, data_in, encode_action, get_err):
Packit Service 21b5d1
        """
Packit Service 21b5d1
        Try to convert data_in via encode_action using the encodings in
Packit Service 21b5d1
        self.encodings.
Packit Service 21b5d1
Packit Service 21b5d1
        This function tries all encodings in self.encodings to run
Packit Service 21b5d1
        encode_action with the parameters (data_in, encoding). If encode_action
Packit Service 21b5d1
        raises a UnicodeError, the next encoding is used, otherwise the result
Packit Service 21b5d1
        of encode_action is returned. If an encoding is equal to the type
Packit Service 21b5d1
        bytes, then data_in is returned unmodified.
Packit Service 21b5d1
Packit Service 21b5d1
        If all encodings result in a UnicodeError, then the conversion is
Packit Service 21b5d1
        considered unsuccessful and get_err() is called to obtain an error
Packit Service 21b5d1
        string which is raised as a ValueError.
Packit Service 21b5d1
        """
Packit Service 21b5d1
        result = None
Packit Service 21b5d1
        for encoding in self.encodings:
Packit Service 21b5d1
            if encoding == bytes:
Packit Service 21b5d1
                return data_in
Packit Service 21b5d1
            try:
Packit Service 21b5d1
                result = encode_action(data_in, encoding)
Packit Service 21b5d1
            except UnicodeError:
Packit Service 21b5d1
                pass
Packit Service 21b5d1
            else:
Packit Service 21b5d1
                break
Packit Service 21b5d1
        if result is None:
Packit Service 21b5d1
            raise ValueError(get_err())
Packit Service 21b5d1
Packit Service 21b5d1
        return result
Packit Service 21b5d1
Packit Service 21b5d1
    def _compare_output(self, i, command, got, expected, msg=None):
Packit Service 21b5d1
        """ Compares the expected and actual output of a test case. """
Packit Service 21b5d1
        if isinstance(got, bytes):
Packit Service 21b5d1
            self.assertEqual(got, expected, msg=msg)
Packit Service 21b5d1
        else:
Packit Service 21b5d1
            self.assertMultiLineEqual(
Packit Service 21b5d1
                expected, got, msg=msg
Packit Service 21b5d1
            )
Packit Service 21b5d1
Packit Service 21b5d1
    def compare_stdout(self, i, command, got_stdout, expected_stdout):
Packit Service 21b5d1
        """
Packit Service 21b5d1
        Function to compare whether the expected & obtained stdout match.
Packit Service 21b5d1
Packit Service 21b5d1
        This function is automatically invoked by test_run with the following
Packit Service 21b5d1
        parameters:
Packit Service 21b5d1
        i - the index of the current command that is run in self.commands
Packit Service 21b5d1
        command - the command that was run
Packit Service 21b5d1
        got_stdout - the obtained stdout, post-processed depending on the
Packit Service 21b5d1
                     platform so that lines always end with \n
Packit Service 21b5d1
        expected_stdout - the expected stdout extracted from self.stdout
Packit Service 21b5d1
Packit Service 21b5d1
        The default implementation uses assertMultiLineEqual from
Packit Service 21b5d1
        unittest.TestCase for ordinary strings and assertEqual for binary
Packit Service 21b5d1
        output. This function can be overridden in a child class to implement a
Packit Service 21b5d1
        custom check.
Packit Service 21b5d1
        """
Packit Service 21b5d1
        self._compare_output(
Packit Service 21b5d1
            i, command, expected_stdout, got_stdout,
Packit Service 21b5d1
            msg="Standard output does not match"
Packit Service 21b5d1
        )
Packit Service 21b5d1
Packit Service 21b5d1
    def compare_stderr(self, i, command, got_stderr, expected_stderr):
Packit Service 21b5d1
        """ Same as compare_stdout only for standard-error. """
Packit Service 21b5d1
        self._compare_output(
Packit Service 21b5d1
            i, command, expected_stderr, got_stderr,
Packit Service 21b5d1
            msg="Standard error does not match"
Packit Service 21b5d1
        )
Packit Service 21b5d1
Packit Service 21b5d1
    def expand_variables(self, unexpanded_string):
Packit Service 21b5d1
        """
Packit Service 21b5d1
        Expands all variables of the form ``$var`` in the given string using
Packit Service 21b5d1
        the dictionary `variable_dict`.
Packit Service 21b5d1
Packit Service 21b5d1
        The expansion itself is performed by the string's template module using
Packit Service 21b5d1
        the function `safe_substitute`.
Packit Service 21b5d1
Packit Service 21b5d1
        If unexpanded_string is of the type bytes, then no expansion is
Packit Service 21b5d1
        performed.
Packit Service 21b5d1
        """
Packit Service 21b5d1
        if isinstance(unexpanded_string, bytes) or unexpanded_string is None:
Packit Service 21b5d1
            return unexpanded_string
Packit Service 21b5d1
Packit Service 21b5d1
        return string.Template(str(unexpanded_string))\
Packit Service 21b5d1
            .safe_substitute(**self.variable_dict)
Packit Service 21b5d1
Packit Service 21b5d1
    def post_command_hook(self, i, command):
Packit Service 21b5d1
        """ Function that is run after the successful execution of one command.
Packit Service 21b5d1
Packit Service 21b5d1
        It is invoked with the following parameters:
Packit Service 21b5d1
        i - the index of the current command that is run in self.commands
Packit Service 21b5d1
        command - the command that was run
Packit Service 21b5d1
Packit Service 21b5d1
        It should return nothing.
Packit Service 21b5d1
Packit Service 21b5d1
        This function can be overridden to perform additional checks after the
Packit Service 21b5d1
        command ran, for instance it can check whether files were created.
Packit Service 21b5d1
Packit Service 21b5d1
        The default implementation does nothing.
Packit Service 21b5d1
        """
Packit Service 21b5d1
        pass
Packit Service 21b5d1
Packit Service 21b5d1
    def post_tests_hook(self):
Packit Service 21b5d1
        """
Packit Service 21b5d1
        Function that is run after the successful execution all commands. It
Packit Service 21b5d1
        should return nothing.
Packit Service 21b5d1
Packit Service 21b5d1
        This function can be overridden to run additional checks that only make
Packit Service 21b5d1
        sense after all commands ran.
Packit Service 21b5d1
Packit Service 21b5d1
        The default implementation does nothing.
Packit Service 21b5d1
        """
Packit Service 21b5d1
        pass
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
class CaseMeta(type):
Packit Service 21b5d1
    """ System tests generation metaclass.
Packit Service 21b5d1
Packit Service 21b5d1
    This metaclass is performs the following tasks:
Packit Service 21b5d1
Packit Service 21b5d1
    1. Add the `test_run` function as a member of the test class
Packit Service 21b5d1
    2. Add the `Case` class as the parent class
Packit Service 21b5d1
    3. Expand all variables already defined in the class, so that any
Packit Service 21b5d1
       additional code does not have to perform this task
Packit Service 21b5d1
Packit Service 21b5d1
    Using a metaclass instead of inheriting from Case has the advantage, that
Packit Service 21b5d1
    we can expand all variables in the strings before any test case or even the
Packit Service 21b5d1
    class constructor is run! Thus users will immediately see the expanded
Packit Service 21b5d1
    result. Also adding the `test_run` function as a direct member and not via
Packit Service 21b5d1
    inheritance enforces that it is being run **after** the test cases setUp &
Packit Service 21b5d1
    setUpClass (which oddly enough seems not to be the case in the unittest
Packit Service 21b5d1
    module where test functions of the parent class run before setUpClass of
Packit Service 21b5d1
    the child class).
Packit Service 21b5d1
    """
Packit Service 21b5d1
Packit Service 21b5d1
    def __new__(mcs, clsname, bases, dct):
Packit Service 21b5d1
Packit Service 21b5d1
        assert len(_parameters) != 0, \
Packit Service 21b5d1
            "Internal error: substitution dictionary not populated"
Packit Service 21b5d1
Packit Service 21b5d1
        changed = True
Packit Service 21b5d1
Packit Service 21b5d1
        # expand all non-private variables by brute force
Packit Service 21b5d1
        # => try all expanding all elements defined in the current class until
Packit Service 21b5d1
        # there is no change in them any more
Packit Service 21b5d1
        keys = [key for key in list(dct.keys()) if not key.startswith('_')]
Packit Service 21b5d1
        while changed:
Packit Service 21b5d1
            changed = False
Packit Service 21b5d1
Packit Service 21b5d1
            for key in keys:
Packit Service 21b5d1
Packit Service 21b5d1
                old_value = dct[key]
Packit Service 21b5d1
Packit Service 21b5d1
                # only try expanding strings and lists
Packit Service 21b5d1
                if isinstance(old_value, str):
Packit Service 21b5d1
                    new_value = string.Template(old_value).safe_substitute(
Packit Service 21b5d1
                        **_disjoint_dict_merge(dct, _config_variables)
Packit Service 21b5d1
                    )
Packit Service 21b5d1
                elif isinstance(old_value, list):
Packit Service 21b5d1
                    # do not try to expand anything but strings in the list
Packit Service 21b5d1
                    new_value = [
Packit Service 21b5d1
                        string.Template(elem).safe_substitute(
Packit Service 21b5d1
                            **_disjoint_dict_merge(dct, _config_variables)
Packit Service 21b5d1
                        )
Packit Service 21b5d1
                        if isinstance(elem, str) else elem
Packit Service 21b5d1
                        for elem in old_value
Packit Service 21b5d1
                    ]
Packit Service 21b5d1
                else:
Packit Service 21b5d1
                    continue
Packit Service 21b5d1
Packit Service 21b5d1
                if old_value != new_value:
Packit Service 21b5d1
                    changed = True
Packit Service 21b5d1
                    dct[key] = new_value
Packit Service 21b5d1
Packit Service 21b5d1
        dct['variable_dict'] = _disjoint_dict_merge(dct, _config_variables)
Packit Service 21b5d1
        dct['test_run'] = test_run
Packit Service 21b5d1
Packit Service 21b5d1
        if Case not in bases:
Packit Service 21b5d1
            bases += (Case,)
Packit Service 21b5d1
Packit Service 21b5d1
        CaseMeta.add_default_values(clsname, dct)
Packit Service 21b5d1
Packit Service 21b5d1
        return super(CaseMeta, mcs).__new__(mcs, clsname, bases, dct)
Packit Service 21b5d1
Packit Service 21b5d1
    @staticmethod
Packit Service 21b5d1
    def add_default_values(clsname, dct):
Packit Service 21b5d1
        if 'commands' not in dct:
Packit Service 21b5d1
            raise ValueError(
Packit Service 21b5d1
                "No member 'commands' in class {!s}.".format(clsname)
Packit Service 21b5d1
            )
Packit Service 21b5d1
Packit Service 21b5d1
        cmd_length = len(dct['commands'])
Packit Service 21b5d1
Packit Service 21b5d1
        for member, default in zip(
Packit Service 21b5d1
                ('stderr', 'stdout', 'stdin', 'retval'),
Packit Service 21b5d1
                ('', '', None, 0)):
Packit Service 21b5d1
            if member not in dct:
Packit Service 21b5d1
                dct[member] = [default] * cmd_length
Packit Service 21b5d1
Packit Service 21b5d1
Packit Service 21b5d1
def check_no_ASAN_UBSAN_errors(self, i, command, got_stderr, expected_stderr):
Packit Service 21b5d1
    """
Packit Service 21b5d1
    Drop-in replacement for the default Case.compare_stderr() function that
Packit Service 21b5d1
    **only** checks for any signs of ASAN (address sanitizer) and UBSAN
Packit Service 21b5d1
    (undefined behavior sanitizer).
Packit Service 21b5d1
Packit Service 21b5d1
    Parameters:
Packit Service 21b5d1
    - i, command, expected_stderr: ignored
Packit Service 21b5d1
    - got_stderr: checked for signs of ASAN und UBSAN error messages
Packit Service 21b5d1
Packit Service 21b5d1
    This function ignores the expected output to stderr! It is intended for
Packit Service 21b5d1
    test cases where standard error is filled with useless debugging
Packit Service 21b5d1
    messages/warnings that are not really relevant for the test and not worth
Packit Service 21b5d1
    storing in the test suite. This function can be used to still be able to
Packit Service 21b5d1
    catch ASAN & UBSAN error messages.
Packit Service 21b5d1
Packit Service 21b5d1
    Example usage
Packit Service 21b5d1
    -------------
Packit Service 21b5d1
Packit Service 21b5d1
    Override the default compare_stderr function in your subclass of Case with
Packit Service 21b5d1
    this function:
Packit Service 21b5d1
    >>> class TestNoAsan(Case):
Packit Service 21b5d1
    ...     compare_stderr = check_no_ASAN_UBSAN_errors
Packit Service 21b5d1
Packit Service 21b5d1
    >>> T = TestNoAsan()
Packit Service 21b5d1
Packit Service 21b5d1
    The new compare_stderr will only complain if there are strings inside the
Packit Service 21b5d1
    obtained stderr which could be an error reported by ASAN/UBSAN:
Packit Service 21b5d1
    >>> T.compare_stderr(0, "", "runtime error: load of value 190", "some output")
Packit Service 21b5d1
    Traceback (most recent call last):
Packit Service 21b5d1
     ..
Packit Service 21b5d1
    AssertionError: 'runtime error' unexpectedly found in 'runtime error: load of value 190'
Packit Service 21b5d1
Packit Service 21b5d1
    >>> T.compare_stderr(0, "", "SUMMARY: AddressSanitizer: heap-buffer-overflow", "")
Packit Service 21b5d1
    Traceback (most recent call last):
Packit Service 21b5d1
     ..
Packit Service 21b5d1
    AssertionError: 'AddressSanitizer' unexpectedly found in 'SUMMARY: AddressSanitizer: heap-buffer-overflow'
Packit Service 21b5d1
Packit Service 21b5d1
    It will not complain in all other cases, especially when expected_stderr
Packit Service 21b5d1
    and got_stderr do not match:
Packit Service 21b5d1
    >>> T.compare_stderr(0, "", "some output", "other output")
Packit Service 21b5d1
Packit Service 21b5d1
    This function also supports binary output:
Packit Service 21b5d1
    >>> ASAN_ERROR = bytes("SUMMARY: AddressSanitizer: heap-buffer-overflow", encoding='ascii')
Packit Service 21b5d1
    >>> T.compare_stderr(0, "", ASAN_ERROR, "other output")
Packit Service 21b5d1
    Traceback (most recent call last):
Packit Service 21b5d1
     ..
Packit Service 21b5d1
    AssertionError: b'AddressSanitizer' unexpectedly found in b'SUMMARY: AddressSanitizer: heap-buffer-overflow'
Packit Service 21b5d1
    """
Packit Service 21b5d1
    UBSAN_MSG = "runtime error"
Packit Service 21b5d1
    ASAN_MSG = "AddressSanitizer"
Packit Service 21b5d1
Packit Service 21b5d1
    if isinstance(got_stderr, bytes):
Packit Service 21b5d1
        self.assertNotIn(UBSAN_MSG.encode('ascii'), got_stderr)
Packit Service 21b5d1
        self.assertNotIn(ASAN_MSG.encode('ascii'), got_stderr)
Packit Service 21b5d1
        return
Packit Service 21b5d1
Packit Service 21b5d1
    self.assertNotIn(UBSAN_MSG, got_stderr)
Packit Service 21b5d1
    self.assertNotIn(ASAN_MSG, got_stderr)