|
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)
|