# -*- coding: utf-8 -*-
# Copyright: SCLE SFE
# Contributor: Julien Pagès <j.parkouss@gmail.com>
#
# This software is a computer program whose purpose is to test graphical
# applications written with the QT framework (http://qt.digia.com/).
#
# This software is governed by the CeCILL v2.1 license under French law and
# abiding by the rules of distribution of free software. You can use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty and the software's author, the holder of the
# economic rights, and the successive licensors have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading, using, modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean that it is complicated to manipulate, and that also
# therefore means that it is reserved for developers and experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and, more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL v2.1 license and that you accept its terms.
"""
funq integration in TestCase subclasses.
"""
import unittest
import weakref
from functools import wraps
from funq.client import ApplicationContext
import os
import re
import inspect
[docs]class AssertionSuccessError(AssertionError):
"""
Exception which will be raised if method decorated with :func:`todo`
pass (it is not expected).
:param name: error message.
"""
def __init__(self, name):
super(AssertionSuccessError, self).__init__()
self.name = name
def __str__(self):
return u"Test %s passed but it is decorated as TODO" % self.name
def __rep__(self):
return self.__str__()
[docs]def todo(skip_message, exception_cls=AssertionError):
"""
A decorator to skip a test on given exception types. If the decorated
test pass, an exception :class:`AssertionSuccessError` will be thrown.
It is possible to specify which type of Exception is handled with the
**exception_cls** argument.
Example::
class MyTestCase(FunqTestCase):
__app_config_name__ = 'ma_conf'
@todo("this test needs something to pass")
def test_one(self):
raise AssertionError('this will fail')
:param skip_message: error message when test is skipped
:param exception_cls: Exception type or tuple of Exception type that are
handled to skip a test.
"""
def wrapped(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except exception_cls as err:
err = u"%s" % err
if isinstance(err, unicode):
err = err.encode(
'utf-8', errors='ignore') # pylint: disable=E1103
skip_msg = skip_message.encode('utf-8', errors='ignore')
raise unittest.SkipTest('\nError: %s\n%s' % (err, skip_msg))
raise AssertionSuccessError(func.__name__)
return wrapper
return wrapped
[docs]def parameterized(func_suffix, *args, **kwargs):
"""
A decorator that can generate methods given a base method and some data.
**func_suffix** is used as a suffix for the new created method and must be
unique given a base method. if **func_suffix** countains characters that
are not allowed in normal python function name, these characters will be
replaced with "_".
This decorator can be used more than once on a single base method. The
class must have a metaclass of :class:`MetaParameterized`.
Example::
# This example will generate two methods:
#
# - MyTestCase.test_it_1
# - MyTestCase.test_it_2
#
class MyTestCase(FunqTestCase):
__app_config_name__ = 'ma_conf'
@parameterized("1", 5, named='nom')
@parameterized("2", 6, named='nom2')
def test_it(self, value, named=None):
print value, named
:param func_suffix: will be used as a suffix for the new method
:param \*args: arguments to pass to the new method
:param \*\*kwargs: named arguments to pass to the new method
"""
def wrapped(func):
if not hasattr(func, 'parameters'):
func.parameters = []
func.parameters.append((func_suffix, args, kwargs))
return func
return wrapped
[docs]def with_parameters(parameters):
"""
A decorator that can generate methods given a base method and some data.
Acts like :func:`parameterized`, but define all methods in one call.
Example::
# This example will generate two methods:
#
# - MyTestCase.test_it_1
# - MyTestCase.test_it_2
#
DATA = [("1", [5], {'named':'nom'}), ("2", [6], {'named':'nom2'})]
class MyTestCase(FunqTestCase):
app_config_name = 'ma_conf'
@with_parameters(DATA)
def test_it(self, value, named=None):
print value, named
:param parameters: list of tuples (**func_suffix**, **args**, **kwargs**)
defining parameters like in :func:`todo`.
"""
def wrapped(func):
func.parameters = parameters
return func
return wrapped
def wraps_parameterized(func, func_suffix, args, kwargs):
"""Internal: for MetaParameterized"""
def wrapper(self):
return func(self, *args, **kwargs)
wrapper.__name__ = func.__name__ + '_' + func_suffix
wrapper.__doc__ = '[%s] %s' % (func_suffix, func.__doc__)
return wrapper
class declared_attr(property):
"""
Allow to write a class method that will be accessible as a class
attribute.
"""
def __init__(self, fget, *arg, **kw):
super(declared_attr, self).__init__(fget, *arg, **kw)
self.__doc__ = fget.__doc__
def __get__(desc, self, cls): # pylint: disable=E0213
return desc.fget(cls)
def funq_app_config(confname):
"""
Returns the config named *confname*.
"""
return BaseTestCase.__app_registry__.config(confname)
def register_funq_app_registry(registry):
"""
Saves the configurations registry. Must be called before uses of tests
classes.
"""
BaseTestCase.__app_registry__ = registry
[docs]class BaseTestCase(unittest.TestCase):
"""
Abstract class of a testcase for Funq.
It defines a common behaviour to name tests methods and uses the metaclass
:class:`MetaParameterized` that allows to generate methods from data.
It inherits from :class:`unittest.TestCase`, thus allowing to use very
useful methods like assertEquals, assertFalse, etc.
"""
__metaclass__ = MetaParameterized
__app_registry__ = None
longMessage = True
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
self.funq = None
def _create_funq_ctx(self):
raise NotImplementedError
def setUp(self):
self.__ctx = self._create_funq_ctx()
self.addCleanup(self.__delete_funq_ctx)
def __delete_funq_ctx(self):
del self.__ctx
def id(self):
cls = self.__class__
fname = inspect.getsourcefile(cls)[len(os.getcwd()) + 1:]
return u"%s:%s.%s" % (fname, cls.__name__, self._testMethodName)
[docs]class FunqTestCase(BaseTestCase):
"""
A testcase to launch an application and write tests against it.
The class attribute **__app_config_name__** is required and must contains
the name of a section in the funq configuration file. A class attribute
**__app_config__** will then be automatically created to give access to
the configuration of the application
(:class:`funq.client.ApplicationConfig`).
:var funq: instance of :class:`funq.client.FunqClient`, allowing to
manipulate the application.
"""
__app_config_name__ = None
@declared_attr
def __app_config__(cls): # pylint: disable=E0213
if cls.__app_config_name__ is not None:
return cls.__app_registry__.config(cls.__app_config_name__)
def _create_funq_ctx(self):
ctx = ApplicationContext(self.__app_config__)
self.funq = weakref.proxy(ctx.funq)
return ctx
[docs]class MultiFunqTestCase(BaseTestCase):
"""
A testcase to launch multiple applications at the same time and write tests
against them.
The class attribute **__app_config_names__** is required and must contains
a list of section's names in the funq configuration file. A class attribute
**__app_configs__** will then be automatically created to give access to
the configurations of the application (a dict with values of type
:class:`funq.client.ApplicationConfig`, where the keys are configuration
names).
:var funq: a dict that contains :class:`funq.client.FunqClient`, allowing
to manipulate the application. Keys are configuration names.
"""
__app_config_names__ = None
@declared_attr
def __app_config__(cls): # pylint: disable=E0213
if cls.__app_config_names__ is not None:
return dict([(k, cls.__app_registry__.config(k))
for k in cls.__app_config_names__])
def _create_funq_ctx(self):
ctx = {}
self.funq = {}
for k, v in self.__app_config__.iteritems():
ctx[k] = ApplicationContext(v)
self.funq[k] = weakref.proxy(ctx[k].funq)
return ctx